docusaurus-plugin-generate-schema-docs 1.8.3 → 1.8.5
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/README.md +12 -0
- package/__tests__/__fixtures__/validateSchemas/main-schema-with-not-allof.json +11 -0
- package/__tests__/__fixtures__/validateSchemas/schema-with-not-anyof-multi.json +12 -0
- package/__tests__/__fixtures__/validateSchemas/schema-with-not-anyof.json +30 -0
- package/__tests__/__fixtures__/validateSchemas/schema-with-not-edge-cases.json +24 -0
- package/__tests__/__fixtures__/validateSchemas/schema-with-not-non-object.json +15 -0
- package/__tests__/__snapshots__/generateEventDocs.anchor.test.js.snap +6 -0
- package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +6 -0
- package/__tests__/__snapshots__/generateEventDocs.test.js.snap +15 -0
- package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +6 -0
- package/__tests__/components/PropertiesTable.test.js +66 -0
- package/__tests__/components/PropertyRow.test.js +85 -4
- package/__tests__/components/SchemaJsonViewer.test.js +118 -0
- package/__tests__/generateEventDocs.anchor.test.js +1 -1
- package/__tests__/generateEventDocs.nested.test.js +1 -1
- package/__tests__/generateEventDocs.partials.test.js +1 -1
- package/__tests__/generateEventDocs.test.js +506 -1
- package/__tests__/generateEventDocs.versioned.test.js +1 -1
- package/__tests__/helpers/buildExampleFromSchema.test.js +240 -0
- package/__tests__/helpers/constraintSchemaPaths.test.js +208 -0
- package/__tests__/helpers/continuingLinesStyle.test.js +492 -0
- package/__tests__/helpers/example-helper.test.js +12 -0
- package/__tests__/helpers/exampleModel.test.js +209 -0
- package/__tests__/helpers/file-system.test.js +73 -1
- package/__tests__/helpers/getConstraints.test.js +43 -0
- package/__tests__/helpers/mergeSchema.test.js +94 -0
- package/__tests__/helpers/processSchema.test.js +309 -1
- package/__tests__/helpers/schema-doc-template.test.js +54 -0
- package/__tests__/helpers/schema-processing.test.js +122 -2
- package/__tests__/helpers/schemaToExamples.test.js +1007 -0
- package/__tests__/helpers/schemaToTableData.mutations.test.js +970 -0
- package/__tests__/helpers/schemaToTableData.test.js +157 -0
- package/__tests__/helpers/schemaTraversal.test.js +110 -0
- package/__tests__/helpers/snippetTargets.test.js +432 -0
- package/__tests__/helpers/trackingTargets.test.js +319 -0
- package/__tests__/helpers/validator.test.js +385 -1
- package/__tests__/index.test.js +436 -0
- package/__tests__/syncGtm.test.js +366 -6
- package/__tests__/update-schema-ids.test.js +70 -1
- package/__tests__/validateSchemas-integration.test.js +2 -2
- package/__tests__/validateSchemas.test.js +192 -1
- package/components/PropertiesTable.js +32 -2
- package/components/PropertyRow.js +29 -2
- package/components/SchemaJsonViewer.js +234 -131
- package/components/SchemaRows.css +40 -0
- package/components/SchemaViewer.js +11 -2
- package/generateEventDocs.js +21 -1
- package/helpers/constraintSchemaPaths.js +10 -14
- package/helpers/example-helper.js +2 -2
- package/helpers/getConstraints.js +20 -0
- package/helpers/processSchema.js +32 -1
- package/helpers/schema-doc-template.js +4 -0
- package/helpers/schemaToExamples.js +29 -35
- package/helpers/schemaToTableData.js +538 -492
- package/helpers/schemaTraversal.cjs +148 -0
- package/helpers/trackingTargets.js +26 -3
- package/helpers/validator.js +18 -4
- package/index.js +1 -2
- package/package.json +1 -1
- package/scripts/sync-gtm.js +65 -34
- package/test-data/payloadContracts.js +35 -0
- package/validateSchemas.js +1 -1
|
@@ -14,6 +14,29 @@ jest.mock(
|
|
|
14
14
|
{ virtual: true },
|
|
15
15
|
);
|
|
16
16
|
|
|
17
|
+
jest.mock('@docusaurus/theme-common', () => ({
|
|
18
|
+
usePrismTheme: () => ({
|
|
19
|
+
plain: {
|
|
20
|
+
color: 'rgb(1, 2, 3)',
|
|
21
|
+
backgroundColor: 'rgb(4, 5, 6)',
|
|
22
|
+
},
|
|
23
|
+
styles: [
|
|
24
|
+
{
|
|
25
|
+
types: ['property'],
|
|
26
|
+
style: { color: 'rgb(0, 0, 255)' },
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
types: ['string'],
|
|
30
|
+
style: { color: 'rgb(255, 0, 0)' },
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
types: ['number'],
|
|
34
|
+
style: { color: 'rgb(0, 128, 0)' },
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
}),
|
|
38
|
+
}));
|
|
39
|
+
|
|
17
40
|
describe('SchemaJsonViewer', () => {
|
|
18
41
|
it('renders the schema in a syntax-highlighted pre block', () => {
|
|
19
42
|
const schema = {
|
|
@@ -38,6 +61,22 @@ describe('SchemaJsonViewer', () => {
|
|
|
38
61
|
);
|
|
39
62
|
});
|
|
40
63
|
|
|
64
|
+
it('uses the prism theme for base json token styling', () => {
|
|
65
|
+
const schema = {
|
|
66
|
+
type: 'string',
|
|
67
|
+
examples: ['x'],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
render(<SchemaJsonViewer schema={schema} />);
|
|
71
|
+
|
|
72
|
+
fireEvent.click(screen.getByText('View Raw JSON Schema'));
|
|
73
|
+
|
|
74
|
+
expect(screen.getByText('"type"')).toHaveStyle({ color: 'rgb(0, 0, 255)' });
|
|
75
|
+
expect(screen.getByText('"string"')).toHaveStyle({
|
|
76
|
+
color: 'rgb(255, 0, 0)',
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
41
80
|
it('navigates local $ref values inside the viewer and resets to root', () => {
|
|
42
81
|
const rootSchema = {
|
|
43
82
|
type: 'object',
|
|
@@ -83,6 +122,84 @@ describe('SchemaJsonViewer', () => {
|
|
|
83
122
|
).toBeInTheDocument();
|
|
84
123
|
});
|
|
85
124
|
|
|
125
|
+
it('highlights schema meta keywords inside the raw json view', () => {
|
|
126
|
+
const schema = {
|
|
127
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
128
|
+
$id: 'https://example.com/event.json',
|
|
129
|
+
$anchor: 'eventRoot',
|
|
130
|
+
$comment: 'Internal authoring note',
|
|
131
|
+
$vocabulary: {
|
|
132
|
+
'https://json-schema.org/draft/2020-12/vocab/validation': true,
|
|
133
|
+
},
|
|
134
|
+
type: 'object',
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
render(<SchemaJsonViewer schema={schema} sourcePath="event.json" />);
|
|
138
|
+
|
|
139
|
+
fireEvent.click(screen.getByText('View Raw JSON Schema'));
|
|
140
|
+
|
|
141
|
+
expect(screen.getByText('"$schema"')).toHaveClass(
|
|
142
|
+
'schema-json-viewer__keyword',
|
|
143
|
+
'schema-json-viewer__keyword--meta',
|
|
144
|
+
);
|
|
145
|
+
expect(screen.getByText('"$anchor"')).toHaveClass(
|
|
146
|
+
'schema-json-viewer__keyword',
|
|
147
|
+
'schema-json-viewer__keyword--meta',
|
|
148
|
+
);
|
|
149
|
+
expect(screen.getByText('"$vocabulary"')).toHaveClass(
|
|
150
|
+
'schema-json-viewer__keyword',
|
|
151
|
+
'schema-json-viewer__keyword--meta',
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('highlights structural schema keywords inside the raw json view', () => {
|
|
156
|
+
const schema = {
|
|
157
|
+
type: 'object',
|
|
158
|
+
allOf: [{ $ref: './component.json' }],
|
|
159
|
+
properties: {
|
|
160
|
+
name: { type: 'string' },
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
render(<SchemaJsonViewer schema={schema} sourcePath="event.json" />);
|
|
165
|
+
|
|
166
|
+
fireEvent.click(screen.getByText('View Raw JSON Schema'));
|
|
167
|
+
|
|
168
|
+
expect(screen.getByText('"allOf"')).toHaveClass(
|
|
169
|
+
'schema-json-viewer__keyword',
|
|
170
|
+
'schema-json-viewer__keyword--structural',
|
|
171
|
+
);
|
|
172
|
+
expect(screen.getByText('"properties"')).toHaveClass(
|
|
173
|
+
'schema-json-viewer__keyword',
|
|
174
|
+
'schema-json-viewer__keyword--structural',
|
|
175
|
+
);
|
|
176
|
+
expect(screen.getByText('"$ref"')).toHaveClass(
|
|
177
|
+
'schema-json-viewer__keyword',
|
|
178
|
+
'schema-json-viewer__keyword--structural',
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('does not highlight payload property names inside properties as meta keywords', () => {
|
|
183
|
+
const schema = {
|
|
184
|
+
type: 'object',
|
|
185
|
+
properties: {
|
|
186
|
+
$schema: {
|
|
187
|
+
type: 'string',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
render(<SchemaJsonViewer schema={schema} sourcePath="event.json" />);
|
|
193
|
+
|
|
194
|
+
fireEvent.click(screen.getByText('View Raw JSON Schema'));
|
|
195
|
+
|
|
196
|
+
expect(screen.getByText('"properties"')).toHaveClass(
|
|
197
|
+
'schema-json-viewer__keyword',
|
|
198
|
+
'schema-json-viewer__keyword--structural',
|
|
199
|
+
);
|
|
200
|
+
expect(screen.getByText('"$schema"')).toHaveClass('token property');
|
|
201
|
+
});
|
|
202
|
+
|
|
86
203
|
it('renders external $ref values as new-tab links', () => {
|
|
87
204
|
const schema = {
|
|
88
205
|
$ref: 'https://example.com/schema.json',
|
|
@@ -98,5 +215,6 @@ describe('SchemaJsonViewer', () => {
|
|
|
98
215
|
expect(refLink).toHaveAttribute('href', 'https://example.com/schema.json');
|
|
99
216
|
expect(refLink).toHaveAttribute('target', '_blank');
|
|
100
217
|
expect(refLink).toHaveAttribute('rel', 'noreferrer');
|
|
218
|
+
expect(refLink).toHaveClass('schema-json-viewer__ref-link');
|
|
101
219
|
});
|
|
102
220
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @jest-environment node
|
|
2
|
+
* @jest-environment @stryker-mutator/jest-runner/jest-env/node
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import generateEventDocs from '../generateEventDocs';
|
|
@@ -98,3 +98,508 @@ describe('generateEventDocs (non-versioned)', () => {
|
|
|
98
98
|
);
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
|
+
|
|
102
|
+
describe('generateEventDocs (edge cases)', () => {
|
|
103
|
+
beforeEach(() => {
|
|
104
|
+
fs.vol.reset();
|
|
105
|
+
console.log = jest.fn();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('handles being called with no arguments', async () => {
|
|
109
|
+
// Covers L284: options || {}
|
|
110
|
+
// getPathsForVersion(undefined, undefined) will produce a path that won't exist
|
|
111
|
+
await expect(generateEventDocs()).rejects.toThrow();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('handles being called with null options', async () => {
|
|
115
|
+
// Covers L284: options || {}
|
|
116
|
+
await expect(generateEventDocs(null)).rejects.toThrow();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('sets $id with non-trailing-slash URL for versioned schemas', async () => {
|
|
120
|
+
// Covers L298: url without trailing slash (no slice needed)
|
|
121
|
+
const realFs = jest.requireActual('fs');
|
|
122
|
+
const versionedFixturesDir = path.resolve(
|
|
123
|
+
__dirname,
|
|
124
|
+
'__fixtures_versioned__',
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
function readDirRecursive(dir) {
|
|
128
|
+
const entries = realFs.readdirSync(dir, { withFileTypes: true });
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
const entryPath = path.join(dir, entry.name);
|
|
131
|
+
if (entry.isDirectory()) {
|
|
132
|
+
fs.vol.mkdirSync(entryPath, { recursive: true });
|
|
133
|
+
readDirRecursive(entryPath);
|
|
134
|
+
} else {
|
|
135
|
+
fs.vol.writeFileSync(entryPath, realFs.readFileSync(entryPath));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
readDirRecursive(versionedFixturesDir);
|
|
140
|
+
|
|
141
|
+
const versionedOptions = {
|
|
142
|
+
organizationName: 'test-org',
|
|
143
|
+
projectName: 'test-project',
|
|
144
|
+
siteDir: versionedFixturesDir,
|
|
145
|
+
url: 'https://tracking-docs-demo.buchert.digital',
|
|
146
|
+
version: '1.1.1',
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
await generateEventDocs(versionedOptions);
|
|
150
|
+
|
|
151
|
+
const versionedOutputDir = path.join(
|
|
152
|
+
versionedFixturesDir,
|
|
153
|
+
'versioned_docs',
|
|
154
|
+
'version-1.1.1',
|
|
155
|
+
);
|
|
156
|
+
const content = fs.readFileSync(
|
|
157
|
+
path.join(versionedOutputDir, 'add-to-cart-event.mdx'),
|
|
158
|
+
'utf-8',
|
|
159
|
+
);
|
|
160
|
+
// URL had no trailing slash, so no slice applied; $id should still be set correctly
|
|
161
|
+
expect(content).toContain(
|
|
162
|
+
'https://tracking-docs-demo.buchert.digital/schemas/1.1.1/add-to-cart-event.json',
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('sets $id with next for current version', async () => {
|
|
167
|
+
const realFs = jest.requireActual('fs');
|
|
168
|
+
const versionedFixturesDir = path.resolve(
|
|
169
|
+
__dirname,
|
|
170
|
+
'__fixtures_versioned__',
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
function readDirRecursive(dir) {
|
|
174
|
+
const entries = realFs.readdirSync(dir, { withFileTypes: true });
|
|
175
|
+
for (const entry of entries) {
|
|
176
|
+
const entryPath = path.join(dir, entry.name);
|
|
177
|
+
if (entry.isDirectory()) {
|
|
178
|
+
fs.vol.mkdirSync(entryPath, { recursive: true });
|
|
179
|
+
readDirRecursive(entryPath);
|
|
180
|
+
} else {
|
|
181
|
+
fs.vol.writeFileSync(entryPath, realFs.readFileSync(entryPath));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
readDirRecursive(versionedFixturesDir);
|
|
186
|
+
|
|
187
|
+
const currentOptions = {
|
|
188
|
+
organizationName: 'test-org',
|
|
189
|
+
projectName: 'test-project',
|
|
190
|
+
siteDir: versionedFixturesDir,
|
|
191
|
+
url: 'https://tracking-docs-demo.buchert.digital',
|
|
192
|
+
version: 'current',
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
await generateEventDocs(currentOptions);
|
|
196
|
+
|
|
197
|
+
const currentOutputDir = path.join(versionedFixturesDir, 'docs');
|
|
198
|
+
const content = fs.readFileSync(
|
|
199
|
+
path.join(currentOutputDir, 'add-to-cart-event.mdx'),
|
|
200
|
+
'utf-8',
|
|
201
|
+
);
|
|
202
|
+
expect(content).toContain(
|
|
203
|
+
'https://tracking-docs-demo.buchert.digital/schemas/next/add-to-cart-event.json',
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('handles schemas with $ref pointing outside schema directory', async () => {
|
|
208
|
+
// Covers L72 (walkSchema with currentPath not in allSchemaSources)
|
|
209
|
+
// The component references a file outside the schema dir via ../
|
|
210
|
+
// which resolves to a key not present in schemaSources
|
|
211
|
+
const testSiteDir = path.resolve(__dirname, '__fixtures_outside_ref__');
|
|
212
|
+
const schemaDir = path.join(testSiteDir, 'static/schemas');
|
|
213
|
+
const componentsDir = path.join(schemaDir, 'components');
|
|
214
|
+
const outsideDir = path.join(testSiteDir, 'static');
|
|
215
|
+
fs.vol.mkdirSync(componentsDir, { recursive: true });
|
|
216
|
+
|
|
217
|
+
// A shared schema placed outside the schemas directory
|
|
218
|
+
const sharedSchema = {
|
|
219
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
220
|
+
$id: 'https://example.com/shared.json',
|
|
221
|
+
title: 'Shared',
|
|
222
|
+
type: 'object',
|
|
223
|
+
properties: {
|
|
224
|
+
shared_prop: { type: 'string' },
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
fs.vol.writeFileSync(
|
|
228
|
+
path.join(outsideDir, 'shared.json'),
|
|
229
|
+
JSON.stringify(sharedSchema, null, 2),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// A component that references the shared schema outside the schema dir
|
|
233
|
+
const componentSchema = {
|
|
234
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
235
|
+
$id: 'https://example.com/schemas/components/widget.json',
|
|
236
|
+
title: 'Widget',
|
|
237
|
+
type: 'object',
|
|
238
|
+
properties: {
|
|
239
|
+
name: { type: 'string' },
|
|
240
|
+
base: { $ref: '../../shared.json' },
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
fs.vol.writeFileSync(
|
|
244
|
+
path.join(componentsDir, 'widget.json'),
|
|
245
|
+
JSON.stringify(componentSchema, null, 2),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Main schema referencing the component
|
|
249
|
+
const mainSchema = {
|
|
250
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
251
|
+
$id: 'https://example.com/schemas/outside-ref.json',
|
|
252
|
+
title: 'Outside Ref Event',
|
|
253
|
+
type: 'object',
|
|
254
|
+
properties: {
|
|
255
|
+
event: { type: 'string', const: 'outside_ref' },
|
|
256
|
+
widget: { $ref: './components/widget.json' },
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
fs.vol.writeFileSync(
|
|
260
|
+
path.join(schemaDir, 'outside-ref.json'),
|
|
261
|
+
JSON.stringify(mainSchema, null, 2),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const outsideRefOptions = {
|
|
265
|
+
organizationName: 'test-org',
|
|
266
|
+
projectName: 'test-project',
|
|
267
|
+
siteDir: testSiteDir,
|
|
268
|
+
url: 'https://example.com',
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
await generateEventDocs(outsideRefOptions);
|
|
272
|
+
|
|
273
|
+
const outputDir = path.join(testSiteDir, 'docs');
|
|
274
|
+
expect(fs.existsSync(path.join(outputDir, 'outside-ref.mdx'))).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('handles schemas with circular $ref between source files', async () => {
|
|
278
|
+
// Covers L72 (walkSchema with visited path - circular reference)
|
|
279
|
+
const testSiteDir = path.resolve(__dirname, '__fixtures_circular_ref__');
|
|
280
|
+
const schemaDir = path.join(testSiteDir, 'static/schemas');
|
|
281
|
+
const componentsDir = path.join(schemaDir, 'components');
|
|
282
|
+
fs.vol.mkdirSync(componentsDir, { recursive: true });
|
|
283
|
+
|
|
284
|
+
const mainSchema = {
|
|
285
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
286
|
+
$id: 'https://example.com/schemas/circular.json',
|
|
287
|
+
title: 'Circular Ref Event',
|
|
288
|
+
type: 'object',
|
|
289
|
+
properties: {
|
|
290
|
+
event: { type: 'string', const: 'circular' },
|
|
291
|
+
node: { $ref: './components/node.json' },
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const nodeSchema = {
|
|
296
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
297
|
+
$id: 'https://example.com/schemas/components/node.json',
|
|
298
|
+
title: 'Node',
|
|
299
|
+
type: 'object',
|
|
300
|
+
properties: {
|
|
301
|
+
value: { type: 'string' },
|
|
302
|
+
parent: { $ref: '../circular.json' },
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
fs.vol.writeFileSync(
|
|
307
|
+
path.join(schemaDir, 'circular.json'),
|
|
308
|
+
JSON.stringify(mainSchema, null, 2),
|
|
309
|
+
);
|
|
310
|
+
fs.vol.writeFileSync(
|
|
311
|
+
path.join(componentsDir, 'node.json'),
|
|
312
|
+
JSON.stringify(nodeSchema, null, 2),
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const circularOptions = {
|
|
316
|
+
organizationName: 'test-org',
|
|
317
|
+
projectName: 'test-project',
|
|
318
|
+
siteDir: testSiteDir,
|
|
319
|
+
url: 'https://example.com',
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
await generateEventDocs(circularOptions);
|
|
323
|
+
|
|
324
|
+
const outputDir = path.join(testSiteDir, 'docs');
|
|
325
|
+
expect(fs.existsSync(path.join(outputDir, 'circular.mdx'))).toBe(true);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('covers collectLeafEventNames for non-oneOf schemas via nested oneOf', async () => {
|
|
329
|
+
// Covers L104-106: collectLeafEventNames with schema that has no oneOf
|
|
330
|
+
// This requires a nested oneOf where processOneOfSchema returns a schema
|
|
331
|
+
// that itself has oneOf, so collectLeafEventNames recurses
|
|
332
|
+
const testSiteDir = path.resolve(__dirname, '__fixtures_nested_leaf__');
|
|
333
|
+
const schemaDir = path.join(testSiteDir, 'static/schemas');
|
|
334
|
+
fs.vol.mkdirSync(schemaDir, { recursive: true });
|
|
335
|
+
|
|
336
|
+
const realFs = jest.requireActual('fs');
|
|
337
|
+
|
|
338
|
+
// Copy the nested fixture files
|
|
339
|
+
const nestedSchemasDir = path.resolve(
|
|
340
|
+
__dirname,
|
|
341
|
+
'__fixtures__/static/schemas/nested',
|
|
342
|
+
);
|
|
343
|
+
const files = realFs.readdirSync(nestedSchemasDir);
|
|
344
|
+
for (const file of files) {
|
|
345
|
+
const content = realFs.readFileSync(path.join(nestedSchemasDir, file));
|
|
346
|
+
fs.vol.writeFileSync(path.join(schemaDir, file), content);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const nestedLeafOptions = {
|
|
350
|
+
organizationName: 'test-org',
|
|
351
|
+
projectName: 'test-project',
|
|
352
|
+
siteDir: testSiteDir,
|
|
353
|
+
url: 'https://example.com',
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
await generateEventDocs(nestedLeafOptions);
|
|
357
|
+
|
|
358
|
+
const outputDir = path.join(testSiteDir, 'docs');
|
|
359
|
+
const parentDir = path.join(outputDir, 'parent-event');
|
|
360
|
+
expect(fs.existsSync(parentDir)).toBe(true);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('uses fallback partial for oneOf sub-option when scoped partial does not exist', async () => {
|
|
364
|
+
// Covers L34 (cond-expr): fallback partial path is selected when
|
|
365
|
+
// the scoped partial does not exist but the basename fallback does
|
|
366
|
+
const testSiteDir = path.resolve(
|
|
367
|
+
__dirname,
|
|
368
|
+
'__fixtures_fallback_partial__',
|
|
369
|
+
);
|
|
370
|
+
const schemaDir = path.join(testSiteDir, 'static/schemas');
|
|
371
|
+
const outputDir = path.join(testSiteDir, 'docs');
|
|
372
|
+
const partialsDir = path.join(outputDir, 'partials');
|
|
373
|
+
fs.vol.mkdirSync(schemaDir, { recursive: true });
|
|
374
|
+
fs.vol.mkdirSync(partialsDir, { recursive: true });
|
|
375
|
+
|
|
376
|
+
const schema = {
|
|
377
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
378
|
+
$id: 'https://example.com/schemas/parent-fb.json',
|
|
379
|
+
title: 'Parent FB',
|
|
380
|
+
oneOf: [
|
|
381
|
+
{
|
|
382
|
+
title: 'Child Alpha',
|
|
383
|
+
type: 'object',
|
|
384
|
+
properties: {
|
|
385
|
+
event: { type: 'string', const: 'child_alpha' },
|
|
386
|
+
},
|
|
387
|
+
required: ['event'],
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
};
|
|
391
|
+
fs.vol.writeFileSync(
|
|
392
|
+
path.join(schemaDir, 'parent-fb.json'),
|
|
393
|
+
JSON.stringify(schema, null, 2),
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// Create a fallback partial in base partials dir (not in the scoped subdir)
|
|
397
|
+
// The scoped path would be partials/parent-fb/_child-alpha.mdx which does not exist
|
|
398
|
+
// The fallback path is partials/_child-alpha.mdx which we create here
|
|
399
|
+
fs.vol.writeFileSync(
|
|
400
|
+
path.join(partialsDir, '_child-alpha.mdx'),
|
|
401
|
+
'## Fallback partial content',
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
const fbOptions = {
|
|
405
|
+
organizationName: 'test-org',
|
|
406
|
+
projectName: 'test-project',
|
|
407
|
+
siteDir: testSiteDir,
|
|
408
|
+
url: 'https://example.com',
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
await generateEventDocs(fbOptions);
|
|
412
|
+
|
|
413
|
+
const choiceOutput = fs.readFileSync(
|
|
414
|
+
path.join(outputDir, 'parent-fb', '01-child-alpha.mdx'),
|
|
415
|
+
'utf-8',
|
|
416
|
+
);
|
|
417
|
+
// The fallback partial should be used since the scoped one doesn't exist
|
|
418
|
+
expect(choiceOutput).toContain(
|
|
419
|
+
"import TopPartial from '@site/docs/partials/_child-alpha.mdx'",
|
|
420
|
+
);
|
|
421
|
+
expect(choiceOutput).toContain('<TopPartial />');
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('removes stale oneOf output files and directories between runs', async () => {
|
|
425
|
+
const testSiteDir = path.resolve(
|
|
426
|
+
__dirname,
|
|
427
|
+
'__fixtures_stale_oneof_cleanup__',
|
|
428
|
+
);
|
|
429
|
+
const schemaDir = path.join(testSiteDir, 'static/schemas');
|
|
430
|
+
const docsDir = path.join(testSiteDir, 'docs');
|
|
431
|
+
fs.vol.mkdirSync(schemaDir, { recursive: true });
|
|
432
|
+
|
|
433
|
+
const schemaFile = path.join(schemaDir, 'stale-choice.json');
|
|
434
|
+
const cleanupOptions = {
|
|
435
|
+
organizationName: 'test-org',
|
|
436
|
+
projectName: 'test-project',
|
|
437
|
+
siteDir: testSiteDir,
|
|
438
|
+
url: 'https://example.com',
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const initialSchema = {
|
|
442
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
443
|
+
$id: 'https://example.com/schemas/stale-choice.json',
|
|
444
|
+
title: 'Stale Choice',
|
|
445
|
+
oneOf: [
|
|
446
|
+
{
|
|
447
|
+
title: 'Leaf A',
|
|
448
|
+
type: 'object',
|
|
449
|
+
properties: {
|
|
450
|
+
event: { type: 'string', const: 'leaf_a' },
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
title: 'Group B',
|
|
455
|
+
oneOf: [
|
|
456
|
+
{
|
|
457
|
+
title: 'Leaf B',
|
|
458
|
+
type: 'object',
|
|
459
|
+
properties: {
|
|
460
|
+
event: { type: 'string', const: 'leaf_b' },
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
fs.vol.writeFileSync(schemaFile, JSON.stringify(initialSchema, null, 2));
|
|
469
|
+
await generateEventDocs(cleanupOptions);
|
|
470
|
+
|
|
471
|
+
expect(
|
|
472
|
+
fs.existsSync(path.join(docsDir, 'stale-choice', '01-leaf-a.mdx')),
|
|
473
|
+
).toBe(true);
|
|
474
|
+
expect(
|
|
475
|
+
fs.existsSync(path.join(docsDir, 'stale-choice', '02-group-b')),
|
|
476
|
+
).toBe(true);
|
|
477
|
+
|
|
478
|
+
const updatedSchema = {
|
|
479
|
+
...initialSchema,
|
|
480
|
+
oneOf: [initialSchema.oneOf[0]],
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
fs.vol.writeFileSync(schemaFile, JSON.stringify(updatedSchema, null, 2));
|
|
484
|
+
|
|
485
|
+
const unlinkSpy = jest.spyOn(fs, 'unlinkSync');
|
|
486
|
+
const rmSpy = jest.spyOn(fs, 'rmSync');
|
|
487
|
+
|
|
488
|
+
await generateEventDocs(cleanupOptions);
|
|
489
|
+
|
|
490
|
+
expect(
|
|
491
|
+
fs.existsSync(path.join(docsDir, 'stale-choice', '01-leaf-a.mdx')),
|
|
492
|
+
).toBe(true);
|
|
493
|
+
expect(
|
|
494
|
+
fs.existsSync(path.join(docsDir, 'stale-choice', '02-group-b')),
|
|
495
|
+
).toBe(false);
|
|
496
|
+
expect(unlinkSpy).not.toHaveBeenCalledWith(
|
|
497
|
+
path.join(docsDir, 'stale-choice', '01-leaf-a.mdx'),
|
|
498
|
+
);
|
|
499
|
+
expect(rmSpy).toHaveBeenCalledWith(
|
|
500
|
+
path.join(docsDir, 'stale-choice', '02-group-b'),
|
|
501
|
+
{ recursive: true },
|
|
502
|
+
);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('generates docs for inline nested oneOf schemas', async () => {
|
|
506
|
+
// Covers L259 (sourceFilePath || filePath fallback in generateOneOfDocs)
|
|
507
|
+
// and L114 (sourceFilePath || filePath fallback in collectLeafEventNames)
|
|
508
|
+
// and L105-106 (collectLeafEventNames for non-oneOf leaf via recursion)
|
|
509
|
+
const testSiteDir = path.resolve(
|
|
510
|
+
__dirname,
|
|
511
|
+
'__fixtures_inline_nested_oneof__',
|
|
512
|
+
);
|
|
513
|
+
const schemaDir = path.join(testSiteDir, 'static/schemas');
|
|
514
|
+
fs.vol.mkdirSync(schemaDir, { recursive: true });
|
|
515
|
+
|
|
516
|
+
// A schema with inline nested oneOf (no $ref, so sourceFilePath is null)
|
|
517
|
+
const schema = {
|
|
518
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
519
|
+
$id: 'https://example.com/schemas/inline-nested.json',
|
|
520
|
+
title: 'Inline Nested',
|
|
521
|
+
oneOf: [
|
|
522
|
+
{
|
|
523
|
+
title: 'Group One',
|
|
524
|
+
oneOf: [
|
|
525
|
+
{
|
|
526
|
+
title: 'Leaf A',
|
|
527
|
+
type: 'object',
|
|
528
|
+
properties: {
|
|
529
|
+
event: { type: 'string', const: 'leaf_a' },
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
title: 'Leaf B',
|
|
534
|
+
type: 'object',
|
|
535
|
+
properties: {
|
|
536
|
+
event: { type: 'string', const: 'leaf_b' },
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
],
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
fs.vol.writeFileSync(
|
|
545
|
+
path.join(schemaDir, 'inline-nested.json'),
|
|
546
|
+
JSON.stringify(schema, null, 2),
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
const inlineOptions = {
|
|
550
|
+
organizationName: 'test-org',
|
|
551
|
+
projectName: 'test-project',
|
|
552
|
+
siteDir: testSiteDir,
|
|
553
|
+
url: 'https://example.com',
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
await generateEventDocs(inlineOptions);
|
|
557
|
+
|
|
558
|
+
const outputDir = path.join(testSiteDir, 'docs');
|
|
559
|
+
const parentDir = path.join(outputDir, 'inline-nested');
|
|
560
|
+
expect(fs.existsSync(parentDir)).toBe(true);
|
|
561
|
+
|
|
562
|
+
// The nested oneOf should create a subdirectory with index and leaf docs
|
|
563
|
+
const nestedDir = path.join(parentDir, '01-group-one');
|
|
564
|
+
expect(fs.existsSync(nestedDir)).toBe(true);
|
|
565
|
+
expect(fs.existsSync(path.join(nestedDir, 'index.mdx'))).toBe(true);
|
|
566
|
+
expect(fs.existsSync(path.join(nestedDir, '01-leaf-a.mdx'))).toBe(true);
|
|
567
|
+
expect(fs.existsSync(path.join(nestedDir, '02-leaf-b.mdx'))).toBe(true);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('handles collectReachableSchemaSources with empty sourcePath', async () => {
|
|
571
|
+
// Covers L66: collectReachableSchemaSources early return when sourcePath is not in schemaSources
|
|
572
|
+
// We create a schema with only local $ref (#) so the sourceKey for the
|
|
573
|
+
// oneOf sub-option maps to a synthetic filename not present in schemaSources
|
|
574
|
+
const testSiteDir = path.resolve(__dirname, '__fixtures_empty_source__');
|
|
575
|
+
const schemaDir = path.join(testSiteDir, 'static/schemas');
|
|
576
|
+
fs.vol.mkdirSync(schemaDir, { recursive: true });
|
|
577
|
+
|
|
578
|
+
// A minimal schema with no external refs
|
|
579
|
+
const schema = {
|
|
580
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
581
|
+
$id: 'https://example.com/schemas/simple.json',
|
|
582
|
+
title: 'Simple Event',
|
|
583
|
+
type: 'object',
|
|
584
|
+
properties: {
|
|
585
|
+
event: { type: 'string', const: 'simple' },
|
|
586
|
+
},
|
|
587
|
+
};
|
|
588
|
+
fs.vol.writeFileSync(
|
|
589
|
+
path.join(schemaDir, 'simple.json'),
|
|
590
|
+
JSON.stringify(schema, null, 2),
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
const simpleOptions = {
|
|
594
|
+
organizationName: 'test-org',
|
|
595
|
+
projectName: 'test-project',
|
|
596
|
+
siteDir: testSiteDir,
|
|
597
|
+
url: 'https://example.com',
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
await generateEventDocs(simpleOptions);
|
|
601
|
+
|
|
602
|
+
const outputDir = path.join(testSiteDir, 'docs');
|
|
603
|
+
expect(fs.existsSync(path.join(outputDir, 'simple.mdx'))).toBe(true);
|
|
604
|
+
});
|
|
605
|
+
});
|