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.
Files changed (43) hide show
  1. package/README.md +2 -0
  2. package/__tests__/__fixtures__/validateSchemas/main-schema-with-not-allof.json +11 -0
  3. package/__tests__/__snapshots__/generateEventDocs.anchor.test.js.snap +21 -3
  4. package/__tests__/__snapshots__/generateEventDocs.nested.test.js.snap +26 -4
  5. package/__tests__/__snapshots__/generateEventDocs.test.js.snap +45 -6
  6. package/__tests__/__snapshots__/generateEventDocs.versioned.test.js.snap +16 -2
  7. package/__tests__/components/ConditionalRows.test.js +28 -0
  8. package/__tests__/components/FoldableRows.test.js +31 -290
  9. package/__tests__/components/PropertiesTable.test.js +66 -0
  10. package/__tests__/components/PropertyRow.test.js +297 -0
  11. package/__tests__/components/SchemaJsonViewer.test.js +194 -10
  12. package/__tests__/components/SchemaRows.test.js +62 -12
  13. package/__tests__/components/__snapshots__/ConnectorLines.visualRegression.test.js.snap +3 -3
  14. package/__tests__/generateEventDocs.test.js +3 -0
  15. package/__tests__/helpers/example-helper.test.js +12 -0
  16. package/__tests__/helpers/getConstraints.test.js +16 -0
  17. package/__tests__/helpers/processSchema.test.js +18 -0
  18. package/__tests__/helpers/schemaToTableData.test.js +112 -0
  19. package/__tests__/helpers/schemaTraversal.test.js +110 -0
  20. package/__tests__/syncGtm.test.js +227 -3
  21. package/__tests__/validateSchemas.test.js +50 -0
  22. package/components/ConditionalRows.js +6 -3
  23. package/components/FoldableRows.js +9 -3
  24. package/components/PropertiesTable.js +34 -3
  25. package/components/PropertyRow.js +118 -6
  26. package/components/SchemaJsonViewer.js +324 -4
  27. package/components/SchemaRows.css +138 -7
  28. package/components/SchemaRows.js +11 -1
  29. package/components/SchemaViewer.js +11 -2
  30. package/generateEventDocs.js +87 -1
  31. package/helpers/choice-index-template.js +6 -2
  32. package/helpers/example-helper.js +2 -2
  33. package/helpers/file-system.js +28 -0
  34. package/helpers/getConstraints.js +20 -0
  35. package/helpers/processSchema.js +32 -1
  36. package/helpers/schema-doc-template.js +11 -1
  37. package/helpers/schemaToExamples.js +29 -35
  38. package/helpers/schemaToTableData.js +68 -7
  39. package/helpers/schemaTraversal.cjs +148 -0
  40. package/package.json +1 -1
  41. package/scripts/sync-gtm.js +41 -28
  42. package/test-data/payloadContracts.js +35 -0
  43. package/validateSchemas.js +1 -1
@@ -147,4 +147,32 @@ describe('ConditionalRows', () => {
147
147
  screen.getByText(JSON.stringify([{ name: 'postal_code' }])),
148
148
  ).toBeInTheDocument();
149
149
  });
150
+
151
+ it('keeps conditional control rows neutral instead of zebra-striped', () => {
152
+ const { container, getByText } = render(
153
+ <table>
154
+ <tbody>
155
+ <ConditionalRows
156
+ row={conditionalRow}
157
+ stripeIndex={1}
158
+ stripeState={{ current: 2 }}
159
+ />
160
+ </tbody>
161
+ </table>,
162
+ );
163
+
164
+ const ifRow = getByText('If').closest('tr');
165
+ const thenRow = getByText('Then').closest('tr');
166
+ const elseRow = getByText('Else').closest('tr');
167
+
168
+ expect(ifRow).toHaveClass('schema-row--control');
169
+ expect(thenRow).toHaveClass('schema-row--control');
170
+ expect(elseRow).toHaveClass('schema-row--control');
171
+ expect(ifRow).not.toHaveClass('schema-row--zebra-even');
172
+ expect(thenRow).not.toHaveClass('schema-row--zebra-even');
173
+ expect(elseRow).not.toHaveClass('schema-row--zebra-even');
174
+ expect(
175
+ container.querySelector('[data-testid="schema-rows"]'),
176
+ ).toBeInTheDocument();
177
+ });
150
178
  });
