Skip to content

Commit f9f804f

Browse files
authored
feat: support multiple combiners (#82)
* feat: support multiple combiners * chore: allOf goes first * refactor: tweak mergeOneOrAnyOf
1 parent 985268c commit f9f804f

File tree

14 files changed

+470
-57
lines changed

14 files changed

+470
-57
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
"ts-jest": "^24.0.2",
9191
"tslint": "^5.19.0",
9292
"tslint-config-stoplight": "^1.4.0",
93-
"typescript": "^3.7.5"
93+
"typescript": "^4.0.3"
9494
},
9595
"lint-staged": {
9696
"*.{ts,tsx}$": [

src/components/__tests__/Property.spec.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,4 +416,68 @@ describe('Property component', () => {
416416
expect(wrapper.find(Types)).toHaveProp('title', void 0);
417417
});
418418
});
419+
420+
test("no title for combiner's children", () => {
421+
const schema: JSONSchema4 = {
422+
type: 'object',
423+
title: 'Account',
424+
allOf: [
425+
{
426+
type: 'object',
427+
properties: {
428+
type: {
429+
type: 'string',
430+
enum: ['admin', 'editor'],
431+
},
432+
enabled: {
433+
type: 'boolean',
434+
description: 'Is this account enabled',
435+
},
436+
},
437+
required: ['type'],
438+
},
439+
],
440+
oneOf: [
441+
{
442+
type: 'object',
443+
title: 'Admin',
444+
properties: {
445+
root: {
446+
type: 'boolean',
447+
},
448+
group: {
449+
type: 'string',
450+
},
451+
expirationDate: {
452+
type: 'string',
453+
},
454+
},
455+
},
456+
{
457+
type: 'object',
458+
title: 'Editor',
459+
properties: {
460+
supervisor: {
461+
type: 'string',
462+
},
463+
key: {
464+
type: 'string',
465+
},
466+
},
467+
},
468+
],
469+
};
470+
471+
const tree = new SchemaTree(schema, new TreeState(), {
472+
expandedDepth: Infinity,
473+
mergeAllOf: true,
474+
resolveRef: void 0,
475+
shouldResolveEagerly: false,
476+
onPopulate: void 0,
477+
});
478+
479+
tree.populate();
480+
const wrapper = shallow(<Property node={Array.from(tree)[1]} />);
481+
expect(wrapper.children().first()).toEqual(wrapper.find(Types));
482+
});
419483
});

