docusaurus-plugin-generate-schema-docs 1.8.2 → 1.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/__snapshots__/generateEventDocs.anchor.test.js.snap +15 -3
- package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +20 -4
- package/__tests__/__snapshots__/generateEventDocs.test.js.snap +30 -6
- package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +10 -2
- package/__tests__/components/ConditionalRows.test.js +28 -0
- package/__tests__/components/FoldableRows.test.js +31 -290
- package/__tests__/components/PropertyRow.test.js +216 -0
- package/__tests__/components/SchemaJsonViewer.test.js +76 -10
- package/__tests__/components/SchemaRows.test.js +62 -12
- package/__tests__/components/__snapshots__/ConnectorLines.visualRegression.test.js.snap +3 -3
- package/__tests__/generateEventDocs.test.js +3 -0
- package/__tests__/helpers/schemaToTableData.test.js +112 -0
- package/components/ConditionalRows.js +6 -3
- package/components/FoldableRows.js +9 -3
- package/components/PropertiesTable.js +2 -1
- package/components/PropertyRow.js +90 -5
- package/components/SchemaJsonViewer.js +221 -4
- package/components/SchemaRows.css +98 -7
- package/components/SchemaRows.js +11 -1
- package/generateEventDocs.js +87 -1
- package/helpers/choice-index-template.js +6 -2
- package/helpers/file-system.js +28 -0
- package/helpers/schema-doc-template.js +7 -1
- package/helpers/schemaToTableData.js +68 -7
- package/package.json +1 -1
|
@@ -1,17 +1,21 @@
|
|
|
1
|
+
/* eslint-disable @docusaurus/no-html-links */
|
|
1
2
|
import '@testing-library/jest-dom';
|
|
2
3
|
import React from 'react';
|
|
3
|
-
import { render } from '@testing-library/react';
|
|
4
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
4
5
|
import SchemaJsonViewer from '../../components/SchemaJsonViewer';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return
|
|
10
|
-
|
|
11
|
-
}
|
|
7
|
+
jest.mock(
|
|
8
|
+
'@docusaurus/Link',
|
|
9
|
+
() => {
|
|
10
|
+
return function DocusaurusLink({ children, ...props }) {
|
|
11
|
+
return <a {...props}>{children}</a>;
|
|
12
|
+
};
|
|
13
|
+
},
|
|
14
|
+
{ virtual: true },
|
|
15
|
+
);
|
|
12
16
|
|
|
13
17
|
describe('SchemaJsonViewer', () => {
|
|
14
|
-
it('renders the schema in a
|
|
18
|
+
it('renders the schema in a syntax-highlighted pre block', () => {
|
|
15
19
|
const schema = {
|
|
16
20
|
type: 'object',
|
|
17
21
|
properties: {
|
|
@@ -29,8 +33,70 @@ describe('SchemaJsonViewer', () => {
|
|
|
29
33
|
const codeBlockElement = container.querySelector('pre');
|
|
30
34
|
expect(codeBlockElement).toBeInTheDocument();
|
|
31
35
|
expect(codeBlockElement).toHaveAttribute('data-language', 'json');
|
|
32
|
-
expect(codeBlockElement.textContent).toEqual(
|
|
33
|
-
JSON.stringify(schema, null, 2),
|
|
36
|
+
expect(codeBlockElement.textContent.replace(/\s+/g, '')).toEqual(
|
|
37
|
+
JSON.stringify(schema, null, 2).replace(/\s+/g, ''),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('navigates local $ref values inside the viewer and resets to root', () => {
|
|
42
|
+
const rootSchema = {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
component: { $ref: './components/referenced.json' },
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
const referencedSchema = {
|
|
49
|
+
title: 'Referenced Component',
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
prop: { type: 'string' },
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
render(
|
|
57
|
+
<SchemaJsonViewer
|
|
58
|
+
schema={rootSchema}
|
|
59
|
+
sourcePath="main-schema.json"
|
|
60
|
+
schemaSources={{
|
|
61
|
+
'main-schema.json': rootSchema,
|
|
62
|
+
'components/referenced.json': referencedSchema,
|
|
63
|
+
}}
|
|
64
|
+
/>,
|
|
34
65
|
);
|
|
66
|
+
|
|
67
|
+
fireEvent.click(screen.getByText('View Raw JSON Schema'));
|
|
68
|
+
fireEvent.click(
|
|
69
|
+
screen.getByRole('button', { name: '"./components/referenced.json"' }),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(screen.getAllByText(/Referenced Component/).length).toBeGreaterThan(
|
|
73
|
+
0,
|
|
74
|
+
);
|
|
75
|
+
expect(
|
|
76
|
+
screen.getByRole('button', { name: 'Back to root' }),
|
|
77
|
+
).toBeInTheDocument();
|
|
78
|
+
|
|
79
|
+
fireEvent.click(screen.getByRole('button', { name: 'Back to root' }));
|
|
80
|
+
|
|
81
|
+
expect(
|
|
82
|
+
screen.getByText('"./components/referenced.json"'),
|
|
83
|
+
).toBeInTheDocument();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('renders external $ref values as new-tab links', () => {
|
|
87
|
+
const schema = {
|
|
88
|
+
$ref: 'https://example.com/schema.json',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
render(<SchemaJsonViewer schema={schema} sourcePath="root.json" />);
|
|
92
|
+
|
|
93
|
+
fireEvent.click(screen.getByText('View Raw JSON Schema'));
|
|
94
|
+
const refLink = screen.getByRole('link', {
|
|
95
|
+
name: '"https://example.com/schema.json"',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(refLink).toHaveAttribute('href', 'https://example.com/schema.json');
|
|
99
|
+
expect(refLink).toHaveAttribute('target', '_blank');
|
|
100
|
+
expect(refLink).toHaveAttribute('rel', 'noreferrer');
|
|
35
101
|
});
|
|
36
102
|
});
|
|
@@ -7,7 +7,9 @@ import SchemaRows from '../../components/SchemaRows';
|
|
|
7
7
|
jest.mock('../../components/PropertyRow', () => {
|
|
8
8
|
const MockPropertyRow = (props) => (
|
|
9
9
|
<tr>
|
|
10
|
-
<td>
|
|
10
|
+
<td>
|
|
11
|
+
Mocked PropertyRow: {props.row.name} ({props.stripeIndex})
|
|
12
|
+
</td>
|
|
11
13
|
</tr>
|
|
12
14
|
);
|
|
13
15
|
MockPropertyRow.displayName = 'MockPropertyRow';
|
|
@@ -17,7 +19,10 @@ jest.mock('../../components/PropertyRow', () => {
|
|
|
17
19
|
jest.mock('../../components/FoldableRows', () => {
|
|
18
20
|
const MockFoldableRows = (props) => (
|
|
19
21
|
<tr>
|
|
20
|
-
<td>
|
|
22
|
+
<td>
|
|
23
|
+
Mocked FoldableRows: {props.row.choiceType} ({props.stripeIndex},{' '}
|
|
24
|
+
{String(!!props.stripeState)})
|
|
25
|
+
</td>
|
|
21
26
|
</tr>
|
|
22
27
|
);
|
|
23
28
|
MockFoldableRows.displayName = 'MockFoldableRows';
|
|
@@ -27,7 +32,10 @@ jest.mock('../../components/FoldableRows', () => {
|
|
|
27
32
|
jest.mock('../../components/ConditionalRows', () => {
|
|
28
33
|
const MockConditionalRows = (props) => (
|
|
29
34
|
<tr>
|
|
30
|
-
<td>
|
|
35
|
+
<td>
|
|
36
|
+
Mocked ConditionalRows: {props.row.condition.title} ({props.stripeIndex}
|
|
37
|
+
, {String(!!props.stripeState)})
|
|
38
|
+
</td>
|
|
31
39
|
</tr>
|
|
32
40
|
);
|
|
33
41
|
MockConditionalRows.displayName = 'MockConditionalRows';
|
|
@@ -49,8 +57,8 @@ describe('SchemaRows', () => {
|
|
|
49
57
|
</table>,
|
|
50
58
|
);
|
|
51
59
|
|
|
52
|
-
expect(getByText('Mocked PropertyRow: name')).toBeInTheDocument();
|
|
53
|
-
expect(getByText('Mocked PropertyRow: age')).toBeInTheDocument();
|
|
60
|
+
expect(getByText('Mocked PropertyRow: name (0)')).toBeInTheDocument();
|
|
61
|
+
expect(getByText('Mocked PropertyRow: age (1)')).toBeInTheDocument();
|
|
54
62
|
});
|
|
55
63
|
|
|
56
64
|
it('renders nested properties from a flat list', () => {
|
|
@@ -68,8 +76,8 @@ describe('SchemaRows', () => {
|
|
|
68
76
|
);
|
|
69
77
|
|
|
70
78
|
// It should render both the parent and child property from the flat list
|
|
71
|
-
expect(getByText('Mocked PropertyRow: user')).toBeInTheDocument();
|
|
72
|
-
expect(getByText('Mocked PropertyRow: id')).toBeInTheDocument();
|
|
79
|
+
expect(getByText('Mocked PropertyRow: user (0)')).toBeInTheDocument();
|
|
80
|
+
expect(getByText('Mocked PropertyRow: id (1)')).toBeInTheDocument();
|
|
73
81
|
});
|
|
74
82
|
|
|
75
83
|
it('renders a FoldableRows for choice type items in tableData', () => {
|
|
@@ -90,7 +98,9 @@ describe('SchemaRows', () => {
|
|
|
90
98
|
</table>,
|
|
91
99
|
);
|
|
92
100
|
|
|
93
|
-
expect(
|
|
101
|
+
expect(
|
|
102
|
+
getByText('Mocked FoldableRows: oneOf (0, true)'),
|
|
103
|
+
).toBeInTheDocument();
|
|
94
104
|
});
|
|
95
105
|
|
|
96
106
|
it('renders a mix of properties and choices', () => {
|
|
@@ -113,9 +123,11 @@ describe('SchemaRows', () => {
|
|
|
113
123
|
</table>,
|
|
114
124
|
);
|
|
115
125
|
|
|
116
|
-
expect(getByText('Mocked PropertyRow: prop1')).toBeInTheDocument();
|
|
117
|
-
expect(
|
|
118
|
-
|
|
126
|
+
expect(getByText('Mocked PropertyRow: prop1 (0)')).toBeInTheDocument();
|
|
127
|
+
expect(
|
|
128
|
+
getByText('Mocked FoldableRows: anyOf (1, true)'),
|
|
129
|
+
).toBeInTheDocument();
|
|
130
|
+
expect(getByText('Mocked PropertyRow: prop2 (2)')).toBeInTheDocument();
|
|
119
131
|
});
|
|
120
132
|
|
|
121
133
|
it('renders a ConditionalRows for conditional type items in tableData', () => {
|
|
@@ -136,6 +148,44 @@ describe('SchemaRows', () => {
|
|
|
136
148
|
</table>,
|
|
137
149
|
);
|
|
138
150
|
|
|
139
|
-
expect(
|
|
151
|
+
expect(
|
|
152
|
+
getByText('Mocked ConditionalRows: If (0, true)'),
|
|
153
|
+
).toBeInTheDocument();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('increments stripe indices across logical rows', () => {
|
|
157
|
+
const tableData = [
|
|
158
|
+
{ type: 'property', name: 'prop1', path: ['prop1'] },
|
|
159
|
+
{
|
|
160
|
+
type: 'choice',
|
|
161
|
+
choiceType: 'anyOf',
|
|
162
|
+
path: ['choice'],
|
|
163
|
+
options: [],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
type: 'conditional',
|
|
167
|
+
path: ['if/then/else'],
|
|
168
|
+
condition: { title: 'If', rows: [] },
|
|
169
|
+
branches: [],
|
|
170
|
+
},
|
|
171
|
+
{ type: 'property', name: 'prop2', path: ['prop2'] },
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
const { getByText } = render(
|
|
175
|
+
<table>
|
|
176
|
+
<tbody>
|
|
177
|
+
<SchemaRows tableData={tableData} />
|
|
178
|
+
</tbody>
|
|
179
|
+
</table>,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
expect(getByText('Mocked PropertyRow: prop1 (0)')).toBeInTheDocument();
|
|
183
|
+
expect(
|
|
184
|
+
getByText('Mocked FoldableRows: anyOf (1, true)'),
|
|
185
|
+
).toBeInTheDocument();
|
|
186
|
+
expect(
|
|
187
|
+
getByText('Mocked ConditionalRows: If (2, true)'),
|
|
188
|
+
).toBeInTheDocument();
|
|
189
|
+
expect(getByText('Mocked PropertyRow: prop2 (3)')).toBeInTheDocument();
|
|
140
190
|
});
|
|
141
191
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
|
2
2
|
|
|
3
|
-
exports[`connector lines visual regressions keeps cvv row open when payment choice has following options 1`] = `"<td rowspan="1" style="padding-left: 1.75rem;" class="property-cell level-1"><span class="property-name"><strong>cvv</strong></span></td>"`;
|
|
3
|
+
exports[`connector lines visual regressions keeps cvv row open when payment choice has following options 1`] = `"<td rowspan="1" style="padding-left: 1.75rem;" class="property-cell property-cell--tree level-1"><span class="property-name"><strong>cvv</strong></span></td>"`;
|
|
4
4
|
|
|
5
|
-
exports[`connector lines visual regressions keeps user_id option row open when user conditional follows 1`] = `"<td rowspan="1" style="padding-left: 1.75rem;" class="property-cell level-1"><span class="property-name"><strong>user_id</strong></span></td>"`;
|
|
5
|
+
exports[`connector lines visual regressions keeps user_id option row open when user conditional follows 1`] = `"<td rowspan="1" style="padding-left: 1.75rem;" class="property-cell property-cell--tree level-1"><span class="property-name"><strong>user_id</strong></span></td>"`;
|
|
6
6
|
|
|
7
|
-
exports[`connector lines visual regressions keeps wallet_provider option row open when wallet_email follows 1`] = `"<td rowspan="2" style="padding-left: 1.75rem;" class="property-cell level-1"><span class="property-name"><strong>wallet_provider</strong></span></td>"`;
|
|
7
|
+
exports[`connector lines visual regressions keeps wallet_provider option row open when wallet_email follows 1`] = `"<td rowspan="2" style="padding-left: 1.75rem;" class="property-cell property-cell--required property-cell--tree level-1"><span class="property-name"><strong>wallet_provider</strong></span></td>"`;
|
|
@@ -56,6 +56,9 @@ describe('generateEventDocs (non-versioned)', () => {
|
|
|
56
56
|
'utf-8',
|
|
57
57
|
);
|
|
58
58
|
expect(addToCart).toMatchSnapshot();
|
|
59
|
+
expect(addToCart).toContain('sourcePath={"add-to-cart-event.json"}');
|
|
60
|
+
expect(addToCart).toContain(JSON.stringify('components/product.json'));
|
|
61
|
+
expect(addToCart).toContain('"$ref":"./components/product.json"');
|
|
59
62
|
|
|
60
63
|
const choiceEvent = fs.readFileSync(
|
|
61
64
|
path.join(outputDir, 'choice-event.mdx'),
|
|
@@ -217,6 +217,118 @@ describe('schemaToTableData', () => {
|
|
|
217
217
|
expect(tableData[0].examples).toEqual(['default-value']);
|
|
218
218
|
});
|
|
219
219
|
|
|
220
|
+
it('renders schema-valued additionalProperties as a synthetic row', () => {
|
|
221
|
+
const schema = {
|
|
222
|
+
properties: {
|
|
223
|
+
metadata: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
description: 'Free-form metadata.',
|
|
226
|
+
additionalProperties: {
|
|
227
|
+
type: 'string',
|
|
228
|
+
description: 'Metadata value.',
|
|
229
|
+
pattern: '^[a-z]+$',
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const tableData = schemaToTableData(schema);
|
|
236
|
+
|
|
237
|
+
const metadataRow = tableData.find((row) => row.name === 'metadata');
|
|
238
|
+
expect(metadataRow).toBeDefined();
|
|
239
|
+
expect(metadataRow.containerType).toBe('object');
|
|
240
|
+
|
|
241
|
+
const additionalPropertiesRow = tableData.find(
|
|
242
|
+
(row) =>
|
|
243
|
+
row.type === 'property' &&
|
|
244
|
+
row.name === 'additionalProperties' &&
|
|
245
|
+
row.level === 1,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
expect(additionalPropertiesRow).toBeDefined();
|
|
249
|
+
expect(additionalPropertiesRow.propertyType).toBe('string');
|
|
250
|
+
expect(additionalPropertiesRow.description).toBe('Metadata value.');
|
|
251
|
+
expect(additionalPropertiesRow.constraints).toContain(
|
|
252
|
+
'pattern: /^[a-z]+$/',
|
|
253
|
+
);
|
|
254
|
+
expect(additionalPropertiesRow.isSchemaKeywordRow).toBe(true);
|
|
255
|
+
expect(additionalPropertiesRow.keepConnectorOpen).toBe(false);
|
|
256
|
+
expect(additionalPropertiesRow.isLastInGroup).toBe(true);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('treats additionalProperties as the last sibling after declared properties', () => {
|
|
260
|
+
const schema = {
|
|
261
|
+
properties: {
|
|
262
|
+
user_properties: {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: {
|
|
265
|
+
allow_ad_personalization_signals: {
|
|
266
|
+
type: ['string', 'null'],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
additionalProperties: {
|
|
270
|
+
type: ['string', 'null'],
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const tableData = schemaToTableData(schema);
|
|
277
|
+
|
|
278
|
+
const explicitPropertyRow = tableData.find(
|
|
279
|
+
(row) =>
|
|
280
|
+
row.name === 'allow_ad_personalization_signals' && row.level === 1,
|
|
281
|
+
);
|
|
282
|
+
const additionalPropertiesRow = tableData.find(
|
|
283
|
+
(row) => row.name === 'additionalProperties' && row.level === 1,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
expect(explicitPropertyRow).toBeDefined();
|
|
287
|
+
expect(additionalPropertiesRow).toBeDefined();
|
|
288
|
+
expect(explicitPropertyRow.isLastInGroup).toBe(false);
|
|
289
|
+
expect(additionalPropertiesRow.isLastInGroup).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('renders schema-valued patternProperties as synthetic keyword rows', () => {
|
|
293
|
+
const schema = {
|
|
294
|
+
properties: {
|
|
295
|
+
attributes: {
|
|
296
|
+
type: 'object',
|
|
297
|
+
properties: {
|
|
298
|
+
segment: { type: 'string' },
|
|
299
|
+
},
|
|
300
|
+
patternProperties: {
|
|
301
|
+
'^custom_': {
|
|
302
|
+
type: 'number',
|
|
303
|
+
description: 'Numeric custom attribute values.',
|
|
304
|
+
minimum: 0,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const tableData = schemaToTableData(schema);
|
|
312
|
+
|
|
313
|
+
const explicitPropertyRow = tableData.find(
|
|
314
|
+
(row) => row.name === 'segment' && row.level === 1,
|
|
315
|
+
);
|
|
316
|
+
const patternPropertyRow = tableData.find(
|
|
317
|
+
(row) => row.name === 'patternProperties /^custom_/' && row.level === 1,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
expect(explicitPropertyRow).toBeDefined();
|
|
321
|
+
expect(patternPropertyRow).toBeDefined();
|
|
322
|
+
expect(explicitPropertyRow.isLastInGroup).toBe(false);
|
|
323
|
+
expect(patternPropertyRow.isLastInGroup).toBe(true);
|
|
324
|
+
expect(patternPropertyRow.propertyType).toBe('number');
|
|
325
|
+
expect(patternPropertyRow.description).toBe(
|
|
326
|
+
'Numeric custom attribute values.',
|
|
327
|
+
);
|
|
328
|
+
expect(patternPropertyRow.constraints).toContain('minimum: 0');
|
|
329
|
+
expect(patternPropertyRow.isSchemaKeywordRow).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
|
|
220
332
|
describe('if/then/else conditional support', () => {
|
|
221
333
|
it('creates a conditional row for schema with if/then/else at root level', () => {
|
|
222
334
|
const tableData = schemaToTableData(conditionalEventSchema);
|
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
*/
|
|
18
18
|
export default function ConditionalRows({
|
|
19
19
|
row,
|
|
20
|
+
stripeIndex = 0,
|
|
21
|
+
stripeState,
|
|
20
22
|
bracketEnds: parentBracketEnds,
|
|
21
23
|
}) {
|
|
22
24
|
const {
|
|
@@ -91,7 +93,7 @@ export default function ConditionalRows({
|
|
|
91
93
|
return (
|
|
92
94
|
<>
|
|
93
95
|
{/* Condition (if) section - always visible */}
|
|
94
|
-
<tr className="conditional-condition-header">
|
|
96
|
+
<tr className="conditional-condition-header schema-row--control">
|
|
95
97
|
<td colSpan={5} style={headerStyle}>
|
|
96
98
|
<span className="conditional-condition-label">
|
|
97
99
|
<span className="conditional-info-icon-wrapper">
|
|
@@ -111,7 +113,7 @@ export default function ConditionalRows({
|
|
|
111
113
|
)}
|
|
112
114
|
</td>
|
|
113
115
|
</tr>
|
|
114
|
-
<SchemaRows tableData={condition.rows} />
|
|
116
|
+
<SchemaRows tableData={condition.rows} stripeState={stripeState} />
|
|
115
117
|
|
|
116
118
|
{/* Branch toggles (then / else) */}
|
|
117
119
|
{branches.map((branch, index) => {
|
|
@@ -121,7 +123,7 @@ export default function ConditionalRows({
|
|
|
121
123
|
isLastBranch && !isActive ? lastToggleStyle : middleStyle;
|
|
122
124
|
return (
|
|
123
125
|
<React.Fragment key={branch.title}>
|
|
124
|
-
<tr className="choice-row">
|
|
126
|
+
<tr className="choice-row schema-row--control">
|
|
125
127
|
<td colSpan={5} style={toggleStyle}>
|
|
126
128
|
<label className="choice-row-header">
|
|
127
129
|
<input
|
|
@@ -141,6 +143,7 @@ export default function ConditionalRows({
|
|
|
141
143
|
{isActive && (
|
|
142
144
|
<SchemaRows
|
|
143
145
|
tableData={branch.rows}
|
|
146
|
+
stripeState={stripeState}
|
|
144
147
|
bracketEnds={
|
|
145
148
|
isLastBranch
|
|
146
149
|
? [ownBracket, ...(parentBracketEnds || [])]
|
|
@@ -18,7 +18,7 @@ const ChoiceRow = ({
|
|
|
18
18
|
name,
|
|
19
19
|
continuingLinesStyle,
|
|
20
20
|
}) => (
|
|
21
|
-
<tr className="choice-row">
|
|
21
|
+
<tr className="choice-row schema-row--control">
|
|
22
22
|
<td colSpan={5} style={continuingLinesStyle}>
|
|
23
23
|
<label className="choice-row-header">
|
|
24
24
|
<input
|
|
@@ -41,7 +41,12 @@ const ChoiceRow = ({
|
|
|
41
41
|
* Renders 'oneOf' and 'anyOf' choices as a set of foldable `<tr>` elements
|
|
42
42
|
* that integrate directly into the main table body.
|
|
43
43
|
*/
|
|
44
|
-
export default function FoldableRows({
|
|
44
|
+
export default function FoldableRows({
|
|
45
|
+
row,
|
|
46
|
+
stripeIndex = 0,
|
|
47
|
+
stripeState,
|
|
48
|
+
bracketEnds: parentBracketEnds,
|
|
49
|
+
}) {
|
|
45
50
|
const {
|
|
46
51
|
choiceType,
|
|
47
52
|
options,
|
|
@@ -136,7 +141,7 @@ export default function FoldableRows({ row, bracketEnds: parentBracketEnds }) {
|
|
|
136
141
|
return (
|
|
137
142
|
<>
|
|
138
143
|
{/* A header row for the entire choice block */}
|
|
139
|
-
<tr>
|
|
144
|
+
<tr className="schema-row--control">
|
|
140
145
|
<td colSpan={5} style={headerStyle}>
|
|
141
146
|
<Heading as="h4" className="choice-row-header-headline">
|
|
142
147
|
{header}
|
|
@@ -176,6 +181,7 @@ export default function FoldableRows({ row, bracketEnds: parentBracketEnds }) {
|
|
|
176
181
|
{isActive && (
|
|
177
182
|
<SchemaRows
|
|
178
183
|
tableData={option.rows}
|
|
184
|
+
stripeState={stripeState}
|
|
179
185
|
bracketEnds={
|
|
180
186
|
isLastOption
|
|
181
187
|
? [ownBracket, ...(parentBracketEnds || [])]
|
|
@@ -8,6 +8,7 @@ import styles from './PropertiesTable.module.css';
|
|
|
8
8
|
export default function PropertiesTable({ schema }) {
|
|
9
9
|
const [isWordWrapOn, setIsWordWrapOn] = useState(true);
|
|
10
10
|
const tableData = schemaToTableData(schema);
|
|
11
|
+
const stripeState = { current: 0 };
|
|
11
12
|
|
|
12
13
|
return (
|
|
13
14
|
<div
|
|
@@ -24,7 +25,7 @@ export default function PropertiesTable({ schema }) {
|
|
|
24
25
|
<table className="schema-table">
|
|
25
26
|
<TableHeader />
|
|
26
27
|
<tbody>
|
|
27
|
-
<SchemaRows tableData={tableData} />
|
|
28
|
+
<SchemaRows tableData={tableData} stripeState={stripeState} />
|
|
28
29
|
</tbody>
|
|
29
30
|
</table>
|
|
30
31
|
</div>
|
|
@@ -26,12 +26,36 @@ const getContainerSymbol = (containerType) => {
|
|
|
26
26
|
return '';
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
const KEYWORD_HELP_TEXT = {
|
|
30
|
+
additionalProperties:
|
|
31
|
+
'Controls properties not listed in properties and not matched by patternProperties.',
|
|
32
|
+
patternProperties:
|
|
33
|
+
'Applies the subschema to property names that match the given regular expression.',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function splitKeywordLabel(name) {
|
|
37
|
+
const match = /^patternProperties (\/.+\/)$/.exec(name);
|
|
38
|
+
if (!match) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
keyword: 'patternProperties',
|
|
44
|
+
pattern: match[1],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
/**
|
|
30
49
|
* Renders a single property row in the schema table.
|
|
31
50
|
* All data is passed in via the `row` prop, which comes from `tableData`.
|
|
32
51
|
* This component handles multi-row constraints using `rowSpan`.
|
|
33
52
|
*/
|
|
34
|
-
export default function PropertyRow({
|
|
53
|
+
export default function PropertyRow({
|
|
54
|
+
row,
|
|
55
|
+
stripeIndex,
|
|
56
|
+
isLastInGroup,
|
|
57
|
+
bracketEnds,
|
|
58
|
+
}) {
|
|
35
59
|
const {
|
|
36
60
|
name,
|
|
37
61
|
level,
|
|
@@ -44,6 +68,8 @@ export default function PropertyRow({ row, isLastInGroup, bracketEnds }) {
|
|
|
44
68
|
containerType,
|
|
45
69
|
continuingLevels = [],
|
|
46
70
|
groupBrackets = [],
|
|
71
|
+
isSchemaKeywordRow = false,
|
|
72
|
+
keepConnectorOpen = false,
|
|
47
73
|
} = row;
|
|
48
74
|
|
|
49
75
|
const indentStyle = {
|
|
@@ -112,11 +138,27 @@ export default function PropertyRow({ row, isLastInGroup, bracketEnds }) {
|
|
|
112
138
|
const bracketStyle = getBracketLinesStyle(groupBrackets, bracketCaps);
|
|
113
139
|
|
|
114
140
|
const containerSymbol = getContainerSymbol(containerType);
|
|
141
|
+
const shouldCloseConnector = isLastInGroup && !keepConnectorOpen;
|
|
142
|
+
const splitKeyword = splitKeywordLabel(name);
|
|
143
|
+
const keywordHelpKey = name.startsWith('patternProperties ')
|
|
144
|
+
? 'patternProperties'
|
|
145
|
+
: name;
|
|
146
|
+
const keywordHelpText = KEYWORD_HELP_TEXT[keywordHelpKey];
|
|
147
|
+
const keywordHelpId = keywordHelpText
|
|
148
|
+
? `schema-keyword-help-${name}`
|
|
149
|
+
: undefined;
|
|
150
|
+
const zebraClassName =
|
|
151
|
+
stripeIndex === undefined
|
|
152
|
+
? undefined
|
|
153
|
+
: stripeIndex % 2 === 0
|
|
154
|
+
? 'schema-row--zebra-even'
|
|
155
|
+
: 'schema-row--zebra-odd';
|
|
115
156
|
|
|
116
157
|
return (
|
|
117
158
|
<>
|
|
118
159
|
<tr
|
|
119
160
|
className={clsx(
|
|
161
|
+
zebraClassName,
|
|
120
162
|
required && 'required-row',
|
|
121
163
|
row.isCondition && 'conditional-condition-row',
|
|
122
164
|
)}
|
|
@@ -126,17 +168,57 @@ export default function PropertyRow({ row, isLastInGroup, bracketEnds }) {
|
|
|
126
168
|
style={{ ...indentStyle, ...continuingLinesStyle }}
|
|
127
169
|
className={clsx(
|
|
128
170
|
'property-cell',
|
|
171
|
+
isSchemaKeywordRow && 'property-cell--keyword',
|
|
172
|
+
required && 'property-cell--required',
|
|
173
|
+
level > 0 && 'property-cell--tree',
|
|
129
174
|
level > 0 && `level-${level}`,
|
|
130
|
-
|
|
175
|
+
shouldCloseConnector && 'is-last',
|
|
131
176
|
hasChildren && 'has-children',
|
|
132
177
|
containerType && `container-${containerType}`,
|
|
133
178
|
)}
|
|
134
179
|
>
|
|
135
|
-
<span
|
|
180
|
+
<span
|
|
181
|
+
className={clsx(
|
|
182
|
+
'property-name',
|
|
183
|
+
isSchemaKeywordRow && 'property-name--keyword',
|
|
184
|
+
)}
|
|
185
|
+
>
|
|
136
186
|
{containerSymbol && (
|
|
137
187
|
<span className="container-symbol">{containerSymbol}</span>
|
|
138
188
|
)}
|
|
139
|
-
|
|
189
|
+
{isSchemaKeywordRow ? (
|
|
190
|
+
<span className="property-keyword-wrapper">
|
|
191
|
+
{splitKeyword ? (
|
|
192
|
+
<span className="property-keyword-stack">
|
|
193
|
+
<code
|
|
194
|
+
className="property-keyword"
|
|
195
|
+
aria-describedby={keywordHelpId}
|
|
196
|
+
>
|
|
197
|
+
{splitKeyword.keyword}
|
|
198
|
+
</code>
|
|
199
|
+
<code className="property-keyword-pattern">
|
|
200
|
+
{splitKeyword.pattern}
|
|
201
|
+
</code>
|
|
202
|
+
</span>
|
|
203
|
+
) : (
|
|
204
|
+
<code
|
|
205
|
+
className="property-keyword"
|
|
206
|
+
aria-describedby={keywordHelpId}
|
|
207
|
+
>
|
|
208
|
+
{name}
|
|
209
|
+
</code>
|
|
210
|
+
)}
|
|
211
|
+
<span
|
|
212
|
+
id={keywordHelpId}
|
|
213
|
+
className="property-keyword-tooltip"
|
|
214
|
+
role="tooltip"
|
|
215
|
+
>
|
|
216
|
+
{keywordHelpText}
|
|
217
|
+
</span>
|
|
218
|
+
</span>
|
|
219
|
+
) : (
|
|
220
|
+
<strong>{name}</strong>
|
|
221
|
+
)}
|
|
140
222
|
</span>
|
|
141
223
|
</td>
|
|
142
224
|
<td rowSpan={rowSpan}>
|
|
@@ -181,7 +263,10 @@ export default function PropertyRow({ row, isLastInGroup, bracketEnds }) {
|
|
|
181
263
|
|
|
182
264
|
{/* Render subsequent constraints in their own rows */}
|
|
183
265
|
{remainingConstraints.map((constraint) => (
|
|
184
|
-
<tr
|
|
266
|
+
<tr
|
|
267
|
+
className={clsx(zebraClassName, required && 'required-row')}
|
|
268
|
+
key={constraint}
|
|
269
|
+
>
|
|
185
270
|
<td className="constraint-cell">
|
|
186
271
|
<code
|
|
187
272
|
className={clsx(
|