docusaurus-plugin-generate-schema-docs 1.8.2 → 1.8.4
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 +2 -0
- package/__tests__/__fixtures__/validateSchemas/main-schema-with-not-allof.json +11 -0
- package/__tests__/__snapshots__/generateEventDocs.anchor.test.js.snap +21 -3
- package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +26 -4
- package/__tests__/__snapshots__/generateEventDocs.test.js.snap +45 -6
- package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +16 -2
- package/__tests__/components/ConditionalRows.test.js +28 -0
- package/__tests__/components/FoldableRows.test.js +31 -290
- package/__tests__/components/PropertiesTable.test.js +66 -0
- package/__tests__/components/PropertyRow.test.js +297 -0
- package/__tests__/components/SchemaJsonViewer.test.js +194 -10
- package/__tests__/components/SchemaRows.test.js +62 -12
- package/__tests__/components/__snapshots__/ConnectorLines.visualRegression.test.js.snap +3 -3
- package/__tests__/generateEventDocs.test.js +3 -0
- package/__tests__/helpers/example-helper.test.js +12 -0
- package/__tests__/helpers/getConstraints.test.js +16 -0
- package/__tests__/helpers/processSchema.test.js +18 -0
- package/__tests__/helpers/schemaToTableData.test.js +112 -0
- package/__tests__/helpers/schemaTraversal.test.js +110 -0
- package/__tests__/syncGtm.test.js +227 -3
- package/__tests__/validateSchemas.test.js +50 -0
- package/components/ConditionalRows.js +6 -3
- package/components/FoldableRows.js +9 -3
- package/components/PropertiesTable.js +34 -3
- package/components/PropertyRow.js +118 -6
- package/components/SchemaJsonViewer.js +324 -4
- package/components/SchemaRows.css +138 -7
- package/components/SchemaRows.js +11 -1
- package/components/SchemaViewer.js +11 -2
- package/generateEventDocs.js +87 -1
- package/helpers/choice-index-template.js +6 -2
- package/helpers/example-helper.js +2 -2
- package/helpers/file-system.js +28 -0
- package/helpers/getConstraints.js +20 -0
- package/helpers/processSchema.js +32 -1
- package/helpers/schema-doc-template.js +11 -1
- package/helpers/schemaToExamples.js +29 -35
- package/helpers/schemaToTableData.js +68 -7
- package/helpers/schemaTraversal.cjs +148 -0
- package/package.json +1 -1
- package/scripts/sync-gtm.js +41 -28
- package/test-data/payloadContracts.js +35 -0
- package/validateSchemas.js +1 -1
|
@@ -56,6 +56,9 @@ describe('generateEventDocs (non-versioned)', () => {
|
|
|
56
56
|
'utf-8',
|
|
57
57
|
);
|
|
58
58
|
expect(addToCart).toMatchSnapshot();
|
|
59
|
+
expect(addToCart).toContain('sourcePath={"add-to-cart-event.json"}');
|
|
60
|
+
expect(addToCart).toContain(JSON.stringify('components/product.json'));
|
|
61
|
+
expect(addToCart).toContain('"$ref":"./components/product.json"');
|
|
59
62
|
|
|
60
63
|
const choiceEvent = fs.readFileSync(
|
|
61
64
|
path.join(outputDir, 'choice-event.mdx'),
|
|
@@ -34,6 +34,12 @@ describe('example-helper', () => {
|
|
|
34
34
|
};
|
|
35
35
|
expect(getExamples(schema)).toEqual(['example1']);
|
|
36
36
|
});
|
|
37
|
+
|
|
38
|
+
it('preserves falsy "example" values', () => {
|
|
39
|
+
expect(getExamples({ example: false })).toEqual([false]);
|
|
40
|
+
expect(getExamples({ example: 0 })).toEqual([0]);
|
|
41
|
+
expect(getExamples({ example: '' })).toEqual(['']);
|
|
42
|
+
});
|
|
37
43
|
});
|
|
38
44
|
|
|
39
45
|
describe('getSingleExampleValue', () => {
|
|
@@ -67,5 +73,11 @@ describe('example-helper', () => {
|
|
|
67
73
|
};
|
|
68
74
|
expect(getSingleExampleValue(schema)).toBe('default-value');
|
|
69
75
|
});
|
|
76
|
+
|
|
77
|
+
it('returns falsy "example" values when present', () => {
|
|
78
|
+
expect(getSingleExampleValue({ example: false })).toBe(false);
|
|
79
|
+
expect(getSingleExampleValue({ example: 0 })).toBe(0);
|
|
80
|
+
expect(getSingleExampleValue({ example: '' })).toBe('');
|
|
81
|
+
});
|
|
70
82
|
});
|
|
71
83
|
});
|
|
@@ -59,6 +59,7 @@ describe('getConstraints', () => {
|
|
|
59
59
|
contains: { type: 'string' },
|
|
60
60
|
enum: ['a', 'b', 'c'],
|
|
61
61
|
const: 'hello',
|
|
62
|
+
not: { const: 'US' },
|
|
62
63
|
};
|
|
63
64
|
const expected = [
|
|
64
65
|
'pattern: /^[a-z]+$/',
|
|
@@ -69,6 +70,7 @@ describe('getConstraints', () => {
|
|
|
69
70
|
'contains: {"type":"string"}',
|
|
70
71
|
'enum: [a, b, c]',
|
|
71
72
|
'const: "hello"',
|
|
73
|
+
'not: { const: "US" }',
|
|
72
74
|
];
|
|
73
75
|
expect(getConstraints(prop, false)).toEqual(
|
|
74
76
|
expect.arrayContaining(expected),
|
|
@@ -100,4 +102,18 @@ describe('getConstraints', () => {
|
|
|
100
102
|
expect.arrayContaining(['required', ...expected]),
|
|
101
103
|
);
|
|
102
104
|
});
|
|
105
|
+
|
|
106
|
+
it('should render "not" constraint in JSON schema syntax for const', () => {
|
|
107
|
+
const prop = {
|
|
108
|
+
not: { const: 'US' },
|
|
109
|
+
};
|
|
110
|
+
expect(getConstraints(prop, false)).toEqual(['not: { const: "US" }']);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should render "not" constraint in JSON schema syntax for type', () => {
|
|
114
|
+
const prop = {
|
|
115
|
+
not: { type: 'string' },
|
|
116
|
+
};
|
|
117
|
+
expect(getConstraints(prop, false)).toEqual(['not: { type: "string" }']);
|
|
118
|
+
});
|
|
103
119
|
});
|
|
@@ -82,4 +82,22 @@ describe('processSchema', () => {
|
|
|
82
82
|
}),
|
|
83
83
|
);
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
it('keeps simple not const constraints readable after allOf merge', async () => {
|
|
87
|
+
const filePath = path.join(
|
|
88
|
+
__dirname,
|
|
89
|
+
'..',
|
|
90
|
+
'__fixtures__',
|
|
91
|
+
'validateSchemas',
|
|
92
|
+
'main-schema-with-not-allof.json',
|
|
93
|
+
);
|
|
94
|
+
const mergedSchema = await processSchema(filePath);
|
|
95
|
+
|
|
96
|
+
expect(mergedSchema.properties.country).toEqual(
|
|
97
|
+
expect.objectContaining({
|
|
98
|
+
type: 'string',
|
|
99
|
+
not: { const: 'US' },
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
});
|
|
85
103
|
});
|
|
@@ -217,6 +217,118 @@ describe('schemaToTableData', () => {
|
|
|
217
217
|
expect(tableData[0].examples).toEqual(['default-value']);
|
|
218
218
|
});
|
|
219
219
|
|
|
220
|
+
it('renders schema-valued additionalProperties as a synthetic row', () => {
|
|
221
|
+
const schema = {
|
|
222
|
+
properties: {
|
|
223
|
+
metadata: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
description: 'Free-form metadata.',
|
|
226
|
+
additionalProperties: {
|
|
227
|
+
type: 'string',
|
|
228
|
+
description: 'Metadata value.',
|
|
229
|
+
pattern: '^[a-z]+$',
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const tableData = schemaToTableData(schema);
|
|
236
|
+
|
|
237
|
+
const metadataRow = tableData.find((row) => row.name === 'metadata');
|
|
238
|
+
expect(metadataRow).toBeDefined();
|
|
239
|
+
expect(metadataRow.containerType).toBe('object');
|
|
240
|
+
|
|
241
|
+
const additionalPropertiesRow = tableData.find(
|
|
242
|
+
(row) =>
|
|
243
|
+
row.type === 'property' &&
|
|
244
|
+
row.name === 'additionalProperties' &&
|
|
245
|
+
row.level === 1,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
expect(additionalPropertiesRow).toBeDefined();
|
|
249
|
+
expect(additionalPropertiesRow.propertyType).toBe('string');
|
|
250
|
+
expect(additionalPropertiesRow.description).toBe('Metadata value.');
|
|
251
|
+
expect(additionalPropertiesRow.constraints).toContain(
|
|
252
|
+
'pattern: /^[a-z]+$/',
|
|
253
|
+
);
|
|
254
|
+
expect(additionalPropertiesRow.isSchemaKeywordRow).toBe(true);
|
|
255
|
+
expect(additionalPropertiesRow.keepConnectorOpen).toBe(false);
|
|
256
|
+
expect(additionalPropertiesRow.isLastInGroup).toBe(true);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('treats additionalProperties as the last sibling after declared properties', () => {
|
|
260
|
+
const schema = {
|
|
261
|
+
properties: {
|
|
262
|
+
user_properties: {
|
|
263
|
+
type: 'object',
|
|
264
|
+
properties: {
|
|
265
|
+
allow_ad_personalization_signals: {
|
|
266
|
+
type: ['string', 'null'],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
additionalProperties: {
|
|
270
|
+
type: ['string', 'null'],
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const tableData = schemaToTableData(schema);
|
|
277
|
+
|
|
278
|
+
const explicitPropertyRow = tableData.find(
|
|
279
|
+
(row) =>
|
|
280
|
+
row.name === 'allow_ad_personalization_signals' && row.level === 1,
|
|
281
|
+
);
|
|
282
|
+
const additionalPropertiesRow = tableData.find(
|
|
283
|
+
(row) => row.name === 'additionalProperties' && row.level === 1,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
expect(explicitPropertyRow).toBeDefined();
|
|
287
|
+
expect(additionalPropertiesRow).toBeDefined();
|
|
288
|
+
expect(explicitPropertyRow.isLastInGroup).toBe(false);
|
|
289
|
+
expect(additionalPropertiesRow.isLastInGroup).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('renders schema-valued patternProperties as synthetic keyword rows', () => {
|
|
293
|
+
const schema = {
|
|
294
|
+
properties: {
|
|
295
|
+
attributes: {
|
|
296
|
+
type: 'object',
|
|
297
|
+
properties: {
|
|
298
|
+
segment: { type: 'string' },
|
|
299
|
+
},
|
|
300
|
+
patternProperties: {
|
|
301
|
+
'^custom_': {
|
|
302
|
+
type: 'number',
|
|
303
|
+
description: 'Numeric custom attribute values.',
|
|
304
|
+
minimum: 0,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const tableData = schemaToTableData(schema);
|
|
312
|
+
|
|
313
|
+
const explicitPropertyRow = tableData.find(
|
|
314
|
+
(row) => row.name === 'segment' && row.level === 1,
|
|
315
|
+
);
|
|
316
|
+
const patternPropertyRow = tableData.find(
|
|
317
|
+
(row) => row.name === 'patternProperties /^custom_/' && row.level === 1,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
expect(explicitPropertyRow).toBeDefined();
|
|
321
|
+
expect(patternPropertyRow).toBeDefined();
|
|
322
|
+
expect(explicitPropertyRow.isLastInGroup).toBe(false);
|
|
323
|
+
expect(patternPropertyRow.isLastInGroup).toBe(true);
|
|
324
|
+
expect(patternPropertyRow.propertyType).toBe('number');
|
|
325
|
+
expect(patternPropertyRow.description).toBe(
|
|
326
|
+
'Numeric custom attribute values.',
|
|
327
|
+
);
|
|
328
|
+
expect(patternPropertyRow.constraints).toContain('minimum: 0');
|
|
329
|
+
expect(patternPropertyRow.isSchemaKeywordRow).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
|
|
220
332
|
describe('if/then/else conditional support', () => {
|
|
221
333
|
it('creates a conditional row for schema with if/then/else at root level', () => {
|
|
222
334
|
const tableData = schemaToTableData(conditionalEventSchema);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const {
|
|
2
|
+
visitSchemaNodes,
|
|
3
|
+
visitSchemaPropertyEntries,
|
|
4
|
+
} = require('../../helpers/schemaTraversal.cjs');
|
|
5
|
+
|
|
6
|
+
describe('schemaTraversal', () => {
|
|
7
|
+
it('visits nested choice and conditional schema nodes', () => {
|
|
8
|
+
const schema = {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
payment_method: {
|
|
12
|
+
oneOf: [
|
|
13
|
+
{
|
|
14
|
+
properties: {
|
|
15
|
+
card_number: { type: 'string' },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
if: {
|
|
22
|
+
properties: {
|
|
23
|
+
platform: { const: 'ios' },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
then: {
|
|
27
|
+
properties: {
|
|
28
|
+
att_status: { type: 'string' },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const visitedPaths = [];
|
|
34
|
+
visitSchemaNodes(schema, (_node, context) => {
|
|
35
|
+
visitedPaths.push(context.path.join('.'));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(visitedPaths).toEqual(
|
|
39
|
+
expect.arrayContaining([
|
|
40
|
+
'',
|
|
41
|
+
'properties.payment_method',
|
|
42
|
+
'properties.payment_method.oneOf.0',
|
|
43
|
+
'properties.payment_method.oneOf.0.properties.card_number',
|
|
44
|
+
'if',
|
|
45
|
+
'then',
|
|
46
|
+
'then.properties.att_status',
|
|
47
|
+
]),
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('collects property entries through object, array, choice, and conditional branches', () => {
|
|
52
|
+
const schema = {
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
event: { type: 'string' },
|
|
56
|
+
items: {
|
|
57
|
+
type: 'array',
|
|
58
|
+
items: {
|
|
59
|
+
properties: {
|
|
60
|
+
sku: { type: 'string' },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
contact_method: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
oneOf: [
|
|
67
|
+
{
|
|
68
|
+
properties: {
|
|
69
|
+
email: { type: 'string' },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
properties: {
|
|
74
|
+
phone_number: { type: 'string' },
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
then: {
|
|
81
|
+
properties: {
|
|
82
|
+
att_status: { type: 'string' },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
else: {
|
|
86
|
+
properties: {
|
|
87
|
+
ad_personalization_enabled: { type: 'boolean' },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const variableNames = [];
|
|
93
|
+
visitSchemaPropertyEntries(schema, (_property, context) => {
|
|
94
|
+
variableNames.push(context.name);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(variableNames).toEqual(
|
|
98
|
+
expect.arrayContaining([
|
|
99
|
+
'event',
|
|
100
|
+
'items',
|
|
101
|
+
'items.0.sku',
|
|
102
|
+
'contact_method',
|
|
103
|
+
'contact_method.email',
|
|
104
|
+
'contact_method.phone_number',
|
|
105
|
+
'att_status',
|
|
106
|
+
'ad_personalization_enabled',
|
|
107
|
+
]),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -79,7 +79,7 @@ describe('getVariablesFromSchemas', () => {
|
|
|
79
79
|
|
|
80
80
|
const SCHEMA_PATH = '/fake/schemas';
|
|
81
81
|
const mockFiles = {
|
|
82
|
-
[SCHEMA_PATH]: ['complex-event.json', 'components'],
|
|
82
|
+
[SCHEMA_PATH]: ['complex-event.json', 'mobile-event.json', 'components'],
|
|
83
83
|
[path.join(SCHEMA_PATH, 'components')]: ['address.json'],
|
|
84
84
|
};
|
|
85
85
|
const addressSchema = {
|
|
@@ -92,6 +92,7 @@ describe('getVariablesFromSchemas', () => {
|
|
|
92
92
|
};
|
|
93
93
|
const complexEventSchema = {
|
|
94
94
|
title: 'Complex Event',
|
|
95
|
+
'x-tracking-targets': ['web-datalayer-js'],
|
|
95
96
|
type: 'object',
|
|
96
97
|
properties: {
|
|
97
98
|
$schema: { type: 'string', description: 'Should now be included.' },
|
|
@@ -107,12 +108,79 @@ describe('getVariablesFromSchemas', () => {
|
|
|
107
108
|
},
|
|
108
109
|
},
|
|
109
110
|
},
|
|
111
|
+
contact_method: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
oneOf: [
|
|
114
|
+
{
|
|
115
|
+
title: 'Email Contact',
|
|
116
|
+
properties: {
|
|
117
|
+
email: {
|
|
118
|
+
type: 'string',
|
|
119
|
+
description: 'Email address.',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
title: 'Phone Contact',
|
|
125
|
+
properties: {
|
|
126
|
+
phone_number: {
|
|
127
|
+
type: 'string',
|
|
128
|
+
description: 'Phone number.',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
platform: {
|
|
135
|
+
type: 'string',
|
|
136
|
+
description: 'Target platform.',
|
|
137
|
+
},
|
|
110
138
|
timestamp: { type: 'number', description: 'Event timestamp.' },
|
|
111
139
|
},
|
|
140
|
+
if: {
|
|
141
|
+
properties: {
|
|
142
|
+
platform: { const: 'ios' },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
then: {
|
|
146
|
+
properties: {
|
|
147
|
+
att_status: {
|
|
148
|
+
type: 'string',
|
|
149
|
+
description: 'App Tracking Transparency status.',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
else: {
|
|
154
|
+
properties: {
|
|
155
|
+
ad_personalization_enabled: {
|
|
156
|
+
type: 'boolean',
|
|
157
|
+
description: 'Whether ad personalization is enabled.',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
const mobileEventSchema = {
|
|
163
|
+
title: 'Mobile Event',
|
|
164
|
+
'x-tracking-targets': ['android-firebase-kotlin-sdk'],
|
|
165
|
+
type: 'object',
|
|
166
|
+
properties: {
|
|
167
|
+
event: { type: 'string', const: 'screen_view' },
|
|
168
|
+
screen_name: { type: 'string', description: 'Screen name.' },
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
const untaggedEventSchema = {
|
|
172
|
+
title: 'Untagged Event',
|
|
173
|
+
type: 'object',
|
|
174
|
+
properties: {
|
|
175
|
+
event: { type: 'string', const: 'legacy_event' },
|
|
176
|
+
legacy_field: { type: 'string', description: 'Legacy field.' },
|
|
177
|
+
},
|
|
112
178
|
};
|
|
113
179
|
const mockFileContents = {
|
|
114
180
|
[path.join(SCHEMA_PATH, 'complex-event.json')]:
|
|
115
181
|
JSON.stringify(complexEventSchema),
|
|
182
|
+
[path.join(SCHEMA_PATH, 'mobile-event.json')]:
|
|
183
|
+
JSON.stringify(mobileEventSchema),
|
|
116
184
|
[path.join(SCHEMA_PATH, 'components', 'address.json')]:
|
|
117
185
|
JSON.stringify(addressSchema),
|
|
118
186
|
};
|
|
@@ -148,7 +216,7 @@ describe('getVariablesFromSchemas', () => {
|
|
|
148
216
|
expect.objectContaining({ name: 'timestamp' }),
|
|
149
217
|
]);
|
|
150
218
|
|
|
151
|
-
expect(result.length).toBe(
|
|
219
|
+
expect(result.length).toBe(14);
|
|
152
220
|
expect(result).toEqual(expected);
|
|
153
221
|
});
|
|
154
222
|
|
|
@@ -172,7 +240,163 @@ describe('getVariablesFromSchemas', () => {
|
|
|
172
240
|
];
|
|
173
241
|
|
|
174
242
|
expect(result.map((r) => r.name)).toEqual(expect.arrayContaining(expected));
|
|
175
|
-
expect(result.length).toBe(
|
|
243
|
+
expect(result.length).toBe(12);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should include variables from oneOf choices and conditional branches', async () => {
|
|
247
|
+
const bundledSchema = JSON.parse(JSON.stringify(complexEventSchema));
|
|
248
|
+
bundledSchema.properties.user_data.properties.addresses.items =
|
|
249
|
+
addressSchema;
|
|
250
|
+
RefParser.bundle.mockResolvedValue(bundledSchema);
|
|
251
|
+
|
|
252
|
+
const result = await gtmScript.getVariablesFromSchemas(SCHEMA_PATH, {});
|
|
253
|
+
const variableNames = result.map((variable) => variable.name);
|
|
254
|
+
|
|
255
|
+
expect(variableNames).toEqual(
|
|
256
|
+
expect.arrayContaining([
|
|
257
|
+
'contact_method.email',
|
|
258
|
+
'contact_method.phone_number',
|
|
259
|
+
'att_status',
|
|
260
|
+
'ad_personalization_enabled',
|
|
261
|
+
]),
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should only include schemas explicitly targeted to web-datalayer-js', async () => {
|
|
266
|
+
const untaggedEventPath = path.join(SCHEMA_PATH, 'legacy-event.json');
|
|
267
|
+
mockFiles[SCHEMA_PATH].push('legacy-event.json');
|
|
268
|
+
mockFileContents[untaggedEventPath] = JSON.stringify(untaggedEventSchema);
|
|
269
|
+
|
|
270
|
+
const bundledWebSchema = JSON.parse(JSON.stringify(complexEventSchema));
|
|
271
|
+
bundledWebSchema.properties.user_data.properties.addresses.items =
|
|
272
|
+
addressSchema;
|
|
273
|
+
|
|
274
|
+
RefParser.bundle.mockImplementation(async (filePath) => {
|
|
275
|
+
if (filePath.endsWith('complex-event.json')) {
|
|
276
|
+
return bundledWebSchema;
|
|
277
|
+
}
|
|
278
|
+
if (filePath.endsWith('mobile-event.json')) {
|
|
279
|
+
return mobileEventSchema;
|
|
280
|
+
}
|
|
281
|
+
if (filePath.endsWith('legacy-event.json')) {
|
|
282
|
+
return untaggedEventSchema;
|
|
283
|
+
}
|
|
284
|
+
if (filePath.endsWith('address.json')) {
|
|
285
|
+
return addressSchema;
|
|
286
|
+
}
|
|
287
|
+
throw new Error(`Unexpected schema file: ${filePath}`);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const result = await gtmScript.getVariablesFromSchemas(SCHEMA_PATH, {});
|
|
291
|
+
|
|
292
|
+
expect(result.map((variable) => variable.name)).toEqual(
|
|
293
|
+
expect.arrayContaining([
|
|
294
|
+
'$schema',
|
|
295
|
+
'event',
|
|
296
|
+
'user_data',
|
|
297
|
+
'user_data.user_id',
|
|
298
|
+
'user_data.addresses',
|
|
299
|
+
'user_data.addresses.0.street',
|
|
300
|
+
'user_data.addresses.0.city',
|
|
301
|
+
'timestamp',
|
|
302
|
+
]),
|
|
303
|
+
);
|
|
304
|
+
expect(result.map((variable) => variable.name)).not.toContain(
|
|
305
|
+
'screen_name',
|
|
306
|
+
);
|
|
307
|
+
expect(result.map((variable) => variable.name)).not.toContain(
|
|
308
|
+
'legacy_field',
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should include root tracking schemas based on content instead of path names', async () => {
|
|
313
|
+
const nestedSchemaDir = path.join(SCHEMA_PATH, 'event-components-demo');
|
|
314
|
+
const nestedSchemaPath = path.join(nestedSchemaDir, 'checkout-event.json');
|
|
315
|
+
const nestedSchema = {
|
|
316
|
+
title: 'Checkout Event',
|
|
317
|
+
'x-tracking-targets': ['web-datalayer-js'],
|
|
318
|
+
type: 'object',
|
|
319
|
+
properties: {
|
|
320
|
+
event: { type: 'string', const: 'checkout' },
|
|
321
|
+
order_id: { type: 'string', description: 'Order identifier.' },
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
mockFiles[SCHEMA_PATH].push('event-components-demo');
|
|
326
|
+
mockFiles[nestedSchemaDir] = ['checkout-event.json'];
|
|
327
|
+
mockFileContents[nestedSchemaPath] = JSON.stringify(nestedSchema);
|
|
328
|
+
|
|
329
|
+
const bundledWebSchema = JSON.parse(JSON.stringify(complexEventSchema));
|
|
330
|
+
bundledWebSchema.properties.user_data.properties.addresses.items =
|
|
331
|
+
addressSchema;
|
|
332
|
+
|
|
333
|
+
RefParser.bundle.mockImplementation(async (filePath) => {
|
|
334
|
+
if (filePath.endsWith('complex-event.json')) {
|
|
335
|
+
return bundledWebSchema;
|
|
336
|
+
}
|
|
337
|
+
if (filePath.endsWith('mobile-event.json')) {
|
|
338
|
+
return mobileEventSchema;
|
|
339
|
+
}
|
|
340
|
+
if (filePath.endsWith('address.json')) {
|
|
341
|
+
return addressSchema;
|
|
342
|
+
}
|
|
343
|
+
if (filePath.endsWith('legacy-event.json')) {
|
|
344
|
+
return untaggedEventSchema;
|
|
345
|
+
}
|
|
346
|
+
if (filePath.endsWith('checkout-event.json')) {
|
|
347
|
+
return nestedSchema;
|
|
348
|
+
}
|
|
349
|
+
throw new Error(`Unexpected schema file: ${filePath}`);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const result = await gtmScript.getVariablesFromSchemas(SCHEMA_PATH, {});
|
|
353
|
+
const variableNames = result.map((variable) => variable.name);
|
|
354
|
+
|
|
355
|
+
expect(variableNames).toContain('order_id');
|
|
356
|
+
expect(RefParser.bundle).toHaveBeenCalledWith(nestedSchemaPath);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('should ignore component schemas even when scanning all json files', async () => {
|
|
360
|
+
const bundledWebSchema = JSON.parse(JSON.stringify(complexEventSchema));
|
|
361
|
+
bundledWebSchema.properties.user_data.properties.addresses.items =
|
|
362
|
+
addressSchema;
|
|
363
|
+
const nestedSchema = {
|
|
364
|
+
title: 'Checkout Event',
|
|
365
|
+
'x-tracking-targets': ['web-datalayer-js'],
|
|
366
|
+
type: 'object',
|
|
367
|
+
properties: {
|
|
368
|
+
event: { type: 'string', const: 'checkout' },
|
|
369
|
+
order_id: { type: 'string', description: 'Order identifier.' },
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
RefParser.bundle.mockImplementation(async (filePath) => {
|
|
374
|
+
if (filePath.endsWith('complex-event.json')) {
|
|
375
|
+
return bundledWebSchema;
|
|
376
|
+
}
|
|
377
|
+
if (filePath.endsWith('mobile-event.json')) {
|
|
378
|
+
return mobileEventSchema;
|
|
379
|
+
}
|
|
380
|
+
if (filePath.endsWith('address.json')) {
|
|
381
|
+
return addressSchema;
|
|
382
|
+
}
|
|
383
|
+
if (filePath.endsWith('legacy-event.json')) {
|
|
384
|
+
return untaggedEventSchema;
|
|
385
|
+
}
|
|
386
|
+
if (filePath.endsWith('checkout-event.json')) {
|
|
387
|
+
return nestedSchema;
|
|
388
|
+
}
|
|
389
|
+
throw new Error(`Unexpected schema file: ${filePath}`);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const result = await gtmScript.getVariablesFromSchemas(SCHEMA_PATH, {});
|
|
393
|
+
const variableNames = result.map((variable) => variable.name);
|
|
394
|
+
|
|
395
|
+
expect(RefParser.bundle).toHaveBeenCalledWith(
|
|
396
|
+
path.join(SCHEMA_PATH, 'components', 'address.json'),
|
|
397
|
+
);
|
|
398
|
+
expect(variableNames).not.toContain('street');
|
|
399
|
+
expect(variableNames).not.toContain('city');
|
|
176
400
|
});
|
|
177
401
|
});
|
|
178
402
|
|
|
@@ -102,4 +102,54 @@ describe('validateSchemas', () => {
|
|
|
102
102
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
103
103
|
expect(consoleErrorSpy.mock.calls[0][0]).toContain('x-tracking-targets');
|
|
104
104
|
});
|
|
105
|
+
|
|
106
|
+
it('should treat falsy scalar examples as valid examples', async () => {
|
|
107
|
+
const schemaDir = path.join(tmpDir, 'schemas');
|
|
108
|
+
fs.mkdirSync(schemaDir, { recursive: true });
|
|
109
|
+
fs.writeFileSync(
|
|
110
|
+
path.join(schemaDir, 'event.json'),
|
|
111
|
+
JSON.stringify({
|
|
112
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
event: {
|
|
116
|
+
type: 'string',
|
|
117
|
+
const: 'test_event',
|
|
118
|
+
},
|
|
119
|
+
is_enabled: {
|
|
120
|
+
type: 'boolean',
|
|
121
|
+
example: false,
|
|
122
|
+
},
|
|
123
|
+
retry_count: {
|
|
124
|
+
type: 'integer',
|
|
125
|
+
example: 0,
|
|
126
|
+
},
|
|
127
|
+
note: {
|
|
128
|
+
type: 'string',
|
|
129
|
+
example: '',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
required: ['event', 'is_enabled', 'retry_count', 'note'],
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const result = await validateSchemas(schemaDir);
|
|
137
|
+
expect(result).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should allow a top-level falsy example value', async () => {
|
|
141
|
+
const schemaDir = path.join(tmpDir, 'schemas');
|
|
142
|
+
fs.mkdirSync(schemaDir, { recursive: true });
|
|
143
|
+
fs.writeFileSync(
|
|
144
|
+
path.join(schemaDir, 'flag.json'),
|
|
145
|
+
JSON.stringify({
|
|
146
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
147
|
+
type: 'boolean',
|
|
148
|
+
example: false,
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const result = await validateSchemas(schemaDir);
|
|
153
|
+
expect(result).toBe(true);
|
|
154
|
+
});
|
|
105
155
|
});
|
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
*/
|
|
18
18
|
export default function ConditionalRows({
|
|
19
19
|
row,
|
|
20
|
+
stripeIndex = 0,
|
|
21
|
+
stripeState,
|
|
20
22
|
bracketEnds: parentBracketEnds,
|
|
21
23
|
}) {
|
|
22
24
|
const {
|
|
@@ -91,7 +93,7 @@ export default function ConditionalRows({
|
|
|
91
93
|
return (
|
|
92
94
|
<>
|
|
93
95
|
{/* Condition (if) section - always visible */}
|
|
94
|
-
<tr className="conditional-condition-header">
|
|
96
|
+
<tr className="conditional-condition-header schema-row--control">
|
|
95
97
|
<td colSpan={5} style={headerStyle}>
|
|
96
98
|
<span className="conditional-condition-label">
|
|
97
99
|
<span className="conditional-info-icon-wrapper">
|
|
@@ -111,7 +113,7 @@ export default function ConditionalRows({
|
|
|
111
113
|
)}
|
|
112
114
|
</td>
|
|
113
115
|
</tr>
|
|
114
|
-
<SchemaRows tableData={condition.rows} />
|
|
116
|
+
<SchemaRows tableData={condition.rows} stripeState={stripeState} />
|
|
115
117
|
|
|
116
118
|
{/* Branch toggles (then / else) */}
|
|
117
119
|
{branches.map((branch, index) => {
|
|
@@ -121,7 +123,7 @@ export default function ConditionalRows({
|
|
|
121
123
|
isLastBranch && !isActive ? lastToggleStyle : middleStyle;
|
|
122
124
|
return (
|
|
123
125
|
<React.Fragment key={branch.title}>
|
|
124
|
-
<tr className="choice-row">
|
|
126
|
+
<tr className="choice-row schema-row--control">
|
|
125
127
|
<td colSpan={5} style={toggleStyle}>
|
|
126
128
|
<label className="choice-row-header">
|
|
127
129
|
<input
|
|
@@ -141,6 +143,7 @@ export default function ConditionalRows({
|
|
|
141
143
|
{isActive && (
|
|
142
144
|
<SchemaRows
|
|
143
145
|
tableData={branch.rows}
|
|
146
|
+
stripeState={stripeState}
|
|
144
147
|
bracketEnds={
|
|
145
148
|
isLastBranch
|
|
146
149
|
? [ownBracket, ...(parentBracketEnds || [])]
|