src/components/shared/Property.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function shouldShowPropertyName(treeNode: SchemaTreeListNode) {
2727
if (treeNode.parent === null) return false;
2828
try {
2929
const { schemaNode } = getSchemaNodeMetadata(treeNode.parent);
30-
if (!('type' in schemaNode)) {
30+
if (!('type' in schemaNode) || 'combiner' in schemaNode) {
3131
return false;
3232
}
3333

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

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,183 @@ exports[`SchemaTree expanding $refs in allOf given very complex model with circu
7878
└─ children
7979
"
8080
`;
81+
82+
exports[`SchemaTree tree correctness given anyOf combiner placed next to allOf given allOf merging disabled, should preserve both combiners 1`] = `
83+
"├─ 0
84+
│ └─ #
85+
│ ├─ type: object
86+
│ ├─ combiner: anyOf
87+
│ └─ children
88+
│ ├─ 0
89+
│ │ └─ #/anyOf/0
90+
│ │ ├─ type: object
91+
│ │ └─ children
92+
│ │ ├─ 0
93+
│ │ │ └─ #/anyOf/0/properties/root
94+
│ │ │ └─ type: boolean
95+
│ │ ├─ 1
96+
│ │ │ └─ #/anyOf/0/properties/group
97+
│ │ │ └─ type: string
98+
│ │ └─ 2
99+
│ │ └─ #/anyOf/0/properties/expirationDate
100+
│ │ └─ type: string
101+
│ └─ 1
102+
│ └─ #/anyOf/1
103+
│ ├─ type: object
104+
│ └─ children
105+
│ ├─ 0
106+
│ │ └─ #/anyOf/1/properties/supervisor
107+
│ │ └─ type: string
108+
│ └─ 1
109+
│ └─ #/anyOf/1/properties/key
110+
│ └─ type: string
111+
└─ 1
112+
└─ #
113+
├─ type: object
114+
├─ combiner: allOf
115+
└─ children
116+
└─ 0
117+
└─ #/allOf/0
118+
├─ type: object
119+
└─ children
120+
├─ 0
121+
│ └─ #/allOf/0/properties/type
122+
│ └─ type: string
123+
└─ 1
124+
└─ #/allOf/0/properties/enabled
125+
└─ type: boolean
126+
"
127+
`;
128+
129+
exports[`SchemaTree tree correctness given anyOf combiner placed next to allOf given allOf merging enabled, should merge contents of allOf combiners 1`] = `
130+
"└─ #
131+
├─ type: object
132+
├─ combiner: anyOf
133+
└─ children
134+
├─ 0
135+
│ └─ #/anyOf/0
136+
│ ├─ type: object
137+
│ └─ children
138+
│ ├─ 0
139+
│ │ └─ #/anyOf/0/properties/type
140+
│ │ └─ type: string
141+
│ ├─ 1
142+
│ │ └─ #/anyOf/0/properties/enabled
143+
│ │ └─ type: boolean
144+
│ ├─ 2
145+
│ │ └─ #/anyOf/0/properties/root
146+
│ │ └─ type: boolean
147+
│ ├─ 3
148+
│ │ └─ #/anyOf/0/properties/group
149+
│ │ └─ type: string
150+
│ └─ 4
151+
│ └─ #/anyOf/0/properties/expirationDate
152+
│ └─ type: string
153+
└─ 1
154+
└─ #/anyOf/1
155+
├─ type: object
156+
└─ children
157+
├─ 0
158+
│ └─ #/anyOf/1/properties/type
159+
│ └─ type: string
160+
├─ 1
161+
│ └─ #/anyOf/1/properties/enabled
162+
│ └─ type: boolean
163+
├─ 2
164+
│ └─ #/anyOf/1/properties/supervisor
165+
│ └─ type: string
166+
└─ 3
167+
└─ #/anyOf/1/properties/key
168+
└─ type: string
169+
"
170+
`;
171+
172+
exports[`SchemaTree tree correctness given oneOf combiner placed next to allOf given allOf merging disabled, should preserve both combiners 1`] = `
173+
"├─ 0
174+
│ └─ #
175+
│ ├─ type: object
176+
│ ├─ combiner: oneOf
177+
│ └─ children
178+
│ ├─ 0
179+
│ │ └─ #/oneOf/0
180+
│ │ ├─ type: object
181+
│ │ └─ children
182+
│ │ ├─ 0
183+
│ │ │ └─ #/oneOf/0/properties/root
184+
│ │ │ └─ type: boolean
185+
│ │ ├─ 1
186+
│ │ │ └─ #/oneOf/0/properties/group
187+
│ │ │ └─ type: string
188+
│ │ └─ 2
189+
│ │ └─ #/oneOf/0/properties/expirationDate
190+
│ │ └─ type: string
191+
│ └─ 1
192+
│ └─ #/oneOf/1
193+
│ ├─ type: object
194+
│ └─ children
195+
│ ├─ 0
196+
│ │ └─ #/oneOf/1/properties/supervisor
197+
│ │ └─ type: string
198+
│ └─ 1
199+
│ └─ #/oneOf/1/properties/key
200+
│ └─ type: string
201+
└─ 1
202+
└─ #
203+
├─ type: object
204+
├─ combiner: allOf
205+
└─ children
206+
└─ 0
207+
└─ #/allOf/0
208+
├─ type: object
209+
└─ children
210+
├─ 0
211+
│ └─ #/allOf/0/properties/type
212+
│ └─ type: string
213+
└─ 1
214+
└─ #/allOf/0/properties/enabled
215+
└─ type: boolean
216+
"
217+
`;
218+
219+
exports[`SchemaTree tree correctness given oneOf combiner placed next to allOf given allOf merging enabled, should merge contents of allOf combiners 1`] = `
220+
"└─ #
221+
├─ type: object
222+
├─ combiner: oneOf
223+
└─ children
224+
├─ 0
225+
│ └─ #/oneOf/0
226+
│ ├─ type: object
227+
│ └─ children
228+
│ ├─ 0
229+
│ │ └─ #/oneOf/0/properties/type
230+
│ │ └─ type: string
231+
│ ├─ 1
232+
│ │ └─ #/oneOf/0/properties/enabled
233+
│ │ └─ type: boolean
234+
│ ├─ 2
235+
│ │ └─ #/oneOf/0/properties/root
236+
│ │ └─ type: boolean
237+
│ ├─ 3
238+
│ │ └─ #/oneOf/0/properties/group
239+
│ │ └─ type: string
240+
│ └─ 4
241+
│ └─ #/oneOf/0/properties/expirationDate
242+
│ └─ type: string
243+
└─ 1
244+
└─ #/oneOf/1
245+
├─ type: object
246+
└─ children
247+
├─ 0
248+
│ └─ #/oneOf/1/properties/type
249+
│ └─ type: string
250+
├─ 1
251+
│ └─ #/oneOf/1/properties/enabled
252+
│ └─ type: boolean
253+
├─ 2
254+
│ └─ #/oneOf/1/properties/supervisor
255+
│ └─ type: string
256+
└─ 3
257+
└─ #/oneOf/1/properties/key
258+
└─ type: string
259+
"
260+
`;

src/tree/__tests__/tree.spec.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,88 @@ describe('SchemaTree', () => {
12831283
"
12841284
`);
12851285
});
1286+
1287+
describe.each(['anyOf', 'oneOf'])('given %s combiner placed next to allOf', combiner => {
1288+
let schema: JSONSchema4;
1289+
1290+
beforeEach(() => {
1291+
schema = {
1292+
type: 'object',
1293+
title: 'Account',
1294+
allOf: [
1295+
{
1296+
type: 'object',
1297+
properties: {
1298+
type: {
1299+
type: 'string',
1300+
enum: ['admin', 'editor'],
1301+
},
1302+
enabled: {
1303+
type: 'boolean',
1304+
description: 'Is this account enabled',
1305+
},
1306+
},
1307+
required: ['type'],
1308+
},
1309+
],
1310+
[combiner]: [
1311+
{
1312+
type: 'object',
1313+
title: 'Admin',
1314+
properties: {
1315+
root: {
1316+
type: 'boolean',
1317+
},
1318+
group: {
1319+
type: 'string',
1320+
},
1321+
expirationDate: {
1322+
type: 'string',
1323+
},
1324+
},
1325+
},
1326+
{
1327+
type: 'object',
1328+
title: 'Editor',
1329+
properties: {
1330+
supervisor: {
1331+
type: 'string',
1332+
},
1333+
key: {
1334+
type: 'string',
1335+
},
1336+
},
1337+
},
1338+
],
1339+
};
1340+
});
1341+
1342+
test('given allOf merging disabled, should preserve both combiners', () => {
1343+
const tree = new SchemaTree(schema, new SchemaTreeState(), {
1344+
expandedDepth: Infinity,
1345+
mergeAllOf: false,
1346+
resolveRef: void 0,
1347+
shouldResolveEagerly: true,
1348+
onPopulate: void 0,
1349+
});
1350+
1351+
tree.populate();
1352+
expect(printTree(tree)).toMatchSnapshot();
1353+
});
1354+
1355+
test('given allOf merging enabled, should merge contents of allOf combiners', () => {
1356+
const tree = new SchemaTree(schema, new SchemaTreeState(), {
1357+
expandedDepth: Infinity,
1358+
mergeAllOf: true,
1359+
resolveRef: void 0,
1360+
shouldResolveEagerly: true,
1361+
onPopulate: void 0,
1362+
});
1363+
1364+
tree.populate();
1365+
expect(printTree(tree)).toMatchSnapshot();
1366+
});
1367+
});
12861368
});
12871369

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

src/tree/__tests__/utils/printTree.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import { getNodeMetadata } from '../../metadata';
99
import { SchemaTree } from '../../tree';
1010

1111
export function printTree(tree: SchemaTree) {
12-
return treeify.asTree(prepareTree(tree.root.children[0]) as treeify.TreeObject, true, true);
12+
const root: unknown =
13+
tree.root.children.length > 1 ? tree.root.children.map(prepareTree) : prepareTree(tree.root.children[0]);
14+
15+
return treeify.asTree(root as treeify.TreeObject, true, true);
1316
}
1417

1518
type PrintableNode = {

0 commit comments

Comments
 (0)