@@ -1,12 +1,10 @@
1
1
  import '@testing-library/jest-dom';
2
2
  import React from 'react';
3
- import { render, screen, fireEvent } from '@testing-library/react';
3
+ import { render, screen } from '@testing-library/react';
4
4
  import FoldableRows from '../../components/FoldableRows';
5
5
 
6
6
  jest.mock('../../components/SchemaRows', () => {
7
7
  const MockSchemaRows = (props) => (
8
- // The mock needs to return a valid element to be a child of a tbody
9
- // We'll use a data-testid to check for its presence.
10
8
  <tr data-testid="schema-rows">
11
9
  <td>{JSON.stringify(props.tableData)}</td>
12
10
  </tr>
@@ -16,318 +14,61 @@ jest.mock('../../components/SchemaRows', () => {
16
14
  });
17
15
 
18
16
  jest.mock('@theme/Heading', () => {
19
- const MockHeading = ({ as: Component, children, ...props }) => (
20
- <Component {...props}>{children}</Component>
17
+ const MockHeading = ({ as: Tag = 'h4', children, className }) => (
18
+ <Tag className={className}>{children}</Tag>
21
19
  );
22
20
  MockHeading.displayName = 'MockHeading';
23
21
  return MockHeading;
24
22
  });
25
23
 
26
24
  describe('FoldableRows', () => {
27
- const oneOfRow = {
25
+ const choiceRow = {
28
26
  type: 'choice',
29
27
  choiceType: 'oneOf',
30
- path: ['payment'],
31
- level: 1,
32
- description: 'Select one payment method:',
28
+ description: 'Choose one payment type.',
29
+ name: 'payment',
30
+ level: 0,
31
+ continuingLevels: [],
33
32
  options: [
34
33
  {
35
34
  title: 'Credit Card',
36
- description: 'Pay with card',
37
- rows: [{ name: 'cardNumber' }],
35
+ description: 'Card payment.',
36
+ rows: [{ name: 'card_number' }],
38
37
  },
39
38
  {
40
39
  title: 'PayPal',
41
- description: 'Pay with PayPal',
40
+ description: 'PayPal payment.',
42
41
  rows: [{ name: 'email' }],
43
42
  },
44
43
  ],
45
44
  };
46
45
 
47
- const anyOfRow = {
48
- type: 'choice',
49
- choiceType: 'anyOf',
50
- path: ['contact'],
51
- level: 1,
52
- description: 'Select any contact method:',
53
- options: [
54
- {
55
- title: 'Email',
56
- description: 'Contact via email',
57
- rows: [{ name: 'email_address' }],
58
- },
59
- {
60
- title: 'Phone',
61
- description: 'Contact via phone',
62
- rows: [{ name: 'phone_number' }],
63
- },
64
- ],
65
- };
66
-
67
- it('renders oneOf as a radio-style accordion', () => {
68
- render(
69
- <table>
70
- <tbody>
71
- <FoldableRows row={oneOfRow} />
72
- </tbody>
73
- </table>,
74
- );
75
-
76
- const creditCardToggle = screen.getByText('Credit Card');
77
- const paypalToggle = screen.getByText('PayPal');
78
-
79
- // Initially, first option is open
80
- expect(
81
- screen.getByText(JSON.stringify([{ name: 'cardNumber' }])),
82
- ).toBeInTheDocument();
83
- expect(
84
- screen.queryByText(JSON.stringify([{ name: 'email' }])),
85
- ).not.toBeInTheDocument();
86
-
87
- // Click the second option
88
- fireEvent.click(paypalToggle);
89
-
90
- // Now, second option should be open and first should be closed
91
- expect(
92
- screen.queryByText(JSON.stringify([{ name: 'cardNumber' }])),
93
- ).not.toBeInTheDocument();
94
- expect(
95
- screen.getByText(JSON.stringify([{ name: 'email' }])),
96
- ).toBeInTheDocument();
97
- });
98
-
99
- it('renders anyOf as a checkbox-style accordion', () => {
46
+ it('keeps choice headers and toggles neutral instead of zebra-striped', () => {
100
47
  render(
101
48
  <table>
102
49
  <tbody>
103
- <FoldableRows row={anyOfRow} />
50
+ <FoldableRows
51
+ row={choiceRow}
52
+ stripeIndex={1}
53
+ stripeState={{ current: 2 }}
54
+ />
104
55
  </tbody>
105
56
  </table>,
106
57
  );
107
58
 
108
- const emailToggle = screen.getByText('Email');
109
- const phoneToggle = screen.getByText('Phone');
110
-
111
- // Initially, nothing is open
112
- expect(screen.queryByTestId('schema-rows')).not.toBeInTheDocument();
113
-
114
- // Click the first option
115
- fireEvent.click(emailToggle);
116
- expect(
117
- screen.getByText(JSON.stringify([{ name: 'email_address' }])),
118
- ).toBeInTheDocument();
119
- expect(
120
- screen.queryByText(JSON.stringify([{ name: 'phone_number' }])),
121
- ).not.toBeInTheDocument();
122
-
123
- // Click the second option
124
- fireEvent.click(phoneToggle);
125
- expect(
126
- screen.getByText(JSON.stringify([{ name: 'email_address' }])),
127
- ).toBeInTheDocument();
128
- expect(
129
- screen.getByText(JSON.stringify([{ name: 'phone_number' }])),
130
- ).toBeInTheDocument();
131
-
132
- // Click the first option again to close it
133
- fireEvent.click(emailToggle);
134
- expect(
135
- screen.queryByText(JSON.stringify([{ name: 'email_address' }])),
136
- ).not.toBeInTheDocument();
137
- expect(
138
- screen.getByText(JSON.stringify([{ name: 'phone_number' }])),
139
- ).toBeInTheDocument();
140
- });
141
-
142
- describe('hierarchical lines feature', () => {
143
- it('applies padding-left based on level using rem units', () => {
144
- const rowWithLevel = {
145
- type: 'choice',
146
- choiceType: 'oneOf',
147
- path: ['nested', 'payment'],
148
- level: 2,
149
- description: 'Nested choice',
150
- continuingLevels: [],
151
- options: [
152
- {
153
- title: 'Option A',
154
- description: 'First option',
155
- rows: [{ name: 'fieldA' }],
156
- },
157
- ],
158
- };
159
-
160
- const { container } = render(
161
- <table>
162
- <tbody>
163
- <FoldableRows row={rowWithLevel} />
164
- </tbody>
165
- </table>,
166
- );
167
-
168
- const cells = container.querySelectorAll('td[colspan="5"]');
169
- // level 2: 2 * 1.25 + 0.5 = 3rem
170
- cells.forEach((cell) => {
171
- expect(cell.style.paddingLeft).toBe('3rem');
172
- });
173
- });
174
-
175
- it('applies background-image for continuing ancestor lines', () => {
176
- const rowWithContinuingLevels = {
177
- type: 'choice',
178
- choiceType: 'anyOf',
179
- path: ['deeply', 'nested', 'choice'],
180
- level: 2,
181
- description: 'Choice with continuing lines',
182
- continuingLevels: [0], // Ancestor at level 0 has more siblings
183
- options: [
184
- {
185
- title: 'Option A',
186
- rows: [{ name: 'fieldA' }],
187
- },
188
- ],
189
- };
190
-
191
- const { container } = render(
192
- <table>
193
- <tbody>
194
- <FoldableRows row={rowWithContinuingLevels} />
195
- </tbody>
196
- </table>,
197
- );
198
-
199
- const cells = container.querySelectorAll('td[colspan="5"]');
200
- cells.forEach((cell) => {
201
- expect(cell.style.backgroundImage).toContain('linear-gradient');
202
- });
203
- });
204
-
205
- it('applies background-image for immediate parent level connection', () => {
206
- const rowWithParentLevel = {
207
- type: 'choice',
208
- choiceType: 'oneOf',
209
- path: ['parent', 'choice'],
210
- level: 1,
211
- description: 'Choice at level 1',
212
- continuingLevels: [],
213
- options: [
214
- {
215
- title: 'Option A',
216
- rows: [{ name: 'fieldA' }],
217
- },
218
- ],
219
- };
220
-
221
- const { container } = render(
222
- <table>
223
- <tbody>
224
- <FoldableRows row={rowWithParentLevel} />
225
- </tbody>
226
- </table>,
227
- );
228
-
229
- const cells = container.querySelectorAll('td[colspan="5"]');
230
- // Should have a line at parent level (level 0) position
231
- cells.forEach((cell) => {
232
- expect(cell.style.backgroundImage).toContain('linear-gradient');
233
- });
234
- });
235
-
236
- it('has a bracket gradient for root level choices with no continuing levels', () => {
237
- const rootLevelRow = {
238
- type: 'choice',
239
- choiceType: 'oneOf',
240
- path: ['user_id'],
241
- level: 0,
242
- description: 'Root level choice',
243
- continuingLevels: [],
244
- groupBrackets: [],
245
- options: [
246
- {
247
- title: 'Option A',
248
- rows: [{ name: 'fieldA' }],
249
- },
250
- ],
251
- };
252
-
253
- const { container } = render(
254
- <table>
255
- <tbody>
256
- <FoldableRows row={rootLevelRow} />
257
- </tbody>
258
- </table>,
259
- );
260
-
261
- // Root-level choice rows get a bracket gradient even without tree-line continuing levels
262
- const cells = container.querySelectorAll('td[colspan="5"]');
263
- cells.forEach((cell) => {
264
- expect(cell.style.backgroundImage).toContain('linear-gradient');
265
- });
266
- });
267
-
268
- it('combines multiple continuing levels in background-image', () => {
269
- const rowWithMultipleLevels = {
270
- type: 'choice',
271
- choiceType: 'anyOf',
272
- path: ['a', 'b', 'c', 'choice'],
273
- level: 3,
274
- description: 'Deeply nested choice',
275
- continuingLevels: [0, 1], // Multiple ancestors have siblings
276
- options: [
277
- {
278
- title: 'Option A',
279
- rows: [{ name: 'fieldA' }],
280
- },
281
- ],
282
- };
283
-
284
- const { container } = render(
285
- <table>
286
- <tbody>
287
- <FoldableRows row={rowWithMultipleLevels} />
288
- </tbody>
289
- </table>,
290
- );
291
-
292
- const cells = container.querySelectorAll('td[colspan="5"]');
293
- cells.forEach((cell) => {
294
- const bgImage = cell.style.backgroundImage;
295
- // colSpan=5 rows only have bracket lines (right side), not tree lines (left side).
296
- // Should have exactly 1 bracket gradient.
297
- const gradientCount = (bgImage.match(/linear-gradient/g) || []).length;
298
- expect(gradientCount).toBeGreaterThanOrEqual(1);
299
- });
300
- });
301
-
302
- it('applies correct indentation for level 0', () => {
303
- const levelZeroRow = {
304
- type: 'choice',
305
- choiceType: 'oneOf',
306
- path: ['user_id'],
307
- level: 0,
308
- description: 'Level 0 choice',
309
- continuingLevels: [],
310
- options: [
311
- {
312
- title: 'Option A',
313
- rows: [{ name: 'fieldA' }],
314
- },
315
- ],
316
- };
317
-
318
- const { container } = render(
319
- <table>
320
- <tbody>
321
- <FoldableRows row={levelZeroRow} />
322
- </tbody>
323
- </table>,
324
- );
325
-
326
- const cells = container.querySelectorAll('td[colspan="5"]');
327
- // level 0: 0 * 1.25 + 0.5 = 0.5rem
328
- cells.forEach((cell) => {
329
- expect(cell.style.paddingLeft).toBe('0.5rem');
330
- });
331
- });
59
+ const headerRow = screen
60
+ .getByText((content) =>
61
+ content.includes('Select one of the following options:'),
62
+ )
63
+ .closest('tr');
64
+ const firstToggleRow = screen.getByText('Credit Card').closest('tr');
65
+ const secondToggleRow = screen.getByText('PayPal').closest('tr');
66
+
67
+ expect(headerRow).toHaveClass('schema-row--control');
68
+ expect(firstToggleRow).toHaveClass('schema-row--control');
69
+ expect(secondToggleRow).toHaveClass('schema-row--control');
70
+ expect(headerRow).not.toHaveClass('schema-row--zebra-even');
71
+ expect(firstToggleRow).not.toHaveClass('schema-row--zebra-even');
72
+ expect(secondToggleRow).not.toHaveClass('schema-row--zebra-even');
332
73
  });
333
74
  });
@@ -2,6 +2,11 @@ import '@testing-library/jest-dom';
2
2
  import React from 'react';
3
3
  import { render, fireEvent } from '@testing-library/react';
4
4
  import PropertiesTable from '../../components/PropertiesTable';
5
+ import { schemaToTableData } from '../../helpers/schemaToTableData';
6
+
7
+ jest.mock('../../helpers/schemaToTableData', () => ({
8
+ schemaToTableData: jest.fn(() => []),
9
+ }));
5
10
 
6
11
  jest.mock('../../components/TableHeader', () => {
7
12
  const MockTableHeader = () => (
@@ -32,6 +37,10 @@ jest.mock('../../components/WordWrapButton', () => {
32
37
  });
33
38
 
34
39
  describe('PropertiesTable', () => {
40
+ beforeEach(() => {
41
+ schemaToTableData.mockClear();
42
+ });
43
+
35
44
  it('renders the table with header and schema rows', () => {
36
45
  const schema = {
37
46
  properties: {
@@ -46,6 +55,63 @@ describe('PropertiesTable', () => {
46
55
  expect(getByText('Mocked SchemaRows')).toBeInTheDocument();
47
56
  });
48
57
 
58
+ it('filters out inherited top-level properties without description or examples', () => {
59
+ const schema = {
60
+ properties: {
61
+ event: { type: 'string' },
62
+ items: { type: 'array' },
63
+ },
64
+ required: ['event'],
65
+ };
66
+ const sourceSchema = {
67
+ properties: {
68
+ event: { type: 'string' },
69
+ },
70
+ };
71
+
72
+ render(<PropertiesTable schema={schema} sourceSchema={sourceSchema} />);
73
+
74
+ expect(schemaToTableData).toHaveBeenCalledWith({
75
+ ...schema,
76
+ properties: {
77
+ event: { type: 'string' },
78
+ },
79
+ });
80
+ });
81
+
82
+ it('keeps inherited top-level properties when they include documentation fields', () => {
83
+ const schema = {
84
+ properties: {
85
+ event: { type: 'string' },
86
+ app_version: {
87
+ type: 'string',
88
+ description: 'Shared app version for all mobile events.',
89
+ examples: ['1.2.3'],
90
+ },
91
+ },
92
+ required: ['event'],
93
+ };
94
+ const sourceSchema = {
95
+ properties: {
96
+ event: { type: 'string' },
97
+ },
98
+ };
99
+
100
+ render(<PropertiesTable schema={schema} sourceSchema={sourceSchema} />);
101
+
102
+ expect(schemaToTableData).toHaveBeenCalledWith({
103
+ ...schema,
104
+ properties: {
105
+ event: { type: 'string' },
106
+ app_version: {
107
+ type: 'string',
108
+ description: 'Shared app version for all mobile events.',
109
+ examples: ['1.2.3'],
110
+ },
111
+ },
112
+ });
113
+ });
114
+
49
115
  it('toggles word wrap when the button is clicked', () => {
50
116
  const schema = {
51
117
  properties: {