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.
Files changed (62) hide show
  1. package/README.md +12 -0
  2. package/__tests__/__fixtures__/validateSchemas/main-schema-with-not-allof.json +11 -0
  3. package/__tests__/__fixtures__/validateSchemas/schema-with-not-anyof-multi.json +12 -0
  4. package/__tests__/__fixtures__/validateSchemas/schema-with-not-anyof.json +30 -0
  5. package/__tests__/__fixtures__/validateSchemas/schema-with-not-edge-cases.json +24 -0
  6. package/__tests__/__fixtures__/validateSchemas/schema-with-not-non-object.json +15 -0
  7. package/__tests__/__snapshots__/generateEventDocs.anchor.test.js.snap +6 -0
  8. package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +6 -0
  9. package/__tests__/__snapshots__/generateEventDocs.test.js.snap +15 -0
  10. package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +6 -0
  11. package/__tests__/components/PropertiesTable.test.js +66 -0
  12. package/__tests__/components/PropertyRow.test.js +85 -4
  13. package/__tests__/components/SchemaJsonViewer.test.js +118 -0
  14. package/__tests__/generateEventDocs.anchor.test.js +1 -1
  15. package/__tests__/generateEventDocs.nested.test.js +1 -1
  16. package/__tests__/generateEventDocs.partials.test.js +1 -1
  17. package/__tests__/generateEventDocs.test.js +506 -1
  18. package/__tests__/generateEventDocs.versioned.test.js +1 -1
  19. package/__tests__/helpers/buildExampleFromSchema.test.js +240 -0
  20. package/__tests__/helpers/constraintSchemaPaths.test.js +208 -0
  21. package/__tests__/helpers/continuingLinesStyle.test.js +492 -0
  22. package/__tests__/helpers/example-helper.test.js +12 -0
  23. package/__tests__/helpers/exampleModel.test.js +209 -0
  24. package/__tests__/helpers/file-system.test.js +73 -1
  25. package/__tests__/helpers/getConstraints.test.js +43 -0
  26. package/__tests__/helpers/mergeSchema.test.js +94 -0
  27. package/__tests__/helpers/processSchema.test.js +309 -1
  28. package/__tests__/helpers/schema-doc-template.test.js +54 -0
  29. package/__tests__/helpers/schema-processing.test.js +122 -2
  30. package/__tests__/helpers/schemaToExamples.test.js +1007 -0
  31. package/__tests__/helpers/schemaToTableData.mutations.test.js +970 -0
  32. package/__tests__/helpers/schemaToTableData.test.js +157 -0
  33. package/__tests__/helpers/schemaTraversal.test.js +110 -0
  34. package/__tests__/helpers/snippetTargets.test.js +432 -0
  35. package/__tests__/helpers/trackingTargets.test.js +319 -0
  36. package/__tests__/helpers/validator.test.js +385 -1
  37. package/__tests__/index.test.js +436 -0
  38. package/__tests__/syncGtm.test.js +366 -6
  39. package/__tests__/update-schema-ids.test.js +70 -1
  40. package/__tests__/validateSchemas-integration.test.js +2 -2
  41. package/__tests__/validateSchemas.test.js +192 -1
  42. package/components/PropertiesTable.js +32 -2
  43. package/components/PropertyRow.js +29 -2
  44. package/components/SchemaJsonViewer.js +234 -131
  45. package/components/SchemaRows.css +40 -0
  46. package/components/SchemaViewer.js +11 -2
  47. package/generateEventDocs.js +21 -1
  48. package/helpers/constraintSchemaPaths.js +10 -14
  49. package/helpers/example-helper.js +2 -2
  50. package/helpers/getConstraints.js +20 -0
  51. package/helpers/processSchema.js +32 -1
  52. package/helpers/schema-doc-template.js +4 -0
  53. package/helpers/schemaToExamples.js +29 -35
  54. package/helpers/schemaToTableData.js +538 -492
  55. package/helpers/schemaTraversal.cjs +148 -0
  56. package/helpers/trackingTargets.js +26 -3
  57. package/helpers/validator.js +18 -4
  58. package/index.js +1 -2
  59. package/package.json +1 -1
  60. package/scripts/sync-gtm.js +65 -34
  61. package/test-data/payloadContracts.js +35 -0
  62. package/validateSchemas.js +1 -1
