@auto-engineer/narrative 0.12.1 → 0.13.1

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 (95) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +24 -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 +157 -161
  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 +23 -10
  11. package/dist/src/id/addAutoIds.js.map +1 -1
  12. package/dist/src/id/addAutoIds.specs.js +55 -46
  13. package/dist/src/id/addAutoIds.specs.js.map +1 -1
  14. package/dist/src/id/generators.js +1 -1
  15. package/dist/src/id/generators.js.map +1 -1
  16. package/dist/src/id/hasAllIds.d.ts.map +1 -1
  17. package/dist/src/id/hasAllIds.js +8 -3
  18. package/dist/src/id/hasAllIds.js.map +1 -1
  19. package/dist/src/id/hasAllIds.specs.js +142 -215
  20. package/dist/src/id/hasAllIds.specs.js.map +1 -1
  21. package/dist/src/index.d.ts +7 -8
  22. package/dist/src/index.d.ts.map +1 -1
  23. package/dist/src/index.js +3 -3
  24. package/dist/src/index.js.map +1 -1
  25. package/dist/src/loader/graph.d.ts.map +1 -1
  26. package/dist/src/loader/graph.js +13 -6
  27. package/dist/src/loader/graph.js.map +1 -1
  28. package/dist/src/loader/ts-utils.d.ts +1 -0
  29. package/dist/src/loader/ts-utils.d.ts.map +1 -1
  30. package/dist/src/loader/ts-utils.js +95 -16
  31. package/dist/src/loader/ts-utils.js.map +1 -1
  32. package/dist/src/model-to-narrative.specs.js +548 -466
  33. package/dist/src/model-to-narrative.specs.js.map +1 -1
  34. package/dist/src/narrative-context.d.ts +8 -8
  35. package/dist/src/narrative-context.d.ts.map +1 -1
  36. package/dist/src/narrative-context.js +111 -301
  37. package/dist/src/narrative-context.js.map +1 -1
  38. package/dist/src/narrative-context.specs.js +15 -55
  39. package/dist/src/narrative-context.specs.js.map +1 -1
  40. package/dist/src/narrative.d.ts +19 -22
  41. package/dist/src/narrative.d.ts.map +1 -1
  42. package/dist/src/narrative.js +42 -71
  43. package/dist/src/narrative.js.map +1 -1
  44. package/dist/src/samples/questionnaires.narrative.js +10 -10
  45. package/dist/src/samples/questionnaires.narrative.js.map +1 -1
  46. package/dist/src/samples/test-with-ids.narrative.js +13 -29
  47. package/dist/src/samples/test-with-ids.narrative.js.map +1 -1
  48. package/dist/src/schema.d.ts +2704 -8293
  49. package/dist/src/schema.d.ts.map +1 -1
  50. package/dist/src/schema.js +26 -47
  51. package/dist/src/schema.js.map +1 -1
  52. package/dist/src/slice-builder.js +3 -3
  53. package/dist/src/slice-builder.js.map +1 -1
  54. package/dist/src/transformers/model-to-narrative/generators/flow.d.ts.map +1 -1
  55. package/dist/src/transformers/model-to-narrative/generators/flow.js +118 -74
  56. package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
  57. package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts +9 -1
  58. package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts.map +1 -1
  59. package/dist/src/transformers/model-to-narrative/generators/gwt.js +112 -112
  60. package/dist/src/transformers/model-to-narrative/generators/gwt.js.map +1 -1
  61. package/dist/src/transformers/model-to-narrative/generators/imports.d.ts +1 -1
  62. package/dist/src/transformers/model-to-narrative/generators/imports.d.ts.map +1 -1
  63. package/dist/src/transformers/model-to-narrative/generators/imports.js +13 -9
  64. package/dist/src/transformers/model-to-narrative/generators/imports.js.map +1 -1
  65. package/dist/src/transformers/narrative-to-model/index.d.ts.map +1 -1
  66. package/dist/src/transformers/narrative-to-model/index.js +50 -23
  67. package/dist/src/transformers/narrative-to-model/index.js.map +1 -1
  68. package/dist/src/transformers/narrative-to-model/type-inference.specs.js +100 -90
  69. package/dist/src/transformers/narrative-to-model/type-inference.specs.js.map +1 -1
  70. package/dist/tsconfig.tsbuildinfo +1 -1
  71. package/package.json +5 -5
  72. package/src/commands/export-schema-runner.ts +3 -1
  73. package/src/fluent-builder.ts +3 -3
  74. package/src/getNarratives.specs.ts +176 -184
  75. package/src/id/addAutoIds.specs.ts +55 -48
  76. package/src/id/addAutoIds.ts +28 -11
  77. package/src/id/generators.ts +1 -1
  78. package/src/id/hasAllIds.specs.ts +147 -245
  79. package/src/id/hasAllIds.ts +11 -4
  80. package/src/index.ts +11 -12
  81. package/src/loader/graph.ts +23 -6
  82. package/src/loader/ts-utils.ts +169 -26
  83. package/src/model-to-narrative.specs.ts +548 -466
  84. package/src/narrative-context.specs.ts +73 -116
  85. package/src/narrative-context.ts +127 -374
  86. package/src/narrative.ts +70 -120
  87. package/src/samples/questionnaires.narrative.ts +10 -10
  88. package/src/samples/test-with-ids.narrative.ts +23 -31
  89. package/src/schema.ts +33 -52
  90. package/src/slice-builder.ts +3 -3
  91. package/src/transformers/model-to-narrative/generators/flow.ts +191 -85
  92. package/src/transformers/model-to-narrative/generators/gwt.ts +195 -178
  93. package/src/transformers/model-to-narrative/generators/imports.ts +13 -9
  94. package/src/transformers/narrative-to-model/index.ts +87 -26
  95. package/src/transformers/narrative-to-model/type-inference.specs.ts +100 -90
