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)}
-
- )}
-
-
- );
+ 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)}
+
+ )}
+
+
+ );
+ }
}
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 .",