@auto-engineer/narrative 0.20.0 → 0.21.0
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-format.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +6 -6
- package/.turbo/turbo-type-check.log +5 -4
- package/CHANGELOG.md +13 -0
- package/dist/src/commands/export-schema-runner.js +6 -1
- package/dist/src/commands/export-schema-runner.js.map +1 -1
- package/dist/src/fluent-builder.specs.js +2 -2
- package/dist/src/fluent-builder.specs.js.map +1 -1
- package/dist/src/getNarratives.cache.specs.js +5 -5
- package/dist/src/getNarratives.cache.specs.js.map +1 -1
- package/dist/src/getNarratives.specs.js +194 -207
- package/dist/src/getNarratives.specs.js.map +1 -1
- package/dist/src/id/addAutoIds.specs.js +72 -409
- package/dist/src/id/addAutoIds.specs.js.map +1 -1
- package/dist/src/id/hasAllIds.specs.js +215 -408
- package/dist/src/id/hasAllIds.specs.js.map +1 -1
- package/dist/src/model-to-narrative.specs.js +505 -564
- package/dist/src/model-to-narrative.specs.js.map +1 -1
- package/dist/src/narrative-context.specs.js +58 -133
- package/dist/src/narrative-context.specs.js.map +1 -1
- package/dist/src/schema.d.ts +302 -302
- package/dist/src/transformers/narrative-to-model/type-inference.specs.js +94 -104
- package/dist/src/transformers/narrative-to-model/type-inference.specs.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/dist/src/transformers/model-to-narrative/generators/gwt.specs.d.ts +0 -2
- package/dist/src/transformers/model-to-narrative/generators/gwt.specs.d.ts.map +0 -1
- package/dist/src/transformers/model-to-narrative/generators/gwt.specs.js +0 -142
- package/dist/src/transformers/model-to-narrative/generators/gwt.specs.js.map +0 -1
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { InMemoryFileStore } from '@auto-engineer/file-store';
|
|
4
|
-
import { NodeFileStore } from '@auto-engineer/file-store/node';
|
|
5
1
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
6
|
-
import { getNarratives } from './getNarratives.js';
|
|
7
|
-
import { modelToNarrative } from './index.js';
|
|
8
2
|
import { modelSchema } from './schema.js';
|
|
3
|
+
import { modelToNarrative } from './index.js';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { InMemoryFileStore, NodeFileStore } from '@auto-engineer/file-store';
|
|
7
|
+
import { getNarratives } from './getNarratives.js';
|
|
9
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
9
|
const __dirname = path.dirname(__filename);
|
|
11
10
|
const pattern = /\.(narrative)\.(ts)$/;
|
|
@@ -47,56 +46,42 @@ describe('getNarratives', (_mode) => {
|
|
|
47
46
|
expect(createItemSlice.stream).toBe('item-${id}');
|
|
48
47
|
if (createItemSlice.type === 'command') {
|
|
49
48
|
expect(createItemSlice.client.specs).toBeDefined();
|
|
50
|
-
expect(
|
|
51
|
-
expect(createItemSlice.client.specs).toHaveLength(1);
|
|
52
|
-
expect(createItemSlice.client.specs[0].type).toBe('describe');
|
|
53
|
-
expect(createItemSlice.client.specs[0].title).toBe('A form that allows users to add items');
|
|
54
|
-
if (createItemSlice.client.specs[0].type === 'describe') {
|
|
55
|
-
expect(createItemSlice.client.specs[0].children).toHaveLength(1);
|
|
56
|
-
}
|
|
49
|
+
expect(createItemSlice.client.specs?.name).toBe('A form that allows users to add items');
|
|
50
|
+
expect(createItemSlice.client.specs?.rules).toHaveLength(1);
|
|
57
51
|
expect(createItemSlice.server.specs).toBeDefined();
|
|
58
|
-
|
|
59
|
-
expect(
|
|
60
|
-
const spec = createItemSlice.server.specs[0];
|
|
61
|
-
expect(spec.feature).toBeDefined();
|
|
52
|
+
const spec = createItemSlice.server.specs;
|
|
53
|
+
expect(spec.name).toBeDefined();
|
|
62
54
|
expect(spec.rules).toHaveLength(1);
|
|
63
55
|
const rule = spec.rules[0];
|
|
64
|
-
expect(rule.
|
|
56
|
+
expect(rule.description).toBeDefined();
|
|
65
57
|
expect(rule.examples).toHaveLength(1);
|
|
66
58
|
const example = rule.examples[0];
|
|
67
|
-
expect(example.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (whenStep && 'text' in whenStep) {
|
|
74
|
-
expect(whenStep.text).toBe('CreateItem');
|
|
75
|
-
expect(whenStep.docString).toMatchObject({
|
|
59
|
+
expect(typeof example.when === 'object' && !Array.isArray(example.when)).toBe(true);
|
|
60
|
+
if (typeof example.when === 'object' && !Array.isArray(example.when)) {
|
|
61
|
+
if ('commandRef' in example.when) {
|
|
62
|
+
expect(example.when.commandRef).toBe('CreateItem');
|
|
63
|
+
}
|
|
64
|
+
expect(example.when.exampleData).toMatchObject({
|
|
76
65
|
itemId: 'item_123',
|
|
77
66
|
description: 'A new item',
|
|
78
67
|
});
|
|
79
68
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
69
|
+
expect(example.then).toHaveLength(1);
|
|
70
|
+
expect(example.then[0]).toMatchObject({
|
|
71
|
+
eventRef: 'ItemCreated',
|
|
72
|
+
exampleData: {
|
|
83
73
|
id: 'item_123',
|
|
84
74
|
description: 'A new item',
|
|
85
75
|
addedAt: new Date('2024-01-15T10:00:00.000Z'),
|
|
86
|
-
}
|
|
87
|
-
}
|
|
76
|
+
},
|
|
77
|
+
});
|
|
88
78
|
}
|
|
89
79
|
const viewItemSlice = items.slices[1];
|
|
90
80
|
expect(viewItemSlice.type).toBe('query');
|
|
91
81
|
expect(viewItemSlice.name).toBe('view items');
|
|
92
82
|
expect(viewItemSlice.client.specs).toBeDefined();
|
|
93
|
-
expect(
|
|
94
|
-
expect(viewItemSlice.client.specs).toHaveLength(
|
|
95
|
-
expect(viewItemSlice.client.specs[0].type).toBe('describe');
|
|
96
|
-
expect(viewItemSlice.client.specs[0].title).toBe('view Items Screen');
|
|
97
|
-
if (viewItemSlice.client.specs[0].type === 'describe') {
|
|
98
|
-
expect(viewItemSlice.client.specs[0].children).toHaveLength(3);
|
|
99
|
-
}
|
|
83
|
+
expect(viewItemSlice.client.specs?.name).toBe('view Items Screen');
|
|
84
|
+
expect(viewItemSlice.client.specs?.rules).toHaveLength(3);
|
|
100
85
|
expect(viewItemSlice.request).toBeDefined();
|
|
101
86
|
expect(viewItemSlice.request).toMatch(/query items\(\$itemId: String!\) {\s+items\(itemId: \$itemId\) {\s+id\s+description\s+}/);
|
|
102
87
|
const data = viewItemSlice?.server?.data;
|
|
@@ -106,7 +91,7 @@ describe('getNarratives', (_mode) => {
|
|
|
106
91
|
expect(data[0].target).toMatchObject({ type: 'State', name: 'items' });
|
|
107
92
|
expect(data[0].origin).toMatchObject({ name: 'ItemsProjection', type: 'projection' });
|
|
108
93
|
const specs = viewItemSlice?.server?.specs;
|
|
109
|
-
if (specs == null || specs.
|
|
94
|
+
if (specs == null || specs.name === '')
|
|
110
95
|
throw new Error('No specs found in view items slice');
|
|
111
96
|
expect(specs).toBeDefined();
|
|
112
97
|
}
|
|
@@ -118,40 +103,31 @@ describe('getNarratives', (_mode) => {
|
|
|
118
103
|
expect(submitOrderSlice.stream).toBe('order-${orderId}');
|
|
119
104
|
if (submitOrderSlice.type === 'command') {
|
|
120
105
|
expect(submitOrderSlice.client.specs).toBeDefined();
|
|
121
|
-
expect(
|
|
122
|
-
expect(submitOrderSlice.client.specs).toHaveLength(
|
|
123
|
-
expect(submitOrderSlice.client.specs[0].type).toBe('describe');
|
|
124
|
-
expect(submitOrderSlice.client.specs[0].title).toBe('Order submission form');
|
|
125
|
-
if (submitOrderSlice.client.specs[0].type === 'describe') {
|
|
126
|
-
expect(submitOrderSlice.client.specs[0].children).toHaveLength(2);
|
|
127
|
-
}
|
|
106
|
+
expect(submitOrderSlice.client.specs?.name).toBe('Order submission form');
|
|
107
|
+
expect(submitOrderSlice.client.specs?.rules).toHaveLength(2);
|
|
128
108
|
expect(submitOrderSlice.server.specs).toBeDefined();
|
|
129
|
-
|
|
130
|
-
expect(submitOrderSlice.server.specs).toHaveLength(1);
|
|
131
|
-
const spec = submitOrderSlice.server.specs[0];
|
|
109
|
+
const spec = submitOrderSlice.server.specs;
|
|
132
110
|
expect(spec.rules).toHaveLength(1);
|
|
133
111
|
const rule = spec.rules[0];
|
|
134
112
|
expect(rule.examples).toHaveLength(1);
|
|
135
113
|
const example = rule.examples[0];
|
|
136
|
-
expect(example.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (whenStep && 'text' in whenStep) {
|
|
143
|
-
expect(whenStep.text).toBe('PlaceOrder');
|
|
144
|
-
expect(whenStep.docString).toMatchObject({ productId: 'product_789', quantity: 3 });
|
|
114
|
+
expect(typeof example.when === 'object' && !Array.isArray(example.when)).toBe(true);
|
|
115
|
+
if (typeof example.when === 'object' && !Array.isArray(example.when)) {
|
|
116
|
+
if ('commandRef' in example.when) {
|
|
117
|
+
expect(example.when.commandRef).toBe('PlaceOrder');
|
|
118
|
+
}
|
|
119
|
+
expect(example.when.exampleData).toMatchObject({ productId: 'product_789', quantity: 3 });
|
|
145
120
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
121
|
+
expect(example.then).toHaveLength(1);
|
|
122
|
+
expect(example.then[0]).toMatchObject({
|
|
123
|
+
eventRef: 'OrderPlaced',
|
|
124
|
+
exampleData: {
|
|
149
125
|
orderId: 'order_001',
|
|
150
126
|
productId: 'product_789',
|
|
151
127
|
quantity: 3,
|
|
152
128
|
placedAt: new Date('2024-01-20T10:00:00.000Z'),
|
|
153
|
-
}
|
|
154
|
-
}
|
|
129
|
+
},
|
|
130
|
+
});
|
|
155
131
|
}
|
|
156
132
|
}
|
|
157
133
|
const messages = schemas.messages;
|
|
@@ -205,15 +181,16 @@ describe('getNarratives', (_mode) => {
|
|
|
205
181
|
if (slice.type === 'react') {
|
|
206
182
|
expect(slice.server).toBeDefined();
|
|
207
183
|
expect(slice.server.specs).toBeDefined();
|
|
208
|
-
expect(Array.isArray(slice.server.specs)).toBe(true);
|
|
209
|
-
|
|
210
|
-
const spec = slice.server.specs[0];
|
|
184
|
+
expect(typeof slice.server.specs === 'object' && !Array.isArray(slice.server.specs)).toBe(true);
|
|
185
|
+
const spec = slice.server.specs;
|
|
211
186
|
expect(spec.rules).toBeDefined();
|
|
212
187
|
expect(Array.isArray(spec.rules)).toBe(true);
|
|
213
188
|
spec.rules.forEach((rule) => {
|
|
214
189
|
rule.examples.forEach((example) => {
|
|
215
|
-
expect(example.
|
|
216
|
-
expect(Array.isArray(example.
|
|
190
|
+
expect(example.when).toBeDefined();
|
|
191
|
+
expect(Array.isArray(example.when)).toBe(true);
|
|
192
|
+
expect(example.then).toBeDefined();
|
|
193
|
+
expect(Array.isArray(example.then)).toBe(true);
|
|
217
194
|
});
|
|
218
195
|
});
|
|
219
196
|
}
|
|
@@ -266,10 +243,10 @@ describe('getNarratives', (_mode) => {
|
|
|
266
243
|
const commandSlice = testFlowWithIds.slices.find((s) => s.name === 'Create test item');
|
|
267
244
|
if (commandSlice?.type !== 'command')
|
|
268
245
|
return;
|
|
269
|
-
expect(commandSlice.server.specs
|
|
270
|
-
const rule1 = commandSlice.server.specs
|
|
246
|
+
expect(commandSlice.server.specs.rules).toHaveLength(2);
|
|
247
|
+
const rule1 = commandSlice.server.specs.rules.find((r) => r.description === 'Valid test items should be created successfully');
|
|
271
248
|
expect(rule1?.id).toBe('RULE-001');
|
|
272
|
-
const rule2 = commandSlice.server.specs
|
|
249
|
+
const rule2 = commandSlice.server.specs.rules.find((r) => r.description === 'Invalid test items should be rejected');
|
|
273
250
|
expect(rule2?.id).toBe('RULE-002');
|
|
274
251
|
});
|
|
275
252
|
it('should have ids for query slice rules', async () => {
|
|
@@ -281,8 +258,8 @@ describe('getNarratives', (_mode) => {
|
|
|
281
258
|
const querySlice = testFlowWithIds.slices.find((s) => s.name === 'Get test items');
|
|
282
259
|
if (querySlice?.type !== 'query')
|
|
283
260
|
return;
|
|
284
|
-
expect(querySlice.server.specs
|
|
285
|
-
const rule3 = querySlice.server.specs
|
|
261
|
+
expect(querySlice.server.specs.rules).toHaveLength(1);
|
|
262
|
+
const rule3 = querySlice.server.specs.rules.find((r) => r.description === 'Items should be retrievable after creation');
|
|
286
263
|
expect(rule3?.id).toBe('RULE-003');
|
|
287
264
|
});
|
|
288
265
|
it('should have ids for react slice rules', async () => {
|
|
@@ -294,8 +271,8 @@ describe('getNarratives', (_mode) => {
|
|
|
294
271
|
const reactSlice = testFlowWithIds.slices.find((s) => s.name === 'React to test event');
|
|
295
272
|
if (reactSlice?.type !== 'react')
|
|
296
273
|
return;
|
|
297
|
-
expect(reactSlice.server.specs
|
|
298
|
-
const rule4 = reactSlice.server.specs
|
|
274
|
+
expect(reactSlice.server.specs.rules).toHaveLength(1);
|
|
275
|
+
const rule4 = reactSlice.server.specs.rules.find((r) => r.description === 'System should react to test item creation');
|
|
299
276
|
expect(rule4?.id).toBe('RULE-004');
|
|
300
277
|
});
|
|
301
278
|
it('should handle when examples correctly', async () => {
|
|
@@ -311,10 +288,14 @@ describe('getNarratives', (_mode) => {
|
|
|
311
288
|
const submitSlice = questionnaireFlow.slices.find((s) => s.name === 'submits the questionnaire');
|
|
312
289
|
expect(submitSlice?.type).toBe('command');
|
|
313
290
|
if (submitSlice?.type === 'command') {
|
|
314
|
-
const example = submitSlice.server?.specs?.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
291
|
+
const example = submitSlice.server?.specs?.rules[0]?.examples[0];
|
|
292
|
+
if (example !== null &&
|
|
293
|
+
example !== undefined &&
|
|
294
|
+
typeof example.when === 'object' &&
|
|
295
|
+
example.when !== null &&
|
|
296
|
+
!Array.isArray(example.when) &&
|
|
297
|
+
'commandRef' in example.when) {
|
|
298
|
+
expect(example.when.commandRef).toBe('SubmitQuestionnaire');
|
|
318
299
|
}
|
|
319
300
|
}
|
|
320
301
|
}
|
|
@@ -332,13 +313,13 @@ describe('getNarratives', (_mode) => {
|
|
|
332
313
|
it('should handle experience slice with client specs', async () => {
|
|
333
314
|
const memoryVfs = new InMemoryFileStore();
|
|
334
315
|
const flowWithExperienceContent = `
|
|
335
|
-
import { flow, experience,
|
|
316
|
+
import { flow, experience, should, specs } from '@auto-engineer/narrative';
|
|
336
317
|
|
|
337
318
|
flow('Test Experience Flow', () => {
|
|
338
|
-
experience('Homepage', 'H1a4Bn6Cy').client(() => {
|
|
319
|
+
experience('Homepage', 'AUTO-H1a4Bn6Cy').client(() => {
|
|
339
320
|
specs(() => {
|
|
340
|
-
|
|
341
|
-
|
|
321
|
+
should('show a hero section with a welcome message');
|
|
322
|
+
should('allow user to start the questionnaire');
|
|
342
323
|
});
|
|
343
324
|
});
|
|
344
325
|
});
|
|
@@ -355,24 +336,26 @@ flow('Test Experience Flow', () => {
|
|
|
355
336
|
if (homepageSlice?.type === 'experience') {
|
|
356
337
|
expect(homepageSlice.client).toBeDefined();
|
|
357
338
|
expect(homepageSlice.client.specs).toBeDefined();
|
|
358
|
-
expect(
|
|
359
|
-
expect(homepageSlice.client.specs).toHaveLength(2);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
339
|
+
expect(homepageSlice.client.specs?.rules).toBeDefined();
|
|
340
|
+
expect(homepageSlice.client.specs?.rules).toHaveLength(2);
|
|
341
|
+
const rules = homepageSlice.client.specs?.rules;
|
|
342
|
+
if (rules && Array.isArray(rules)) {
|
|
343
|
+
expect(rules).toHaveLength(2);
|
|
344
|
+
expect(rules[0]).toBe('show a hero section with a welcome message');
|
|
345
|
+
expect(rules[1]).toBe('allow user to start the questionnaire');
|
|
346
|
+
}
|
|
364
347
|
}
|
|
365
348
|
}
|
|
366
349
|
});
|
|
367
350
|
it('simulates browser execution with transpiled CommonJS code', async () => {
|
|
368
351
|
const memoryVfs = new InMemoryFileStore();
|
|
369
352
|
const flowContent = `
|
|
370
|
-
import { flow, experience,
|
|
353
|
+
import { flow, experience, should, specs } from '@auto-engineer/narrative';
|
|
371
354
|
|
|
372
355
|
flow('Browser Test Flow', () => {
|
|
373
356
|
experience('HomePage').client(() => {
|
|
374
357
|
specs(() => {
|
|
375
|
-
|
|
358
|
+
should('render correctly');
|
|
376
359
|
});
|
|
377
360
|
});
|
|
378
361
|
});
|
|
@@ -392,10 +375,8 @@ flow('Browser Test Flow', () => {
|
|
|
392
375
|
if (slice.type === 'experience') {
|
|
393
376
|
expect(slice.client).toBeDefined();
|
|
394
377
|
expect(slice.client.specs).toBeDefined();
|
|
395
|
-
expect(
|
|
396
|
-
expect(slice.client.specs).
|
|
397
|
-
expect(slice.client.specs[0].type).toBe('it');
|
|
398
|
-
expect(slice.client.specs[0].title).toBe('render correctly');
|
|
378
|
+
expect(slice.client.specs?.rules).toHaveLength(1);
|
|
379
|
+
expect(slice.client.specs?.rules?.[0]).toBe('render correctly');
|
|
399
380
|
}
|
|
400
381
|
});
|
|
401
382
|
it('handles experience slice with ES module interop correctly', async () => {
|
|
@@ -405,8 +386,8 @@ flow('Browser Test Flow', () => {
|
|
|
405
386
|
const flowContent = `
|
|
406
387
|
import { flow, experience } from '@auto-engineer/narrative';
|
|
407
388
|
|
|
408
|
-
flow('Questionnaires', 'Q9m2Kp4Lx', () => {
|
|
409
|
-
experience('Homepage', 'H1a4Bn6Cy').client(() => {});
|
|
389
|
+
flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
|
|
390
|
+
experience('Homepage', 'AUTO-H1a4Bn6Cy').client(() => {});
|
|
410
391
|
});
|
|
411
392
|
`;
|
|
412
393
|
await memoryVfs.write('/browser/questionnaires.narrative.ts', new TextEncoder().encode(flowContent));
|
|
@@ -541,7 +522,7 @@ flow('questionnaires-test', () => {
|
|
|
541
522
|
expect(querySlice?.type).toBe('query');
|
|
542
523
|
if (querySlice?.type !== 'query')
|
|
543
524
|
return;
|
|
544
|
-
const example = querySlice.server.specs
|
|
525
|
+
const example = querySlice.server.specs.rules[0]?.examples[0];
|
|
545
526
|
expect(example).toBeDefined();
|
|
546
527
|
if (example !== null && example !== undefined) {
|
|
547
528
|
validateMixedGivenTypes(example);
|
|
@@ -580,7 +561,7 @@ flow('questionnaires-test', () => {
|
|
|
580
561
|
const model = await createQuestionnaireBugTestModel();
|
|
581
562
|
validateQuestionnaireBugFix(model);
|
|
582
563
|
});
|
|
583
|
-
it('should convert all given events to eventRef', async ()
|
|
564
|
+
it('should convert all given events to eventRef', async function () {
|
|
584
565
|
const memoryVfs = new InMemoryFileStore();
|
|
585
566
|
const todoSummaryFlowContent = `
|
|
586
567
|
import { flow, query, specs, rule, example, type Event, type State } from '@auto-engineer/narrative';
|
|
@@ -680,37 +661,39 @@ flow('Todo List', () => {
|
|
|
680
661
|
expect(summarySlice?.type).toBe('query');
|
|
681
662
|
if (summarySlice?.type !== 'query')
|
|
682
663
|
return;
|
|
683
|
-
const example = summarySlice.server.specs
|
|
664
|
+
const example = summarySlice.server.specs.rules[0]?.examples[0];
|
|
684
665
|
expect(example).toBeDefined();
|
|
685
|
-
expect(example.
|
|
686
|
-
expect(Array.isArray(example.
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
666
|
+
expect(example.given).toBeDefined();
|
|
667
|
+
expect(Array.isArray(example.given)).toBe(true);
|
|
668
|
+
expect(example.given).toHaveLength(5);
|
|
669
|
+
if (!example.given) {
|
|
670
|
+
throw new Error('expected example.given to be defined');
|
|
671
|
+
}
|
|
672
|
+
validateGivenItemsHaveEventRef(example.given);
|
|
673
|
+
validateTodoEventRefs(example.given);
|
|
691
674
|
validateTodoMessages(model);
|
|
692
675
|
});
|
|
693
676
|
});
|
|
694
|
-
function validateGivenItemsHaveEventRef(
|
|
695
|
-
for (let i = 0; i <
|
|
696
|
-
const
|
|
697
|
-
if (typeof
|
|
698
|
-
expect(
|
|
699
|
-
expect('
|
|
677
|
+
function validateGivenItemsHaveEventRef(given) {
|
|
678
|
+
for (let i = 0; i < given.length; i++) {
|
|
679
|
+
const givenItem = given[i];
|
|
680
|
+
if (typeof givenItem === 'object' && givenItem !== null) {
|
|
681
|
+
expect('eventRef' in givenItem).toBe(true);
|
|
682
|
+
expect('stateRef' in givenItem).toBe(false);
|
|
700
683
|
}
|
|
701
684
|
}
|
|
702
685
|
}
|
|
703
|
-
function
|
|
704
|
-
if (
|
|
705
|
-
expect(
|
|
686
|
+
function expectEventRef(item, expectedType) {
|
|
687
|
+
if (item !== null && item !== undefined && typeof item === 'object' && 'eventRef' in item) {
|
|
688
|
+
expect(item.eventRef).toBe(expectedType);
|
|
706
689
|
}
|
|
707
690
|
}
|
|
708
|
-
function validateTodoEventRefs(
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
691
|
+
function validateTodoEventRefs(given) {
|
|
692
|
+
expectEventRef(given[0], 'TodoAdded');
|
|
693
|
+
expectEventRef(given[1], 'TodoAdded');
|
|
694
|
+
expectEventRef(given[2], 'TodoAdded');
|
|
695
|
+
expectEventRef(given[3], 'TodoMarkedInProgress');
|
|
696
|
+
expectEventRef(given[4], 'TodoMarkedComplete');
|
|
714
697
|
}
|
|
715
698
|
function validateTodoMessages(model) {
|
|
716
699
|
const todoAddedEvent = model.messages.find((m) => m.name === 'TodoAdded');
|
|
@@ -730,10 +713,14 @@ function validateSubmitQuestionnaireCommand(questionnaireFlow) {
|
|
|
730
713
|
const submitSlice = questionnaireFlow.slices.find((s) => s.name === 'submits questionnaire');
|
|
731
714
|
expect(submitSlice?.type).toBe('command');
|
|
732
715
|
if (submitSlice?.type === 'command') {
|
|
733
|
-
const example = submitSlice.server?.specs?.
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
716
|
+
const example = submitSlice.server?.specs?.rules[0]?.examples[0];
|
|
717
|
+
if (example !== null &&
|
|
718
|
+
example !== undefined &&
|
|
719
|
+
typeof example.when === 'object' &&
|
|
720
|
+
example.when !== null &&
|
|
721
|
+
!Array.isArray(example.when) &&
|
|
722
|
+
'commandRef' in example.when) {
|
|
723
|
+
expect(example.when.commandRef).toBe('SubmitQuestionnaire');
|
|
737
724
|
}
|
|
738
725
|
}
|
|
739
726
|
}
|
|
@@ -744,11 +731,15 @@ function validateQuestionAnsweredEvent(model) {
|
|
|
744
731
|
function validateGivenSectionEventRefs(questionnaireFlow) {
|
|
745
732
|
const viewsSlice = questionnaireFlow.slices.find((s) => s.name === 'views progress');
|
|
746
733
|
if (viewsSlice?.type === 'query') {
|
|
747
|
-
const example = viewsSlice.server?.specs?.
|
|
748
|
-
if (example?.
|
|
749
|
-
const
|
|
750
|
-
if (
|
|
751
|
-
expect(
|
|
734
|
+
const example = viewsSlice.server?.specs?.rules[0]?.examples[0];
|
|
735
|
+
if (example?.given && Array.isArray(example.given) && example.given.length > 0) {
|
|
736
|
+
const givenItem = example.given[0];
|
|
737
|
+
if (typeof givenItem === 'object' && givenItem !== null) {
|
|
738
|
+
expect('eventRef' in givenItem).toBe(true);
|
|
739
|
+
expect('stateRef' in givenItem).toBe(false);
|
|
740
|
+
if ('eventRef' in givenItem) {
|
|
741
|
+
expect(givenItem.eventRef).toBe('QuestionAnswered');
|
|
742
|
+
}
|
|
752
743
|
}
|
|
753
744
|
}
|
|
754
745
|
}
|
|
@@ -760,43 +751,45 @@ function validateCurrentQuestionIdType(model) {
|
|
|
760
751
|
expect(currentQuestionIdField?.type).toBe('string | null');
|
|
761
752
|
}
|
|
762
753
|
function validateMixedGivenTypes(example) {
|
|
763
|
-
expect(example.
|
|
764
|
-
expect(example.
|
|
765
|
-
expect(Array.isArray(example.
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
754
|
+
expect(example.description).toBe('system with 2 items reaches max of 2');
|
|
755
|
+
expect(example.given).toBeDefined();
|
|
756
|
+
expect(Array.isArray(example.given)).toBe(true);
|
|
757
|
+
if (!example.given)
|
|
758
|
+
return;
|
|
759
|
+
expect(example.given).toHaveLength(4);
|
|
760
|
+
const firstGiven = example.given[0];
|
|
761
|
+
expect('stateRef' in firstGiven).toBe(true);
|
|
762
|
+
expect('eventRef' in firstGiven).toBe(false);
|
|
763
|
+
if ('stateRef' in firstGiven) {
|
|
764
|
+
expect(firstGiven.stateRef).toBe('ConfigState');
|
|
772
765
|
}
|
|
773
|
-
const secondGiven =
|
|
774
|
-
expect('
|
|
775
|
-
if ('
|
|
776
|
-
expect(secondGiven.
|
|
766
|
+
const secondGiven = example.given[1];
|
|
767
|
+
expect('eventRef' in secondGiven).toBe(true);
|
|
768
|
+
if ('eventRef' in secondGiven) {
|
|
769
|
+
expect(secondGiven.eventRef).toBe('SystemInitialized');
|
|
777
770
|
}
|
|
778
|
-
const thirdGiven =
|
|
779
|
-
expect('
|
|
780
|
-
if ('
|
|
781
|
-
expect(thirdGiven.
|
|
771
|
+
const thirdGiven = example.given[2];
|
|
772
|
+
expect('eventRef' in thirdGiven).toBe(true);
|
|
773
|
+
if ('eventRef' in thirdGiven) {
|
|
774
|
+
expect(thirdGiven.eventRef).toBe('ItemAdded');
|
|
782
775
|
}
|
|
783
|
-
const fourthGiven =
|
|
784
|
-
expect('
|
|
785
|
-
if ('
|
|
786
|
-
expect(fourthGiven.
|
|
776
|
+
const fourthGiven = example.given[3];
|
|
777
|
+
expect('eventRef' in fourthGiven).toBe(true);
|
|
778
|
+
if ('eventRef' in fourthGiven) {
|
|
779
|
+
expect(fourthGiven.eventRef).toBe('ItemAdded');
|
|
787
780
|
}
|
|
788
781
|
}
|
|
789
782
|
function validateEmptyWhenClause(example) {
|
|
790
|
-
|
|
791
|
-
expect(whenStep).toBeUndefined();
|
|
783
|
+
expect(example.when).toBeUndefined();
|
|
792
784
|
}
|
|
793
785
|
function validateThenClause(example) {
|
|
794
|
-
|
|
795
|
-
expect(
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
786
|
+
expect(example.then).toBeDefined();
|
|
787
|
+
expect(Array.isArray(example.then)).toBe(true);
|
|
788
|
+
expect(example.then).toHaveLength(1);
|
|
789
|
+
const thenOutcome = example.then[0];
|
|
790
|
+
expect('stateRef' in thenOutcome).toBe(true);
|
|
791
|
+
if ('stateRef' in thenOutcome) {
|
|
792
|
+
expect(thenOutcome.stateRef).toBe('SystemStatus');
|
|
800
793
|
}
|
|
801
794
|
}
|
|
802
795
|
function validateMixedGivenTypeMessages(model) {
|
|
@@ -827,8 +820,7 @@ import {
|
|
|
827
820
|
query,
|
|
828
821
|
experience,
|
|
829
822
|
flow,
|
|
830
|
-
|
|
831
|
-
it,
|
|
823
|
+
should,
|
|
832
824
|
specs,
|
|
833
825
|
rule,
|
|
834
826
|
example,
|
|
@@ -876,11 +868,11 @@ type SubmitQuestionnaire = Command<
|
|
|
876
868
|
}
|
|
877
869
|
>;
|
|
878
870
|
|
|
879
|
-
flow('Questionnaires', 'Q9m2Kp4Lx', () => {
|
|
880
|
-
command('sends the questionnaire link', 'S2b5Cp7Dz')
|
|
871
|
+
flow('Questionnaires', 'AUTO-Q9m2Kp4Lx', () => {
|
|
872
|
+
command('sends the questionnaire link', 'AUTO-S2b5Cp7Dz')
|
|
881
873
|
.server(() => {
|
|
882
874
|
specs(() => {
|
|
883
|
-
rule('questionnaire link is sent to participant', 'r0A1Bo8X', () => {
|
|
875
|
+
rule('questionnaire link is sent to participant', 'AUTO-r0A1Bo8X', () => {
|
|
884
876
|
example('sends the questionnaire link successfully')
|
|
885
877
|
.when<SendQuestionnaireLink>({
|
|
886
878
|
questionnaireId: 'q-001',
|
|
@@ -904,16 +896,16 @@ flow('Questionnaires', 'Q9m2Kp4Lx', () => {
|
|
|
904
896
|
}
|
|
905
897
|
\`)
|
|
906
898
|
.client(() => {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
899
|
+
specs('Questionnaire Link', () => {
|
|
900
|
+
should('display a confirmation message when the link is sent');
|
|
901
|
+
should('handle errors when the link cannot be sent');
|
|
910
902
|
});
|
|
911
903
|
});
|
|
912
904
|
|
|
913
|
-
command('submits the questionnaire', 'T5k9Jw3V')
|
|
905
|
+
command('submits the questionnaire', 'AUTO-T5k9Jw3V')
|
|
914
906
|
.server(() => {
|
|
915
907
|
specs(() => {
|
|
916
|
-
rule('questionnaire allowed to be submitted when all questions are answered', 'r4H0Lx4U', () => {
|
|
908
|
+
rule('questionnaire allowed to be submitted when all questions are answered', 'AUTO-r4H0Lx4U', () => {
|
|
917
909
|
example('submits the questionnaire successfully')
|
|
918
910
|
.when<SubmitQuestionnaire>({
|
|
919
911
|
questionnaireId: 'q-001',
|
|
@@ -936,8 +928,8 @@ flow('Questionnaires', 'Q9m2Kp4Lx', () => {
|
|
|
936
928
|
}
|
|
937
929
|
\`)
|
|
938
930
|
.client(() => {
|
|
939
|
-
|
|
940
|
-
|
|
931
|
+
specs('Submission Confirmation', () => {
|
|
932
|
+
should('display a confirmation message upon successful submission');
|
|
941
933
|
});
|
|
942
934
|
});
|
|
943
935
|
});`;
|
|
@@ -967,34 +959,32 @@ function getSubmitSlice(questionnaireFlow) {
|
|
|
967
959
|
return submitSlice;
|
|
968
960
|
}
|
|
969
961
|
function getSubmitExample(submitSlice) {
|
|
970
|
-
const rule = submitSlice.server?.specs?.
|
|
962
|
+
const rule = submitSlice.server?.specs?.rules[0];
|
|
971
963
|
expect(rule).toBeDefined();
|
|
972
964
|
expect(rule?.examples).toHaveLength(1);
|
|
973
965
|
const example = rule?.examples[0];
|
|
974
|
-
expect(example?.
|
|
966
|
+
expect(example?.description).toBe('submits the questionnaire successfully');
|
|
975
967
|
return example;
|
|
976
968
|
}
|
|
977
969
|
function validateSubmitCommandRef(example) {
|
|
978
970
|
const ex = example;
|
|
979
|
-
expect(ex?.
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
expect(
|
|
983
|
-
expect(whenStep.text).not.toBe('SendQuestionnaireLink');
|
|
971
|
+
expect(ex?.when).toBeDefined();
|
|
972
|
+
if (typeof ex?.when === 'object' && ex.when !== null && !Array.isArray(ex.when) && 'commandRef' in ex.when) {
|
|
973
|
+
expect(ex.when.commandRef).toBe('SubmitQuestionnaire');
|
|
974
|
+
expect(ex.when.commandRef).not.toBe('SendQuestionnaireLink');
|
|
984
975
|
}
|
|
985
976
|
else {
|
|
986
|
-
throw new Error('Expected
|
|
977
|
+
throw new Error('Expected when to have commandRef property');
|
|
987
978
|
}
|
|
988
979
|
}
|
|
989
980
|
function validateLinkSliceCommandRef(questionnaireFlow) {
|
|
990
981
|
const linkSlice = questionnaireFlow.slices.find((s) => s.name === 'sends the questionnaire link');
|
|
991
982
|
expect(linkSlice?.type).toBe('command');
|
|
992
983
|
if (linkSlice?.type === 'command') {
|
|
993
|
-
const linkExample = linkSlice.server?.specs?.
|
|
984
|
+
const linkExample = linkSlice.server?.specs?.rules[0]?.examples[0];
|
|
994
985
|
const ex = linkExample;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
expect(whenStep.text).toBe('SendQuestionnaireLink');
|
|
986
|
+
if (typeof ex?.when === 'object' && ex.when !== null && !Array.isArray(ex.when) && 'commandRef' in ex.when) {
|
|
987
|
+
expect(ex.when.commandRef).toBe('SendQuestionnaireLink');
|
|
998
988
|
}
|
|
999
989
|
}
|
|
1000
990
|
}
|
|
@@ -1020,19 +1010,17 @@ function getServerSpecsFromSlice(submitSlice) {
|
|
|
1020
1010
|
const slice = submitSlice;
|
|
1021
1011
|
const serverSpecs = slice.server?.specs;
|
|
1022
1012
|
expect(serverSpecs).toBeDefined();
|
|
1023
|
-
|
|
1024
|
-
expect(
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
expect(firstSpec?.rules).toHaveLength(1);
|
|
1028
|
-
return firstSpec;
|
|
1013
|
+
const specs = serverSpecs;
|
|
1014
|
+
expect(specs?.rules).toBeDefined();
|
|
1015
|
+
expect(specs?.rules).toHaveLength(1);
|
|
1016
|
+
return serverSpecs;
|
|
1029
1017
|
}
|
|
1030
1018
|
function getFirstRuleFromSpecs(serverSpecs) {
|
|
1031
1019
|
const specs = serverSpecs;
|
|
1032
1020
|
const rule = specs?.rules?.[0];
|
|
1033
1021
|
expect(rule).toBeDefined();
|
|
1034
1022
|
const r = rule;
|
|
1035
|
-
expect(r?.
|
|
1023
|
+
expect(r?.description).toBe('questionnaire allowed to be submitted when all questions are answered');
|
|
1036
1024
|
expect(r?.examples).toBeDefined();
|
|
1037
1025
|
expect(r?.examples).toHaveLength(1);
|
|
1038
1026
|
return rule;
|
|
@@ -1042,35 +1030,34 @@ function getFirstExampleFromRule(rule) {
|
|
|
1042
1030
|
const example = r?.examples?.[0];
|
|
1043
1031
|
expect(example).toBeDefined();
|
|
1044
1032
|
const ex = example;
|
|
1045
|
-
expect(ex?.
|
|
1033
|
+
expect(ex?.description).toBe('submits the questionnaire successfully');
|
|
1046
1034
|
return example;
|
|
1047
1035
|
}
|
|
1048
1036
|
function validateExampleCommandRef(example) {
|
|
1049
1037
|
const ex = example;
|
|
1050
|
-
expect(ex?.
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
expect(
|
|
1055
|
-
expect(whenStep.text).not.toBe('SendQuestionnaireLink');
|
|
1056
|
-
expect(whenStep.docString).toEqual({
|
|
1038
|
+
expect(ex?.when).toBeDefined();
|
|
1039
|
+
if (typeof ex?.when === 'object' && ex.when !== null && !Array.isArray(ex.when) && 'commandRef' in ex.when) {
|
|
1040
|
+
expect(ex.when.commandRef).toBe('SubmitQuestionnaire');
|
|
1041
|
+
expect(ex.when.commandRef).not.toBe('SendQuestionnaireLink');
|
|
1042
|
+
expect(ex.when.exampleData).toEqual({
|
|
1057
1043
|
questionnaireId: 'q-001',
|
|
1058
1044
|
participantId: 'participant-abc',
|
|
1059
1045
|
});
|
|
1060
1046
|
}
|
|
1061
1047
|
else {
|
|
1062
|
-
throw new Error('Expected when
|
|
1048
|
+
throw new Error('Expected when to have commandRef property');
|
|
1063
1049
|
}
|
|
1064
1050
|
}
|
|
1065
1051
|
function validateThenEvents(example) {
|
|
1066
1052
|
const ex = example;
|
|
1067
|
-
expect(ex?.
|
|
1068
|
-
|
|
1069
|
-
expect(
|
|
1070
|
-
const
|
|
1071
|
-
if (
|
|
1072
|
-
|
|
1073
|
-
expect(
|
|
1053
|
+
expect(ex?.then).toBeDefined();
|
|
1054
|
+
expect(Array.isArray(ex?.then)).toBe(true);
|
|
1055
|
+
expect(ex?.then).toHaveLength(1);
|
|
1056
|
+
const thenEvent = ex?.then?.[0];
|
|
1057
|
+
if (thenEvent !== null && thenEvent !== undefined && 'eventRef' in thenEvent) {
|
|
1058
|
+
const event = thenEvent;
|
|
1059
|
+
expect(event.eventRef).toBe('QuestionnaireSubmitted');
|
|
1060
|
+
expect(event.exampleData).toEqual({
|
|
1074
1061
|
questionnaireId: 'q-001',
|
|
1075
1062
|
participantId: 'participant-abc',
|
|
1076
1063
|
submittedAt: new Date('2030-01-01T09:00:00.000Z'),
|