@@ -234,4 +234,244 @@ describe('buildExampleFromSchema', () => {
234
234
  expect(example.shipping).toHaveProperty('method', 'express');
235
235
  expect(example.shipping).toHaveProperty('priority', 'high');
236
236
  });
237
+
238
+ it('should use example value from a primitive schema that has examples (L19)', () => {
239
+ // primitive with examples — must return example value, not placeholder
240
+ const schema = { type: 'string', examples: ['hello'] };
241
+ expect(buildExampleFromSchema(schema)).toBe('hello');
242
+ });
243
+
244
+ it('should use example value from integer schema with examples (L19)', () => {
245
+ const schema = { type: 'integer', examples: [42] };
246
+ expect(buildExampleFromSchema(schema)).toBe(42);
247
+ });
248
+
249
+ it('should use the first option from oneOf when items length > 0 (L25)', () => {
250
+ const schema = {
251
+ type: 'object',
252
+ properties: { event: { type: 'string', examples: ['purchase'] } },
253
+ oneOf: [
254
+ {
255
+ title: 'Option A',
256
+ properties: { extra: { type: 'string', examples: ['a'] } },
257
+ },
258
+ {
259
+ title: 'Option B',
260
+ properties: { extra: { type: 'string', examples: ['b'] } },
261
+ },
262
+ ],
263
+ };
264
+ const example = buildExampleFromSchema(schema);
265
+ expect(example).toHaveProperty('extra', 'a');
266
+ expect(example).toHaveProperty('event', 'purchase');
267
+ });
268
+
269
+ it('should use the first option from anyOf when items length > 0 (L25)', () => {
270
+ const schema = {
271
+ anyOf: [
272
+ { type: 'string', examples: ['first'] },
273
+ { type: 'string', examples: ['second'] },
274
+ ],
275
+ };
276
+ expect(buildExampleFromSchema(schema)).toBe('first');
277
+ });
278
+
279
+ it('should skip if/then branch when there is only if but no then (L34)', () => {
280
+ // schema.if && schema.then — the branch is only entered when both are present
281
+ const schema = {
282
+ type: 'object',
283
+ properties: {
284
+ event: { type: 'string', examples: ['test'] },
285
+ extra: { type: 'string', examples: ['value'] },
286
+ },
287
+ if: { properties: { event: { const: 'test' } } },
288
+ // deliberately no then
289
+ else: {
290
+ properties: {
291
+ error_code: { type: 'integer', examples: [404] },
292
+ },
293
+ },
294
+ };
295
+ const example = buildExampleFromSchema(schema);
296
+ expect(example).toHaveProperty('event', 'test');
297
+ expect(example).toHaveProperty('extra', 'value');
298
+ expect(example).not.toHaveProperty('error_code');
299
+ });
300
+
301
+ it('should infer object type from properties when no type is specified (L52)', () => {
302
+ const schema = {
303
+ properties: {
304
+ name: { type: 'string', examples: ['Alice'] },
305
+ age: { type: 'integer', examples: [30] },
306
+ },
307
+ };
308
+ const example = buildExampleFromSchema(schema);
309
+ expect(example).toEqual({ name: 'Alice', age: 30 });
310
+ });
311
+
312
+ it('should return undefined for object with type but no properties (L52)', () => {
313
+ const schema = { type: 'object' };
314
+ expect(buildExampleFromSchema(schema)).toBeUndefined();
315
+ });
316
+
317
+ it('should handle array type with items (L69)', () => {
318
+ const schema = {
319
+ type: 'array',
320
+ items: { type: 'string', examples: ['tag'] },
321
+ };
322
+ expect(buildExampleFromSchema(schema)).toEqual(['tag']);
323
+ });
324
+
325
+ it('should return undefined for array type with no items (L69)', () => {
326
+ const schema = { type: 'array' };
327
+ expect(buildExampleFromSchema(schema)).toBeUndefined();
328
+ });
329
+
330
+ it('should return empty string placeholder for string type without examples (L71 fallback)', () => {
331
+ const schema = { type: 'string' };
332
+ expect(buildExampleFromSchema(schema)).toBe('');
333
+ });
334
+
335
+ it('should return undefined for an unknown type (L71 fallback)', () => {
336
+ const schema = { type: 'unknown-type' };
337
+ expect(buildExampleFromSchema(schema)).toBeUndefined();
338
+ });
339
+
340
+ it('should return false placeholder for boolean type', () => {
341
+ const schema = { type: 'boolean' };
342
+ expect(buildExampleFromSchema(schema)).toBe(false);
343
+ });
344
+
345
+ it('should return 0 placeholder for number type', () => {
346
+ const schema = { type: 'number' };
347
+ expect(buildExampleFromSchema(schema)).toBe(0);
348
+ });
349
+
350
+ it('should use first type when schema.type is an array (L52)', () => {
351
+ const schema = {
352
+ type: ['string', 'null'],
353
+ examples: ['hello'],
354
+ };
355
+ expect(buildExampleFromSchema(schema)).toBe('hello');
356
+ });
357
+
358
+ it('should use first type placeholder when schema.type is array and no examples (L52)', () => {
359
+ const schema = { type: ['integer', 'null'] };
360
+ expect(buildExampleFromSchema(schema)).toBe(0);
361
+ });
362
+
363
+ // --- Mutant-killing tests ---
364
+
365
+ it('should return undefined for null/undefined schema (L19 guard)', () => {
366
+ expect(buildExampleFromSchema(null)).toBeUndefined();
367
+ expect(buildExampleFromSchema(undefined)).toBeUndefined();
368
+ expect(buildExampleFromSchema(0)).toBeUndefined();
369
+ expect(buildExampleFromSchema(false)).toBeUndefined();
370
+ });
371
+
372
+ it('should fall through to primitive placeholder when type is not object/array (L13 default)', () => {
373
+ // Ensures the default branch in getPrimitivePlaceholder returns undefined
374
+ // for unrecognized types, killing the ConditionalExpression mutant on L13-14
375
+ const schema = { type: 'null' };
376
+ expect(buildExampleFromSchema(schema)).toBeUndefined();
377
+ });
378
+
379
+ it('should skip oneOf branch when oneOf is an empty array (L25 length > 0)', () => {
380
+ const schema = {
381
+ type: 'object',
382
+ properties: {
383
+ name: { type: 'string', examples: ['Alice'] },
384
+ },
385
+ oneOf: [],
386
+ };
387
+ const example = buildExampleFromSchema(schema);
388
+ expect(example).toEqual({ name: 'Alice' });
389
+ });
390
+
391
+ it('should skip anyOf branch when anyOf is an empty array (L25 length > 0)', () => {
392
+ const schema = {
393
+ type: 'object',
394
+ properties: {
395
+ name: { type: 'string', examples: ['Bob'] },
396
+ },
397
+ anyOf: [],
398
+ };
399
+ const example = buildExampleFromSchema(schema);
400
+ expect(example).toEqual({ name: 'Bob' });
401
+ });
402
+
403
+ it('should not infer object type when type is explicitly set to a non-object type (L52 && vs ||)', () => {
404
+ // type is 'string' and properties exist — should NOT be treated as object
405
+ // Kills the LogicalOperator mutant that changes && to || on L52
406
+ const schema = {
407
+ type: 'string',
408
+ properties: {
409
+ name: { type: 'string', examples: ['ignored'] },
410
+ },
411
+ };
412
+ const example = buildExampleFromSchema(schema);
413
+ expect(example).toBe('');
414
+ });
415
+
416
+ it('should not enter object branch when inferredType is not object (L55 ConditionalExpression true)', () => {
417
+ // type is 'string' with properties — should return string placeholder, not object
418
+ const schema = {
419
+ type: 'string',
420
+ properties: {
421
+ foo: { type: 'integer', examples: [99] },
422
+ },
423
+ };
424
+ expect(buildExampleFromSchema(schema)).toBe('');
425
+ });
426
+
427
+ it('should not enter array branch when type is not array but items exists (L69 && vs ||)', () => {
428
+ // type is 'string' with items — should return string placeholder, not array
429
+ const schema = {
430
+ type: 'string',
431
+ items: { type: 'string', examples: ['ignored'] },
432
+ };
433
+ expect(buildExampleFromSchema(schema)).toBe('');
434
+ });
435
+
436
+ it('should not enter array branch when type is array but items is missing (L69 ConditionalExpression true)', () => {
437
+ // Already tested above but with strict assertion
438
+ const schema = { type: 'array' };
439
+ const result = buildExampleFromSchema(schema);
440
+ // Should fall through to getPrimitivePlaceholder which returns undefined for 'array'
441
+ expect(result).toBeUndefined();
442
+ });
443
+
444
+ it('should return undefined for array items that resolve to undefined (L71)', () => {
445
+ // Array with items that produce undefined — the whole array should be undefined
446
+ const schema = {
447
+ type: 'array',
448
+ items: { type: 'object' }, // object with no properties returns undefined
449
+ };
450
+ expect(buildExampleFromSchema(schema)).toBeUndefined();
451
+ });
452
+
453
+ it('should wrap array items value in array, not return empty string (L71 StringLiteral)', () => {
454
+ // Kills the StringLiteral mutant that replaces [itemValue] with ""
455
+ const schema = {
456
+ type: 'array',
457
+ items: { type: 'integer' },
458
+ };
459
+ const result = buildExampleFromSchema(schema);
460
+ expect(result).toEqual([0]);
461
+ expect(Array.isArray(result)).toBe(true);
462
+ expect(result).not.toBe('');
463
+ });
464
+
465
+ it('should handle oneOf with null/undefined choiceType length gracefully (L25 OptionalChaining)', () => {
466
+ // oneOf is present but not an array — optional chaining should prevent crash
467
+ const schema = {
468
+ type: 'object',
469
+ properties: {
470
+ name: { type: 'string', examples: ['test'] },
471
+ },
472
+ oneOf: null,
473
+ };
474
+ const example = buildExampleFromSchema(schema);
475
+ expect(example).toEqual({ name: 'test' });
476
+ });
237
477
  });
