@auto-engineer/narrative 0.15.0 → 0.17.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/CHANGELOG.md +28 -0
- package/dist/src/getNarratives.js +1 -1
- package/dist/src/getNarratives.js.map +1 -1
- package/dist/src/id/addAutoIds.d.ts.map +1 -1
- package/dist/src/id/addAutoIds.js +15 -0
- package/dist/src/id/addAutoIds.js.map +1 -1
- package/dist/src/id/hasAllIds.d.ts.map +1 -1
- package/dist/src/id/hasAllIds.js +6 -1
- package/dist/src/id/hasAllIds.js.map +1 -1
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/loader/runtime-cjs.d.ts.map +1 -1
- package/dist/src/loader/runtime-cjs.js +3 -2
- package/dist/src/loader/runtime-cjs.js.map +1 -1
- package/dist/src/schema.d.ts +301 -143
- package/dist/src/schema.d.ts.map +1 -1
- package/dist/src/schema.js +26 -0
- package/dist/src/schema.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/cross-module-imports.d.ts +6 -0
- package/dist/src/transformers/model-to-narrative/cross-module-imports.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/cross-module-imports.js +63 -0
- package/dist/src/transformers/model-to-narrative/cross-module-imports.js.map +1 -0
- package/dist/src/transformers/model-to-narrative/generators/flow.d.ts +1 -4
- package/dist/src/transformers/model-to-narrative/generators/flow.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/generators/module-code.d.ts +9 -0
- package/dist/src/transformers/model-to-narrative/generators/module-code.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/generators/module-code.js +102 -0
- package/dist/src/transformers/model-to-narrative/generators/module-code.js.map +1 -0
- package/dist/src/transformers/model-to-narrative/index.d.ts +6 -4
- package/dist/src/transformers/model-to-narrative/index.d.ts.map +1 -1
- package/dist/src/transformers/model-to-narrative/index.js +10 -6
- package/dist/src/transformers/model-to-narrative/index.js.map +1 -1
- package/dist/src/transformers/model-to-narrative/ordering.d.ts +10 -0
- package/dist/src/transformers/model-to-narrative/ordering.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/ordering.js +37 -0
- package/dist/src/transformers/model-to-narrative/ordering.js.map +1 -0
- package/dist/src/transformers/model-to-narrative/spec-traversal.d.ts +3 -0
- package/dist/src/transformers/model-to-narrative/spec-traversal.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/spec-traversal.js +54 -0
- package/dist/src/transformers/model-to-narrative/spec-traversal.js.map +1 -0
- package/dist/src/transformers/model-to-narrative/types.d.ts +12 -0
- package/dist/src/transformers/model-to-narrative/types.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/types.js +2 -0
- package/dist/src/transformers/model-to-narrative/types.js.map +1 -0
- package/dist/src/transformers/model-to-narrative/validate-modules.d.ts +8 -0
- package/dist/src/transformers/model-to-narrative/validate-modules.d.ts.map +1 -0
- package/dist/src/transformers/model-to-narrative/validate-modules.js +121 -0
- package/dist/src/transformers/model-to-narrative/validate-modules.js.map +1 -0
- package/dist/src/transformers/narrative-to-model/assemble.d.ts.map +1 -1
- package/dist/src/transformers/narrative-to-model/assemble.js +5 -1
- package/dist/src/transformers/narrative-to-model/assemble.js.map +1 -1
- package/dist/src/transformers/narrative-to-model/derive-modules.d.ts +3 -0
- package/dist/src/transformers/narrative-to-model/derive-modules.d.ts.map +1 -0
- package/dist/src/transformers/narrative-to-model/derive-modules.js +29 -0
- package/dist/src/transformers/narrative-to-model/derive-modules.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/src/getNarratives.specs.ts +214 -1
- package/src/getNarratives.ts +1 -1
- package/src/id/addAutoIds.specs.ts +180 -0
- package/src/id/addAutoIds.ts +16 -1
- package/src/id/hasAllIds.specs.ts +87 -0
- package/src/id/hasAllIds.ts +10 -2
- package/src/index.ts +7 -0
- package/src/loader/runtime-cjs.ts +3 -2
- package/src/model-to-narrative.specs.ts +467 -17
- package/src/schema.ts +28 -0
- package/src/transformers/model-to-narrative/cross-module-imports.specs.ts +450 -0
- package/src/transformers/model-to-narrative/cross-module-imports.ts +83 -0
- package/src/transformers/model-to-narrative/generators/flow.ts +11 -10
- package/src/transformers/model-to-narrative/generators/module-code.ts +186 -0
- package/src/transformers/model-to-narrative/index.ts +19 -7
- package/src/transformers/model-to-narrative/modules.specs.ts +625 -0
- package/src/transformers/model-to-narrative/ordering.specs.ts +104 -0
- package/src/transformers/model-to-narrative/ordering.ts +46 -0
- package/src/transformers/model-to-narrative/spec-traversal.specs.ts +418 -0
- package/src/transformers/model-to-narrative/spec-traversal.ts +63 -0
- package/src/transformers/model-to-narrative/types.ts +13 -0
- package/src/transformers/model-to-narrative/validate-modules.ts +159 -0
- package/src/transformers/narrative-to-model/assemble.ts +7 -2
- package/src/transformers/narrative-to-model/derive-modules.specs.ts +121 -0
- package/src/transformers/narrative-to-model/derive-modules.ts +36 -0
- package/tsconfig.json +1 -1
- package/tsconfig.test.json +2 -1
- package/dist/src/fluent-builder.specs.d.ts +0 -2
- package/dist/src/fluent-builder.specs.d.ts.map +0 -1
- package/dist/src/fluent-builder.specs.js +0 -28
- package/dist/src/fluent-builder.specs.js.map +0 -1
- package/dist/src/getNarratives.cache.specs.d.ts +0 -2
- package/dist/src/getNarratives.cache.specs.d.ts.map +0 -1
- package/dist/src/getNarratives.cache.specs.js +0 -234
- package/dist/src/getNarratives.cache.specs.js.map +0 -1
- package/dist/src/getNarratives.specs.d.ts +0 -2
- package/dist/src/getNarratives.specs.d.ts.map +0 -1
- package/dist/src/getNarratives.specs.js +0 -1307
- package/dist/src/getNarratives.specs.js.map +0 -1
- package/dist/src/id/addAutoIds.specs.d.ts +0 -2
- package/dist/src/id/addAutoIds.specs.d.ts.map +0 -1
- package/dist/src/id/addAutoIds.specs.js +0 -602
- package/dist/src/id/addAutoIds.specs.js.map +0 -1
- package/dist/src/id/hasAllIds.specs.d.ts +0 -2
- package/dist/src/id/hasAllIds.specs.d.ts.map +0 -1
- package/dist/src/id/hasAllIds.specs.js +0 -424
- package/dist/src/id/hasAllIds.specs.js.map +0 -1
- package/dist/src/model-to-narrative.specs.d.ts +0 -2
- package/dist/src/model-to-narrative.specs.d.ts.map +0 -1
- package/dist/src/model-to-narrative.specs.js +0 -2437
- package/dist/src/model-to-narrative.specs.js.map +0 -1
- package/dist/src/narrative-context.specs.d.ts +0 -2
- package/dist/src/narrative-context.specs.d.ts.map +0 -1
- package/dist/src/narrative-context.specs.js +0 -260
- package/dist/src/narrative-context.specs.js.map +0 -1
- 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
- package/dist/src/transformers/narrative-to-model/type-inference.specs.d.ts +0 -2
- package/dist/src/transformers/narrative-to-model/type-inference.specs.d.ts.map +0 -1
- package/dist/src/transformers/narrative-to-model/type-inference.specs.js +0 -177
- package/dist/src/transformers/narrative-to-model/type-inference.specs.js.map +0 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { parseMessageKey, sortFilesByPath, sortImportsBySource, sortMessagesByKey, toMessageKey } from './ordering';
|
|
3
|
+
|
|
4
|
+
describe('ordering utilities', () => {
|
|
5
|
+
describe('toMessageKey', () => {
|
|
6
|
+
it('creates key from kind and name', () => {
|
|
7
|
+
expect(toMessageKey('command', 'AddTodo')).toBe('command:AddTodo');
|
|
8
|
+
expect(toMessageKey('event', 'TodoAdded')).toBe('event:TodoAdded');
|
|
9
|
+
expect(toMessageKey('state', 'TodoList')).toBe('state:TodoList');
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('parseMessageKey', () => {
|
|
14
|
+
it('parses key back to kind and name', () => {
|
|
15
|
+
expect(parseMessageKey('command:AddTodo')).toEqual({ kind: 'command', name: 'AddTodo' });
|
|
16
|
+
expect(parseMessageKey('event:TodoAdded')).toEqual({ kind: 'event', name: 'TodoAdded' });
|
|
17
|
+
expect(parseMessageKey('state:TodoList')).toEqual({ kind: 'state', name: 'TodoList' });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('throws error for invalid format without colon', () => {
|
|
21
|
+
expect(() => parseMessageKey('invalid')).toThrow('Invalid message key format: invalid');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('throws error for invalid kind', () => {
|
|
25
|
+
expect(() => parseMessageKey('unknown:SomeMessage')).toThrow('Invalid message kind: unknown');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('handles names containing colons', () => {
|
|
29
|
+
expect(parseMessageKey('event:Foo:Bar')).toEqual({ kind: 'event', name: 'Foo:Bar' });
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('sortMessagesByKey', () => {
|
|
34
|
+
it('sorts messages alphabetically by messageKey', () => {
|
|
35
|
+
const messages = [
|
|
36
|
+
{ kind: 'state' as const, name: 'TodoList' },
|
|
37
|
+
{ kind: 'command' as const, name: 'AddTodo' },
|
|
38
|
+
{ kind: 'event' as const, name: 'TodoAdded' },
|
|
39
|
+
{ kind: 'command' as const, name: 'DeleteTodo' },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const sorted = sortMessagesByKey(messages);
|
|
43
|
+
|
|
44
|
+
expect(sorted).toEqual([
|
|
45
|
+
{ kind: 'command', name: 'AddTodo' },
|
|
46
|
+
{ kind: 'command', name: 'DeleteTodo' },
|
|
47
|
+
{ kind: 'event', name: 'TodoAdded' },
|
|
48
|
+
{ kind: 'state', name: 'TodoList' },
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('returns empty array for empty input', () => {
|
|
53
|
+
expect(sortMessagesByKey([])).toEqual([]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('sortFilesByPath', () => {
|
|
58
|
+
it('sorts files alphabetically by path', () => {
|
|
59
|
+
const files = [
|
|
60
|
+
{ path: 'src/users.narrative.ts', code: 'users' },
|
|
61
|
+
{ path: 'generated.narrative.ts', code: 'default' },
|
|
62
|
+
{ path: 'src/orders.narrative.ts', code: 'orders' },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const sorted = sortFilesByPath(files);
|
|
66
|
+
|
|
67
|
+
expect(sorted.map((f) => f.path)).toEqual([
|
|
68
|
+
'generated.narrative.ts',
|
|
69
|
+
'src/orders.narrative.ts',
|
|
70
|
+
'src/users.narrative.ts',
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('returns empty array for empty input', () => {
|
|
75
|
+
expect(sortFilesByPath([])).toEqual([]);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('sortImportsBySource', () => {
|
|
80
|
+
it('sorts imports alphabetically by source path then by type names', () => {
|
|
81
|
+
const imports = [
|
|
82
|
+
{ fromPath: './users', typeNames: ['CreateUser', 'UserCreated'] },
|
|
83
|
+
{ fromPath: './orders', typeNames: ['PlaceOrder', 'OrderPlaced'] },
|
|
84
|
+
{ fromPath: './shared', typeNames: ['BaseEvent'] },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const sorted = sortImportsBySource(imports);
|
|
88
|
+
|
|
89
|
+
expect(sorted.map((i) => i.fromPath)).toEqual(['./orders', './shared', './users']);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('sorts type names within each import', () => {
|
|
93
|
+
const imports = [{ fromPath: './types', typeNames: ['Zebra', 'Alpha', 'Beta'] }];
|
|
94
|
+
|
|
95
|
+
const sorted = sortImportsBySource(imports);
|
|
96
|
+
|
|
97
|
+
expect(sorted[0].typeNames).toEqual(['Alpha', 'Beta', 'Zebra']);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('returns empty array for empty input', () => {
|
|
101
|
+
expect(sortImportsBySource([])).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { MessageRef } from '../../index';
|
|
2
|
+
import type { CrossModuleImport, GeneratedFile } from './types';
|
|
3
|
+
|
|
4
|
+
type MessageKind = MessageRef['kind'];
|
|
5
|
+
|
|
6
|
+
export function toMessageKey(kind: MessageKind, name: string): string {
|
|
7
|
+
return `${kind}:${name}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isMessageKind(value: string): value is MessageKind {
|
|
11
|
+
return value === 'command' || value === 'event' || value === 'state';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parseMessageKey(key: string): MessageRef {
|
|
15
|
+
const colonIndex = key.indexOf(':');
|
|
16
|
+
if (colonIndex === -1) {
|
|
17
|
+
throw new Error(`Invalid message key format: ${key}`);
|
|
18
|
+
}
|
|
19
|
+
const kindStr = key.slice(0, colonIndex);
|
|
20
|
+
if (!isMessageKind(kindStr)) {
|
|
21
|
+
throw new Error(`Invalid message kind: ${kindStr}`);
|
|
22
|
+
}
|
|
23
|
+
const name = key.slice(colonIndex + 1);
|
|
24
|
+
return { kind: kindStr, name };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function sortMessagesByKey<T extends MessageRef>(messages: T[]): T[] {
|
|
28
|
+
return [...messages].sort((a, b) => {
|
|
29
|
+
const keyA = toMessageKey(a.kind, a.name);
|
|
30
|
+
const keyB = toMessageKey(b.kind, b.name);
|
|
31
|
+
return keyA.localeCompare(keyB);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function sortFilesByPath<T extends GeneratedFile>(files: T[]): T[] {
|
|
36
|
+
return [...files].sort((a, b) => a.path.localeCompare(b.path));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function sortImportsBySource(imports: CrossModuleImport[]): CrossModuleImport[] {
|
|
40
|
+
return [...imports]
|
|
41
|
+
.map((imp) => ({
|
|
42
|
+
...imp,
|
|
43
|
+
typeNames: [...imp.typeNames].sort((a, b) => a.localeCompare(b)),
|
|
44
|
+
}))
|
|
45
|
+
.sort((a, b) => a.fromPath.localeCompare(b.fromPath));
|
|
46
|
+
}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { Narrative } from '../../index';
|
|
3
|
+
import { collectMessageKeysFromNarratives } from './spec-traversal';
|
|
4
|
+
|
|
5
|
+
describe('collectMessageKeysFromNarratives', () => {
|
|
6
|
+
it('returns empty set for empty narratives array', () => {
|
|
7
|
+
const keys = collectMessageKeysFromNarratives([]);
|
|
8
|
+
|
|
9
|
+
expect(keys.size).toBe(0);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('returns empty set for narratives with no slices', () => {
|
|
13
|
+
const narratives: Narrative[] = [{ name: 'Empty', slices: [] }];
|
|
14
|
+
|
|
15
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
16
|
+
|
|
17
|
+
expect(keys.size).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('ignores experience slices', () => {
|
|
21
|
+
const narratives: Narrative[] = [
|
|
22
|
+
{
|
|
23
|
+
name: 'Test',
|
|
24
|
+
slices: [
|
|
25
|
+
{
|
|
26
|
+
name: 'Homepage',
|
|
27
|
+
type: 'experience',
|
|
28
|
+
client: { specs: [] },
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
35
|
+
|
|
36
|
+
expect(keys.size).toBe(0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('collects command keys from When steps', () => {
|
|
40
|
+
const narratives: Narrative[] = [
|
|
41
|
+
{
|
|
42
|
+
name: 'Test',
|
|
43
|
+
slices: [
|
|
44
|
+
{
|
|
45
|
+
name: 'create order',
|
|
46
|
+
type: 'command',
|
|
47
|
+
client: { specs: [] },
|
|
48
|
+
server: {
|
|
49
|
+
description: 'Test',
|
|
50
|
+
specs: [
|
|
51
|
+
{
|
|
52
|
+
type: 'gherkin',
|
|
53
|
+
feature: 'Test',
|
|
54
|
+
rules: [
|
|
55
|
+
{
|
|
56
|
+
name: 'test rule',
|
|
57
|
+
examples: [
|
|
58
|
+
{
|
|
59
|
+
name: 'test example',
|
|
60
|
+
steps: [{ keyword: 'When', text: 'CreateOrder' }],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
74
|
+
|
|
75
|
+
expect(keys.has('command:CreateOrder')).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('collects event and state keys from Given steps', () => {
|
|
79
|
+
const narratives: Narrative[] = [
|
|
80
|
+
{
|
|
81
|
+
name: 'Test',
|
|
82
|
+
slices: [
|
|
83
|
+
{
|
|
84
|
+
name: 'view order',
|
|
85
|
+
type: 'query',
|
|
86
|
+
client: { specs: [] },
|
|
87
|
+
server: {
|
|
88
|
+
description: 'Test',
|
|
89
|
+
specs: [
|
|
90
|
+
{
|
|
91
|
+
type: 'gherkin',
|
|
92
|
+
feature: 'Test',
|
|
93
|
+
rules: [
|
|
94
|
+
{
|
|
95
|
+
name: 'test rule',
|
|
96
|
+
examples: [
|
|
97
|
+
{
|
|
98
|
+
name: 'test example',
|
|
99
|
+
steps: [{ keyword: 'Given', text: 'OrderCreated' }],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
113
|
+
|
|
114
|
+
expect(keys.has('event:OrderCreated')).toBe(true);
|
|
115
|
+
expect(keys.has('state:OrderCreated')).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('collects event and state keys from And steps', () => {
|
|
119
|
+
const narratives: Narrative[] = [
|
|
120
|
+
{
|
|
121
|
+
name: 'Test',
|
|
122
|
+
slices: [
|
|
123
|
+
{
|
|
124
|
+
name: 'test',
|
|
125
|
+
type: 'react',
|
|
126
|
+
server: {
|
|
127
|
+
description: 'Test',
|
|
128
|
+
specs: [
|
|
129
|
+
{
|
|
130
|
+
type: 'gherkin',
|
|
131
|
+
feature: 'Test',
|
|
132
|
+
rules: [
|
|
133
|
+
{
|
|
134
|
+
name: 'test rule',
|
|
135
|
+
examples: [
|
|
136
|
+
{
|
|
137
|
+
name: 'test example',
|
|
138
|
+
steps: [{ keyword: 'And', text: 'UserLoggedIn' }],
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
152
|
+
|
|
153
|
+
expect(keys.has('event:UserLoggedIn')).toBe(true);
|
|
154
|
+
expect(keys.has('state:UserLoggedIn')).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('collects event and state keys from Then steps', () => {
|
|
158
|
+
const narratives: Narrative[] = [
|
|
159
|
+
{
|
|
160
|
+
name: 'Test',
|
|
161
|
+
slices: [
|
|
162
|
+
{
|
|
163
|
+
name: 'test',
|
|
164
|
+
type: 'command',
|
|
165
|
+
client: { specs: [] },
|
|
166
|
+
server: {
|
|
167
|
+
description: 'Test',
|
|
168
|
+
specs: [
|
|
169
|
+
{
|
|
170
|
+
type: 'gherkin',
|
|
171
|
+
feature: 'Test',
|
|
172
|
+
rules: [
|
|
173
|
+
{
|
|
174
|
+
name: 'test rule',
|
|
175
|
+
examples: [
|
|
176
|
+
{
|
|
177
|
+
name: 'test example',
|
|
178
|
+
steps: [{ keyword: 'Then', text: 'OrderCompleted' }],
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
192
|
+
|
|
193
|
+
expect(keys.has('event:OrderCompleted')).toBe(true);
|
|
194
|
+
expect(keys.has('state:OrderCompleted')).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('handles slices without server specs', () => {
|
|
198
|
+
const narratives: Narrative[] = [
|
|
199
|
+
{
|
|
200
|
+
name: 'Test',
|
|
201
|
+
slices: [
|
|
202
|
+
{
|
|
203
|
+
name: 'test',
|
|
204
|
+
type: 'experience',
|
|
205
|
+
client: { specs: [] },
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
212
|
+
|
|
213
|
+
expect(keys.size).toBe(0);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('handles specs without rules', () => {
|
|
217
|
+
const narratives: Narrative[] = [
|
|
218
|
+
{
|
|
219
|
+
name: 'Test',
|
|
220
|
+
slices: [
|
|
221
|
+
{
|
|
222
|
+
name: 'test',
|
|
223
|
+
type: 'command',
|
|
224
|
+
client: { specs: [] },
|
|
225
|
+
server: {
|
|
226
|
+
description: 'Test',
|
|
227
|
+
specs: [{ type: 'gherkin', feature: 'Test', rules: [] }],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
235
|
+
|
|
236
|
+
expect(keys.size).toBe(0);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('handles rules without examples', () => {
|
|
240
|
+
const narratives: Narrative[] = [
|
|
241
|
+
{
|
|
242
|
+
name: 'Test',
|
|
243
|
+
slices: [
|
|
244
|
+
{
|
|
245
|
+
name: 'test',
|
|
246
|
+
type: 'command',
|
|
247
|
+
client: { specs: [] },
|
|
248
|
+
server: {
|
|
249
|
+
description: 'Test',
|
|
250
|
+
specs: [
|
|
251
|
+
{
|
|
252
|
+
type: 'gherkin',
|
|
253
|
+
feature: 'Test',
|
|
254
|
+
rules: [{ name: 'empty rule', examples: [] }],
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
264
|
+
|
|
265
|
+
expect(keys.size).toBe(0);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('handles examples without steps', () => {
|
|
269
|
+
const narratives: Narrative[] = [
|
|
270
|
+
{
|
|
271
|
+
name: 'Test',
|
|
272
|
+
slices: [
|
|
273
|
+
{
|
|
274
|
+
name: 'test',
|
|
275
|
+
type: 'command',
|
|
276
|
+
client: { specs: [] },
|
|
277
|
+
server: {
|
|
278
|
+
description: 'Test',
|
|
279
|
+
specs: [
|
|
280
|
+
{
|
|
281
|
+
type: 'gherkin',
|
|
282
|
+
feature: 'Test',
|
|
283
|
+
rules: [
|
|
284
|
+
{
|
|
285
|
+
name: 'rule',
|
|
286
|
+
examples: [{ name: 'empty example', steps: [] }],
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
298
|
+
|
|
299
|
+
expect(keys.size).toBe(0);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('handles steps with empty text', () => {
|
|
303
|
+
const narratives: Narrative[] = [
|
|
304
|
+
{
|
|
305
|
+
name: 'Test',
|
|
306
|
+
slices: [
|
|
307
|
+
{
|
|
308
|
+
name: 'test',
|
|
309
|
+
type: 'command',
|
|
310
|
+
client: { specs: [] },
|
|
311
|
+
server: {
|
|
312
|
+
description: 'Test',
|
|
313
|
+
specs: [
|
|
314
|
+
{
|
|
315
|
+
type: 'gherkin',
|
|
316
|
+
feature: 'Test',
|
|
317
|
+
rules: [
|
|
318
|
+
{
|
|
319
|
+
name: 'rule',
|
|
320
|
+
examples: [
|
|
321
|
+
{
|
|
322
|
+
name: 'example',
|
|
323
|
+
steps: [{ keyword: 'When', text: '' }],
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
337
|
+
|
|
338
|
+
expect(keys.size).toBe(0);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('collects keys from multiple narratives and slices', () => {
|
|
342
|
+
const narratives: Narrative[] = [
|
|
343
|
+
{
|
|
344
|
+
name: 'Orders',
|
|
345
|
+
slices: [
|
|
346
|
+
{
|
|
347
|
+
name: 'create',
|
|
348
|
+
type: 'command',
|
|
349
|
+
client: { specs: [] },
|
|
350
|
+
server: {
|
|
351
|
+
description: 'Test',
|
|
352
|
+
specs: [
|
|
353
|
+
{
|
|
354
|
+
type: 'gherkin',
|
|
355
|
+
feature: 'Test',
|
|
356
|
+
rules: [
|
|
357
|
+
{
|
|
358
|
+
name: 'rule',
|
|
359
|
+
examples: [
|
|
360
|
+
{
|
|
361
|
+
name: 'example',
|
|
362
|
+
steps: [
|
|
363
|
+
{ keyword: 'When', text: 'CreateOrder' },
|
|
364
|
+
{ keyword: 'Then', text: 'OrderCreated' },
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: 'Users',
|
|
378
|
+
slices: [
|
|
379
|
+
{
|
|
380
|
+
name: 'register',
|
|
381
|
+
type: 'command',
|
|
382
|
+
client: { specs: [] },
|
|
383
|
+
server: {
|
|
384
|
+
description: 'Test',
|
|
385
|
+
specs: [
|
|
386
|
+
{
|
|
387
|
+
type: 'gherkin',
|
|
388
|
+
feature: 'Test',
|
|
389
|
+
rules: [
|
|
390
|
+
{
|
|
391
|
+
name: 'rule',
|
|
392
|
+
examples: [
|
|
393
|
+
{
|
|
394
|
+
name: 'example',
|
|
395
|
+
steps: [
|
|
396
|
+
{ keyword: 'When', text: 'RegisterUser' },
|
|
397
|
+
{ keyword: 'Then', text: 'UserRegistered' },
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
},
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
const keys = collectMessageKeysFromNarratives(narratives);
|
|
412
|
+
|
|
413
|
+
expect(keys.has('command:CreateOrder')).toBe(true);
|
|
414
|
+
expect(keys.has('event:OrderCreated')).toBe(true);
|
|
415
|
+
expect(keys.has('command:RegisterUser')).toBe(true);
|
|
416
|
+
expect(keys.has('event:UserRegistered')).toBe(true);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Narrative } from '../../index';
|
|
2
|
+
|
|
3
|
+
export function collectMessageKeysFromNarratives(narratives: Narrative[]): Set<string> {
|
|
4
|
+
const usedKeys = new Set<string>();
|
|
5
|
+
|
|
6
|
+
for (const narrative of narratives) {
|
|
7
|
+
for (const slice of narrative.slices) {
|
|
8
|
+
collectMessageKeysFromSlice(slice, usedKeys);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return usedKeys;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function collectMessageKeysFromSlice(slice: Narrative['slices'][number], usedKeys: Set<string>): void {
|
|
16
|
+
if (slice.type !== 'command' && slice.type !== 'query' && slice.type !== 'react') {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const specs = slice.server?.specs;
|
|
21
|
+
if (!Array.isArray(specs)) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const spec of specs) {
|
|
26
|
+
if (!spec.rules || !Array.isArray(spec.rules)) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const rule of spec.rules) {
|
|
31
|
+
if (!rule.examples || !Array.isArray(rule.examples)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const example of rule.examples) {
|
|
36
|
+
if (!example.steps || !Array.isArray(example.steps)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const step of example.steps) {
|
|
41
|
+
collectMessageKeyFromStep(step, usedKeys);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function collectMessageKeyFromStep(step: { keyword?: string; text?: string }, usedKeys: Set<string>): void {
|
|
49
|
+
if (!('text' in step) || !step.text) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const keyword = step.keyword;
|
|
54
|
+
if (keyword === 'Given' || keyword === 'And') {
|
|
55
|
+
usedKeys.add(`event:${step.text}`);
|
|
56
|
+
usedKeys.add(`state:${step.text}`);
|
|
57
|
+
} else if (keyword === 'When') {
|
|
58
|
+
usedKeys.add(`command:${step.text}`);
|
|
59
|
+
} else if (keyword === 'Then') {
|
|
60
|
+
usedKeys.add(`event:${step.text}`);
|
|
61
|
+
usedKeys.add(`state:${step.text}`);
|
|
62
|
+
}
|
|
63
|
+
}
|