docusaurus-plugin-generate-schema-docs 1.8.4 → 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.
Files changed (39) hide show
  1. package/README.md +10 -0
  2. package/__tests__/__fixtures__/validateSchemas/schema-with-not-anyof-multi.json +12 -0
  3. package/__tests__/__fixtures__/validateSchemas/schema-with-not-anyof.json +30 -0
  4. package/__tests__/__fixtures__/validateSchemas/schema-with-not-edge-cases.json +24 -0
  5. package/__tests__/__fixtures__/validateSchemas/schema-with-not-non-object.json +15 -0
  6. package/__tests__/generateEventDocs.anchor.test.js +1 -1
  7. package/__tests__/generateEventDocs.nested.test.js +1 -1
  8. package/__tests__/generateEventDocs.partials.test.js +1 -1
  9. package/__tests__/generateEventDocs.test.js +506 -1
  10. package/__tests__/generateEventDocs.versioned.test.js +1 -1
  11. package/__tests__/helpers/buildExampleFromSchema.test.js +240 -0
  12. package/__tests__/helpers/constraintSchemaPaths.test.js +208 -0
  13. package/__tests__/helpers/continuingLinesStyle.test.js +492 -0
  14. package/__tests__/helpers/exampleModel.test.js +209 -0
  15. package/__tests__/helpers/file-system.test.js +73 -1
  16. package/__tests__/helpers/getConstraints.test.js +27 -0
  17. package/__tests__/helpers/mergeSchema.test.js +94 -0
  18. package/__tests__/helpers/processSchema.test.js +291 -1
  19. package/__tests__/helpers/schema-doc-template.test.js +54 -0
  20. package/__tests__/helpers/schema-processing.test.js +122 -2
  21. package/__tests__/helpers/schemaToExamples.test.js +1007 -0
  22. package/__tests__/helpers/schemaToTableData.mutations.test.js +970 -0
  23. package/__tests__/helpers/schemaToTableData.test.js +157 -0
  24. package/__tests__/helpers/snippetTargets.test.js +432 -0
  25. package/__tests__/helpers/trackingTargets.test.js +319 -0
  26. package/__tests__/helpers/validator.test.js +385 -1
  27. package/__tests__/index.test.js +436 -0
  28. package/__tests__/syncGtm.test.js +139 -3
  29. package/__tests__/update-schema-ids.test.js +70 -1
  30. package/__tests__/validateSchemas-integration.test.js +2 -2
  31. package/__tests__/validateSchemas.test.js +142 -1
  32. package/generateEventDocs.js +21 -1
  33. package/helpers/constraintSchemaPaths.js +10 -14
  34. package/helpers/schemaToTableData.js +538 -492
  35. package/helpers/trackingTargets.js +26 -3
  36. package/helpers/validator.js +18 -4
  37. package/index.js +1 -2
  38. package/package.json +1 -1
  39. package/scripts/sync-gtm.js +25 -7
package/README.md CHANGED
@@ -203,6 +203,16 @@ If you have `event-reference/add-to-cart-event` and `mobile-reference/add-to-car
203
203
  - `docs/partials/event-reference/_add-to-cart-event.mdx`
204
204
  - `docs/partials/event-reference/_add-to-cart-event_bottom.mdx`
205
205
 
206
+ ## Related packages
207
+
208
+ ### [`eslint-plugin-tracking-schema`](https://www.npmjs.com/package/eslint-plugin-tracking-schema)
209
+
210
+ An ESLint plugin that enforces annotation quality on the JSON Schema files you feed into this plugin. It ensures every property has a `description`, a `type`, and `examples` — catching incomplete schemas at author time before they produce misleading documentation.
211
+
212
+ ```bash
213
+ npm install --save-dev eslint-plugin-tracking-schema jsonc-eslint-parser
214
+ ```
215
+
206
216
  ## Contributing
207
217
 
208
218
  Contributions are welcome! Please open an issue or submit a pull request if you have any ideas or improvements.
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://example.com/schema-with-not-anyof-multi.json",
4
+ "title": "Schema With Not AnyOf Multi",
5
+ "type": "object",
6
+ "properties": {
7
+ "country": {
8
+ "type": "string",
9
+ "not": { "anyOf": [{ "const": "US" }, { "const": "CA" }] }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://example.com/schema-with-not-anyof.json",
4
+ "title": "Schema With Not AnyOf",
5
+ "type": "object",
6
+ "properties": {
7
+ "country": {
8
+ "type": "string",
9
+ "not": { "anyOf": [{ "const": "US" }] }
10
+ },
11
+ "nested": {
12
+ "type": "object",
13
+ "properties": {
14
+ "code": {
15
+ "type": "string",
16
+ "not": { "anyOf": [{ "anyOf": [{ "const": "XX" }] }] }
17
+ }
18
+ }
19
+ },
20
+ "items": {
21
+ "type": "array",
22
+ "items": [
23
+ {
24
+ "type": "string",
25
+ "not": { "anyOf": [{ "const": "forbidden" }] }
26
+ }
27
+ ]
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://example.com/schema-with-not-edge-cases.json",
4
+ "title": "Schema With Not Edge Cases",
5
+ "type": "object",
6
+ "properties": {
7
+ "falsy_not": {
8
+ "type": "string",
9
+ "not": false
10
+ },
11
+ "not_is_array": {
12
+ "type": "string",
13
+ "not": [{ "const": "A" }, { "const": "B" }]
14
+ },
15
+ "not_anyof_contains_array": {
16
+ "type": "string",
17
+ "not": { "anyOf": [[1, 2, 3]] }
18
+ },
19
+ "not_with_null_candidate": {
20
+ "type": "string",
21
+ "not": { "anyOf": [null] }
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://example.com/schema-with-not-non-object.json",
4
+ "title": "Schema With Not Non Object",
5
+ "type": "object",
6
+ "properties": {
7
+ "flag": {
8
+ "not": true
9
+ },
10
+ "name": {
11
+ "type": "string",
12
+ "not": { "const": "forbidden" }
13
+ }
14
+ }
15
+ }
@@ -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';
@@ -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';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @jest-environment node
2
+ * @jest-environment @stryker-mutator/jest-runner/jest-env/node
3
3
  *
4
4
  * Tests that top and bottom partials are correctly injected into generated MDX
5
5
  * when matching partial files exist in the partials directory, and that the
@@ -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
+ });
@@ -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';