@@ -0,0 +1,208 @@
1
+ import path from 'path';
2
+ import { resolveConstraintSchemaPath } from '../../helpers/constraintSchemaPaths';
3
+
4
+ jest.mock('fs');
5
+ import fs from 'fs';
6
+
7
+ // A stable resolved root path that mocked existsSync returns truthy for
8
+ const MOCK_ROOT = path.resolve(
9
+ process.cwd(),
10
+ 'packages',
11
+ 'tracking-target-constraints',
12
+ );
13
+
14
+ beforeEach(() => {
15
+ jest.clearAllMocks();
16
+ // By default, make the first candidate exist
17
+ fs.existsSync.mockImplementation((p) => p === MOCK_ROOT);
18
+ });
19
+
20
+ describe('resolveConstraintSchemaPath — input validation', () => {
21
+ it('returns null for null uri', () => {
22
+ expect(resolveConstraintSchemaPath(null)).toBeNull();
23
+ });
24
+
25
+ it('returns null for undefined uri', () => {
26
+ expect(resolveConstraintSchemaPath(undefined)).toBeNull();
27
+ });
28
+
29
+ it('returns null for empty string', () => {
30
+ expect(resolveConstraintSchemaPath('')).toBeNull();
31
+ });
32
+
33
+ it('returns null for a number', () => {
34
+ expect(resolveConstraintSchemaPath(42)).toBeNull();
35
+ });
36
+
37
+ it('returns null for an object', () => {
38
+ expect(resolveConstraintSchemaPath({})).toBeNull();
39
+ });
40
+
41
+ it('rejects non-string via typeof check, not just falsiness', () => {
42
+ // 0 is falsy AND not a string — both checks reject it
43
+ expect(resolveConstraintSchemaPath(0)).toBeNull();
44
+ // true is not a string but truthy — only typeof check catches it
45
+ expect(resolveConstraintSchemaPath(true)).toBeNull();
46
+ });
47
+ });
48
+
49
+ describe('resolveConstraintSchemaPath — when package root not found', () => {
50
+ it('returns null when no candidate directory exists', () => {
51
+ fs.existsSync.mockReturnValue(false);
52
+ expect(resolveConstraintSchemaPath('/constraints/foo.json')).toBeNull();
53
+ });
54
+
55
+ it('checks multiple candidate paths (at least the primary one)', () => {
56
+ fs.existsSync.mockReturnValue(false);
57
+ resolveConstraintSchemaPath('/constraints/foo.json');
58
+ expect(fs.existsSync).toHaveBeenCalledWith(
59
+ path.resolve(process.cwd(), 'packages', 'tracking-target-constraints'),
60
+ );
61
+ });
62
+
63
+ it('falls back to second candidate (one level up) when primary missing', () => {
64
+ const secondCandidate = path.resolve(
65
+ process.cwd(),
66
+ '..',
67
+ 'packages',
68
+ 'tracking-target-constraints',
69
+ );
70
+ fs.existsSync.mockImplementation((p) => p === secondCandidate);
71
+ const result = resolveConstraintSchemaPath('/constraints/foo.json');
72
+ expect(result).toBe(path.join(secondCandidate, 'foo.json'));
73
+ });
74
+
75
+ it('falls back to third candidate (two levels up) when both others missing', () => {
76
+ const thirdCandidate = path.resolve(
77
+ process.cwd(),
78
+ '..',
79
+ '..',
80
+ 'packages',
81
+ 'tracking-target-constraints',
82
+ );
83
+ fs.existsSync.mockImplementation((p) => p === thirdCandidate);
84
+ const result = resolveConstraintSchemaPath('/constraints/foo.json');
85
+ expect(result).toBe(path.join(thirdCandidate, 'foo.json'));
86
+ });
87
+
88
+ it('falls back to node_modules package when monorepo candidates are missing', () => {
89
+ const installedPackageCandidate = path.resolve(
90
+ process.cwd(),
91
+ 'node_modules',
92
+ 'tracking-target-constraints',
93
+ );
94
+ fs.existsSync.mockImplementation((p) => p === installedPackageCandidate);
95
+
96
+ const result = resolveConstraintSchemaPath('/constraints/foo.json');
97
+
98
+ expect(result).toBe(path.join(installedPackageCandidate, 'foo.json'));
99
+ });
100
+ });
101
+
102
+ describe('resolveConstraintSchemaPath — HTTP/HTTPS URIs', () => {
103
+ it('resolves https URI with /constraints/ pathname', () => {
104
+ const result = resolveConstraintSchemaPath(
105
+ 'https://example.com/constraints/schemas/foo.json',
106
+ );
107
+ expect(result).toBe(path.join(MOCK_ROOT, 'schemas/foo.json'));
108
+ });
109
+
110
+ it('resolves http URI with /constraints/ pathname', () => {
111
+ const result = resolveConstraintSchemaPath(
112
+ 'http://example.com/constraints/bar.json',
113
+ );
114
+ expect(result).toBe(path.join(MOCK_ROOT, 'bar.json'));
115
+ });
116
+
117
+ it('returns null for https URI whose pathname does not start with /constraints/', () => {
118
+ expect(
119
+ resolveConstraintSchemaPath('https://example.com/other/schemas/foo.json'),
120
+ ).toBeNull();
121
+ });
122
+
123
+ it('returns null for https URI with empty pathname', () => {
124
+ expect(resolveConstraintSchemaPath('https://example.com/')).toBeNull();
125
+ });
126
+
127
+ it('strips leading /constraints/ from the pathname before joining', () => {
128
+ const result = resolveConstraintSchemaPath(
129
+ 'https://example.com/constraints/nested/schema.json',
130
+ );
131
+ expect(result).toBe(path.join(MOCK_ROOT, 'nested/schema.json'));
132
+ });
133
+
134
+ it('uses startsWith not endsWith for http/https detection', () => {
135
+ // A local URI containing "http://" somewhere other than the start should not match
136
+ // this test validates the startsWith constraint; local URIs fall into the next branch
137
+ const result = resolveConstraintSchemaPath('/constraints/foo.json');
138
+ // Should resolve via local path branch, not via URL parsing
139
+ expect(result).toBe(path.join(MOCK_ROOT, 'foo.json'));
140
+ });
141
+ });
142
+
143
+ describe('resolveConstraintSchemaPath — local constraint URIs', () => {
144
+ it('resolves /constraints/ absolute path', () => {
145
+ const result = resolveConstraintSchemaPath('/constraints/schemas/foo.json');
146
+ expect(result).toBe(path.join(MOCK_ROOT, 'schemas/foo.json'));
147
+ });
148
+
149
+ it('resolves constraints/ relative path (no leading slash)', () => {
150
+ const result = resolveConstraintSchemaPath('constraints/schemas/bar.json');
151
+ expect(result).toBe(path.join(MOCK_ROOT, 'schemas/bar.json'));
152
+ });
153
+
154
+ it('strips /constraints/ prefix correctly (not just constraints/)', () => {
155
+ const withSlash = resolveConstraintSchemaPath('/constraints/foo.json');
156
+ const withoutSlash = resolveConstraintSchemaPath('constraints/foo.json');
157
+ // Both should resolve to the same file
158
+ expect(withSlash).toBe(withoutSlash);
159
+ });
160
+
161
+ it('returns null for a local path that does not start with /constraints/ or constraints/', () => {
162
+ expect(resolveConstraintSchemaPath('/other/foo.json')).toBeNull();
163
+ });
164
+
165
+ it('returns null for a path starting with /constraint/ (missing trailing s)', () => {
166
+ expect(resolveConstraintSchemaPath('/constraint/foo.json')).toBeNull();
167
+ });
168
+
169
+ it('returns null for a plain filename with no path prefix', () => {
170
+ expect(resolveConstraintSchemaPath('foo.json')).toBeNull();
171
+ });
172
+
173
+ it('uses OR not AND — resolves both /constraints/ and constraints/ variants', () => {
174
+ const absolute = resolveConstraintSchemaPath('/constraints/x.json');
175
+ const relative = resolveConstraintSchemaPath('constraints/x.json');
176
+ expect(absolute).not.toBeNull();
177
+ expect(relative).not.toBeNull();
178
+ });
179
+
180
+ it('uses startsWith not endsWith for local path detection', () => {
181
+ // A URI ending in constraints/ should NOT resolve
182
+ expect(resolveConstraintSchemaPath('foo/constraints/')).toBeNull();
183
+ });
184
+ });
185
+
186
+ describe('resolveConstraintSchemaPath — regex stripping', () => {
187
+ it('strips leading /constraints/ from pathname', () => {
188
+ const result = resolveConstraintSchemaPath(
189
+ '/constraints/deep/nested/file.json',
190
+ );
191
+ expect(result).toBe(path.join(MOCK_ROOT, 'deep/nested/file.json'));
192
+ });
193
+
194
+ it('strips leading constraints/ (no slash) from pathname', () => {
195
+ const result = resolveConstraintSchemaPath(
196
+ 'constraints/deep/nested/file.json',
197
+ );
198
+ expect(result).toBe(path.join(MOCK_ROOT, 'deep/nested/file.json'));
199
+ });
200
+
201
+ it('only strips the prefix once, not globally', () => {
202
+ const result = resolveConstraintSchemaPath(
203
+ '/constraints/constraints/nested.json',
204
+ );
205
+ // After stripping /constraints/ prefix, we should have constraints/nested.json remaining
206
+ expect(result).toContain('constraints/nested.json');
207
+ });
208
+ });