Skip to content

Commit e367595

Browse files
authored
fix: handle combiners placed under arrays (#86)
1 parent d56181f commit e367595

File tree

8 files changed

+136
-38
lines changed

8 files changed

+136
-38
lines changed

src/components/__tests__/Property.spec.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,80 @@ describe('Property component', () => {
252252
expect(wrapper.find('div').first()).toHaveText('foo');
253253
});
254254

255+
test('given an array with a combiner inside, should just render the type of combiner', () => {
256+
const schema: JSONSchema4 = {
257+
type: 'array',
258+
items: {
259+
oneOf: [
260+
{
261+
properties: {},
262+
},
263+
],
264+
type: 'object',
265+
},
266+
};
267+
268+
const tree = new SchemaTree(schema, new TreeState(), {
269+
expandedDepth: Infinity,
270+
mergeAllOf: false,
271+
resolveRef: void 0,
272+
shouldResolveEagerly: false,
273+
onPopulate: void 0,
274+
});
275+
276+
tree.populate();
277+
278+
const wrapper = shallow(<Property node={tree.itemAt(1)!} />);
279+
expect(wrapper).toHaveHTML('<span class="text-orange-5 truncate">oneOf</span>');
280+
});
281+
282+
test('given an array with an allOf inside and enabled allOf merging, should display the name of properties', () => {
283+
const schema: JSONSchema4 = {
284+
type: 'object',
285+
properties: {
286+
'array-all-objects': {
287+
type: 'array',
288+
items: {
289+
allOf: [
290+
{
291+
properties: {
292+
foo: {
293+
type: 'string',
294+
},
295+
},
296+
},
297+
{
298+
properties: {
299+
bar: {
300+
type: 'string',
301+
},
302+
},
303+
},
304+
],
305+
type: 'object',
306+
},
307+
},
308+
},
309+
};
310+
311+
const tree = new SchemaTree(schema, new TreeState(), {
312+
expandedDepth: Infinity,
313+
mergeAllOf: true,
314+
resolveRef: void 0,
315+
shouldResolveEagerly: false,
316+
onPopulate: void 0,
317+
});
318+
319+
tree.populate();
320+
321+
expect(shallow(<Property node={tree.itemAt(2)!} />)).toHaveHTML(
322+
'<div class="mr-2">foo</div><span class="text-green-7 dark:text-green-5 truncate">string</span>',
323+
);
324+
expect(shallow(<Property node={tree.itemAt(3)!} />)).toHaveHTML(
325+
'<div class="mr-2">bar</div><span class="text-green-7 dark:text-green-5 truncate">string</span>',
326+
);
327+
});
328+
255329
test('given a ref pointing at primitive type, should not display property name', () => {
256330
const schema: JSONSchema4 = {
257331
properties: {

src/components/shared/Property.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ function shouldShowPropertyName(treeNode: SchemaTreeListNode) {
3131
return false;
3232
}
3333

34-
let type = getPrimaryType(schemaNode);
34+
const type = getPrimaryType(schemaNode);
3535

3636
if (type === SchemaKind.Array && (schemaNode as IArrayNode).items) {
37-
type = getPrimaryType((schemaNode as IArrayNode).items!);
37+
const { schemaNode: itemsSchemaNode } = getSchemaNodeMetadata(treeNode);
38+
return !('combiner' in itemsSchemaNode);
3839
}
3940

4041
return type === SchemaKind.Object;
File renamed without changes.

src/tree/__tests__/__snapshots__/populateTree.spec.ts.snap

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,5 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`populateTree util should match array-of-allofs.json 1`] = `
4-
Object {
5-
"children": Array [
6-
Object {
7-
"children": Array [
8-
Object {
9-
"id": "random-id",
10-
"name": "",
11-
"parent": [Circular],
12-
},
13-
],
14-
"id": "random-id",
15-
"name": "",
16-
"parent": [Circular],
17-
},
18-
],
19-
"id": "random-id",
20-
"name": "",
21-
"parent": null,
22-
}
23-
`;
24-
253
exports[`populateTree util should match array-of-objects.json 1`] = `
264
Object {
275
"children": Array [

src/tree/__tests__/__snapshots__/tree.spec.ts.snap

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[` 1`] = `
4+
"└─ #
5+
├─ type: object
6+
└─ children
7+
└─ 0
8+
└─ #/properties/array-all-objects
9+
├─ type: array
10+
├─ subtype: object
11+
└─ children
12+
├─ 0
13+
│ └─ #/properties/array-all-objects/items/properties/foo
14+
│ └─ type: string
15+
└─ 1
16+
└─ #/properties/array-all-objects/items/properties/bar
17+
└─ type: string
18+
"
19+
`;
20+
321
exports[`SchemaTree expanding $refs in allOf given very complex model with circular references, should bail out and display unmerged allOf 1`] = `
422
"└─ #
523
├─ type: object

src/tree/__tests__/populateTree.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ describe('populateTree util', () => {
1919
'combiner-schema.json',
2020
'array-of-objects.json',
2121
'array-of-refs.json',
22-
'array-of-allofs.json',
2322
'todo-allof.schema.json',
2423
'tickets.schema.json',
2524
'nullish-ref.schema.json',
@@ -37,7 +36,6 @@ describe('populateTree util', () => {
3736
'combiner-schema.json',
3837
'array-of-objects.json',
3938
'array-of-refs.json',
40-
'array-of-allofs.json',
4139
'tickets.schema.json',
4240
])('should not mutate original object %s', filename => {
4341
const content = fs.readFileSync(path.resolve(BASE_PATH, filename), 'utf8');

src/tree/__tests__/tree.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { TreeListParentNode } from '@stoplight/tree-list';
2+
import * as fs from 'fs';
23
import { JSONSchema4 } from 'json-schema';
4+
import * as path from 'path';
35
import { ResolvingError } from '../../errors';
46
import { ViewMode } from '../../types';
57
import { getNodeMetadata } from '../metadata';
@@ -1434,6 +1436,20 @@ describe('SchemaTree', () => {
14341436
tree.populate();
14351437
expect(tree.count).toEqual(mode === 'standalone' ? 3 : 2);
14361438
});
1439+
1440+
describe.each(['array-of-allofs.json'])('should match %s', filename => {
1441+
const schema = JSON.parse(fs.readFileSync(path.resolve(__dirname, '__fixtures__', filename), 'utf8'));
1442+
const tree = new SchemaTree(schema, new SchemaTreeState(), {
1443+
expandedDepth: Infinity,
1444+
mergeAllOf: true,
1445+
resolveRef: void 0,
1446+
shouldResolveEagerly: true,
1447+
onPopulate: void 0,
1448+
});
1449+
1450+
tree.populate();
1451+
expect(printTree(tree)).toMatchSnapshot();
1452+
});
14371453
});
14381454

14391455
test('given visible $ref node, should try to inject the title immediately', () => {

src/tree/utils/populateTree.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { TreeListNode, TreeListParentNode } from '@stoplight/tree-list';
22
import { JsonPath, Optional } from '@stoplight/types';
33
import { JSONSchema4 } from 'json-schema';
44
import { isObject as _isObject } from 'lodash';
5+
56
import { IArrayNode, IObjectNode, SchemaKind, SchemaNode, SchemaTreeListNode } from '../../types';
67
import { generateId } from '../../utils/generateId';
7-
import { getCombiners } from '../../utils/getCombiners';
88
import { getPrimaryType } from '../../utils/getPrimaryType';
99
import { isCombinerNode, isRefNode } from '../../utils/guards';
1010
import { getNodeMetadata, getSchemaNodeMetadata, metadataStore } from '../metadata';
@@ -139,18 +139,31 @@ function processArray(
139139
}
140140
}
141141
} else {
142-
const subtype = getPrimaryType(items);
143-
switch (subtype) {
144-
case SchemaKind.Object:
145-
return processObject(node, items as IObjectNode, level, [...path, 'items'], options);
146-
case SchemaKind.Array:
147-
return processArray(node, items as IObjectNode, level, [...path, 'items'], options);
148-
default:
149-
const combiners = getCombiners(items);
150-
if (combiners !== void 0) {
151-
(node as TreeListParentNode).children = [];
152-
populateTree(items, node as TreeListParentNode, level, [...path, 'items'], options);
142+
const children: TreeListNode[] = [];
143+
(node as TreeListParentNode).children = children;
144+
populateTree(items, node as TreeListParentNode, level, [...path, 'items'], options);
145+
146+
// optional flattening
147+
if (children.length === 1) {
148+
let schemaNode;
149+
try {
150+
({ schemaNode } = getSchemaNodeMetadata(children[0]));
151+
} catch {
152+
return node;
153+
}
154+
155+
if (!('children' in children[0])) {
156+
// we'll render this in subtype next to array, i.e. array[subtype], so let's get rid of these redundant nodes
157+
// @ts-ignore
158+
delete node.children;
159+
} else if (!('combiner' in schemaNode)) {
160+
for (const child of children[0].children) {
161+
// re-parenting
162+
child.parent = node as TreeListParentNode;
153163
}
164+
165+
children.splice(0, children.length, ...children[0].children);
166+
}
154167
}
155168
}
156169

0 commit comments

Comments
 (0)