@auto-engineer/narrative 0.13.0 → 0.13.2

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 (89) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +22 -0
  3. package/dist/src/commands/export-schema-runner.js +1 -1
  4. package/dist/src/commands/export-schema-runner.js.map +1 -1
  5. package/dist/src/fluent-builder.js +3 -3
  6. package/dist/src/fluent-builder.js.map +1 -1
  7. package/dist/src/getNarratives.specs.js +149 -153
  8. package/dist/src/getNarratives.specs.js.map +1 -1
  9. package/dist/src/id/addAutoIds.d.ts.map +1 -1
  10. package/dist/src/id/addAutoIds.js +50 -12
  11. package/dist/src/id/addAutoIds.js.map +1 -1
  12. package/dist/src/id/addAutoIds.specs.js +396 -45
  13. package/dist/src/id/addAutoIds.specs.js.map +1 -1
  14. package/dist/src/id/hasAllIds.d.ts.map +1 -1
  15. package/dist/src/id/hasAllIds.js +28 -5
  16. package/dist/src/id/hasAllIds.js.map +1 -1
  17. package/dist/src/id/hasAllIds.specs.js +407 -214
  18. package/dist/src/id/hasAllIds.specs.js.map +1 -1
  19. package/dist/src/index.d.ts +6 -8
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/index.js +3 -3
  22. package/dist/src/index.js.map +1 -1
  23. package/dist/src/loader/graph.d.ts.map +1 -1
  24. package/dist/src/loader/graph.js +13 -6
  25. package/dist/src/loader/graph.js.map +1 -1
  26. package/dist/src/loader/ts-utils.d.ts +1 -0
  27. package/dist/src/loader/ts-utils.d.ts.map +1 -1
  28. package/dist/src/loader/ts-utils.js +95 -16
  29. package/dist/src/loader/ts-utils.js.map +1 -1
  30. package/dist/src/model-to-narrative.specs.js +531 -449
  31. package/dist/src/model-to-narrative.specs.js.map +1 -1
  32. package/dist/src/narrative-context.d.ts +8 -8
  33. package/dist/src/narrative-context.d.ts.map +1 -1
  34. package/dist/src/narrative-context.js +111 -301
  35. package/dist/src/narrative-context.js.map +1 -1
  36. package/dist/src/narrative-context.specs.js +15 -55
  37. package/dist/src/narrative-context.specs.js.map +1 -1
  38. package/dist/src/narrative.d.ts +19 -22
  39. package/dist/src/narrative.d.ts.map +1 -1
  40. package/dist/src/narrative.js +42 -71
  41. package/dist/src/narrative.js.map +1 -1
  42. package/dist/src/samples/test-with-ids.narrative.js +13 -29
  43. package/dist/src/samples/test-with-ids.narrative.js.map +1 -1
  44. package/dist/src/schema.d.ts +3205 -8287
  45. package/dist/src/schema.d.ts.map +1 -1
  46. package/dist/src/schema.js +29 -47
  47. package/dist/src/schema.js.map +1 -1
  48. package/dist/src/slice-builder.js +3 -3
  49. package/dist/src/slice-builder.js.map +1 -1
  50. package/dist/src/transformers/model-to-narrative/generators/flow.d.ts.map +1 -1
  51. package/dist/src/transformers/model-to-narrative/generators/flow.js +118 -74
  52. package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
  53. package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts +9 -1
  54. package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts.map +1 -1
  55. package/dist/src/transformers/model-to-narrative/generators/gwt.js +112 -112
  56. package/dist/src/transformers/model-to-narrative/generators/gwt.js.map +1 -1
  57. package/dist/src/transformers/model-to-narrative/generators/imports.d.ts +1 -1
  58. package/dist/src/transformers/model-to-narrative/generators/imports.d.ts.map +1 -1
  59. package/dist/src/transformers/model-to-narrative/generators/imports.js +13 -9
  60. package/dist/src/transformers/model-to-narrative/generators/imports.js.map +1 -1
  61. package/dist/src/transformers/narrative-to-model/index.d.ts.map +1 -1
  62. package/dist/src/transformers/narrative-to-model/index.js +50 -23
  63. package/dist/src/transformers/narrative-to-model/index.js.map +1 -1
  64. package/dist/src/transformers/narrative-to-model/type-inference.specs.js +100 -90
  65. package/dist/src/transformers/narrative-to-model/type-inference.specs.js.map +1 -1
  66. package/dist/tsconfig.tsbuildinfo +1 -1
  67. package/package.json +5 -5
  68. package/src/commands/export-schema-runner.ts +3 -1
  69. package/src/fluent-builder.ts +3 -3
  70. package/src/getNarratives.specs.ts +168 -176
  71. package/src/id/addAutoIds.specs.ts +424 -47
  72. package/src/id/addAutoIds.ts +57 -13
  73. package/src/id/hasAllIds.specs.ts +400 -223
  74. package/src/id/hasAllIds.ts +32 -6
  75. package/src/index.ts +9 -12
  76. package/src/loader/graph.ts +23 -6
  77. package/src/loader/ts-utils.ts +169 -26
  78. package/src/model-to-narrative.specs.ts +531 -449
  79. package/src/narrative-context.specs.ts +73 -116
  80. package/src/narrative-context.ts +127 -374
  81. package/src/narrative.ts +70 -120
  82. package/src/samples/test-with-ids.narrative.ts +23 -31
  83. package/src/schema.ts +36 -52
  84. package/src/slice-builder.ts +3 -3
  85. package/src/transformers/model-to-narrative/generators/flow.ts +191 -85
  86. package/src/transformers/model-to-narrative/generators/gwt.ts +195 -178
  87. package/src/transformers/model-to-narrative/generators/imports.ts +13 -9
  88. package/src/transformers/narrative-to-model/index.ts +87 -26
  89. package/src/transformers/narrative-to-model/type-inference.specs.ts +100 -90
