From d5e40d2dfe056498a63f78f7a365d76254e2465e Mon Sep 17 00:00:00 2001 From: Jon Ursenbach Date: Wed, 21 Oct 2020 13:29:14 -0700 Subject: [PATCH] fix: updating the ResponseSchemaBody for the async flattenSchema changes (#999) * fix: updating the ResponseSchemaBody for the async flattenSchema changes * chore(deps): upgrading oas to 4.1.0 * chore(deps): bumping oas to 5.0.0 --- .../__tests__/ResponseSchemaBody.test.jsx | 409 +++++++++++------- packages/api-explorer/package.json | 3 +- .../api-explorer/src/ResponseSchemaBody.jsx | 106 +++-- packages/oas-to-har/package.json | 2 +- packages/oas-to-snippet/package-lock.json | 39 +- packages/oas-to-snippet/package.json | 2 +- 6 files changed, 357 insertions(+), 204 deletions(-) diff --git a/packages/api-explorer/__tests__/ResponseSchemaBody.test.jsx b/packages/api-explorer/__tests__/ResponseSchemaBody.test.jsx index 019c49fbb..9f441282f 100644 --- a/packages/api-explorer/__tests__/ResponseSchemaBody.test.jsx +++ b/packages/api-explorer/__tests__/ResponseSchemaBody.test.jsx @@ -1,5 +1,6 @@ const React = require('react'); const { shallow, mount } = require('enzyme'); +const { waitFor } = require('@testing-library/dom'); const Oas = require('oas/tooling'); const ResponseSchemaBody = require('../src/ResponseSchemaBody'); @@ -16,28 +17,14 @@ test('display object properties in the table', () => { }, }, }; - const responseSchemaBody = shallow(); - expect(responseSchemaBody.find('th').text()).toContain('String'); - expect(responseSchemaBody.find('td').text()).toBe('a'); -}); - -test('display properties if object contains $ref type', () => { - const schema = { - type: 'object', - properties: { - category: { - $ref: '#/components/schemas/Category', - }, - }, - }; + const comp = shallow(); + return waitFor(() => { + comp.update(); - expect( - shallow() - .find('td') - .map(a => a.text()) - .filter(a => a === 'category.name') - ).toHaveLength(1); + expect(comp.find('th').text()).toContain('String'); + expect(comp.find('td').text()).toBe('a'); + }); }); test('display object properties inside another object in the table', () => { @@ -54,58 +41,18 @@ test('display object properties inside another object in the table', () => { }, }, }; - expect( - shallow() - .find('td') - .map(a => a.text()) - .filter(a => a === 'a.a') - ).toHaveLength(1); -}); -test('display $ref items inside object', () => { - const schema = { - type: 'object', - properties: { - a: { - type: 'object', - properties: { - pets: { - type: 'array', - items: { - $ref: '#/components/schemas/Pet', - }, - }, - }, - }, - }, - }; - const testOas = { - components: { - schemas: { - Pet: { - type: 'object', - properties: { - index: { - type: 'integer', - }, - }, - }, - }, - }, - }; - const responseSchemaBody = shallow(); - expect( - responseSchemaBody - .find('th') - .map(a => a.text()) - .filter(a => a === '[Object]') - ).toHaveLength(1); - expect( - responseSchemaBody - .find('td') - .map(a => a.text()) - .filter(a => a === 'a.pets[].index') - ).toHaveLength(1); + const comp = shallow(); + return waitFor(() => { + comp.update(); + + expect( + comp + .find('td') + .map(a => a.text()) + .filter(a => a === 'a.a') + ).toHaveLength(1); + }); }); test('not fail when object property missing', () => { @@ -113,44 +60,11 @@ test('not fail when object property missing', () => { type: 'object', }; - expect(shallow().find('th')).toHaveLength(0); -}); - -test('render top level array of $ref', () => { - const schema = { - type: 'array', - items: { - $ref: '#/components/schemas/Pet', - }, - }; - - const testOas = { - components: { - schemas: { - Pet: { - type: 'object', - properties: { - name: { - type: 'string', - }, - }, - }, - }, - }, - }; - const responseSchemaBody = shallow(); - expect( - responseSchemaBody - .find('td') - .map(a => a.text()) - .filter(a => a === 'name') - ).toHaveLength(1); - expect( - responseSchemaBody - .find('th') - .map(a => a.text()) - .filter(a => a === 'String') - ).toHaveLength(1); + const comp = shallow(); + return waitFor(() => { + comp.update(); + expect(comp.find('th')).toHaveLength(0); + }); }); test('not render more than 3 level deep object', () => { @@ -178,19 +92,23 @@ test('not render more than 3 level deep object', () => { }, }; - const responseSchemaBody = shallow(); - expect( - responseSchemaBody - .find('td') - .map(a => a.text()) - .filter(a => a === 'a.a.a') - ).toHaveLength(1); - expect( - responseSchemaBody - .find('td') - .map(a => a.text()) - .filter(a => a === 'a.a.a.a') - ).toHaveLength(0); + const comp = shallow(); + return waitFor(() => { + comp.update(); + + expect( + comp + .find('td') + .map(a => a.text()) + .filter(a => a === 'a.a.a') + ).toHaveLength(1); + expect( + comp + .find('td') + .map(a => a.text()) + .filter(a => a === 'a.a.a.a') + ).toHaveLength(0); + }); }); test('render top level array of objects', () => { @@ -206,19 +124,23 @@ test('render top level array of objects', () => { }, }; - const responseSchemaBody = shallow(); - expect( - responseSchemaBody - .find('td') - .map(a => a.text()) - .filter(a => a === 'name') - ).toHaveLength(1); - expect( - responseSchemaBody - .find('th') - .map(a => a.text()) - .filter(a => a === 'String') - ).toHaveLength(1); + const comp = shallow(); + return waitFor(() => { + comp.update(); + + expect( + comp + .find('td') + .map(a => a.text()) + .filter(a => a === 'name') + ).toHaveLength(1); + expect( + comp + .find('th') + .map(a => a.text()) + .filter(a => a === 'String') + ).toHaveLength(1); + }); }); test.each([ @@ -235,11 +157,11 @@ test.each([ }, }; - expect( - mount() - .find('a') - .html() - ).toBe(expected); + const comp = mount(); + return waitFor(() => { + comp.update(); + expect(comp.find('a').html()).toBe(expected); + }); }); test('should show "string" response type for a simple `string` schema', () => { @@ -247,7 +169,11 @@ test('should show "string" response type for a simple `string` schema', () => { type: 'string', }; - expect(shallow().text()).toBe('Response schema type: string'); + const comp = shallow(); + return waitFor(() => { + comp.update(); + expect(comp.text()).toBe('Response schema type: string'); + }); }); test('should show "string" response type for an `object` schema that contains a string', () => { @@ -260,11 +186,12 @@ test('should show "string" response type for an `object` schema that contains a }, }; - expect( - shallow() - .find('p') - .text() - ).toBe('Response schema type: object'); + const comp = shallow(); + return waitFor(() => { + comp.update(); + + expect(comp.find('p').text()).toBe('Response schema type: object'); + }); }); test('should show "array" response schema type', () => { @@ -280,9 +207,189 @@ test('should show "array" response schema type', () => { }, }; - expect( - shallow() - .find('p') - .text() - ).toBe('Response schema type: array of objects'); + const comp = shallow(); + return waitFor(() => { + comp.update(); + + expect(comp.find('p').text()).toBe('Response schema type: array of objects'); + }); +}); + +describe('$ref handling', () => { + it('display properties if object contains $ref type', () => { + const schema = { + type: 'object', + properties: { + category: { + $ref: '#/components/schemas/Category', + }, + }, + }; + + const comp = shallow(); + return waitFor(() => { + comp.update(); + + expect( + comp + .find('td') + .map(a => a.text()) + .filter(a => a === 'category.name') + ).toHaveLength(1); + }); + }); + + it('display $ref items inside object', () => { + const schema = { + type: 'object', + properties: { + a: { + type: 'object', + properties: { + pets: { + type: 'array', + items: { + $ref: '#/components/schemas/Pet', + }, + }, + }, + }, + }, + }; + + const testOas = { + components: { + schemas: { + Pet: { + type: 'object', + properties: { + index: { + type: 'integer', + }, + }, + }, + }, + }, + }; + + const comp = shallow(); + return waitFor(() => { + comp.update(); + + expect( + comp + .find('th') + .map(a => a.text()) + .filter(a => a === '[Object]') + ).toHaveLength(1); + expect( + comp + .find('td') + .map(a => a.text()) + .filter(a => a === 'a.pets[].index') + ).toHaveLength(1); + }); + }); + + it('render top level array of $ref', () => { + const schema = { + type: 'array', + items: { + $ref: '#/components/schemas/Pet', + }, + }; + + const testOas = { + components: { + schemas: { + Pet: { + type: 'object', + properties: { + name: { + type: 'string', + }, + }, + }, + }, + }, + }; + + const comp = shallow(); + return waitFor(() => { + comp.update(); + + expect( + comp + .find('td') + .map(a => a.text()) + .filter(a => a === 'name') + ).toHaveLength(1); + expect( + comp + .find('th') + .map(a => a.text()) + .filter(a => a === 'String') + ).toHaveLength(1); + }); + }); + + describe('circular refs', () => { + const circularOas = { + components: { + schemas: { + Customfields: { + type: 'array', + items: { + $ref: '#/components/schemas/Customfields', + }, + }, + }, + }, + }; + + it('should not fail on a fully circular ref', () => { + const schema = { + type: 'array', + items: { + $ref: '#/components/schemas/Customfields', + }, + }; + + const comp = shallow(); + return waitFor(() => { + comp.update(); + expect(comp.find('tr')).toHaveLength(0); + }); + }); + + it('should do its best to recognize that a circular ref is present', () => { + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'number', + }, + fields: { + $ref: '#/components/schemas/Customfields', + }, + }, + }, + }; + + const comp = shallow(); + return waitFor(() => { + comp.update(); + + expect(comp.find('tr')).toHaveLength(2); + + expect(comp.find('tr').at(0).find('th').text()).toBe('Number'); + expect(comp.find('tr').at(0).find('td').text()).toBe('id'); + + expect(comp.find('tr').at(1).find('th').text()).toBe('Circular'); + expect(comp.find('tr').at(1).find('td').text()).toBe('fields'); + }); + }); + }); }); diff --git a/packages/api-explorer/package.json b/packages/api-explorer/package.json index f3f43a4fa..f9d08ffda 100644 --- a/packages/api-explorer/package.json +++ b/packages/api-explorer/package.json @@ -18,7 +18,7 @@ "fetch-har": "^4.0.2", "js-cookie": "^2.1.4", "lodash.kebabcase": "^4.1.1", - "oas": "4.0.0", + "oas": "^5.0.0", "prop-types": "^15.7.2", "react": "^16.13.1", "react-copy-to-clipboard": "^5.0.1", @@ -47,6 +47,7 @@ "devDependencies": { "@readme/eslint-config": "^3.2.0", "@readme/oas-examples": "^3.6.0", + "@testing-library/dom": "^7.26.3", "css-loader": "^4.3.0", "eslint": "^7.0.0", "jest": "^26.0.1", diff --git a/packages/api-explorer/src/ResponseSchemaBody.jsx b/packages/api-explorer/src/ResponseSchemaBody.jsx index ca20975fd..22ae05c03 100644 --- a/packages/api-explorer/src/ResponseSchemaBody.jsx +++ b/packages/api-explorer/src/ResponseSchemaBody.jsx @@ -22,47 +22,73 @@ function getDescriptionMarkdown(useNewMarkdownEngine, description) { return markdownMagic(description); } -function ResponseSchemaBody({ schema, oas, useNewMarkdownEngine }) { - const rows = flattenArray(flattenSchema(schema, oas)).map(row => ( - - - {row.type} - - - {row.name} - {row.description && getDescriptionMarkdown(useNewMarkdownEngine, row.description)} - - - )); +class ResponseSchemaBody extends React.Component { + constructor(props) { + super(props); - return ( -
- {schema && schema.type && ( -

- {`Response schema type: `} - {getSchemaType(schema)} -

- )} - - {rows} -
-
- ); + this.state = { + flattenedSchema: null, + }; + } + + componentDidMount() { + const { schema, oas } = this.props; + + flattenSchema(schema, oas).then(flattened => { + this.setState({ + flattenedSchema: flattenArray(flattened), + }); + }); + } + + render() { + const { schema, useNewMarkdownEngine } = this.props; + const { flattenedSchema } = this.state; + + let rows = []; + if (flattenedSchema) { + rows = flattenedSchema.map(row => ( + + + {row.type} + + + {row.name} + {row.description && getDescriptionMarkdown(useNewMarkdownEngine, row.description)} + + + )); + } + + return ( +
+ {schema && schema.type && ( +

+ {`Response schema type: `} + {getSchemaType(schema)} +

+ )} + + {rows} +
+
+ ); + } } ResponseSchemaBody.propTypes = { diff --git a/packages/oas-to-har/package.json b/packages/oas-to-har/package.json index 93646aceb..a777ebb69 100644 --- a/packages/oas-to-har/package.json +++ b/packages/oas-to-har/package.json @@ -5,7 +5,7 @@ "main": "src/index.js", "dependencies": { "@readme/oas-extensions": "^8.0.0", - "oas": "4.0.0", + "oas": "^5.0.0", "parse-data-url": "^3.0.0" }, "scripts": { diff --git a/packages/oas-to-snippet/package-lock.json b/packages/oas-to-snippet/package-lock.json index fccb704fd..05a4744aa 100644 --- a/packages/oas-to-snippet/package-lock.json +++ b/packages/oas-to-snippet/package-lock.json @@ -4178,6 +4178,31 @@ "oas": "4.0.0", "path-to-regexp": "^6.1.0", "stringify-object": "^3.3.0" + }, + "dependencies": { + "oas": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/oas/-/oas-4.0.0.tgz", + "integrity": "sha512-DGeXKXxs9xdCqI30IVLwIJ9IqkQssi+8N0DDfIokrxN70re3wSMYvieEDwFcu/KotzMuHXyjURqFQBDSfDNysw==", + "requires": { + "cardinal": "^2.1.1", + "colors": "^1.1.2", + "figures": "^3.0.0", + "glob": "^7.1.2", + "inquirer": "^7.0.1", + "json2yaml": "^1.1.0", + "jsonfile": "^6.0.0", + "jsonpointer": "^4.1.0", + "lodash": "^4.17.11", + "minimist": "^1.2.0", + "node-status": "^1.0.0", + "oas-normalize": "2.3.1", + "open": "^7.0.0", + "path-to-regexp": "^6.2.0", + "request": "^2.88.0", + "swagger-inline": "3.2.2" + } + } } }, "human-signals": { @@ -6673,10 +6698,11 @@ "dev": true }, "oas": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/oas/-/oas-4.0.0.tgz", - "integrity": "sha512-DGeXKXxs9xdCqI30IVLwIJ9IqkQssi+8N0DDfIokrxN70re3wSMYvieEDwFcu/KotzMuHXyjURqFQBDSfDNysw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/oas/-/oas-5.0.0.tgz", + "integrity": "sha512-6H/U6fc1AhChfr/NuXaQRPnBh+PVTooDphFBgNS39CAqjmSVjjC+C0G4RuywU1wkNj7ir4uwg5f8EDjkz65j+Q==", "requires": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", "cardinal": "^2.1.1", "colors": "^1.1.2", "figures": "^3.0.0", @@ -6693,13 +6719,6 @@ "path-to-regexp": "^6.2.0", "request": "^2.88.0", "swagger-inline": "3.2.2" - }, - "dependencies": { - "path-to-regexp": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz", - "integrity": "sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg==" - } } }, "oas-normalize": { diff --git a/packages/oas-to-snippet/package.json b/packages/oas-to-snippet/package.json index 2a500663b..f95c82a4e 100644 --- a/packages/oas-to-snippet/package.json +++ b/packages/oas-to-snippet/package.json @@ -9,7 +9,7 @@ "@readme/oas-to-har": "^8.0.3", "@readme/syntax-highlighter": "^10.0.0", "httpsnippet-client-api": "^2.4.2", - "oas": "4.0.0" + "oas": "^5.0.0" }, "scripts": { "lint": "eslint .",