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
@@ -1,4 +1,5 @@
1
- import { createValidator } from '../../helpers/validator';
1
+ import { createValidator, clearSchemaFileCache } from '../../helpers/validator';
2
+ import { promises as fsPromises } from 'fs';
2
3
  import path from 'path';
3
4
 
4
5
  describe('createValidator', () => {
@@ -114,6 +115,44 @@ describe('createValidator', () => {
114
115
  expect(result.errors).toEqual([]);
115
116
  });
116
117
 
118
+ describe('schema file caching', () => {
119
+ beforeEach(() => {
120
+ clearSchemaFileCache();
121
+ });
122
+
123
+ afterEach(() => {
124
+ jest.restoreAllMocks();
125
+ });
126
+
127
+ it('reads each referenced schema file only once across concurrent validators', async () => {
128
+ const componentSchema = JSON.stringify({
129
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
130
+ type: 'object',
131
+ properties: { id: { type: 'string' } },
132
+ });
133
+
134
+ const readFileSpy = jest
135
+ .spyOn(fsPromises, 'readFile')
136
+ .mockResolvedValue(componentSchema);
137
+
138
+ const schemaWithRef = {
139
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
140
+ type: 'object',
141
+ properties: { item: { $ref: 'components/products.json' } },
142
+ };
143
+
144
+ await Promise.all([
145
+ createValidator([], { ...schemaWithRef }, '/schemas'),
146
+ createValidator([], { ...schemaWithRef }, '/schemas'),
147
+ ]);
148
+
149
+ const componentReads = readFileSpy.mock.calls.filter(([p]) =>
150
+ p.includes('products.json'),
151
+ );
152
+ expect(componentReads).toHaveLength(1);
153
+ });
154
+ });
155
+
117
156
  it('resolves published /constraints refs to local constraint schemas', async () => {
118
157
  const schema = {
119
158
  $schema: 'https://json-schema.org/draft/2020-12/schema',
@@ -144,4 +183,349 @@ describe('createValidator', () => {
144
183
  });
145
184
  expect(invalid.valid).toBe(false);
146
185
  });
186
+
187
+ describe('clearSchemaFileCache', () => {
188
+ afterEach(() => {
189
+ jest.restoreAllMocks();
190
+ clearSchemaFileCache();
191
+ });
192
+
193
+ it('clears the cache so subsequent file reads are performed again (L14)', async () => {
194
+ let callCount = 0;
195
+ const componentSchema = JSON.stringify({
196
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
197
+ type: 'object',
198
+ properties: { id: { type: 'string' } },
199
+ });
200
+ jest.spyOn(fsPromises, 'readFile').mockImplementation(() => {
201
+ callCount++;
202
+ return Promise.resolve(componentSchema);
203
+ });
204
+
205
+ const schemaWithRef = {
206
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
207
+ type: 'object',
208
+ properties: { item: { $ref: 'parts/component.json' } },
209
+ };
210
+
211
+ // First call — populates cache
212
+ await createValidator([], { ...schemaWithRef }, '/schemas');
213
+ const countAfterFirst = callCount;
214
+
215
+ // Clear cache — subsequent calls should re-read the file
216
+ clearSchemaFileCache();
217
+
218
+ // Second call — cache was cleared, so file must be read again
219
+ await createValidator([], { ...schemaWithRef }, '/schemas');
220
+ const countAfterSecond = callCount;
221
+
222
+ expect(countAfterSecond).toBeGreaterThan(countAfterFirst);
223
+ });
224
+ });
225
+
226
+ describe('readSchemaFile encoding (L22)', () => {
227
+ afterEach(() => {
228
+ jest.restoreAllMocks();
229
+ clearSchemaFileCache();
230
+ });
231
+
232
+ it('reads schema files with utf-8 encoding', async () => {
233
+ const componentSchema = JSON.stringify({
234
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
235
+ type: 'object',
236
+ properties: { id: { type: 'string' } },
237
+ });
238
+ const readFileSpy = jest
239
+ .spyOn(fsPromises, 'readFile')
240
+ .mockResolvedValue(componentSchema);
241
+
242
+ const schemaWithRef = {
243
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
244
+ type: 'object',
245
+ properties: { item: { $ref: 'parts/comp.json' } },
246
+ };
247
+
248
+ await createValidator([], { ...schemaWithRef }, '/schemas');
249
+
250
+ const refCall = readFileSpy.mock.calls.find(([p]) =>
251
+ p.includes('comp.json'),
252
+ );
253
+ expect(refCall).toBeDefined();
254
+ expect(refCall[1]).toBe('utf-8');
255
+ });
256
+ });
257
+
258
+ describe('optional chaining on mainSchema.$schema (L29)', () => {
259
+ it('handles mainSchema with no $schema field without throwing', async () => {
260
+ const schema = {
261
+ type: 'object',
262
+ properties: { name: { type: 'string' } },
263
+ };
264
+ // mainSchema has no $schema — schemaVersion must be undefined, not throw
265
+ const validator = await createValidator([], schema);
266
+ expect(validator({ name: 'hello' }).valid).toBe(true);
267
+ });
268
+ });
269
+
270
+ describe('URI normalization (L38-L41)', () => {
271
+ afterEach(() => {
272
+ jest.restoreAllMocks();
273
+ clearSchemaFileCache();
274
+ });
275
+
276
+ it('strips /schemas/ prefix from http URI pathnames when loading refs (L38, L40, L41)', async () => {
277
+ const componentSchema = JSON.stringify({
278
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
279
+ type: 'object',
280
+ properties: { id: { type: 'string' } },
281
+ });
282
+ const readFileSpy = jest
283
+ .spyOn(fsPromises, 'readFile')
284
+ .mockResolvedValue(componentSchema);
285
+
286
+ const schemaWithRef = {
287
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
288
+ type: 'object',
289
+ properties: {
290
+ item: {
291
+ $ref: 'http://example.com/schemas/components/widget.json',
292
+ },
293
+ },
294
+ };
295
+
296
+ await createValidator([], { ...schemaWithRef }, '/base');
297
+
298
+ const refCall = readFileSpy.mock.calls.find(([p]) =>
299
+ p.includes('widget.json'),
300
+ );
301
+ expect(refCall).toBeDefined();
302
+ // /schemas/ prefix should be stripped: path should be /base/components/widget.json
303
+ expect(refCall[0]).toBe('/base/components/widget.json');
304
+ });
305
+
306
+ it('preserves full pathname for http URIs without /schemas/ prefix (L40)', async () => {
307
+ const componentSchema = JSON.stringify({
308
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
309
+ type: 'object',
310
+ properties: { id: { type: 'string' } },
311
+ });
312
+ const readFileSpy = jest
313
+ .spyOn(fsPromises, 'readFile')
314
+ .mockResolvedValue(componentSchema);
315
+
316
+ const schemaWithRef = {
317
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
318
+ type: 'object',
319
+ properties: {
320
+ item: {
321
+ $ref: 'http://example.com/other/widget.json',
322
+ },
323
+ },
324
+ };
325
+
326
+ await createValidator([], { ...schemaWithRef }, '/base');
327
+
328
+ const refCall = readFileSpy.mock.calls.find(([p]) =>
329
+ p.includes('widget.json'),
330
+ );
331
+ expect(refCall).toBeDefined();
332
+ // No /schemas/ prefix — full pathname is used
333
+ expect(refCall[0]).toBe('/base/other/widget.json');
334
+ });
335
+
336
+ it('resolves relative URI refs against schemaPath (L38 else branch)', async () => {
337
+ const componentSchema = JSON.stringify({
338
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
339
+ type: 'object',
340
+ properties: { id: { type: 'string' } },
341
+ });
342
+ const readFileSpy = jest
343
+ .spyOn(fsPromises, 'readFile')
344
+ .mockResolvedValue(componentSchema);
345
+
346
+ const schemaWithRef = {
347
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
348
+ type: 'object',
349
+ properties: { item: { $ref: 'relative/schema.json' } },
350
+ };
351
+
352
+ await createValidator([], { ...schemaWithRef }, '/base');
353
+
354
+ const refCall = readFileSpy.mock.calls.find(([p]) =>
355
+ p.includes('schema.json'),
356
+ );
357
+ expect(refCall).toBeDefined();
358
+ // Relative path resolved against /base
359
+ expect(refCall[0]).toContain('relative/schema.json');
360
+ });
361
+ });
362
+
363
+ describe('validation return shape (L51)', () => {
364
+ it('returns errors array (not empty) when data is invalid', async () => {
365
+ const schema = {
366
+ type: 'object',
367
+ properties: { count: { type: 'number' } },
368
+ required: ['count'],
369
+ };
370
+ const validator = await createValidator([], schema);
371
+ const result = validator({ count: 'not-a-number' });
372
+
373
+ expect(result.valid).toBe(false);
374
+ expect(Array.isArray(result.errors)).toBe(true);
375
+ expect(result.errors.length).toBeGreaterThan(0);
376
+ });
377
+
378
+ it('returns empty errors array when data is valid', async () => {
379
+ const schema = {
380
+ type: 'object',
381
+ properties: { count: { type: 'number' } },
382
+ required: ['count'],
383
+ };
384
+ const validator = await createValidator([], schema);
385
+ const result = validator({ count: 42 });
386
+
387
+ expect(result.valid).toBe(true);
388
+ expect(result.errors).toEqual([]);
389
+ });
390
+ });
391
+
392
+ describe('draft-04 with $id and schemas array (L64, L91-L99)', () => {
393
+ it('compiles draft-04 schema without $id using ajv.compile (L91-L94)', async () => {
394
+ const schema = {
395
+ $schema: 'http://json-schema.org/draft-04/schema#',
396
+ type: 'object',
397
+ properties: { name: { type: 'string' } },
398
+ required: ['name'],
399
+ };
400
+
401
+ const validator = await createValidator([], schema);
402
+ expect(validator({ name: 'Alice' }).valid).toBe(true);
403
+ expect(validator({ name: 123 }).valid).toBe(false);
404
+ });
405
+
406
+ it('adds schemas from array for draft-04 so refs can be resolved (L64)', async () => {
407
+ // draft-04 uses 'id' (not '$id') for schema identification
408
+ const componentId = 'http://example.com/schemas/item-d4.json';
409
+ const componentSchema = {
410
+ id: componentId,
411
+ type: 'object',
412
+ properties: { label: { type: 'string' } },
413
+ required: ['label'],
414
+ };
415
+ const mainSchema = {
416
+ $schema: 'http://json-schema.org/draft-04/schema#',
417
+ type: 'object',
418
+ properties: {
419
+ item: { $ref: componentId },
420
+ },
421
+ required: ['item'],
422
+ };
423
+
424
+ const validator = await createValidator([componentSchema], mainSchema);
425
+ expect(validator({ item: { label: 'Widget' } }).valid).toBe(true);
426
+ expect(validator({ item: { label: 42 } }).valid).toBe(false);
427
+ });
428
+ });
429
+
430
+ describe('fallback branch when compileAsync is absent (L97-L100)', () => {
431
+ it('uses ajv.getSchema when compileAsync is missing and mainSchema has $id (L98-L99)', async () => {
432
+ const Ajv = (await import('ajv')).default;
433
+ const origCompileAsync = Ajv.prototype.compileAsync;
434
+ delete Ajv.prototype.compileAsync;
435
+
436
+ try {
437
+ const schema = {
438
+ $id: 'http://example.com/schemas/test-no-compile-async.json',
439
+ $schema: 'http://json-schema.org/draft-07/schema#',
440
+ type: 'object',
441
+ properties: { name: { type: 'string' } },
442
+ required: ['name'],
443
+ };
444
+
445
+ const validator = await createValidator([schema], schema);
446
+ expect(validator({ name: 'Alice' }).valid).toBe(true);
447
+ expect(validator({ name: 123 }).valid).toBe(false);
448
+ } finally {
449
+ Ajv.prototype.compileAsync = origCompileAsync;
450
+ }
451
+ });
452
+
453
+ it('uses ajv.compile when compileAsync is missing and mainSchema has no $id (L98, L100)', async () => {
454
+ const Ajv = (await import('ajv')).default;
455
+ const origCompileAsync = Ajv.prototype.compileAsync;
456
+ delete Ajv.prototype.compileAsync;
457
+
458
+ try {
459
+ const schema = {
460
+ $schema: 'http://json-schema.org/draft-07/schema#',
461
+ type: 'object',
462
+ properties: { name: { type: 'string' } },
463
+ required: ['name'],
464
+ };
465
+
466
+ const validator = await createValidator([], schema);
467
+ expect(validator({ name: 'Bob' }).valid).toBe(true);
468
+ expect(validator({ name: 42 }).valid).toBe(false);
469
+ } finally {
470
+ Ajv.prototype.compileAsync = origCompileAsync;
471
+ }
472
+ });
473
+ });
474
+
475
+ describe('addKeyword guard (L71)', () => {
476
+ it('covers the addKeyword branch by verifying custom keywords are registered', async () => {
477
+ // The L71 guard checks if ajv.addKeyword exists before calling it.
478
+ // All standard Ajv instances have addKeyword, so L71 is always true.
479
+ // We verify the keywords ARE added (covering L72-L73) by validating
480
+ // a schema that uses x-gtm-clear — if addKeyword hadn't been called, Ajv strict mode would fail.
481
+ const schema = {
482
+ $schema: 'http://json-schema.org/draft-07/schema#',
483
+ type: 'object',
484
+ properties: {
485
+ name: { type: 'string', 'x-gtm-clear': true },
486
+ targets: { type: 'array', 'x-tracking-targets': ['ga4'] },
487
+ },
488
+ };
489
+
490
+ const validator = await createValidator([], schema);
491
+ expect(validator({ name: 'test', targets: ['ga4'] }).valid).toBe(true);
492
+ });
493
+ });
494
+
495
+ describe('error thrown when validate cannot be found (L103-L106)', () => {
496
+ it('throws an error mentioning $id when schema has $id and cannot be found (L105-L106)', async () => {
497
+ // Use draft-04 with a $id that is NOT pre-loaded in schemas, so getSchema returns undefined
498
+ const schema = {
499
+ $id: 'http://example.com/schemas/missing.json',
500
+ $schema: 'http://json-schema.org/draft-04/schema#',
501
+ type: 'object',
502
+ properties: { name: { type: 'string' } },
503
+ };
504
+
505
+ // Pass empty schemas array so $id is not registered; getSchema returns undefined
506
+ await expect(createValidator([], schema)).rejects.toThrow(
507
+ 'http://example.com/schemas/missing.json',
508
+ );
509
+ });
510
+
511
+ it('throws an error mentioning "main schema" when schema has no $id (L106 fallback)', async () => {
512
+ // Force validate to be falsy by mocking — use draft-04 path without $id but make compile return null
513
+ // We do this by spying on AjvDraft4.prototype.compile
514
+ const AjvDraft4 = (await import('ajv-draft-04')).default;
515
+ const origCompile = AjvDraft4.prototype.compile;
516
+ AjvDraft4.prototype.compile = () => null;
517
+
518
+ try {
519
+ const schema = {
520
+ $schema: 'http://json-schema.org/draft-04/schema#',
521
+ type: 'object',
522
+ };
523
+ await expect(createValidator([], schema)).rejects.toThrow(
524
+ 'main schema',
525
+ );
526
+ } finally {
527
+ AjvDraft4.prototype.compile = origCompile;
528
+ }
529
+ });
530
+ });
147
531
  });