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
@@ -0,0 +1,436 @@
1
+ /**
2
+ * @jest-environment @stryker-mutator/jest-runner/jest-env/node
3
+ */
4
+
5
+ import createPlugin from '../index.js';
6
+
7
+ jest.mock('url', () => ({
8
+ fileURLToPath: jest.fn(
9
+ () => '/mocked/packages/docusaurus-plugin-generate-schema-docs/index.js',
10
+ ),
11
+ pathToFileURL: jest.fn((p) => ({ href: 'file://' + p })),
12
+ }));
13
+
14
+ jest.mock('../generateEventDocs.js', () => jest.fn().mockResolvedValue());
15
+ jest.mock('../validateSchemas.js', () => jest.fn().mockResolvedValue(true));
16
+ jest.mock('../helpers/update-schema-ids.js', () => jest.fn());
17
+ jest.mock('child_process', () => ({ execSync: jest.fn() }));
18
+ jest.mock('../helpers/path-helpers.js', () => ({
19
+ getPathsForVersion: jest
20
+ .fn()
21
+ .mockReturnValue({ schemaDir: '/site/static/schemas/next' }),
22
+ }));
23
+
24
+ jest.mock('fs', () => ({
25
+ existsSync: jest.fn(),
26
+ readFileSync: jest.fn(),
27
+ cpSync: jest.fn(),
28
+ }));
29
+
30
+ import generateEventDocs from '../generateEventDocs.js';
31
+ import validateSchemas from '../validateSchemas.js';
32
+ import updateSchemaIds from '../helpers/update-schema-ids.js';
33
+ import { getPathsForVersion } from '../helpers/path-helpers.js';
34
+ import fs from 'fs';
35
+ import { execSync } from 'child_process';
36
+
37
+ const makeContext = (overrides = {}) => ({
38
+ siteDir: '/site',
39
+ siteConfig: {
40
+ organizationName: 'org',
41
+ projectName: 'proj',
42
+ url: 'https://example.com',
43
+ },
44
+ ...overrides,
45
+ });
46
+
47
+ const makeOptions = () => ({ dataLayerName: 'dataLayer' });
48
+
49
+ beforeEach(() => {
50
+ jest.clearAllMocks();
51
+ fs.existsSync.mockReturnValue(false);
52
+ });
53
+
54
+ describe('loadContent', () => {
55
+ it('calls generateEventDocs once without version when not versioned', async () => {
56
+ const plugin = await createPlugin(makeContext(), makeOptions());
57
+ await plugin.loadContent();
58
+
59
+ expect(generateEventDocs).toHaveBeenCalledTimes(1);
60
+ expect(generateEventDocs).toHaveBeenCalledWith(
61
+ expect.not.objectContaining({ version: expect.anything() }),
62
+ );
63
+ });
64
+
65
+ it('calls generateEventDocs for each version plus current when versioned', async () => {
66
+ fs.existsSync.mockReturnValue(true);
67
+ fs.readFileSync.mockReturnValue(JSON.stringify(['1.0', '2.0']));
68
+
69
+ const plugin = await createPlugin(makeContext(), makeOptions());
70
+ await plugin.loadContent();
71
+
72
+ expect(fs.readFileSync).toHaveBeenCalledWith('/site/versions.json', 'utf8');
73
+ expect(generateEventDocs).toHaveBeenCalledTimes(3);
74
+ expect(generateEventDocs).toHaveBeenCalledWith(
75
+ expect.objectContaining({ version: '1.0' }),
76
+ );
77
+ expect(generateEventDocs).toHaveBeenCalledWith(
78
+ expect.objectContaining({ version: '2.0' }),
79
+ );
80
+ expect(generateEventDocs).toHaveBeenCalledWith(
81
+ expect.objectContaining({ version: 'current' }),
82
+ );
83
+ });
84
+ });
85
+
86
+ describe('extendCli - validate-schemas', () => {
87
+ const makeCli = (targetCommand = 'validate-schemas [version]') => {
88
+ const action = { fn: null };
89
+ const cli = {
90
+ command: jest.fn((name) => {
91
+ const cmd = {
92
+ description: jest.fn().mockReturnThis(),
93
+ option: jest.fn().mockReturnThis(),
94
+ action: jest.fn((fn) => {
95
+ if (name === targetCommand) action.fn = fn;
96
+ return cmd;
97
+ }),
98
+ };
99
+ return cmd;
100
+ }),
101
+ };
102
+ return { cli, action };
103
+ };
104
+
105
+ it('calls validateSchemas with schemaDir for given version', async () => {
106
+ const { cli, action } = makeCli();
107
+ const plugin = await createPlugin(makeContext(), makeOptions());
108
+ plugin.extendCli(cli);
109
+
110
+ // first command registered is validate-schemas
111
+ expect(cli.command).toHaveBeenCalledWith('validate-schemas [version]');
112
+ await action.fn('next');
113
+
114
+ expect(validateSchemas).toHaveBeenCalledWith('/site/static/schemas/next');
115
+ });
116
+
117
+ it('defaults to "next" version when no version argument given', async () => {
118
+ const { cli, action } = makeCli();
119
+ const plugin = await createPlugin(makeContext(), makeOptions());
120
+ plugin.extendCli(cli);
121
+
122
+ await action.fn(undefined);
123
+ expect(getPathsForVersion).toHaveBeenCalledWith('next', '/site');
124
+ expect(validateSchemas).toHaveBeenCalledWith('/site/static/schemas/next');
125
+ });
126
+
127
+ it('exits with code 1 when validation fails', async () => {
128
+ validateSchemas.mockResolvedValue(false);
129
+ const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
130
+ jest.spyOn(console, 'error').mockImplementation(() => {});
131
+
132
+ const { cli, action } = makeCli();
133
+ const plugin = await createPlugin(makeContext(), makeOptions());
134
+ plugin.extendCli(cli);
135
+
136
+ await action.fn('next');
137
+ expect(exitSpy).toHaveBeenCalledWith(1);
138
+
139
+ exitSpy.mockRestore();
140
+ });
141
+ });
142
+
143
+ describe('plugin structure', () => {
144
+ it('returns plugin with name docusaurus-plugin-generate-schema-docs', async () => {
145
+ const plugin = await createPlugin(makeContext(), makeOptions());
146
+ expect(plugin.name).toBe('docusaurus-plugin-generate-schema-docs');
147
+ });
148
+
149
+ it('passes all pluginOptions properties to generateEventDocs', async () => {
150
+ const plugin = await createPlugin(makeContext(), makeOptions());
151
+ await plugin.loadContent();
152
+
153
+ expect(generateEventDocs).toHaveBeenCalledWith(
154
+ expect.objectContaining({
155
+ organizationName: 'org',
156
+ projectName: 'proj',
157
+ siteDir: '/site',
158
+ url: 'https://example.com',
159
+ dataLayerName: 'dataLayer',
160
+ }),
161
+ );
162
+ });
163
+
164
+ it('checks for versions.json inside siteDir', async () => {
165
+ await createPlugin(makeContext(), makeOptions());
166
+ expect(fs.existsSync).toHaveBeenCalledWith('/site/versions.json');
167
+ });
168
+
169
+ it('getPathsToWatch returns static/schemas/next path when versioned', async () => {
170
+ fs.existsSync.mockReturnValue(true);
171
+ fs.readFileSync.mockReturnValue(JSON.stringify(['1.0']));
172
+
173
+ const plugin = await createPlugin(makeContext(), makeOptions());
174
+ const paths = plugin.getPathsToWatch();
175
+ expect(paths).toContain('/site/static/schemas/next');
176
+ });
177
+
178
+ it('getPathsToWatch returns static/schemas path when not versioned', async () => {
179
+ const plugin = await createPlugin(makeContext(), makeOptions());
180
+ const paths = plugin.getPathsToWatch();
181
+ expect(paths).toContain('/site/static/schemas');
182
+ expect(paths).not.toContain('/site/static/schemas/next');
183
+ });
184
+
185
+ it('has a getThemePath method that returns ./components', async () => {
186
+ const plugin = await createPlugin(makeContext(), makeOptions());
187
+ expect(typeof plugin.getThemePath).toBe('function');
188
+ expect(plugin.getThemePath()).toBe('./components');
189
+ });
190
+ });
191
+
192
+ describe('extendCli - generate-schema-docs', () => {
193
+ const getActionForCommand = async (commandName) => {
194
+ let capturedAction = null;
195
+ const cli = {
196
+ command: jest.fn((name) => {
197
+ const cmd = {
198
+ description: jest.fn().mockReturnThis(),
199
+ option: jest.fn().mockReturnThis(),
200
+ action: jest.fn((fn) => {
201
+ if (name === commandName) capturedAction = fn;
202
+ return cmd;
203
+ }),
204
+ };
205
+
206
+ return cmd;
207
+ }),
208
+ };
209
+ const plugin = await createPlugin(makeContext(), makeOptions());
210
+ plugin.extendCli(cli);
211
+ return capturedAction;
212
+ };
213
+
214
+ it('calls generateEventDocs once when not versioned', async () => {
215
+ const action = await getActionForCommand('generate-schema-docs');
216
+ await action();
217
+ expect(generateEventDocs).toHaveBeenCalledTimes(1);
218
+ });
219
+
220
+ it('calls generateEventDocs for each version plus current when versioned', async () => {
221
+ fs.existsSync.mockReturnValue(true);
222
+ fs.readFileSync.mockReturnValue(JSON.stringify(['1.0']));
223
+
224
+ const action = await getActionForCommand('generate-schema-docs');
225
+ await action();
226
+
227
+ expect(fs.readFileSync).toHaveBeenCalledWith('/site/versions.json', 'utf8');
228
+ expect(generateEventDocs).toHaveBeenCalledTimes(2);
229
+ expect(generateEventDocs).toHaveBeenCalledWith(
230
+ expect.objectContaining({ version: '1.0' }),
231
+ );
232
+ expect(generateEventDocs).toHaveBeenCalledWith(
233
+ expect.objectContaining({ version: 'current' }),
234
+ );
235
+ });
236
+ });
237
+
238
+ describe('extendCli - update-schema-ids', () => {
239
+ const getAction = async () => {
240
+ let captured = null;
241
+ const cli = {
242
+ command: jest.fn((name) => {
243
+ const cmd = {
244
+ description: jest.fn().mockReturnThis(),
245
+ option: jest.fn().mockReturnThis(),
246
+ action: jest.fn((fn) => {
247
+ if (name === 'update-schema-ids [version]') captured = fn;
248
+ return cmd;
249
+ }),
250
+ };
251
+ return cmd;
252
+ }),
253
+ };
254
+ const plugin = await createPlugin(makeContext(), makeOptions());
255
+ plugin.extendCli(cli);
256
+ return captured;
257
+ };
258
+
259
+ it('calls updateSchemaIds with siteDir, url, and version', async () => {
260
+ const action = await getAction();
261
+ action('1.0.0');
262
+ expect(updateSchemaIds).toHaveBeenCalledWith(
263
+ '/site',
264
+ 'https://example.com',
265
+ '1.0.0',
266
+ );
267
+ });
268
+ });
269
+
270
+ describe('extendCli - sync-gtm', () => {
271
+ const getAction = async () => {
272
+ let captured = null;
273
+ const cli = {
274
+ command: jest.fn((name) => {
275
+ const cmd = {
276
+ description: jest.fn().mockReturnThis(),
277
+ option: jest.fn().mockReturnThis(),
278
+ action: jest.fn((fn) => {
279
+ if (name === 'sync-gtm') captured = fn;
280
+ return cmd;
281
+ }),
282
+ };
283
+ return cmd;
284
+ }),
285
+ };
286
+ const plugin = await createPlugin(makeContext(), makeOptions());
287
+ plugin.extendCli(cli);
288
+ return captured;
289
+ };
290
+
291
+ it('runs the sync-gtm script with the default path', async () => {
292
+ const action = await getAction();
293
+ action({ path: '/site' });
294
+ const cmd = execSync.mock.calls[0][0];
295
+ expect(cmd).toContain('scripts/sync-gtm.js');
296
+ expect(cmd).toContain('--path=/site');
297
+ expect(execSync).toHaveBeenCalledWith(cmd, {
298
+ cwd: '/site',
299
+ stdio: 'inherit',
300
+ });
301
+ });
302
+
303
+ it('joins multiple args with spaces', async () => {
304
+ const action = await getAction();
305
+ action({ path: '/site', json: true, quiet: true });
306
+ const cmd = execSync.mock.calls[0][0];
307
+ expect(cmd).toContain('--path=/site --json --quiet');
308
+ });
309
+
310
+ it('appends --json flag when json option is set', async () => {
311
+ const action = await getAction();
312
+ action({ path: '/site', json: true });
313
+ expect(execSync).toHaveBeenCalledWith(
314
+ expect.stringContaining('--json'),
315
+ expect.any(Object),
316
+ );
317
+ });
318
+
319
+ it('appends --quiet flag when quiet option is set', async () => {
320
+ const action = await getAction();
321
+ action({ path: '/site', quiet: true });
322
+ expect(execSync).toHaveBeenCalledWith(
323
+ expect.stringContaining('--quiet'),
324
+ expect.any(Object),
325
+ );
326
+ });
327
+
328
+ it('appends --skip-array-sub-properties flag when option is set', async () => {
329
+ const action = await getAction();
330
+ action({ path: '/site', skipArraySubProperties: true });
331
+ expect(execSync).toHaveBeenCalledWith(
332
+ expect.stringContaining('--skip-array-sub-properties'),
333
+ expect.any(Object),
334
+ );
335
+ });
336
+
337
+ it('does not append optional flags when options are falsy', async () => {
338
+ const action = await getAction();
339
+ action({ path: '/site' });
340
+ const cmd = execSync.mock.calls[0][0];
341
+ expect(cmd).not.toContain('--json');
342
+ expect(cmd).not.toContain('--quiet');
343
+ expect(cmd).not.toContain('--skip-array-sub-properties');
344
+ });
345
+ });
346
+
347
+ describe('extendCli - version-with-schemas', () => {
348
+ const getAction = async () => {
349
+ let captured = null;
350
+ const cli = {
351
+ command: jest.fn((name) => {
352
+ const cmd = {
353
+ description: jest.fn().mockReturnThis(),
354
+ option: jest.fn().mockReturnThis(),
355
+ action: jest.fn((fn) => {
356
+ if (name === 'version-with-schemas <version>') captured = fn;
357
+ return cmd;
358
+ }),
359
+ };
360
+ return cmd;
361
+ }),
362
+ };
363
+ const plugin = await createPlugin(makeContext(), makeOptions());
364
+ plugin.extendCli(cli);
365
+ return captured;
366
+ };
367
+
368
+ beforeEach(() => {
369
+ jest.spyOn(console, 'log').mockImplementation(() => {});
370
+ jest.spyOn(console, 'error').mockImplementation(() => {});
371
+ // versions.json absent (not versioned); nextSchemasDir present
372
+ fs.existsSync.mockImplementation((p) => !p.includes('versions.json'));
373
+ });
374
+
375
+ it('creates version, copies schemas, updates IDs and generates docs on success', async () => {
376
+ const action = await getAction();
377
+ await action('1.2.0');
378
+
379
+ expect(execSync).toHaveBeenCalledWith(
380
+ expect.stringContaining('docs:version 1.2.0'),
381
+ { cwd: '/site', stdio: 'inherit' },
382
+ );
383
+ expect(fs.cpSync).toHaveBeenCalledWith(
384
+ '/site/static/schemas/next',
385
+ '/site/static/schemas/1.2.0',
386
+ { recursive: true },
387
+ );
388
+ expect(updateSchemaIds).toHaveBeenCalledWith(
389
+ '/site',
390
+ 'https://example.com',
391
+ '1.2.0',
392
+ );
393
+ expect(generateEventDocs).toHaveBeenCalledWith(
394
+ expect.objectContaining({ version: '1.2.0' }),
395
+ );
396
+ });
397
+
398
+ it('exits with code 1 when docusaurus docs:version fails', async () => {
399
+ execSync.mockImplementation(() => {
400
+ throw new Error('cmd failed');
401
+ });
402
+ const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
403
+
404
+ const action = await getAction();
405
+ await action('1.2.0');
406
+
407
+ expect(exitSpy).toHaveBeenCalledWith(1);
408
+ exitSpy.mockRestore();
409
+ });
410
+
411
+ it('exits with code 1 when next schemas directory does not exist', async () => {
412
+ execSync.mockImplementation(() => {});
413
+ fs.existsSync.mockReturnValue(false); // nextSchemasDir also missing
414
+ const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
415
+
416
+ const action = await getAction();
417
+ await action('1.2.0');
418
+
419
+ expect(exitSpy).toHaveBeenCalledWith(1);
420
+ exitSpy.mockRestore();
421
+ });
422
+
423
+ it('exits with code 1 when schema copy fails', async () => {
424
+ execSync.mockImplementation(() => {});
425
+ fs.cpSync.mockImplementation(() => {
426
+ throw new Error('copy failed');
427
+ });
428
+ const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});
429
+
430
+ const action = await getAction();
431
+ await action('1.2.0');
432
+
433
+ expect(exitSpy).toHaveBeenCalledWith(1);
434
+ exitSpy.mockRestore();
435
+ });
436
+ });