@@ -15,20 +15,23 @@ describe('addAutoIds', () => {
15
15
  client: { specs: [] },
16
16
  server: {
17
17
  description: 'Test server',
18
- specs: {
19
- name: 'Test Specs',
20
- rules: [
21
- {
22
- description: 'Test rule without ID',
23
- examples: [],
24
- },
25
- {
26
- id: 'EXISTING-RULE-001',
27
- description: 'Test rule with existing ID',
28
- examples: [],
29
- },
30
- ],
31
- },
18
+ specs: [
19
+ {
20
+ type: 'gherkin',
21
+ feature: 'Test Specs',
22
+ rules: [
23
+ {
24
+ name: 'Test rule without ID',
25
+ examples: [],
26
+ },
27
+ {
28
+ id: 'EXISTING-RULE-001',
29
+ name: 'Test rule with existing ID',
30
+ examples: [],
31
+ },
32
+ ],
33
+ },
34
+ ],
32
35
  },
33
36
  },
34
37
  {
@@ -38,10 +41,13 @@ describe('addAutoIds', () => {
38
41
  client: { specs: [] },
39
42
  server: {
40
43
  description: 'Test server',
41
- specs: {
42
- name: 'Test Specs',
43
- rules: [],
44
- },
44
+ specs: [
45
+ {
46
+ type: 'gherkin',
47
+ feature: 'Test Specs',
48
+ rules: [],
49
+ },
50
+ ],
45
51
  },
46
52
  },
47
53
  ],
@@ -54,16 +60,18 @@ describe('addAutoIds', () => {
54
60
  type: 'react',
55
61
  name: 'React Slice',
56
62
  server: {
57
- description: 'React server',
58
- specs: {
59
- name: 'React Specs',
60
- rules: [
61
- {
62
- description: 'React rule',
63
- examples: [],
64
- },
65
- ],
66
- },
63
+ specs: [
64
+ {
65
+ type: 'gherkin',
66
+ feature: 'React Specs',
67
+ rules: [
68
+ {
69
+ name: 'React rule',
70
+ examples: [],
71
+ },
72
+ ],
73
+ },
74
+ ],
67
75
  },
68
76
  },
69
77
  ],
@@ -86,13 +94,13 @@ describe('addAutoIds', () => {
86
94
  const slice0 = result.narratives[0].slices[0];
87
95
  const slice1 = result.narratives[1].slices[0];
88
96
 
89
- if ('server' in slice0 && slice0.server?.specs?.rules != null) {
90
- expect(slice0.server.specs.rules[0].id).toMatch(AUTO_ID_REGEX);
91
- expect(slice0.server.specs.rules[1].id).toBe('EXISTING-RULE-001');
97
+ if ('server' in slice0 && slice0.server?.specs != null && Array.isArray(slice0.server.specs)) {
98
+ expect(slice0.server.specs[0].rules[0].id).toMatch(AUTO_ID_REGEX);
99
+ expect(slice0.server.specs[0].rules[1].id).toBe('EXISTING-RULE-001');
92
100
  }
93
101
 
94
- if ('server' in slice1 && slice1.server?.specs?.rules != null) {
95
- expect(slice1.server.specs.rules[0].id).toMatch(AUTO_ID_REGEX);
102
+ if ('server' in slice1 && slice1.server?.specs != null && Array.isArray(slice1.server.specs)) {
103
+ expect(slice1.server.specs[0].rules[0].id).toMatch(AUTO_ID_REGEX);
96
104
  }
97
105
  });
98
106
 
@@ -106,10 +114,11 @@ describe('addAutoIds', () => {
106
114
  expect(originalSlice.id).toBeUndefined();
107
115
  if (
108
116
  'server' in originalSlice &&
109
- originalSlice.server?.specs?.rules !== undefined &&
110
- originalSlice.server.specs.rules.length > 0
117
+ originalSlice.server?.specs !== undefined &&
118
+ Array.isArray(originalSlice.server.specs) &&
119
+ originalSlice.server.specs.length > 0
111
120
  ) {
112
- expect(originalSlice.server.specs.rules[0].id).toBeUndefined();
121
+ expect(originalSlice.server.specs[0].rules[0].id).toBeUndefined();
113
122
  }
114
123
  });
115
124
 
@@ -120,8 +129,8 @@ describe('addAutoIds', () => {
120
129
  expect(result.narratives[0].slices[1].id).toBe('EXISTING-SLICE-001');
121
130
 
122
131
  const testSlice = result.narratives[0].slices[0];
123
- if ('server' in testSlice && testSlice.server?.specs?.rules != null) {
124
- expect(testSlice.server.specs.rules[1].id).toBe('EXISTING-RULE-001');
132
+ if ('server' in testSlice && testSlice.server?.specs != null && Array.isArray(testSlice.server.specs)) {
133
+ expect(testSlice.server.specs[0].rules[1].id).toBe('EXISTING-RULE-001');
125
134
  }
126
135
  });
127
136
 
@@ -138,10 +147,13 @@ describe('addAutoIds', () => {
138
147
  client: { specs: [] },
139
148
  server: {
140
149
  description: 'Simple server',
141
- specs: {
142
- name: 'Simple specs',
143
- rules: [],
144
- },
150
+ specs: [
151
+ {
152
+ type: 'gherkin',
153
+ feature: 'Simple specs',
154
+ rules: [],
155
+ },
156
+ ],
145
157
  },
146
158
  },
147
159
  ],
@@ -199,14 +211,9 @@ describe('addAutoIds', () => {
199
211
 
200
212
  const result = addAutoIds(modelWithExperienceSlice);
201
213
 
202
- // Flow should get an auto ID
203
214
  expect(result.narratives[0].id).toMatch(AUTO_ID_REGEX);
204
-
205
- // Experience slices should get auto IDs where missing
206
215
  expect(result.narratives[0].slices[0].id).toMatch(AUTO_ID_REGEX);
207
216
  expect(result.narratives[0].slices[1].id).toBe('EXISTING-EXPERIENCE-SLICE-001');
208
-
209
- // Experience slices only have client specs (no server specs to test)
210
217
  });
211
218
 
212
219
  it('should assign unique IDs to multiple flows with same sourceFile', () => {
@@ -279,4 +286,374 @@ describe('addAutoIds', () => {
279
286
  expect(result.narratives[1].sourceFile).toBe('/path/to/homepage.narrative.ts');
280
287
  expect(result.narratives[2].sourceFile).toBe('/path/to/homepage.narrative.ts');
281
288
  });
289
+
290
+ it('should assign IDs to specs', () => {
291
+ const modelWithSpecs: Model = {
292
+ variant: 'specs',
293
+ narratives: [
294
+ {
295
+ name: 'Test Flow',
296
+ slices: [
297
+ {
298
+ type: 'command',
299
+ name: 'Test Command',
300
+ client: { specs: [] },
301
+ server: {
302
+ description: 'Test server',
303
+ specs: [
304
+ {
305
+ type: 'gherkin',
306
+ feature: 'Test Feature',
307
+ rules: [],
308
+ },
309
+ {
310
+ id: 'EXISTING-SPEC-001',
311
+ type: 'gherkin',
312
+ feature: 'Existing Feature',
313
+ rules: [],
314
+ },
315
+ ],
316
+ },
317
+ },
318
+ ],
319
+ },
320
+ ],
321
+ messages: [],
322
+ integrations: [],
323
+ };
324
+
325
+ const result = addAutoIds(modelWithSpecs);
326
+ const slice = result.narratives[0].slices[0];
327
+
328
+ if ('server' in slice && slice.server?.specs != null && Array.isArray(slice.server.specs)) {
329
+ expect(slice.server.specs[0].id).toMatch(AUTO_ID_REGEX);
330
+ expect(slice.server.specs[1].id).toBe('EXISTING-SPEC-001');
331
+ }
332
+ });
333
+
334
+ it('should assign IDs to steps', () => {
335
+ const modelWithSteps: Model = {
336
+ variant: 'specs',
337
+ narratives: [
338
+ {
339
+ name: 'Test Flow',
340
+ slices: [
341
+ {
342
+ type: 'command',
343
+ name: 'Test Command',
344
+ client: { specs: [] },
345
+ server: {
346
+ description: 'Test server',
347
+ specs: [
348
+ {
349
+ type: 'gherkin',
350
+ feature: 'Test Feature',
351
+ rules: [
352
+ {
353
+ name: 'Test Rule',
354
+ examples: [
355
+ {
356
+ name: 'Test Example',
357
+ steps: [
358
+ { keyword: 'Given', text: 'TestState', docString: { value: 'test' } },
359
+ { keyword: 'When', text: 'TestCommand' },
360
+ { id: 'EXISTING-STEP-001', keyword: 'Then', text: 'TestEvent' },
361
+ ],
362
+ },
363
+ ],
364
+ },
365
+ ],
366
+ },
367
+ ],
368
+ },
369
+ },
370
+ ],
371
+ },
372
+ ],
373
+ messages: [],
374
+ integrations: [],
375
+ };
376
+
377
+ const result = addAutoIds(modelWithSteps);
378
+ const slice = result.narratives[0].slices[0];
379
+
380
+ if ('server' in slice && slice.server?.specs != null && Array.isArray(slice.server.specs)) {
381
+ const steps = slice.server.specs[0].rules[0].examples[0].steps;
382
+ expect(steps[0].id).toMatch(AUTO_ID_REGEX);
383
+ expect(steps[1].id).toMatch(AUTO_ID_REGEX);
384
+ expect(steps[2].id).toBe('EXISTING-STEP-001');
385
+ }
386
+ });
387
+
388
+ it('should preserve existing example IDs', () => {
389
+ const modelWithExistingExampleId: Model = {
390
+ variant: 'specs',
391
+ narratives: [
392
+ {
393
+ name: 'Test Flow',
394
+ slices: [
395
+ {
396
+ type: 'command',
397
+ name: 'Test Command',
398
+ client: { specs: [] },
399
+ server: {
400
+ description: 'Test server',
401
+ specs: [
402
+ {
403
+ type: 'gherkin',
404
+ feature: 'Test Feature',
405
+ rules: [
406
+ {
407
+ name: 'Test Rule',
408
+ examples: [
409
+ {
410
+ name: 'Example without id',
411
+ steps: [{ keyword: 'Given', text: 'TestState' }],
412
+ },
413
+ {
414
+ id: 'EXISTING-EXAMPLE-001',
415
+ name: 'Example with existing id',
416
+ steps: [{ keyword: 'Given', text: 'TestState' }],
417
+ },
418
+ ],
419
+ },
420
+ ],
421
+ },
422
+ ],
423
+ },
424
+ },
425
+ ],
426
+ },
427
+ ],
428
+ messages: [],
429
+ integrations: [],
430
+ };
431
+
432
+ const result = addAutoIds(modelWithExistingExampleId);
433
+ const slice = result.narratives[0].slices[0];
434
+
435
+ if ('server' in slice && slice.server?.specs != null && Array.isArray(slice.server.specs)) {
436
+ const examples = slice.server.specs[0].rules[0].examples;
437
+ expect(examples[0].id).toMatch(AUTO_ID_REGEX);
438
+ expect(examples[1].id).toBe('EXISTING-EXAMPLE-001');
439
+ }
440
+ });
441
+
442
+ it('should assign IDs to steps with errors', () => {
443
+ const modelWithErrorSteps: Model = {
444
+ variant: 'specs',
445
+ narratives: [
446
+ {
447
+ name: 'Test Flow',
448
+ slices: [
449
+ {
450
+ type: 'command',
451
+ name: 'Test Command',
452
+ client: { specs: [] },
453
+ server: {
454
+ description: 'Test server',
455
+ specs: [
456
+ {
457
+ type: 'gherkin',
458
+ feature: 'Test Feature',
459
+ rules: [
460
+ {
461
+ name: 'Error Rule',
462
+ examples: [
463
+ {
464
+ name: 'Error Example',
465
+ steps: [
466
+ { keyword: 'Given', text: 'TestState' },
467
+ { keyword: 'When', text: 'InvalidCommand' },
468
+ { keyword: 'Then', error: { type: 'ValidationError', message: 'Invalid input' } },
469
+ ],
470
+ },
471
+ ],
472
+ },
473
+ ],
474
+ },
475
+ ],
476
+ },
477
+ },
478
+ ],
479
+ },
480
+ ],
481
+ messages: [],
482
+ integrations: [],
483
+ };
484
+
485
+ const result = addAutoIds(modelWithErrorSteps);
486
+ const slice = result.narratives[0].slices[0];
487
+
488
+ if ('server' in slice && slice.server?.specs != null && Array.isArray(slice.server.specs)) {
489
+ const steps = slice.server.specs[0].rules[0].examples[0].steps;
490
+ expect(steps[0].id).toMatch(AUTO_ID_REGEX);
491
+ expect(steps[1].id).toMatch(AUTO_ID_REGEX);
492
+ expect(steps[2].id).toMatch(AUTO_ID_REGEX);
493
+ }
494
+ });
495
+
496
+ it('should assign IDs to client it specs', () => {
497
+ const modelWithClientSpecs: Model = {
498
+ variant: 'specs',
499
+ narratives: [
500
+ {
501
+ name: 'Test Flow',
502
+ slices: [
503
+ {
504
+ type: 'experience',
505
+ name: 'Test Experience',
506
+ client: {
507
+ specs: [
508
+ { type: 'it', title: 'first test' },
509
+ { type: 'it', id: 'EXISTING-IT-001', title: 'second test with id' },
510
+ ],
511
+ },
512
+ },
513
+ ],
514
+ },
515
+ ],
516
+ messages: [],
517
+ integrations: [],
518
+ };
519
+
520
+ const result = addAutoIds(modelWithClientSpecs);
521
+ const slice = result.narratives[0].slices[0];
522
+
523
+ if ('client' in slice && slice.client?.specs != null) {
524
+ expect(slice.client.specs[0].id).toMatch(AUTO_ID_REGEX);
525
+ expect(slice.client.specs[1].id).toBe('EXISTING-IT-001');
526
+ }
527
+ });
528
+
529
+ it('should assign IDs to client describe specs', () => {
530
+ const modelWithDescribe: Model = {
531
+ variant: 'specs',
532
+ narratives: [
533
+ {
534
+ name: 'Test Flow',
535
+ slices: [
536
+ {
537
+ type: 'experience',
538
+ name: 'Test Experience',
539
+ client: {
540
+ specs: [
541
+ {
542
+ type: 'describe',
543
+ title: 'describe without id',
544
+ children: [{ type: 'it', title: 'nested it' }],
545
+ },
546
+ {
547
+ type: 'describe',
548
+ id: 'EXISTING-DESC-001',
549
+ title: 'describe with id',
550
+ children: [],
551
+ },
552
+ ],
553
+ },
554
+ },
555
+ ],
556
+ },
557
+ ],
558
+ messages: [],
559
+ integrations: [],
560
+ };
561
+
562
+ const result = addAutoIds(modelWithDescribe);
563
+ const slice = result.narratives[0].slices[0];
564
+
565
+ if ('client' in slice && slice.client?.specs != null) {
566
+ expect(slice.client.specs[0].id).toMatch(AUTO_ID_REGEX);
567
+ expect(slice.client.specs[1].id).toBe('EXISTING-DESC-001');
568
+ }
569
+ });
570
+
571
+ it('should assign IDs to nested client specs', () => {
572
+ const modelWithNestedSpecs: Model = {
573
+ variant: 'specs',
574
+ narratives: [
575
+ {
576
+ name: 'Test Flow',
577
+ slices: [
578
+ {
579
+ type: 'experience',
580
+ name: 'Test Experience',
581
+ client: {
582
+ specs: [
583
+ {
584
+ type: 'describe',
585
+ title: 'outer describe',
586
+ children: [
587
+ { type: 'it', title: 'outer it' },
588
+ {
589
+ type: 'describe',
590
+ title: 'inner describe',
591
+ children: [
592
+ { type: 'it', title: 'inner it 1' },
593
+ { type: 'it', title: 'inner it 2' },
594
+ ],
595
+ },
596
+ ],
597
+ },
598
+ ],
599
+ },
600
+ },
601
+ ],
602
+ },
603
+ ],
604
+ messages: [],
605
+ integrations: [],
606
+ };
607
+
608
+ const result = addAutoIds(modelWithNestedSpecs);
609
+ const slice = result.narratives[0].slices[0];
610
+
611
+ if ('client' in slice && slice.client?.specs != null) {
612
+ const outerDescribe = slice.client.specs[0];
613
+ expect(outerDescribe.id).toMatch(AUTO_ID_REGEX);
614
+
615
+ if (outerDescribe.type === 'describe' && outerDescribe.children) {
616
+ expect(outerDescribe.children[0].id).toMatch(AUTO_ID_REGEX);
617
+
618
+ const innerDescribe = outerDescribe.children[1];
619
+ expect(innerDescribe.id).toMatch(AUTO_ID_REGEX);
620
+
621
+ if (innerDescribe.type === 'describe' && innerDescribe.children) {
622
+ expect(innerDescribe.children[0].id).toMatch(AUTO_ID_REGEX);
623
+ expect(innerDescribe.children[1].id).toMatch(AUTO_ID_REGEX);
624
+
625
+ expect(innerDescribe.children[0].id).not.toBe(innerDescribe.children[1].id);
626
+ }
627
+ }
628
+ }
629
+ });
630
+
631
+ it('should not mutate original client specs', () => {
632
+ const modelWithClientSpecs: Model = {
633
+ variant: 'specs',
634
+ narratives: [
635
+ {
636
+ name: 'Test Flow',
637
+ slices: [
638
+ {
639
+ type: 'experience',
640
+ name: 'Test Experience',
641
+ client: {
642
+ specs: [{ type: 'it', title: 'test' }],
643
+ },
644
+ },
645
+ ],
646
+ },
647
+ ],
648
+ messages: [],
649
+ integrations: [],
650
+ };
651
+
652
+ const originalSpec = modelWithClientSpecs.narratives[0].slices[0];
653
+ addAutoIds(modelWithClientSpecs);
654
+
655
+ if ('client' in originalSpec && originalSpec.client?.specs != null) {
656
+ expect(originalSpec.client.specs[0].id).toBeUndefined();
657
+ }
658
+ });
282
659
  });
@@ -1,5 +1,5 @@
1
1
  import { generateAutoId } from './generators';
2
- import { Model, Slice } from '../index';
2
+ import { Model, Slice, Spec, Rule, Example, Step, ClientSpecNode } from '../index';
3
3
 
4
4
  function ensureId(item: { id?: string }): void {
5
5
  if (item.id === undefined || item.id === '') {
@@ -7,30 +7,74 @@ function ensureId(item: { id?: string }): void {
7
7
  }
8
8
  }
9
9
 
10
- function addRuleIds(rules: Array<unknown>): Array<unknown> {
10
+ function processSteps(steps: Step[]): Step[] {
11
+ return steps.map((step) => {
12
+ const stepCopy = { ...step };
13
+ ensureId(stepCopy);
14
+ return stepCopy;
15
+ });
16
+ }
17
+
18
+ function processExamples(examples: Example[]): Example[] {
19
+ return examples.map((example) => {
20
+ const exampleCopy = { ...example };
21
+ ensureId(exampleCopy);
22
+ exampleCopy.steps = processSteps(example.steps);
23
+ return exampleCopy;
24
+ });
25
+ }
26
+
27
+ function processRules(rules: Rule[]): Rule[] {
11
28
  return rules.map((rule) => {
12
- if (typeof rule === 'object' && rule !== null && 'description' in rule) {
13
- const ruleCopy = { ...rule } as { id?: string };
14
- ensureId(ruleCopy);
15
- return ruleCopy;
16
- }
17
- return rule;
29
+ const ruleCopy = { ...rule };
30
+ ensureId(ruleCopy);
31
+ ruleCopy.examples = processExamples(rule.examples);
32
+ return ruleCopy;
33
+ });
34
+ }
35
+
36
+ function processSpecs(specs: Spec[]): Spec[] {
37
+ return specs.map((spec) => {
38
+ const specCopy = { ...spec };
39
+ ensureId(specCopy);
40
+ specCopy.rules = processRules(spec.rules);
41
+ return specCopy;
18
42
  });
19
43
  }
20
44
 
21
45
  function processServerSpecs(slice: Slice): Slice {
22
- if (!('server' in slice) || slice.server?.specs?.rules === undefined) return slice;
46
+ if (!('server' in slice) || slice.server?.specs === undefined || !Array.isArray(slice.server.specs)) return slice;
23
47
 
24
48
  const modifiedSlice = structuredClone(slice);
25
- if ('server' in modifiedSlice && modifiedSlice.server?.specs?.rules !== undefined) {
26
- (modifiedSlice.server.specs as { rules: unknown[] }).rules = addRuleIds(modifiedSlice.server.specs.rules);
49
+ if (
50
+ 'server' in modifiedSlice &&
51
+ modifiedSlice.server?.specs !== undefined &&
52
+ Array.isArray(modifiedSlice.server.specs)
53
+ ) {
54
+ modifiedSlice.server.specs = processSpecs(modifiedSlice.server.specs);
27
55
  }
28
56
  return modifiedSlice;
29
57
  }
30
58
 
59
+ function processClientSpecNodes(nodes: ClientSpecNode[]): ClientSpecNode[] {
60
+ return nodes.map((node) => {
61
+ const nodeCopy = { ...node };
62
+ ensureId(nodeCopy);
63
+ if (nodeCopy.type === 'describe' && nodeCopy.children) {
64
+ nodeCopy.children = processClientSpecNodes(nodeCopy.children);
65
+ }
66
+ return nodeCopy;
67
+ });
68
+ }
69
+
31
70
  function processClientSpecs(slice: Slice): Slice {
32
- // Client specs use string rules (no IDs needed), so nothing to process
33
- return slice;
71
+ if (!('client' in slice) || slice.client?.specs === undefined || !Array.isArray(slice.client.specs)) return slice;
72
+
73
+ const modifiedSlice = structuredClone(slice);
74
+ if ('client' in modifiedSlice && modifiedSlice.client?.specs !== undefined) {
75
+ modifiedSlice.client.specs = processClientSpecNodes(modifiedSlice.client.specs);
76
+ }
77
+ return modifiedSlice;
34
78
  }
35
79
 
36
80
  function processSlice(slice: Slice): Slice {