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
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @jest-environment node
|
|
2
|
+
* @jest-environment @stryker-mutator/jest-runner/jest-env/node
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import processSchema from '../../helpers/processSchema';
|
|
7
|
+
import { resolveConstraintSchemaPath } from '../../helpers/constraintSchemaPaths';
|
|
8
|
+
|
|
9
|
+
jest.mock('../../helpers/constraintSchemaPaths', () => {
|
|
10
|
+
const actual = jest.requireActual('../../helpers/constraintSchemaPaths');
|
|
11
|
+
return {
|
|
12
|
+
...actual,
|
|
13
|
+
resolveConstraintSchemaPath: jest.fn(actual.resolveConstraintSchemaPath),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
7
16
|
|
|
8
17
|
describe('processSchema', () => {
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
resolveConstraintSchemaPath.mockReset();
|
|
20
|
+
// Restore to original implementation after each test
|
|
21
|
+
const actual = jest.requireActual('../../helpers/constraintSchemaPaths');
|
|
22
|
+
resolveConstraintSchemaPath.mockImplementation(
|
|
23
|
+
actual.resolveConstraintSchemaPath,
|
|
24
|
+
);
|
|
25
|
+
});
|
|
9
26
|
it('should bundle refs and return a merged schema', async () => {
|
|
10
27
|
const filePath = path.join(
|
|
11
28
|
__dirname,
|
|
@@ -100,4 +117,277 @@ describe('processSchema', () => {
|
|
|
100
117
|
}),
|
|
101
118
|
);
|
|
102
119
|
});
|
|
120
|
+
|
|
121
|
+
// L20: unwrapRedundantNotAnyOf — not: { anyOf: [single] } is unwrapped to the single item
|
|
122
|
+
it('unwraps not: { anyOf: [singleItem] } to the single item', async () => {
|
|
123
|
+
const filePath = path.join(
|
|
124
|
+
__dirname,
|
|
125
|
+
'..',
|
|
126
|
+
'__fixtures__',
|
|
127
|
+
'validateSchemas',
|
|
128
|
+
'schema-with-not-anyof.json',
|
|
129
|
+
);
|
|
130
|
+
const mergedSchema = await processSchema(filePath);
|
|
131
|
+
|
|
132
|
+
// not: { anyOf: [{ const: 'US' }] } should become not: { const: 'US' }
|
|
133
|
+
expect(mergedSchema.properties.country.not).toEqual({ const: 'US' });
|
|
134
|
+
// The anyOf wrapper should be gone
|
|
135
|
+
expect(mergedSchema.properties.country.not.anyOf).toBeUndefined();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// L22-28: chained anyOf unwrapping (double-wrapped)
|
|
139
|
+
it('unwraps doubly-nested not: { anyOf: [{ anyOf: [item] }] } recursively', async () => {
|
|
140
|
+
const filePath = path.join(
|
|
141
|
+
__dirname,
|
|
142
|
+
'..',
|
|
143
|
+
'__fixtures__',
|
|
144
|
+
'validateSchemas',
|
|
145
|
+
'schema-with-not-anyof.json',
|
|
146
|
+
);
|
|
147
|
+
const mergedSchema = await processSchema(filePath);
|
|
148
|
+
|
|
149
|
+
// nested.code has not: { anyOf: [{ anyOf: [{ const: 'XX' }] }] }
|
|
150
|
+
// After two passes of unwrapping it should become not: { const: 'XX' }
|
|
151
|
+
expect(mergedSchema.properties.nested.properties.code.not).toEqual({
|
|
152
|
+
const: 'XX',
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// L11-12: arrays are traversed recursively
|
|
157
|
+
it('unwraps not inside array items', async () => {
|
|
158
|
+
const filePath = path.join(
|
|
159
|
+
__dirname,
|
|
160
|
+
'..',
|
|
161
|
+
'__fixtures__',
|
|
162
|
+
'validateSchemas',
|
|
163
|
+
'schema-with-not-anyof.json',
|
|
164
|
+
);
|
|
165
|
+
const mergedSchema = await processSchema(filePath);
|
|
166
|
+
|
|
167
|
+
// items array contains an object with not: { anyOf: [{ const: 'forbidden' }] }
|
|
168
|
+
// After unwrapping it should be not: { const: 'forbidden' }
|
|
169
|
+
const firstItem = mergedSchema.properties.items.items[0];
|
|
170
|
+
expect(firstItem.not).toEqual({ const: 'forbidden' });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// L20: not value is not an object — should be returned as-is (no crash)
|
|
174
|
+
it('handles not: true (non-object) without unwrapping', async () => {
|
|
175
|
+
const filePath = path.join(
|
|
176
|
+
__dirname,
|
|
177
|
+
'..',
|
|
178
|
+
'__fixtures__',
|
|
179
|
+
'validateSchemas',
|
|
180
|
+
'schema-with-not-non-object.json',
|
|
181
|
+
);
|
|
182
|
+
const mergedSchema = await processSchema(filePath);
|
|
183
|
+
|
|
184
|
+
// not: true should be kept as-is (it is not an object, so no unwrapping)
|
|
185
|
+
expect(mergedSchema.properties.flag.not).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// L20: not value is an object but has no anyOf — not unwrapped
|
|
189
|
+
it('leaves not: { const: X } alone when there is no anyOf wrapper', async () => {
|
|
190
|
+
const filePath = path.join(
|
|
191
|
+
__dirname,
|
|
192
|
+
'..',
|
|
193
|
+
'__fixtures__',
|
|
194
|
+
'validateSchemas',
|
|
195
|
+
'schema-with-not-non-object.json',
|
|
196
|
+
);
|
|
197
|
+
const mergedSchema = await processSchema(filePath);
|
|
198
|
+
|
|
199
|
+
expect(mergedSchema.properties.name.not).toEqual({ const: 'forbidden' });
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// L54-55: the read resolver's null-check guard.
|
|
203
|
+
// This branch is a defensive check: if canRead returns true (because
|
|
204
|
+
// resolveConstraintSchemaPath returned a path), but then read calls
|
|
205
|
+
// resolveConstraintSchemaPath again and it returns null, an error is thrown.
|
|
206
|
+
// Since $RefParser catches read errors and falls back to other resolvers,
|
|
207
|
+
// this branch cannot be exercised through the public processSchema API.
|
|
208
|
+
// We verify the guard's correctness by testing that:
|
|
209
|
+
// 1. canRead correctly identifies constraint URIs
|
|
210
|
+
// 2. read correctly resolves and reads constraint files
|
|
211
|
+
// Both are already covered by the constraint resolver tests above.
|
|
212
|
+
// The null guard on L54-55 exists for safety if the resolver state changes
|
|
213
|
+
// between canRead and read calls (e.g., file deletion race condition).
|
|
214
|
+
|
|
215
|
+
// L46-49: options object — mutateInputSchema: false preserves original schema
|
|
216
|
+
it('does not mutate the original bundled schema (mutateInputSchema: false)', async () => {
|
|
217
|
+
// If mutateInputSchema were true, the original file's schema could be modified.
|
|
218
|
+
// We verify the result is correct, which only works if bundling is non-destructive.
|
|
219
|
+
const filePath = path.join(
|
|
220
|
+
__dirname,
|
|
221
|
+
'..',
|
|
222
|
+
'__fixtures__',
|
|
223
|
+
'validateSchemas',
|
|
224
|
+
'main-schema-with-ref.json',
|
|
225
|
+
);
|
|
226
|
+
const result1 = await processSchema(filePath);
|
|
227
|
+
const result2 = await processSchema(filePath);
|
|
228
|
+
|
|
229
|
+
// Running processSchema twice on the same file should yield the same results,
|
|
230
|
+
// which would fail if the input schema was mutated on the first call.
|
|
231
|
+
expect(result1.title).toBe(result2.title);
|
|
232
|
+
expect(result1.properties.component.type).toBe(
|
|
233
|
+
result2.properties.component.type,
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// L51-59: canRead / read resolver — constraint refs are handled
|
|
238
|
+
it('resolves constraint schema refs via the custom resolver', async () => {
|
|
239
|
+
const filePath = path.join(
|
|
240
|
+
__dirname,
|
|
241
|
+
'..',
|
|
242
|
+
'__fixtures__',
|
|
243
|
+
'validateSchemas',
|
|
244
|
+
'main-schema-with-constraints-ref.json',
|
|
245
|
+
);
|
|
246
|
+
// If the resolver is broken, this will throw. It passing means read() ran successfully.
|
|
247
|
+
const mergedSchema = await processSchema(filePath);
|
|
248
|
+
expect(mergedSchema).toBeDefined();
|
|
249
|
+
expect(mergedSchema.title).toBe('Main Schema with Constraints Ref');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// L27: anyOf with length > 1 stops the while loop early
|
|
253
|
+
it('does not unwrap not: { anyOf: [a, b] } when anyOf has multiple items', async () => {
|
|
254
|
+
const filePath = path.join(
|
|
255
|
+
__dirname,
|
|
256
|
+
'..',
|
|
257
|
+
'__fixtures__',
|
|
258
|
+
'validateSchemas',
|
|
259
|
+
'schema-with-not-anyof-multi.json',
|
|
260
|
+
);
|
|
261
|
+
const mergedSchema = await processSchema(filePath);
|
|
262
|
+
|
|
263
|
+
// not: { anyOf: [{ const: 'US' }, { const: 'CA' }] } should NOT be unwrapped
|
|
264
|
+
expect(mergedSchema.properties.country.not).toEqual({
|
|
265
|
+
anyOf: [{ const: 'US' }, { const: 'CA' }],
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Kills mutant 685: && → || on line 20 (normalized.not && typeof ... === 'object')
|
|
270
|
+
// When not is false (falsy non-object), the && guard must prevent entering the block.
|
|
271
|
+
// With ||, the code would try to access .anyOf on false and corrupt the value.
|
|
272
|
+
it('preserves not: false without entering the unwrap block', async () => {
|
|
273
|
+
const filePath = path.join(
|
|
274
|
+
__dirname,
|
|
275
|
+
'..',
|
|
276
|
+
'__fixtures__',
|
|
277
|
+
'validateSchemas',
|
|
278
|
+
'schema-with-not-edge-cases.json',
|
|
279
|
+
);
|
|
280
|
+
const mergedSchema = await processSchema(filePath);
|
|
281
|
+
|
|
282
|
+
expect(mergedSchema.properties.falsy_not.not).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Kills mutant 686: typeof normalized.not === 'object' → true on line 20
|
|
286
|
+
// The not: true test already exists but we need a strict assertion that the value
|
|
287
|
+
// is exactly true (not transformed into something else by entering the block)
|
|
288
|
+
it('does not enter unwrap block when not is a non-object truthy value', async () => {
|
|
289
|
+
const filePath = path.join(
|
|
290
|
+
__dirname,
|
|
291
|
+
'..',
|
|
292
|
+
'__fixtures__',
|
|
293
|
+
'validateSchemas',
|
|
294
|
+
'schema-with-not-non-object.json',
|
|
295
|
+
);
|
|
296
|
+
const mergedSchema = await processSchema(filePath);
|
|
297
|
+
|
|
298
|
+
// Strict check: not must remain boolean true, not be replaced by undefined or an object
|
|
299
|
+
expect(mergedSchema.properties.flag.not).toBe(true);
|
|
300
|
+
expect(typeof mergedSchema.properties.flag.not).toBe('boolean');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Kills mutants 694-698: while loop condition mutations on lines 23-25
|
|
304
|
+
// When not is an array, !Array.isArray(candidate) should prevent entering the loop.
|
|
305
|
+
it('does not unwrap when not value is an array', async () => {
|
|
306
|
+
const filePath = path.join(
|
|
307
|
+
__dirname,
|
|
308
|
+
'..',
|
|
309
|
+
'__fixtures__',
|
|
310
|
+
'validateSchemas',
|
|
311
|
+
'schema-with-not-edge-cases.json',
|
|
312
|
+
);
|
|
313
|
+
const mergedSchema = await processSchema(filePath);
|
|
314
|
+
|
|
315
|
+
// not: [{ const: 'A' }, { const: 'B' }] should be kept as-is (array, not unwrapped)
|
|
316
|
+
expect(Array.isArray(mergedSchema.properties.not_is_array.not)).toBe(true);
|
|
317
|
+
expect(mergedSchema.properties.not_is_array.not).toEqual([
|
|
318
|
+
{ const: 'A' },
|
|
319
|
+
{ const: 'B' },
|
|
320
|
+
]);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Kills mutants on while loop: candidate being null after first unwrap iteration
|
|
324
|
+
// not: { anyOf: [null] } — the single anyOf element is null, so after unwrapping
|
|
325
|
+
// the while loop must stop because candidate is null (falsy)
|
|
326
|
+
it('stops unwrapping when anyOf single element is null', async () => {
|
|
327
|
+
const filePath = path.join(
|
|
328
|
+
__dirname,
|
|
329
|
+
'..',
|
|
330
|
+
'__fixtures__',
|
|
331
|
+
'validateSchemas',
|
|
332
|
+
'schema-with-not-edge-cases.json',
|
|
333
|
+
);
|
|
334
|
+
const mergedSchema = await processSchema(filePath);
|
|
335
|
+
|
|
336
|
+
// not: { anyOf: [null] } should unwrap to not: null
|
|
337
|
+
expect(mergedSchema.properties.not_with_null_candidate.not).toBeNull();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Kills mutant on while loop: anyOf contains an array element
|
|
341
|
+
// not: { anyOf: [[1, 2, 3]] } — the single anyOf element is an array,
|
|
342
|
+
// so on the next iteration !Array.isArray(candidate) stops the loop
|
|
343
|
+
it('stops unwrapping when anyOf single element is itself an array', async () => {
|
|
344
|
+
const filePath = path.join(
|
|
345
|
+
__dirname,
|
|
346
|
+
'..',
|
|
347
|
+
'__fixtures__',
|
|
348
|
+
'validateSchemas',
|
|
349
|
+
'schema-with-not-edge-cases.json',
|
|
350
|
+
);
|
|
351
|
+
const mergedSchema = await processSchema(filePath);
|
|
352
|
+
|
|
353
|
+
// not: { anyOf: [[1, 2, 3]] } should unwrap to not: [1, 2, 3]
|
|
354
|
+
expect(mergedSchema.properties.not_anyof_contains_array.not).toEqual([
|
|
355
|
+
1, 2, 3,
|
|
356
|
+
]);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Kills mutants 706-710, 712-714, 717: options/resolve block mutations
|
|
360
|
+
// The constraint resolver must actually resolve the ref and produce correct content.
|
|
361
|
+
// If the options object is {}, resolve is {}, canRead returns undefined, read fails,
|
|
362
|
+
// or encoding is wrong, the schema won't have the expected constraint properties.
|
|
363
|
+
it('constraint resolver produces correct schema content with proper encoding', async () => {
|
|
364
|
+
const filePath = path.join(
|
|
365
|
+
__dirname,
|
|
366
|
+
'..',
|
|
367
|
+
'__fixtures__',
|
|
368
|
+
'validateSchemas',
|
|
369
|
+
'main-schema-with-constraints-ref.json',
|
|
370
|
+
);
|
|
371
|
+
const mergedSchema = await processSchema(filePath);
|
|
372
|
+
|
|
373
|
+
// Verify the constraint schema was resolved, read with utf8, and merged correctly
|
|
374
|
+
// These specific values come from the flat-event-params.json constraint schema
|
|
375
|
+
expect(mergedSchema.additionalProperties.type).toEqual([
|
|
376
|
+
'string',
|
|
377
|
+
'number',
|
|
378
|
+
'integer',
|
|
379
|
+
'boolean',
|
|
380
|
+
'null',
|
|
381
|
+
]);
|
|
382
|
+
expect(mergedSchema.properties.items.type).toBe('array');
|
|
383
|
+
expect(mergedSchema.properties.items.items.type).toBe('object');
|
|
384
|
+
expect(
|
|
385
|
+
mergedSchema.properties.items.items.additionalProperties.type,
|
|
386
|
+
).toEqual(['string', 'number', 'integer', 'boolean', 'null']);
|
|
387
|
+
// Verify event property is merged from both constraint and original schema
|
|
388
|
+
expect(mergedSchema.properties.event.type).toBe('string');
|
|
389
|
+
expect(mergedSchema.properties.event.const).toBe('screen_view');
|
|
390
|
+
// Verify screen_name from original schema is preserved
|
|
391
|
+
expect(mergedSchema.properties.screen_name.type).toBe('string');
|
|
392
|
+
});
|
|
103
393
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import MdxTemplate from '../../helpers/schema-doc-template';
|
|
2
|
+
|
|
3
|
+
describe('MdxTemplate', () => {
|
|
4
|
+
const baseData = {
|
|
5
|
+
schema: {
|
|
6
|
+
title: 'Test Event',
|
|
7
|
+
description: 'A test event schema.',
|
|
8
|
+
},
|
|
9
|
+
mergedSchema: { title: 'Test Event', properties: {} },
|
|
10
|
+
editUrl: 'https://github.com/org/repo/edit/main/schema.json',
|
|
11
|
+
file: 'schema.json',
|
|
12
|
+
topPartialImport: '',
|
|
13
|
+
bottomPartialImport: '',
|
|
14
|
+
topPartialComponent: '',
|
|
15
|
+
bottomPartialComponent: '',
|
|
16
|
+
dataLayerName: null,
|
|
17
|
+
sourcePath: 'schemas/test.json',
|
|
18
|
+
schemaSources: null,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
it('renders template without dataLayerName attribute when dataLayerName is falsy', () => {
|
|
22
|
+
const result = MdxTemplate(baseData);
|
|
23
|
+
expect(result).toContain('# Test Event');
|
|
24
|
+
expect(result).not.toContain("dataLayerName={'");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders template with dataLayerName attribute when dataLayerName is provided', () => {
|
|
28
|
+
const result = MdxTemplate({ ...baseData, dataLayerName: 'dataLayer' });
|
|
29
|
+
expect(result).toContain("dataLayerName={'dataLayer'}");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('uses schema as sourceSchema when schemaSources does not contain sourcePath', () => {
|
|
33
|
+
const result = MdxTemplate({
|
|
34
|
+
...baseData,
|
|
35
|
+
schemaSources: { 'other/path.json': { title: 'Other' } },
|
|
36
|
+
});
|
|
37
|
+
// sourceSchema falls back to schema since sourcePath is not in schemaSources
|
|
38
|
+
expect(result).toContain(JSON.stringify(baseData.schema));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('uses schemaSources entry as sourceSchema when sourcePath matches', () => {
|
|
42
|
+
const sourceSchema = { title: 'Source Schema', custom: true };
|
|
43
|
+
const result = MdxTemplate({
|
|
44
|
+
...baseData,
|
|
45
|
+
schemaSources: { 'schemas/test.json': sourceSchema },
|
|
46
|
+
});
|
|
47
|
+
expect(result).toContain(JSON.stringify(sourceSchema));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('uses schema as sourceSchema when schemaSources is null/undefined', () => {
|
|
51
|
+
const result = MdxTemplate({ ...baseData, schemaSources: null });
|
|
52
|
+
expect(result).toContain(JSON.stringify(baseData.schema));
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -1,12 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @jest-environment node
|
|
2
|
+
* @jest-environment @stryker-mutator/jest-runner/jest-env/node
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { processOneOfSchema } from '../../helpers/schema-processing';
|
|
5
|
+
import { processOneOfSchema, slugify } from '../../helpers/schema-processing';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
|
|
9
9
|
describe('schema-processing', () => {
|
|
10
|
+
describe('slugify', () => {
|
|
11
|
+
it('returns "option" for falsy input', () => {
|
|
12
|
+
expect(slugify('')).toBe('option');
|
|
13
|
+
expect(slugify(null)).toBe('option');
|
|
14
|
+
expect(slugify(undefined)).toBe('option');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('replaces multiple spaces with a single hyphen', () => {
|
|
18
|
+
expect(slugify('hello world')).toBe('hello-world');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('removes non-word characters', () => {
|
|
22
|
+
expect(slugify('hello!@#world')).toBe('helloworld');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('replaces multiple consecutive hyphens with a single hyphen', () => {
|
|
26
|
+
expect(slugify('hello---world')).toBe('hello-world');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('trims leading hyphens', () => {
|
|
30
|
+
expect(slugify('-hello')).toBe('hello');
|
|
31
|
+
expect(slugify('--hello')).toBe('hello');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('trims trailing hyphens', () => {
|
|
35
|
+
expect(slugify('hello-')).toBe('hello');
|
|
36
|
+
expect(slugify('hello--')).toBe('hello');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('converts to lowercase', () => {
|
|
40
|
+
expect(slugify('Hello World')).toBe('hello-world');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
10
44
|
describe('processOneOfSchema', () => {
|
|
11
45
|
it('should merge oneOf options with the root schema', async () => {
|
|
12
46
|
const rootSchema = {
|
|
@@ -119,6 +153,92 @@ describe('schema-processing', () => {
|
|
|
119
153
|
expect(mergedEcommerce.properties.transaction_id.type).toBe('string');
|
|
120
154
|
});
|
|
121
155
|
|
|
156
|
+
it('returns an empty array when schema has no oneOf', async () => {
|
|
157
|
+
const schema = {
|
|
158
|
+
$id: 'root.json',
|
|
159
|
+
title: 'Root',
|
|
160
|
+
properties: { foo: { type: 'string' } },
|
|
161
|
+
};
|
|
162
|
+
const result = await processOneOfSchema(schema, '/path/to/schema.json');
|
|
163
|
+
expect(result).toEqual([]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('uses $anchor as slug when option has $anchor but no $id ending in .json', async () => {
|
|
167
|
+
const rootSchema = {
|
|
168
|
+
$id: 'root.json',
|
|
169
|
+
title: 'Root',
|
|
170
|
+
oneOf: [
|
|
171
|
+
{
|
|
172
|
+
$anchor: 'my-anchor',
|
|
173
|
+
title: 'Anchored Option',
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
const result = await processOneOfSchema(
|
|
178
|
+
rootSchema,
|
|
179
|
+
'/path/to/schema.json',
|
|
180
|
+
);
|
|
181
|
+
expect(result[0].slug).toBe('my-anchor');
|
|
182
|
+
expect(result[0].schema.$id).toBe('root.json#my-anchor');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('uses basename of $id ending in .json as slug and preserves original $id', async () => {
|
|
186
|
+
const rootSchema = {
|
|
187
|
+
$id: 'root.json',
|
|
188
|
+
title: 'Root',
|
|
189
|
+
oneOf: [
|
|
190
|
+
{
|
|
191
|
+
$id: 'sub/my-option.json',
|
|
192
|
+
title: 'Option With Id',
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
};
|
|
196
|
+
const result = await processOneOfSchema(
|
|
197
|
+
rootSchema,
|
|
198
|
+
'/path/to/schema.json',
|
|
199
|
+
);
|
|
200
|
+
expect(result[0].slug).toBe('my-option');
|
|
201
|
+
expect(result[0].schema.$id).toBe('sub/my-option.json');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('does not treat $id not ending in .json as hadId', async () => {
|
|
205
|
+
const rootSchema = {
|
|
206
|
+
$id: 'root.json',
|
|
207
|
+
title: 'Root',
|
|
208
|
+
oneOf: [
|
|
209
|
+
{
|
|
210
|
+
$id: 'not-a-json-file',
|
|
211
|
+
title: 'No Json Extension',
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
const result = await processOneOfSchema(
|
|
216
|
+
rootSchema,
|
|
217
|
+
'/path/to/schema.json',
|
|
218
|
+
);
|
|
219
|
+
expect(result[0].slug).toBe('no-json-extension');
|
|
220
|
+
expect(result[0].schema.$id).toBe('root.json#no-json-extension');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('does not resolve $ref that starts with #', async () => {
|
|
224
|
+
const rootSchema = {
|
|
225
|
+
$id: 'root.json',
|
|
226
|
+
title: 'Root',
|
|
227
|
+
oneOf: [
|
|
228
|
+
{
|
|
229
|
+
$ref: '#/definitions/internal',
|
|
230
|
+
title: 'Internal Ref',
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
const result = await processOneOfSchema(
|
|
235
|
+
rootSchema,
|
|
236
|
+
'/path/to/schema.json',
|
|
237
|
+
);
|
|
238
|
+
expect(result[0].schema.$ref).toBe('#/definitions/internal');
|
|
239
|
+
expect(result[0].sourceFilePath).toBeNull();
|
|
240
|
+
});
|
|
241
|
+
|
|
122
242
|
it('preserves parent allOf metadata when root oneOf options are file refs', async () => {
|
|
123
243
|
const rootFile = path.join(
|
|
124
244
|
__dirname,
|