@@ -64,32 +64,36 @@ describe('getNarratives', (_mode) => {
64
64
  expect(createItemSlice.client.specs[0].children).toHaveLength(1);
65
65
  }
66
66
  expect(createItemSlice.server.specs).toBeDefined();
67
- const spec = createItemSlice.server.specs;
68
- expect(spec.name).toBeDefined();
67
+ expect(Array.isArray(createItemSlice.server.specs)).toBe(true);
68
+ expect(createItemSlice.server.specs).toHaveLength(1);
69
+ const spec = createItemSlice.server.specs[0];
70
+ expect(spec.feature).toBeDefined();
69
71
  expect(spec.rules).toHaveLength(1);
70
72
  const rule = spec.rules[0];
71
- expect(rule.description).toBeDefined();
73
+ expect(rule.name).toBeDefined();
72
74
  expect(rule.examples).toHaveLength(1);
73
75
  const example = rule.examples[0];
74
- expect(typeof example.when === 'object' && !Array.isArray(example.when)).toBe(true);
75
- if (typeof example.when === 'object' && !Array.isArray(example.when)) {
76
- if ('commandRef' in example.when) {
77
- expect(example.when.commandRef).toBe('CreateItem');
78
- }
79
- expect(example.when.exampleData).toMatchObject({
76
+ expect(example.steps).toBeDefined();
77
+ expect(example.steps.length).toBeGreaterThanOrEqual(2);
78
+ const whenStep = example.steps.find((s) => s.keyword === 'When');
79
+ const thenStep = example.steps.find((s) => s.keyword === 'Then');
80
+ expect(whenStep).toBeDefined();
81
+ expect(thenStep).toBeDefined();
82
+ if (whenStep && 'text' in whenStep) {
83
+ expect(whenStep.text).toBe('CreateItem');
84
+ expect(whenStep.docString).toMatchObject({
80
85
  itemId: 'item_123',
81
86
  description: 'A new item',
82
87
  });
83
88
  }
84
- expect(example.then).toHaveLength(1);
85
- expect(example.then[0]).toMatchObject({
86
- eventRef: 'ItemCreated',
87
- exampleData: {
89
+ if (thenStep && 'text' in thenStep) {
90
+ expect(thenStep.text).toBe('ItemCreated');
91
+ expect(thenStep.docString).toMatchObject({
88
92
  id: 'item_123',
89
93
  description: 'A new item',
90
94
  addedAt: new Date('2024-01-15T10:00:00.000Z'),
91
- },
92
- });
95
+ });
96
+ }
93
97
  }
94
98
 
95
99
  const viewItemSlice = items.slices[1] as QuerySlice;
@@ -116,7 +120,8 @@ describe('getNarratives', (_mode) => {
116
120
  expect(data[0].origin).toMatchObject({ name: 'ItemsProjection', type: 'projection' });
117
121
 
118
122
  const specs = viewItemSlice?.server?.specs;
119
- if (specs == null || specs.name === '') throw new Error('No specs found in view items slice');
123
+ if (specs == null || specs.length === 0 || specs[0].feature === '')
124
+ throw new Error('No specs found in view items slice');
120
125
  expect(specs).toBeDefined();
121
126
  }
122
127
 
@@ -137,28 +142,32 @@ describe('getNarratives', (_mode) => {
137
142
  expect(submitOrderSlice.client.specs[0].children).toHaveLength(2);
138
143
  }
139
144
  expect(submitOrderSlice.server.specs).toBeDefined();
140
- const spec = submitOrderSlice.server.specs;
145
+ expect(Array.isArray(submitOrderSlice.server.specs)).toBe(true);
146
+ expect(submitOrderSlice.server.specs).toHaveLength(1);
147
+ const spec = submitOrderSlice.server.specs[0];
141
148
  expect(spec.rules).toHaveLength(1);
142
149
  const rule = spec.rules[0];
143
150
  expect(rule.examples).toHaveLength(1);
144
151
  const example = rule.examples[0];
145
- expect(typeof example.when === 'object' && !Array.isArray(example.when)).toBe(true);
146
- if (typeof example.when === 'object' && !Array.isArray(example.when)) {
147
- if ('commandRef' in example.when) {
148
- expect(example.when.commandRef).toBe('PlaceOrder');
149
- }
150
- expect(example.when.exampleData).toMatchObject({ productId: 'product_789', quantity: 3 });
152
+ expect(example.steps).toBeDefined();
153
+ expect(example.steps.length).toBeGreaterThanOrEqual(2);
154
+ const whenStep = example.steps.find((s) => s.keyword === 'When');
155
+ const thenStep = example.steps.find((s) => s.keyword === 'Then');
156
+ expect(whenStep).toBeDefined();
157
+ expect(thenStep).toBeDefined();
158
+ if (whenStep && 'text' in whenStep) {
159
+ expect(whenStep.text).toBe('PlaceOrder');
160
+ expect(whenStep.docString).toMatchObject({ productId: 'product_789', quantity: 3 });
151
161
  }
152
- expect(example.then).toHaveLength(1);
153
- expect(example.then[0]).toMatchObject({
154
- eventRef: 'OrderPlaced',
155
- exampleData: {
162
+ if (thenStep && 'text' in thenStep) {
163
+ expect(thenStep.text).toBe('OrderPlaced');
164
+ expect(thenStep.docString).toMatchObject({
156
165
  orderId: 'order_001',
157
166
  productId: 'product_789',
158
167
  quantity: 3,
159
168
  placedAt: new Date('2024-01-20T10:00:00.000Z'),
160
- },
161
- });
169
+ });
170
+ }
162
171
  }
163
172
  }
164
173
 
@@ -240,16 +249,15 @@ describe('getNarratives', (_mode) => {
240
249
  if (slice.type === 'react') {
241
250
  expect(slice.server).toBeDefined();
242
251
  expect(slice.server.specs).toBeDefined();
243
- expect(typeof slice.server.specs === 'object' && !Array.isArray(slice.server.specs)).toBe(true);
244
- const spec = slice.server.specs;
252
+ expect(Array.isArray(slice.server.specs)).toBe(true);
253
+ expect(slice.server.specs.length).toBeGreaterThanOrEqual(1);
254
+ const spec = slice.server.specs[0];
245
255
  expect(spec.rules).toBeDefined();
246
256
  expect(Array.isArray(spec.rules)).toBe(true);
247
257
  spec.rules.forEach((rule) => {
248
258
  rule.examples.forEach((example) => {
249
- expect(example.when).toBeDefined();
250
- expect(Array.isArray(example.when)).toBe(true);
251
- expect(example.then).toBeDefined();
252
- expect(Array.isArray(example.then)).toBe(true);
259
+ expect(example.steps).toBeDefined();
260
+ expect(Array.isArray(example.steps)).toBe(true);
253
261
  });
254
262
  });
255
263
  }
@@ -311,16 +319,14 @@ describe('getNarratives', (_mode) => {
311
319
  const commandSlice = testFlowWithIds.slices.find((s) => s.name === 'Create test item');
312
320
  if (commandSlice?.type !== 'command') return;
313
321
 
314
- expect(commandSlice.server.specs.rules).toHaveLength(2);
322
+ expect(commandSlice.server.specs[0].rules).toHaveLength(2);
315
323
 
316
- const rule1 = commandSlice.server.specs.rules.find(
317
- (r) => r.description === 'Valid test items should be created successfully',
324
+ const rule1 = commandSlice.server.specs[0].rules.find(
325
+ (r) => r.name === 'Valid test items should be created successfully',
318
326
  );
319
327
  expect(rule1?.id).toBe('RULE-001');
320
328
 
321
- const rule2 = commandSlice.server.specs.rules.find(
322
- (r) => r.description === 'Invalid test items should be rejected',
323
- );
329
+ const rule2 = commandSlice.server.specs[0].rules.find((r) => r.name === 'Invalid test items should be rejected');
324
330
  expect(rule2?.id).toBe('RULE-002');
325
331
  });
326
332
 
@@ -334,11 +340,9 @@ describe('getNarratives', (_mode) => {
334
340
  const querySlice = testFlowWithIds.slices.find((s) => s.name === 'Get test items');
335
341
  if (querySlice?.type !== 'query') return;
336
342
 
337
- expect(querySlice.server.specs.rules).toHaveLength(1);
343
+ expect(querySlice.server.specs[0].rules).toHaveLength(1);
338
344
 
339
- const rule3 = querySlice.server.specs.rules.find(
340
- (r) => r.description === 'Items should be retrievable after creation',
341
- );
345
+ const rule3 = querySlice.server.specs[0].rules.find((r) => r.name === 'Items should be retrievable after creation');
342
346
  expect(rule3?.id).toBe('RULE-003');
343
347
  });
344
348
 
@@ -352,11 +356,9 @@ describe('getNarratives', (_mode) => {
352
356
  const reactSlice = testFlowWithIds.slices.find((s) => s.name === 'React to test event');
353
357
  if (reactSlice?.type !== 'react') return;
354
358
 
355
- expect(reactSlice.server.specs.rules).toHaveLength(1);
359
+ expect(reactSlice.server.specs[0].rules).toHaveLength(1);
356
360
 
357
- const rule4 = reactSlice.server.specs.rules.find(
358
- (r) => r.description === 'System should react to test item creation',
359
- );
361
+ const rule4 = reactSlice.server.specs[0].rules.find((r) => r.name === 'System should react to test item creation');
360
362
  expect(rule4?.id).toBe('RULE-004');
361
363
  });
362
364
 
@@ -376,16 +378,10 @@ describe('getNarratives', (_mode) => {
376
378
  expect(submitSlice?.type).toBe('command');
377
379
 
378
380
  if (submitSlice?.type === 'command') {
379
- const example = submitSlice.server?.specs?.rules[0]?.examples[0];
380
- if (
381
- example !== null &&
382
- example !== undefined &&
383
- typeof example.when === 'object' &&
384
- example.when !== null &&
385
- !Array.isArray(example.when) &&
386
- 'commandRef' in example.when
387
- ) {
388
- expect(example.when.commandRef).toBe('SubmitQuestionnaire');
381
+ const example = submitSlice.server?.specs?.[0]?.rules[0]?.examples[0];
382
+ const whenStep = example?.steps?.find((s) => s.keyword === 'When');
383
+ if (whenStep && 'text' in whenStep) {
384
+ expect(whenStep.text).toBe('SubmitQuestionnaire');
389
385
  }
390
386
  }
391
387
  }
@@ -408,7 +404,7 @@ describe('getNarratives', (_mode) => {
408
404
  import { flow, experience, it, specs } from '@auto-engineer/narrative';
409
405
 
410
406
  flow('Test Experience Flow', () => {
411
- experience('Homepage', 'AUTO-H1a4Bn6Cy').client(() => {
407
+ experience('Homepage', 'H1a4Bn6Cy').client(() => {
412
408
  specs(() => {
413
409
  it('show a hero section with a welcome message');
414
410
  it('allow user to start the questionnaire');
@@ -494,8 +490,8 @@ flow('Browser Test Flow', () => {
494
490
  const flowContent = `
495
491
  import { flow, experience } from '@auto-engineer/narrative';
496
492
 
497
- flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
498
- experience('Homepage', 'AUTO-H1a4Bn6Cy').client(() => {});
493
+ flow('Questionnaires', 'Q9m2Kp4Lx', () => {
494
+ experience('Homepage', 'H1a4Bn6Cy').client(() => {});
499
495
  });
500
496
  `;
501
497
 
@@ -644,7 +640,7 @@ flow('questionnaires-test', () => {
644
640
 
645
641
  if (querySlice?.type !== 'query') return;
646
642
 
647
- const example = querySlice.server.specs.rules[0]?.examples[0];
643
+ const example = querySlice.server.specs[0].rules[0]?.examples[0];
648
644
  expect(example).toBeDefined();
649
645
 
650
646
  if (example !== null && example !== undefined) {
@@ -797,44 +793,42 @@ flow('Todo List', () => {
797
793
 
798
794
  if (summarySlice?.type !== 'query') return;
799
795
 
800
- const example = summarySlice.server.specs.rules[0]?.examples[0];
796
+ const example = summarySlice.server.specs[0].rules[0]?.examples[0];
801
797
  expect(example).toBeDefined();
802
- expect(example.given).toBeDefined();
803
- expect(Array.isArray(example.given)).toBe(true);
804
- expect(example.given).toHaveLength(5);
798
+ expect(example.steps).toBeDefined();
799
+ expect(Array.isArray(example.steps)).toBe(true);
805
800
 
806
- if (!example.given) {
807
- throw new Error('expected example.given to be defined');
808
- }
801
+ const givenAndSteps = example.steps.filter((s) => s.keyword === 'Given' || s.keyword === 'And');
802
+ expect(givenAndSteps).toHaveLength(5);
809
803
 
810
- validateGivenItemsHaveEventRef(example.given);
811
- validateTodoEventRefs(example.given);
804
+ validateGivenItemsHaveEventRef(givenAndSteps);
805
+ validateTodoEventRefs(givenAndSteps);
812
806
  validateTodoMessages(model);
813
807
  });
814
808
  });
815
809
 
816
- function validateGivenItemsHaveEventRef(given: unknown[]): void {
817
- for (let i = 0; i < given.length; i++) {
818
- const givenItem = given[i];
819
- if (typeof givenItem === 'object' && givenItem !== null) {
820
- expect('eventRef' in givenItem).toBe(true);
821
- expect('stateRef' in givenItem).toBe(false);
810
+ function validateGivenItemsHaveEventRef(givenSteps: unknown[]): void {
811
+ for (let i = 0; i < givenSteps.length; i++) {
812
+ const step = givenSteps[i] as { keyword?: string; text?: string };
813
+ if (typeof step === 'object' && step !== null) {
814
+ expect(step.keyword === 'Given' || step.keyword === 'And').toBe(true);
815
+ expect('text' in step).toBe(true);
822
816
  }
823
817
  }
824
818
  }
825
819
 
826
- function expectEventRef(item: unknown, expectedType: string): void {
827
- if (item !== null && item !== undefined && typeof item === 'object' && 'eventRef' in item) {
828
- expect(item.eventRef).toBe(expectedType);
820
+ function expectStepText(step: unknown, expectedType: string): void {
821
+ if (step !== null && step !== undefined && typeof step === 'object' && 'text' in step) {
822
+ expect((step as { text: string }).text).toBe(expectedType);
829
823
  }
830
824
  }
831
825
 
832
- function validateTodoEventRefs(given: unknown[]): void {
833
- expectEventRef(given[0], 'TodoAdded');
834
- expectEventRef(given[1], 'TodoAdded');
835
- expectEventRef(given[2], 'TodoAdded');
836
- expectEventRef(given[3], 'TodoMarkedInProgress');
837
- expectEventRef(given[4], 'TodoMarkedComplete');
826
+ function validateTodoEventRefs(givenSteps: unknown[]): void {
827
+ expectStepText(givenSteps[0], 'TodoAdded');
828
+ expectStepText(givenSteps[1], 'TodoAdded');
829
+ expectStepText(givenSteps[2], 'TodoAdded');
830
+ expectStepText(givenSteps[3], 'TodoMarkedInProgress');
831
+ expectStepText(givenSteps[4], 'TodoMarkedComplete');
838
832
  }
839
833
 
840
834
  function validateTodoMessages(model: Model): void {
@@ -859,16 +853,10 @@ function validateSubmitQuestionnaireCommand(questionnaireFlow: Narrative): void
859
853
  const submitSlice = questionnaireFlow.slices.find((s) => s.name === 'submits questionnaire');
860
854
  expect(submitSlice?.type).toBe('command');
861
855
  if (submitSlice?.type === 'command') {
862
- const example = submitSlice.server?.specs?.rules[0]?.examples[0];
863
- if (
864
- example !== null &&
865
- example !== undefined &&
866
- typeof example.when === 'object' &&
867
- example.when !== null &&
868
- !Array.isArray(example.when) &&
869
- 'commandRef' in example.when
870
- ) {
871
- expect(example.when.commandRef).toBe('SubmitQuestionnaire');
856
+ const example = submitSlice.server?.specs?.[0]?.rules[0]?.examples[0];
857
+ const whenStep = example?.steps?.find((s) => s.keyword === 'When');
858
+ if (whenStep && 'text' in whenStep) {
859
+ expect(whenStep.text).toBe('SubmitQuestionnaire');
872
860
  }
873
861
  }
874
862
  }
@@ -881,15 +869,11 @@ function validateQuestionAnsweredEvent(model: Model): void {
881
869
  function validateGivenSectionEventRefs(questionnaireFlow: Narrative): void {
882
870
  const viewsSlice = questionnaireFlow.slices.find((s) => s.name === 'views progress');
883
871
  if (viewsSlice?.type === 'query') {
884
- const example = viewsSlice.server?.specs?.rules[0]?.examples[0];
885
- if (example?.given && Array.isArray(example.given) && example.given.length > 0) {
886
- const givenItem = example.given[0];
887
- if (typeof givenItem === 'object' && givenItem !== null) {
888
- expect('eventRef' in givenItem).toBe(true);
889
- expect('stateRef' in givenItem).toBe(false);
890
- if ('eventRef' in givenItem) {
891
- expect(givenItem.eventRef).toBe('QuestionAnswered');
892
- }
872
+ const example = viewsSlice.server?.specs?.[0]?.rules[0]?.examples[0];
873
+ if (example?.steps !== undefined && Array.isArray(example.steps)) {
874
+ const givenStep = example.steps.find((s) => s.keyword === 'Given');
875
+ if (givenStep && 'text' in givenStep) {
876
+ expect(givenStep.text).toBe('QuestionAnswered');
893
877
  }
894
878
  }
895
879
  }
@@ -903,53 +887,51 @@ function validateCurrentQuestionIdType(model: Model): void {
903
887
  }
904
888
 
905
889
  function validateMixedGivenTypes(example: Example): void {
906
- expect(example.description).toBe('system with 2 items reaches max of 2');
907
- expect(example.given).toBeDefined();
908
- expect(Array.isArray(example.given)).toBe(true);
909
-
910
- if (!example.given) return;
890
+ expect(example.name).toBe('system with 2 items reaches max of 2');
891
+ expect(example.steps).toBeDefined();
892
+ expect(Array.isArray(example.steps)).toBe(true);
911
893
 
912
- expect(example.given).toHaveLength(4);
894
+ const givenSteps = example.steps.filter((s) => s.keyword === 'Given' || s.keyword === 'And');
895
+ expect(givenSteps).toHaveLength(4);
913
896
 
914
- const firstGiven = example.given[0];
915
- expect('stateRef' in firstGiven).toBe(true);
916
- expect('eventRef' in firstGiven).toBe(false);
917
- if ('stateRef' in firstGiven) {
918
- expect(firstGiven.stateRef).toBe('ConfigState');
897
+ const firstGiven = givenSteps[0];
898
+ expect('text' in firstGiven).toBe(true);
899
+ if ('text' in firstGiven) {
900
+ expect(firstGiven.text).toBe('ConfigState');
919
901
  }
920
902
 
921
- const secondGiven = example.given[1];
922
- expect('eventRef' in secondGiven).toBe(true);
923
- if ('eventRef' in secondGiven) {
924
- expect(secondGiven.eventRef).toBe('SystemInitialized');
903
+ const secondGiven = givenSteps[1];
904
+ expect('text' in secondGiven).toBe(true);
905
+ if ('text' in secondGiven) {
906
+ expect(secondGiven.text).toBe('SystemInitialized');
925
907
  }
926
908
 
927
- const thirdGiven = example.given[2];
928
- expect('eventRef' in thirdGiven).toBe(true);
929
- if ('eventRef' in thirdGiven) {
930
- expect(thirdGiven.eventRef).toBe('ItemAdded');
909
+ const thirdGiven = givenSteps[2];
910
+ expect('text' in thirdGiven).toBe(true);
911
+ if ('text' in thirdGiven) {
912
+ expect(thirdGiven.text).toBe('ItemAdded');
931
913
  }
932
914
 
933
- const fourthGiven = example.given[3];
934
- expect('eventRef' in fourthGiven).toBe(true);
935
- if ('eventRef' in fourthGiven) {
936
- expect(fourthGiven.eventRef).toBe('ItemAdded');
915
+ const fourthGiven = givenSteps[3];
916
+ expect('text' in fourthGiven).toBe(true);
917
+ if ('text' in fourthGiven) {
918
+ expect(fourthGiven.text).toBe('ItemAdded');
937
919
  }
938
920
  }
939
921
 
940
922
  function validateEmptyWhenClause(example: Example): void {
941
- expect(example.when).toBeUndefined();
923
+ const whenStep = example.steps.find((s) => s.keyword === 'When');
924
+ expect(whenStep).toBeUndefined();
942
925
  }
943
926
 
944
927
  function validateThenClause(example: Example): void {
945
- expect(example.then).toBeDefined();
946
- expect(Array.isArray(example.then)).toBe(true);
947
- expect(example.then).toHaveLength(1);
948
-
949
- const thenOutcome = example.then[0];
950
- expect('stateRef' in thenOutcome).toBe(true);
951
- if ('stateRef' in thenOutcome) {
952
- expect(thenOutcome.stateRef).toBe('SystemStatus');
928
+ const thenSteps = example.steps.filter((s) => s.keyword === 'Then');
929
+ expect(thenSteps).toHaveLength(1);
930
+
931
+ const thenOutcome = thenSteps[0];
932
+ expect('text' in thenOutcome).toBe(true);
933
+ if ('text' in thenOutcome) {
934
+ expect(thenOutcome.text).toBe('SystemStatus');
953
935
  }
954
936
  }
955
937
 
@@ -1035,11 +1017,11 @@ type SubmitQuestionnaire = Command<
1035
1017
  }
1036
1018
  >;
1037
1019
 
1038
- flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
1039
- command('sends the questionnaire link', 'AUTO-S2b5Cp7Dz')
1020
+ flow('Questionnaires', 'Q9m2Kp4Lx', () => {
1021
+ command('sends the questionnaire link', 'S2b5Cp7Dz')
1040
1022
  .server(() => {
1041
1023
  specs(() => {
1042
- rule('questionnaire link is sent to participant', 'AUTO-r0A1Bo8X', () => {
1024
+ rule('questionnaire link is sent to participant', 'r0A1Bo8X', () => {
1043
1025
  example('sends the questionnaire link successfully')
1044
1026
  .when<SendQuestionnaireLink>({
1045
1027
  questionnaireId: 'q-001',
@@ -1069,10 +1051,10 @@ flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
1069
1051
  });
1070
1052
  });
1071
1053
 
1072
- command('submits the questionnaire', 'AUTO-T5k9Jw3V')
1054
+ command('submits the questionnaire', 'T5k9Jw3V')
1073
1055
  .server(() => {
1074
1056
  specs(() => {
1075
- rule('questionnaire allowed to be submitted when all questions are answered', 'AUTO-r4H0Lx4U', () => {
1057
+ rule('questionnaire allowed to be submitted when all questions are answered', 'r4H0Lx4U', () => {
1076
1058
  example('submits the questionnaire successfully')
1077
1059
  .when<SubmitQuestionnaire>({
1078
1060
  questionnaireId: 'q-001',
@@ -1122,7 +1104,7 @@ function getQuestionnaireFlowFromModel(model: Model): Narrative {
1122
1104
 
1123
1105
  function getSubmitSlice(questionnaireFlow: Narrative): {
1124
1106
  type: 'command';
1125
- server: { specs: { rules: { examples: unknown[] }[] } };
1107
+ server: { specs: { type: 'gherkin'; feature: string; rules: { examples: unknown[] }[] }[] };
1126
1108
  } {
1127
1109
  const submitSlice = questionnaireFlow.slices.find((s) => s.name === 'submits the questionnaire');
1128
1110
  expect(submitSlice).toBeDefined();
@@ -1130,26 +1112,32 @@ function getSubmitSlice(questionnaireFlow: Narrative): {
1130
1112
  if (submitSlice?.type !== 'command') {
1131
1113
  throw new Error('Submit slice is not a command');
1132
1114
  }
1133
- return submitSlice as { type: 'command'; server: { specs: { rules: { examples: unknown[] }[] } } };
1115
+ return submitSlice as {
1116
+ type: 'command';
1117
+ server: { specs: { type: 'gherkin'; feature: string; rules: { examples: unknown[] }[] }[] };
1118
+ };
1134
1119
  }
1135
1120
 
1136
- function getSubmitExample(submitSlice: { server: { specs: { rules: { examples: unknown[] }[] } } }): unknown {
1137
- const rule = submitSlice.server?.specs?.rules[0];
1121
+ function getSubmitExample(submitSlice: {
1122
+ server: { specs: { type: 'gherkin'; feature: string; rules: { examples: unknown[] }[] }[] };
1123
+ }): unknown {
1124
+ const rule = submitSlice.server?.specs?.[0]?.rules[0];
1138
1125
  expect(rule).toBeDefined();
1139
1126
  expect(rule?.examples).toHaveLength(1);
1140
1127
  const example = rule?.examples[0];
1141
- expect((example as { description?: string })?.description).toBe('submits the questionnaire successfully');
1128
+ expect((example as { name?: string })?.name).toBe('submits the questionnaire successfully');
1142
1129
  return example;
1143
1130
  }
1144
1131
 
1145
1132
  function validateSubmitCommandRef(example: unknown): void {
1146
- const ex = example as { when?: { commandRef?: string } };
1147
- expect(ex?.when).toBeDefined();
1148
- if (typeof ex?.when === 'object' && ex.when !== null && !Array.isArray(ex.when) && 'commandRef' in ex.when) {
1149
- expect(ex.when.commandRef).toBe('SubmitQuestionnaire');
1150
- expect(ex.when.commandRef).not.toBe('SendQuestionnaireLink');
1133
+ const ex = example as { steps?: { keyword: string; text?: string }[] };
1134
+ expect(ex?.steps).toBeDefined();
1135
+ const whenStep = ex?.steps?.find((s) => s.keyword === 'When');
1136
+ if (whenStep && 'text' in whenStep) {
1137
+ expect(whenStep.text).toBe('SubmitQuestionnaire');
1138
+ expect(whenStep.text).not.toBe('SendQuestionnaireLink');
1151
1139
  } else {
1152
- throw new Error('Expected when to have commandRef property');
1140
+ throw new Error('Expected steps to have a When step with text property');
1153
1141
  }
1154
1142
  }
1155
1143
 
@@ -1157,10 +1145,11 @@ function validateLinkSliceCommandRef(questionnaireFlow: Narrative): void {
1157
1145
  const linkSlice = questionnaireFlow.slices.find((s) => s.name === 'sends the questionnaire link');
1158
1146
  expect(linkSlice?.type).toBe('command');
1159
1147
  if (linkSlice?.type === 'command') {
1160
- const linkExample = linkSlice.server?.specs?.rules[0]?.examples[0];
1161
- const ex = linkExample as { when?: { commandRef?: string } };
1162
- if (typeof ex?.when === 'object' && ex.when !== null && !Array.isArray(ex.when) && 'commandRef' in ex.when) {
1163
- expect(ex.when.commandRef).toBe('SendQuestionnaireLink');
1148
+ const linkExample = linkSlice.server?.specs?.[0]?.rules[0]?.examples[0];
1149
+ const ex = linkExample as { steps?: { keyword: string; text?: string }[] };
1150
+ const whenStep = ex?.steps?.find((s) => s.keyword === 'When');
1151
+ if (whenStep && 'text' in whenStep) {
1152
+ expect(whenStep.text).toBe('SendQuestionnaireLink');
1164
1153
  }
1165
1154
  }
1166
1155
  }
@@ -1187,21 +1176,23 @@ function getSubmitSliceFromFlow(questionnaireFlow: Narrative): unknown {
1187
1176
  }
1188
1177
 
1189
1178
  function getServerSpecsFromSlice(submitSlice: unknown): unknown {
1190
- const slice = submitSlice as { server?: { specs?: unknown } };
1179
+ const slice = submitSlice as { server?: { specs?: unknown[] } };
1191
1180
  const serverSpecs = slice.server?.specs;
1192
1181
  expect(serverSpecs).toBeDefined();
1193
- const specs = serverSpecs as { rules?: unknown[] };
1194
- expect(specs?.rules).toBeDefined();
1195
- expect(specs?.rules).toHaveLength(1);
1196
- return serverSpecs;
1182
+ expect(Array.isArray(serverSpecs)).toBe(true);
1183
+ expect(serverSpecs).toHaveLength(1);
1184
+ const firstSpec = (serverSpecs as unknown[])[0] as { rules?: unknown[] };
1185
+ expect(firstSpec?.rules).toBeDefined();
1186
+ expect(firstSpec?.rules).toHaveLength(1);
1187
+ return firstSpec;
1197
1188
  }
1198
1189
 
1199
1190
  function getFirstRuleFromSpecs(serverSpecs: unknown): unknown {
1200
1191
  const specs = serverSpecs as { rules?: unknown[] };
1201
1192
  const rule = specs?.rules?.[0];
1202
1193
  expect(rule).toBeDefined();
1203
- const r = rule as { description?: string; examples?: unknown[] };
1204
- expect(r?.description).toBe('questionnaire allowed to be submitted when all questions are answered');
1194
+ const r = rule as { name?: string; examples?: unknown[] };
1195
+ expect(r?.name).toBe('questionnaire allowed to be submitted when all questions are answered');
1205
1196
  expect(r?.examples).toBeDefined();
1206
1197
  expect(r?.examples).toHaveLength(1);
1207
1198
  return rule;
@@ -1211,37 +1202,38 @@ function getFirstExampleFromRule(rule: unknown): unknown {
1211
1202
  const r = rule as { examples?: unknown[] };
1212
1203
  const example = r?.examples?.[0];
1213
1204
  expect(example).toBeDefined();
1214
- const ex = example as { description?: string };
1215
- expect(ex?.description).toBe('submits the questionnaire successfully');
1205
+ const ex = example as { name?: string };
1206
+ expect(ex?.name).toBe('submits the questionnaire successfully');
1216
1207
  return example;
1217
1208
  }
1218
1209
 
1219
1210
  function validateExampleCommandRef(example: unknown): void {
1220
- const ex = example as { when?: { commandRef?: string; exampleData?: unknown } };
1221
- expect(ex?.when).toBeDefined();
1222
- if (typeof ex?.when === 'object' && ex.when !== null && !Array.isArray(ex.when) && 'commandRef' in ex.when) {
1223
- expect(ex.when.commandRef).toBe('SubmitQuestionnaire');
1224
- expect(ex.when.commandRef).not.toBe('SendQuestionnaireLink');
1225
- expect(ex.when.exampleData).toEqual({
1211
+ const ex = example as { steps?: Array<{ keyword: string; text?: string; docString?: unknown }> };
1212
+ expect(ex?.steps).toBeDefined();
1213
+ const whenStep = ex?.steps?.find((s) => s.keyword === 'When');
1214
+ expect(whenStep).toBeDefined();
1215
+ if (whenStep && 'text' in whenStep) {
1216
+ expect(whenStep.text).toBe('SubmitQuestionnaire');
1217
+ expect(whenStep.text).not.toBe('SendQuestionnaireLink');
1218
+ expect(whenStep.docString).toEqual({
1226
1219
  questionnaireId: 'q-001',
1227
1220
  participantId: 'participant-abc',
1228
1221
  });
1229
1222
  } else {
1230
- throw new Error('Expected when to have commandRef property');
1223
+ throw new Error('Expected when step to have text property');
1231
1224
  }
1232
1225
  }
1233
1226
 
1234
1227
  function validateThenEvents(example: unknown): void {
1235
- const ex = example as { then?: unknown[] };
1236
- expect(ex?.then).toBeDefined();
1237
- expect(Array.isArray(ex?.then)).toBe(true);
1238
- expect(ex?.then).toHaveLength(1);
1239
-
1240
- const thenEvent = ex?.then?.[0];
1241
- if (thenEvent !== null && thenEvent !== undefined && 'eventRef' in (thenEvent as object)) {
1242
- const event = thenEvent as { eventRef?: string; exampleData?: unknown };
1243
- expect(event.eventRef).toBe('QuestionnaireSubmitted');
1244
- expect(event.exampleData).toEqual({
1228
+ const ex = example as { steps?: Array<{ keyword: string; text?: string; docString?: unknown }> };
1229
+ expect(ex?.steps).toBeDefined();
1230
+ const thenSteps = ex?.steps?.filter((s) => s.keyword === 'Then');
1231
+ expect(thenSteps).toHaveLength(1);
1232
+
1233
+ const thenStep = thenSteps?.[0];
1234
+ if (thenStep && 'text' in thenStep) {
1235
+ expect(thenStep.text).toBe('QuestionnaireSubmitted');
1236
+ expect(thenStep.docString).toEqual({
1245
1237
  questionnaireId: 'q-001',
1246
1238
  participantId: 'participant-abc',
1247
1239
  submittedAt: new Date('2030-01-01T09:00:00.000Z'),