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.
- package/README.md +12 -0
- package/__tests__/__fixtures__/validateSchemas/main-schema-with-not-allof.json +11 -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__/__snapshots__/generateEventDocs.anchor.test.js.snap +6 -0
- package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +6 -0
- package/__tests__/__snapshots__/generateEventDocs.test.js.snap +15 -0
- package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +6 -0
- package/__tests__/components/PropertiesTable.test.js +66 -0
- package/__tests__/components/PropertyRow.test.js +85 -4
- package/__tests__/components/SchemaJsonViewer.test.js +118 -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/example-helper.test.js +12 -0
- package/__tests__/helpers/exampleModel.test.js +209 -0
- package/__tests__/helpers/file-system.test.js +73 -1
- package/__tests__/helpers/getConstraints.test.js +43 -0
- package/__tests__/helpers/mergeSchema.test.js +94 -0
- package/__tests__/helpers/processSchema.test.js +309 -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/schemaTraversal.test.js +110 -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 +366 -6
- package/__tests__/update-schema-ids.test.js +70 -1
- package/__tests__/validateSchemas-integration.test.js +2 -2
- package/__tests__/validateSchemas.test.js +192 -1
- package/components/PropertiesTable.js +32 -2
- package/components/PropertyRow.js +29 -2
- package/components/SchemaJsonViewer.js +234 -131
- package/components/SchemaRows.css +40 -0
- package/components/SchemaViewer.js +11 -2
- package/generateEventDocs.js +21 -1
- package/helpers/constraintSchemaPaths.js +10 -14
- package/helpers/example-helper.js +2 -2
- package/helpers/getConstraints.js +20 -0
- package/helpers/processSchema.js +32 -1
- package/helpers/schema-doc-template.js +4 -0
- package/helpers/schemaToExamples.js +29 -35
- package/helpers/schemaToTableData.js +538 -492
- package/helpers/schemaTraversal.cjs +148 -0
- 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 +65 -34
- package/test-data/payloadContracts.js +35 -0
- 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
|
+
});
|