@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
@@ -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
  ],
@@ -73,7 +81,7 @@ describe('addAutoIds', () => {
73
81
  integrations: [],
74
82
  };
75
83
 
76
- const AUTO_ID_REGEX = /^AUTO-[A-Za-z0-9_]{9}$/;
84
+ const AUTO_ID_REGEX = /^[A-Za-z0-9_]{9}$/;
77
85
 
78
86
  it('should assign IDs to entities that do not have them', () => {
79
87
  const result = addAutoIds(flows);
@@ -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', () => {
@@ -1,5 +1,5 @@
1
1
  import { generateAutoId } from './generators';
2
- import { Model, Slice } from '../index';
2
+ import { Model, Slice, Spec, Rule, Example } from '../index';
3
3
 
4
4
  function ensureId(item: { id?: string }): void {
5
5
  if (item.id === undefined || item.id === '') {
@@ -7,23 +7,40 @@ function ensureId(item: { id?: string }): void {
7
7
  }
8
8
  }
9
9
 
10
- function addRuleIds(rules: Array<unknown>): Array<unknown> {
10
+ function processExamples(examples: Example[]): Example[] {
11
+ return examples.map((example) => {
12
+ const exampleCopy = { ...example };
13
+ ensureId(exampleCopy);
14
+ return exampleCopy;
15
+ });
16
+ }
17
+
18
+ function processRules(rules: Rule[]): Rule[] {
11
19
  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;
20
+ const ruleCopy = { ...rule };
21
+ ensureId(ruleCopy);
22
+ ruleCopy.examples = processExamples(rule.examples);
23
+ return ruleCopy;
18
24
  });
19
25
  }
20
26
 
27
+ function processSpecs(specs: Spec[]): Spec[] {
28
+ return specs.map((spec) => ({
29
+ ...spec,
30
+ rules: processRules(spec.rules),
31
+ }));
32
+ }
33
+
21
34
  function processServerSpecs(slice: Slice): Slice {
22
- if (!('server' in slice) || slice.server?.specs?.rules === undefined) return slice;
35
+ if (!('server' in slice) || slice.server?.specs === undefined || !Array.isArray(slice.server.specs)) return slice;
23
36
 
24
37
  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);
38
+ if (
39
+ 'server' in modifiedSlice &&
40
+ modifiedSlice.server?.specs !== undefined &&
41
+ Array.isArray(modifiedSlice.server.specs)
42
+ ) {
43
+ modifiedSlice.server.specs = processSpecs(modifiedSlice.server.specs);
27
44
  }
28
45
  return modifiedSlice;
29
46
  }
@@ -1,5 +1,5 @@
1
1
  import { generateId } from '@auto-engineer/id';
2
2
 
3
3
  export function generateAutoId(): string {
4
- return generateId({ prefix: 'AUTO-' });
4
+ return generateId();
5
5
  }
@@ -1,267 +1,169 @@
1
- import { describe, expect, it, beforeEach } from 'vitest';
1
+ import { describe, expect, it } from 'vitest';
2
2
  import { hasAllIds, addAutoIds } from './index';
3
- import { getNarratives } from '../getNarratives';
4
- import { InMemoryFileStore, type IFileStore } from '@auto-engineer/file-store';
5
- import * as flowApi from '../narrative';
6
- import * as fluent from '../fluent-builder';
7
- import * as dataBuilders from '../data-narrative-builders';
8
- import * as typesApi from '../types';
9
- import gql from 'graphql-tag';
10
-
11
- const importMap = {
12
- '../flow': flowApi,
13
- '../fluent-builder': fluent,
14
- '../data-flow-builders': dataBuilders,
15
- '../types': typesApi,
16
- 'graphql-tag': gql,
17
- };
3
+ import type { Model } from '../index';
18
4
 
19
5
  describe('hasAllIds', () => {
20
- let vfs: IFileStore;
21
- const root = '/test';
22
-
23
- beforeEach(async () => {
24
- vfs = new InMemoryFileStore();
25
-
26
- const flowWithoutIds = `
27
- import { flow, specs, rule, example } from '../narrative';
28
- import { command } from '../fluent-builder';
29
-
30
- flow('Test Flow Without IDs', () => {
31
- command('Test slice without ID')
32
- .server(() => {
33
- specs('Test specs', () => {
34
- rule('Test rule without ID', () => {
35
- example('Test example')
36
- .when({ test: 'data' })
37
- .then({ result: 'success' });
38
- });
39
- });
40
- });
41
- });`;
42
-
43
- const flowWithIds = `
44
- import { flow, specs, rule, example } from '../narrative';
45
- import { command } from '../fluent-builder';
46
-
47
- flow('Test Flow with IDs', 'FLOW-001', () => {
48
- command('Test slice with ID', 'SLICE-001')
49
- .server(() => {
50
- specs('Test specs', () => {
51
- rule('Test rule with ID', 'RULE-001', () => {
52
- example('Test example')
53
- .when({ test: 'data' })
54
- .then({ result: 'success' });
55
- });
56
- });
57
- });
58
- });`;
59
-
60
- const multipleFlowsSameSource = `
61
- import { flow, specs, it } from '../narrative';
62
- import { experience } from '../fluent-builder';
63
-
64
- flow('Home Screen', 'AUTO-aifPcU3hw', () => {
65
- experience('Active Surveys Summary', 'AUTO-slice1').client(() => {
66
- specs(() => {
67
- it('show active surveys summary');
68
- });
69
- });
70
- });
71
-
72
- flow('Create Survey', 'AUTO-MPviTMrQC', () => {
73
- experience('Create Survey Form', 'AUTO-slice2').client(() => {
74
- specs(() => {
75
- it('allow entering survey title');
76
- });
77
- });
78
- });
79
-
80
- flow('Response Analytics', 'AUTO-eME978Euk', () => {
81
- experience('Response Rate Charts', 'AUTO-slice3').client(() => {
82
- specs(() => {
83
- it('show daily response rate charts');
84
- });
85
- });
86
- });`;
87
-
88
- const multipleFlowsIncomplete = `
89
- import { flow, specs, it } from '../narrative';
90
- import { experience } from '../fluent-builder';
91
-
92
- flow('Home Screen', 'AUTO-aifPcU3hw', () => {
93
- experience('Active Surveys Summary', 'AUTO-slice1').client(() => {
94
- specs(() => {
95
- it('show active surveys summary');
96
- });
97
- });
98
- });
99
-
100
- flow('Create Survey', () => {
101
- experience('Create Survey Form', 'AUTO-slice2').client(() => {
102
- specs(() => {
103
- it('allow entering survey title');
104
- });
105
- });
106
- });
107
-
108
- flow('Response Analytics', 'AUTO-eME978Euk', () => {
109
- experience('Response Rate Charts', 'AUTO-slice3').client(() => {
110
- specs(() => {
111
- it('show daily response rate charts');
112
- });
113
- });
114
- });`;
115
-
116
- const multipleFlowsSliceMissing = `
117
- import { flow, specs, it } from '../narrative';
118
- import { experience } from '../fluent-builder';
119
-
120
- flow('Home Screen', 'AUTO-aifPcU3hw', () => {
121
- experience('Active Surveys Summary', 'AUTO-slice1').client(() => {
122
- specs(() => {
123
- it('show active surveys summary');
124
- });
125
- });
126
- });
127
-
128
- flow('Create Survey', 'AUTO-MPviTMrQC', () => {
129
- experience('Create Survey Form').client(() => {
130
- specs(() => {
131
- it('allow entering survey title');
132
- });
133
- });
134
- });
135
-
136
- flow('Response Analytics', 'AUTO-eME978Euk', () => {
137
- experience('Response Rate Charts', 'AUTO-slice3').client(() => {
138
- specs(() => {
139
- it('show daily response rate charts');
140
- });
141
- });
142
- });`;
143
-
144
- const flowContent1 = new TextEncoder().encode(flowWithoutIds);
145
- const flowContent2 = new TextEncoder().encode(flowWithIds);
146
- const flowContent3 = new TextEncoder().encode(multipleFlowsSameSource);
147
- const flowContent4 = new TextEncoder().encode(multipleFlowsIncomplete);
148
- const flowContent5 = new TextEncoder().encode(multipleFlowsSliceMissing);
149
- await vfs.write('/test/flow-without-ids.narrative.ts', flowContent1);
150
- await vfs.write('/test/flow-with-ids.narrative.ts', flowContent2);
151
- await vfs.write('/test/homepage.narrative.ts', flowContent3);
152
- await vfs.write('/test/homepage-incomplete.narrative.ts', flowContent4);
153
- await vfs.write('/test/homepage-slice-missing.narrative.ts', flowContent5);
154
- });
155
- it('should return false for models without IDs', async () => {
156
- const result = await getNarratives({ vfs, root, pattern: /\.(narrative)\.(ts)$/, fastFsScan: true, importMap });
157
- const model = result.toModel();
158
-
159
- const flowWithoutIds = model.narratives.find((f) => f.name === 'Test Flow Without IDs');
160
- expect(flowWithoutIds).toBeDefined();
161
-
162
- if (flowWithoutIds) {
163
- const modelWithoutIds = { ...model, narratives: [flowWithoutIds] };
164
- expect(hasAllIds(modelWithoutIds)).toBe(false);
165
- }
166
- });
167
-
168
- it('should return true for models with complete IDs', async () => {
169
- const result = await getNarratives({ vfs, root, pattern: /\.(narrative)\.(ts)$/, fastFsScan: true, importMap });
170
- const model = result.toModel();
171
-
6
+ const createModelWithoutIds = (): Model => ({
7
+ variant: 'specs',
8
+ narratives: [
9
+ {
10
+ name: 'Test Flow Without IDs',
11
+ slices: [
12
+ {
13
+ type: 'command',
14
+ name: 'Test slice without ID',
15
+ client: { specs: [] },
16
+ server: {
17
+ description: 'Test server',
18
+ specs: [
19
+ {
20
+ type: 'gherkin',
21
+ feature: 'Test specs',
22
+ rules: [
23
+ {
24
+ name: 'Test rule without ID',
25
+ examples: [],
26
+ },
27
+ ],
28
+ },
29
+ ],
30
+ },
31
+ },
32
+ ],
33
+ },
34
+ ],
35
+ messages: [],
36
+ integrations: [],
37
+ });
38
+
39
+ const createModelWithIds = (): Model => ({
40
+ variant: 'specs',
41
+ narratives: [
42
+ {
43
+ name: 'Test Flow with IDs',
44
+ id: 'FLOW-001',
45
+ slices: [
46
+ {
47
+ type: 'command',
48
+ name: 'Test slice with ID',
49
+ id: 'SLICE-001',
50
+ client: { specs: [] },
51
+ server: {
52
+ description: 'Test server',
53
+ specs: [
54
+ {
55
+ type: 'gherkin',
56
+ feature: 'Test specs',
57
+ rules: [
58
+ {
59
+ id: 'RULE-001',
60
+ name: 'Test rule with ID',
61
+ examples: [],
62
+ },
63
+ ],
64
+ },
65
+ ],
66
+ },
67
+ },
68
+ ],
69
+ },
70
+ ],
71
+ messages: [],
72
+ integrations: [],
73
+ });
74
+
75
+ const createMultipleFlowsModel = (includeAllIds: boolean, includeAllSliceIds: boolean): Model => ({
76
+ variant: 'specs',
77
+ narratives: [
78
+ {
79
+ name: 'Home Screen',
80
+ id: 'aifPcU3hw',
81
+ sourceFile: '/path/to/homepage.narrative.ts',
82
+ slices: [
83
+ {
84
+ name: 'Active Surveys Summary',
85
+ id: 'slice1',
86
+ type: 'experience',
87
+ client: { specs: [{ type: 'it', title: 'show active surveys summary' }] },
88
+ },
89
+ ],
90
+ },
91
+ {
92
+ name: 'Create Survey',
93
+ id: includeAllIds ? 'MPviTMrQC' : undefined,
94
+ sourceFile: '/path/to/homepage.narrative.ts',
95
+ slices: [
96
+ {
97
+ name: 'Create Survey Form',
98
+ id: includeAllSliceIds ? 'slice2' : undefined,
99
+ type: 'experience',
100
+ client: { specs: [{ type: 'it', title: 'allow entering survey title' }] },
101
+ },
102
+ ],
103
+ },
104
+ {
105
+ name: 'Response Analytics',
106
+ id: 'eME978Euk',
107
+ sourceFile: '/path/to/homepage.narrative.ts',
108
+ slices: [
109
+ {
110
+ name: 'Response Rate Charts',
111
+ id: 'slice3',
112
+ type: 'experience',
113
+ client: { specs: [{ type: 'it', title: 'show daily response rate charts' }] },
114
+ },
115
+ ],
116
+ },
117
+ ],
118
+ messages: [],
119
+ integrations: [],
120
+ });
121
+
122
+ it('should return false for models without IDs', () => {
123
+ const model = createModelWithoutIds();
124
+ expect(hasAllIds(model)).toBe(false);
125
+ });
126
+
127
+ it('should return true for models with complete IDs', () => {
128
+ const model = createModelWithoutIds();
172
129
  const modelWithIds = addAutoIds(model);
173
130
  expect(hasAllIds(modelWithIds)).toBe(true);
174
131
  });
175
132
 
176
- it('should return true for flows that already have IDs', async () => {
177
- const result = await getNarratives({ vfs, root, pattern: /\.(narrative)\.(ts)$/, fastFsScan: true, importMap });
178
- const model = result.toModel();
179
-
180
- const testFlowWithIds = model.narratives.find((f) => f.name === 'Test Flow with IDs');
181
- expect(testFlowWithIds).toBeDefined();
182
-
183
- if (testFlowWithIds) {
184
- const modelWithExistingIds = { ...model, narratives: [testFlowWithIds] };
185
- expect(hasAllIds(modelWithExistingIds)).toBe(true);
186
- }
133
+ it('should return true for flows that already have IDs', () => {
134
+ const model = createModelWithIds();
135
+ expect(hasAllIds(model)).toBe(true);
187
136
  });
188
137
 
189
- it('should return false if any slice is missing an ID', async () => {
190
- const result = await getNarratives({ vfs, root, pattern: /\.(narrative)\.(ts)$/, fastFsScan: true, importMap });
191
- const model = result.toModel();
192
-
193
- const modelWithIds = addAutoIds(model);
194
- expect(modelWithIds.narratives.length).toBeGreaterThan(0);
195
- expect(modelWithIds.narratives[0].slices.length).toBeGreaterThan(0);
196
-
197
- const modifiedModel = structuredClone(modelWithIds);
138
+ it('should return false if any slice is missing an ID', () => {
139
+ const model = createModelWithIds();
140
+ const modifiedModel = structuredClone(model);
198
141
  modifiedModel.narratives[0].slices[0].id = '';
199
142
  expect(hasAllIds(modifiedModel)).toBe(false);
200
143
  });
201
144
 
202
- it('should return false if any rule is missing an ID', async () => {
203
- const result = await getNarratives({ vfs, root, pattern: /\.(narrative)\.(ts)$/, fastFsScan: true, importMap });
204
- const model = result.toModel();
205
-
206
- const modelWithIds = addAutoIds(model);
207
-
208
- let found = false;
209
- for (const flow of modelWithIds.narratives) {
210
- for (const slice of flow.slices) {
211
- if ('server' in slice && slice.server?.specs?.rules !== undefined && slice.server.specs.rules.length > 0) {
212
- const modifiedModel = structuredClone(modelWithIds);
213
- const modifiedFlow = modifiedModel.narratives.find((f) => f.name === flow.name);
214
- const modifiedSlice = modifiedFlow?.slices.find((s) => s.name === slice.name);
215
- if (modifiedSlice && 'server' in modifiedSlice && modifiedSlice.server?.specs?.rules !== undefined) {
216
- modifiedSlice.server.specs.rules[0].id = '';
217
- expect(hasAllIds(modifiedModel)).toBe(false);
218
- found = true;
219
- break;
220
- }
221
- }
222
- }
223
- if (found) break;
145
+ it('should return false if any rule is missing an ID', () => {
146
+ const model = createModelWithIds();
147
+ const modifiedModel = structuredClone(model);
148
+ const slice = modifiedModel.narratives[0].slices[0];
149
+ if ('server' in slice && slice.server?.specs !== undefined && Array.isArray(slice.server.specs)) {
150
+ slice.server.specs[0].rules[0].id = '';
224
151
  }
225
-
226
- expect(found).toBe(true);
152
+ expect(hasAllIds(modifiedModel)).toBe(false);
227
153
  });
228
154
 
229
- it('should return true when multiple flows with same sourceFile all have IDs', async () => {
230
- const result = await getNarratives({ vfs, root, pattern: /\.(narrative)\.(ts)$/, fastFsScan: true, importMap });
231
- const model = result.toModel();
232
-
233
- const homepageFlows = model.narratives.filter(
234
- (f) => f.sourceFile !== undefined && f.sourceFile.includes('homepage.narrative.ts'),
235
- );
236
- expect(homepageFlows.length).toBe(3);
237
-
238
- const homepageModel = { ...model, narratives: homepageFlows };
239
- expect(hasAllIds(homepageModel)).toBe(true);
155
+ it('should return true when multiple flows with same sourceFile all have IDs', () => {
156
+ const model = createMultipleFlowsModel(true, true);
157
+ expect(hasAllIds(model)).toBe(true);
240
158
  });
241
159
 
242
- it('should return false when any flow in multiple flows with same sourceFile is missing ID', async () => {
243
- const result = await getNarratives({ vfs, root, pattern: /\.(narrative)\.(ts)$/, fastFsScan: true, importMap });
244
- const model = result.toModel();
245
-
246
- const homepageFlows = model.narratives.filter(
247
- (f) => f.sourceFile !== undefined && f.sourceFile.includes('homepage-incomplete.narrative.ts'),
248
- );
249
- expect(homepageFlows.length).toBe(3);
250
-
251
- const homepageModel = { ...model, narratives: homepageFlows };
252
- expect(hasAllIds(homepageModel)).toBe(false);
160
+ it('should return false when any flow in multiple flows with same sourceFile is missing ID', () => {
161
+ const model = createMultipleFlowsModel(false, true);
162
+ expect(hasAllIds(model)).toBe(false);
253
163
  });
254
164
 
255
- it('should return false when any slice in multiple flows with same sourceFile is missing ID', async () => {
256
- const result = await getNarratives({ vfs, root, pattern: /\.(narrative)\.(ts)$/, fastFsScan: true, importMap });
257
- const model = result.toModel();
258
-
259
- const homepageFlows = model.narratives.filter(
260
- (f) => f.sourceFile !== undefined && f.sourceFile.includes('homepage-slice-missing.narrative.ts'),
261
- );
262
- expect(homepageFlows.length).toBe(3);
263
-
264
- const homepageModel = { ...model, narratives: homepageFlows };
265
- expect(hasAllIds(homepageModel)).toBe(false);
165
+ it('should return false when any slice in multiple flows with same sourceFile is missing ID', () => {
166
+ const model = createMultipleFlowsModel(true, false);
167
+ expect(hasAllIds(model)).toBe(false);
266
168
  });
267
169
  });