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.
- package/README.md +10 -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__/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/exampleModel.test.js +209 -0
- package/__tests__/helpers/file-system.test.js +73 -1
- package/__tests__/helpers/getConstraints.test.js +27 -0
- package/__tests__/helpers/mergeSchema.test.js +94 -0
- package/__tests__/helpers/processSchema.test.js +291 -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/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 +139 -3
- package/__tests__/update-schema-ids.test.js +70 -1
- package/__tests__/validateSchemas-integration.test.js +2 -2
- package/__tests__/validateSchemas.test.js +142 -1
- package/generateEventDocs.js +21 -1
- package/helpers/constraintSchemaPaths.js +10 -14
- package/helpers/schemaToTableData.js +538 -492
- 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 +25 -7
|
@@ -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
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-env jest */
|
|
2
2
|
/**
|
|
3
|
-
* @jest-environment node
|
|
3
|
+
* @jest-environment @stryker-mutator/jest-runner/jest-env/node
|
|
4
4
|
*/
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
@@ -168,6 +168,31 @@ describe('getVariablesFromSchemas', () => {
|
|
|
168
168
|
screen_name: { type: 'string', description: 'Screen name.' },
|
|
169
169
|
},
|
|
170
170
|
};
|
|
171
|
+
const mobileConditionalSchema = {
|
|
172
|
+
title: 'Mobile Conditional Event',
|
|
173
|
+
'x-tracking-targets': ['android-firebase-kotlin-sdk'],
|
|
174
|
+
type: 'object',
|
|
175
|
+
allOf: [
|
|
176
|
+
{
|
|
177
|
+
if: {
|
|
178
|
+
properties: {
|
|
179
|
+
mobile_platform_hint: { const: 'ios' },
|
|
180
|
+
},
|
|
181
|
+
required: ['mobile_platform_hint'],
|
|
182
|
+
},
|
|
183
|
+
then: {
|
|
184
|
+
required: ['att_status'],
|
|
185
|
+
},
|
|
186
|
+
else: {
|
|
187
|
+
required: ['ad_personalization_enabled'],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
properties: {
|
|
192
|
+
event: { type: 'string', const: 'screen_view' },
|
|
193
|
+
mobile_platform_hint: { type: 'string' },
|
|
194
|
+
},
|
|
195
|
+
};
|
|
171
196
|
const untaggedEventSchema = {
|
|
172
197
|
title: 'Untagged Event',
|
|
173
198
|
type: 'object',
|
|
@@ -309,6 +334,52 @@ describe('getVariablesFromSchemas', () => {
|
|
|
309
334
|
);
|
|
310
335
|
});
|
|
311
336
|
|
|
337
|
+
it('should skip non-web schemas before mergeAllOf can fail on unsupported keywords', async () => {
|
|
338
|
+
const mobileConditionalPath = path.join(
|
|
339
|
+
SCHEMA_PATH,
|
|
340
|
+
'mobile-conditional-event.json',
|
|
341
|
+
);
|
|
342
|
+
mockFiles[SCHEMA_PATH].push('mobile-conditional-event.json');
|
|
343
|
+
mockFileContents[mobileConditionalPath] = JSON.stringify(
|
|
344
|
+
mobileConditionalSchema,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const bundledWebSchema = JSON.parse(JSON.stringify(complexEventSchema));
|
|
348
|
+
bundledWebSchema.properties.user_data.properties.addresses.items =
|
|
349
|
+
addressSchema;
|
|
350
|
+
const loggerErrorSpy = jest
|
|
351
|
+
.spyOn(console, 'error')
|
|
352
|
+
.mockImplementation(() => {});
|
|
353
|
+
|
|
354
|
+
RefParser.bundle.mockImplementation(async (filePath) => {
|
|
355
|
+
if (filePath.endsWith('complex-event.json')) {
|
|
356
|
+
return bundledWebSchema;
|
|
357
|
+
}
|
|
358
|
+
if (filePath.endsWith('mobile-event.json')) {
|
|
359
|
+
return mobileEventSchema;
|
|
360
|
+
}
|
|
361
|
+
if (filePath.endsWith('mobile-conditional-event.json')) {
|
|
362
|
+
return mobileConditionalSchema;
|
|
363
|
+
}
|
|
364
|
+
if (filePath.endsWith('address.json')) {
|
|
365
|
+
return addressSchema;
|
|
366
|
+
}
|
|
367
|
+
throw new Error(`Unexpected schema file: ${filePath}`);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const result = await gtmScript.getVariablesFromSchemas(SCHEMA_PATH, {});
|
|
371
|
+
|
|
372
|
+
expect(result.map((variable) => variable.name)).not.toContain(
|
|
373
|
+
'mobile_platform_hint',
|
|
374
|
+
);
|
|
375
|
+
expect(loggerErrorSpy).not.toHaveBeenCalledWith(
|
|
376
|
+
expect.stringContaining(
|
|
377
|
+
`Error processing schema ${mobileConditionalPath}`,
|
|
378
|
+
),
|
|
379
|
+
expect.any(Error),
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
|
|
312
383
|
it('should include root tracking schemas based on content instead of path names', async () => {
|
|
313
384
|
const nestedSchemaDir = path.join(SCHEMA_PATH, 'event-components-demo');
|
|
314
385
|
const nestedSchemaPath = path.join(nestedSchemaDir, 'checkout-event.json');
|
|
@@ -494,18 +565,61 @@ describe('GTM Synchronization Logic', () => {
|
|
|
494
565
|
});
|
|
495
566
|
|
|
496
567
|
describe('deleteGtmVariables', () => {
|
|
497
|
-
it('should
|
|
568
|
+
it('should delete variables and return their names', () => {
|
|
498
569
|
const toDelete = gtmScript.getVariablesToDelete(
|
|
499
570
|
schemaVariables,
|
|
500
571
|
gtmVariables,
|
|
501
572
|
);
|
|
502
|
-
const deleted = gtmScript.deleteGtmVariables(toDelete);
|
|
573
|
+
const { deleted, failedDeletes } = gtmScript.deleteGtmVariables(toDelete);
|
|
503
574
|
expect(execSync).toHaveBeenCalledTimes(1);
|
|
504
575
|
expect(execSync).toHaveBeenCalledWith(
|
|
505
576
|
'gtm variables delete --variable-id 123 --force --quiet',
|
|
506
577
|
{ stdio: 'inherit' },
|
|
507
578
|
);
|
|
508
579
|
expect(deleted).toEqual(['old_variable']);
|
|
580
|
+
expect(failedDeletes).toEqual([]);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('should capture failed deletes when GTM rejects the deletion', () => {
|
|
584
|
+
execSync.mockImplementation(() => {
|
|
585
|
+
throw new Error('Returned an error response for your request.');
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
const toDelete = gtmScript.getVariablesToDelete(
|
|
589
|
+
schemaVariables,
|
|
590
|
+
gtmVariables,
|
|
591
|
+
);
|
|
592
|
+
const { deleted, failedDeletes } = gtmScript.deleteGtmVariables(toDelete);
|
|
593
|
+
|
|
594
|
+
expect(deleted).toEqual([]);
|
|
595
|
+
expect(failedDeletes).toEqual([
|
|
596
|
+
{ name: 'old_variable', variableId: '123' },
|
|
597
|
+
]);
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
describe('syncGtmVariables', () => {
|
|
602
|
+
it('should report failed deletes when GTM rejects a deletion', async () => {
|
|
603
|
+
const syncedSchemaVariables = [
|
|
604
|
+
{ name: 'event', description: 'The event name.' },
|
|
605
|
+
];
|
|
606
|
+
|
|
607
|
+
execSync.mockImplementation((command) => {
|
|
608
|
+
if (command === 'gtm variables list -o json --quiet') {
|
|
609
|
+
return Buffer.from(JSON.stringify(gtmVariables));
|
|
610
|
+
}
|
|
611
|
+
if (command.startsWith('gtm variables delete')) {
|
|
612
|
+
throw new Error('Returned an error response for your request.');
|
|
613
|
+
}
|
|
614
|
+
throw new Error(`Unexpected command: ${command}`);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
const summary = await gtmScript.syncGtmVariables(syncedSchemaVariables);
|
|
618
|
+
|
|
619
|
+
expect(summary.deleted).toEqual([]);
|
|
620
|
+
expect(summary.failedDeletes).toEqual([
|
|
621
|
+
{ name: 'old_variable', variableId: '123' },
|
|
622
|
+
]);
|
|
509
623
|
});
|
|
510
624
|
});
|
|
511
625
|
});
|
|
@@ -525,6 +639,12 @@ describe('main function', () => {
|
|
|
525
639
|
syncGtmVariables: jest.fn().mockResolvedValue({
|
|
526
640
|
created: ['var1'],
|
|
527
641
|
deleted: ['var2'],
|
|
642
|
+
failedDeletes: [
|
|
643
|
+
{
|
|
644
|
+
name: 'legacy_field',
|
|
645
|
+
variableId: '88',
|
|
646
|
+
},
|
|
647
|
+
],
|
|
528
648
|
inSync: ['var3'],
|
|
529
649
|
}),
|
|
530
650
|
getVariablesFromSchemas: jest
|
|
@@ -558,6 +678,12 @@ describe('main function', () => {
|
|
|
558
678
|
workspace: { workspaceName: 'test-workspace', workspaceId: '123' },
|
|
559
679
|
created: ['var1'],
|
|
560
680
|
deleted: ['var2'],
|
|
681
|
+
failedDeletes: [
|
|
682
|
+
{
|
|
683
|
+
name: 'legacy_field',
|
|
684
|
+
variableId: '88',
|
|
685
|
+
},
|
|
686
|
+
],
|
|
561
687
|
inSync: ['var3'],
|
|
562
688
|
});
|
|
563
689
|
});
|
|
@@ -567,4 +693,14 @@ describe('main function', () => {
|
|
|
567
693
|
await gtmScript.main(argv, mockDeps);
|
|
568
694
|
expect(mockDeps.getLatestSchemaPath).toHaveBeenCalledWith('./my-demo');
|
|
569
695
|
});
|
|
696
|
+
|
|
697
|
+
it('should report failed deletions in human-readable output', async () => {
|
|
698
|
+
const argv = ['node', 'script.js'];
|
|
699
|
+
await gtmScript.main(argv, mockDeps);
|
|
700
|
+
|
|
701
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
702
|
+
'Skipped deleting 1 GTM variables (GTM rejected the delete, they may still be referenced):',
|
|
703
|
+
);
|
|
704
|
+
expect(logSpy).toHaveBeenCalledWith('- legacy_field (ID: 88)');
|
|
705
|
+
});
|
|
570
706
|
});
|