@polagram/core 0.0.3 → 0.0.4
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/dist/index.d.ts +104 -6
- package/dist/polagram-core.js +2689 -2157
- package/dist/polagram-core.umd.cjs +20 -14
- package/dist/src/api.d.ts +12 -3
- package/dist/src/api.js +26 -3
- package/dist/src/config/schema.d.ts +16 -0
- package/dist/src/config/schema.js +5 -1
- package/dist/src/generator/generators/plantuml.d.ts +17 -0
- package/dist/src/generator/generators/plantuml.js +131 -0
- package/dist/src/generator/generators/plantuml.test.d.ts +1 -0
- package/dist/src/generator/generators/plantuml.test.js +143 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/parser/base/lexer.d.ts +3 -3
- package/dist/src/parser/base/parser.d.ts +9 -9
- package/dist/src/parser/base/token.d.ts +18 -0
- package/dist/src/parser/base/token.js +1 -0
- package/dist/src/parser/base/tokens.d.ts +8 -0
- package/dist/src/parser/base/tokens.js +1 -0
- package/dist/src/parser/format-detector.d.ts +55 -0
- package/dist/src/parser/format-detector.js +98 -0
- package/dist/src/parser/index.d.ts +1 -0
- package/dist/src/parser/index.js +4 -0
- package/dist/src/parser/languages/mermaid/lexer.d.ts +1 -1
- package/dist/src/parser/languages/mermaid/parser.d.ts +2 -1
- package/dist/src/parser/languages/plantuml/index.d.ts +4 -0
- package/dist/src/parser/languages/plantuml/index.js +11 -0
- package/dist/src/parser/languages/plantuml/lexer.d.ts +15 -0
- package/dist/src/parser/languages/plantuml/lexer.js +143 -0
- package/dist/src/parser/languages/plantuml/parser.d.ts +23 -0
- package/dist/src/parser/languages/plantuml/parser.js +481 -0
- package/dist/src/parser/languages/plantuml/parser.test.d.ts +1 -0
- package/dist/src/parser/languages/plantuml/parser.test.js +236 -0
- package/dist/src/parser/languages/plantuml/tokens.d.ts +9 -0
- package/dist/src/parser/languages/plantuml/tokens.js +1 -0
- package/dist/src/transformer/orchestration/engine.test.js +12 -1
- package/dist/src/transformer/selector/matcher.test.js +17 -0
- package/dist/src/transformer/traverse/walker.test.js +67 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -9
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Lexer } from './lexer';
|
|
3
|
+
import { Parser } from './parser';
|
|
4
|
+
describe('PlantUML Parser', () => {
|
|
5
|
+
const parse = (input) => {
|
|
6
|
+
const lexer = new Lexer(input);
|
|
7
|
+
const parser = new Parser(lexer);
|
|
8
|
+
return parser.parse();
|
|
9
|
+
};
|
|
10
|
+
describe('Basic Document Structure', () => {
|
|
11
|
+
it('should parse @startuml/@enduml block', () => {
|
|
12
|
+
const input = `
|
|
13
|
+
@startuml
|
|
14
|
+
@enduml
|
|
15
|
+
`;
|
|
16
|
+
const ast = parse(input);
|
|
17
|
+
expect(ast.kind).toBe('root');
|
|
18
|
+
expect(ast.meta.source).toBe('plantuml');
|
|
19
|
+
});
|
|
20
|
+
it('should parse title', () => {
|
|
21
|
+
const input = `
|
|
22
|
+
@startuml
|
|
23
|
+
title My Diagram
|
|
24
|
+
@enduml
|
|
25
|
+
`;
|
|
26
|
+
const ast = parse(input);
|
|
27
|
+
expect(ast.meta.title).toBe('My Diagram');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('Participants & Types', () => {
|
|
31
|
+
it('should parse various participant types', () => {
|
|
32
|
+
const input = `
|
|
33
|
+
@startuml
|
|
34
|
+
actor User
|
|
35
|
+
database DB
|
|
36
|
+
participant "Service Wrapper" as Svc
|
|
37
|
+
@enduml
|
|
38
|
+
`;
|
|
39
|
+
const ast = parse(input);
|
|
40
|
+
expect(ast.participants).toHaveLength(3);
|
|
41
|
+
expect(ast.participants[0]).toMatchObject({ id: 'User', name: 'User', type: 'actor' });
|
|
42
|
+
expect(ast.participants[1]).toMatchObject({ id: 'DB', name: 'DB', type: 'database' });
|
|
43
|
+
expect(ast.participants[2]).toMatchObject({ id: 'Svc', name: 'Service Wrapper', type: 'participant' });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('Messages & Arrows', () => {
|
|
47
|
+
it('should parse sync and reply messages', () => {
|
|
48
|
+
const input = `
|
|
49
|
+
@startuml
|
|
50
|
+
A -> B : Sync Call
|
|
51
|
+
B --> A : Reply
|
|
52
|
+
A -> A : Internal
|
|
53
|
+
@enduml
|
|
54
|
+
`;
|
|
55
|
+
const ast = parse(input);
|
|
56
|
+
expect(ast.events).toHaveLength(3);
|
|
57
|
+
expect(ast.events[0]).toMatchObject({
|
|
58
|
+
kind: 'message',
|
|
59
|
+
from: 'A',
|
|
60
|
+
to: 'B',
|
|
61
|
+
text: 'Sync Call',
|
|
62
|
+
type: 'sync',
|
|
63
|
+
style: { line: 'solid', head: 'arrow' }
|
|
64
|
+
});
|
|
65
|
+
expect(ast.events[1]).toMatchObject({
|
|
66
|
+
kind: 'message',
|
|
67
|
+
from: 'B',
|
|
68
|
+
to: 'A',
|
|
69
|
+
text: 'Reply',
|
|
70
|
+
type: 'reply',
|
|
71
|
+
style: { line: 'dotted', head: 'arrow' } // PlantUML --> is dotted arrow
|
|
72
|
+
});
|
|
73
|
+
expect(ast.events[2]).toMatchObject({
|
|
74
|
+
kind: 'message',
|
|
75
|
+
from: 'A',
|
|
76
|
+
to: 'A',
|
|
77
|
+
text: 'Internal'
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe('Lifecycle', () => {
|
|
82
|
+
it('should parse activate and deactivate', () => {
|
|
83
|
+
const input = `
|
|
84
|
+
@startuml
|
|
85
|
+
activate A
|
|
86
|
+
A -> B
|
|
87
|
+
deactivate A
|
|
88
|
+
@enduml
|
|
89
|
+
`;
|
|
90
|
+
const ast = parse(input);
|
|
91
|
+
expect(ast.events).toHaveLength(3);
|
|
92
|
+
expect(ast.events[0]).toMatchObject({ kind: 'activation', participantId: 'A', action: 'activate' });
|
|
93
|
+
expect(ast.events[1]).toMatchObject({ kind: 'message', from: 'A', to: 'B' });
|
|
94
|
+
expect(ast.events[2]).toMatchObject({ kind: 'activation', participantId: 'A', action: 'deactivate' });
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('Notes', () => {
|
|
98
|
+
it('should parse notes with position', () => {
|
|
99
|
+
const input = `
|
|
100
|
+
@startuml
|
|
101
|
+
note left of A : Note Left
|
|
102
|
+
note right of A : Note Right
|
|
103
|
+
note over A : Note Over
|
|
104
|
+
note over A, B : Shared Note
|
|
105
|
+
@enduml
|
|
106
|
+
`;
|
|
107
|
+
const ast = parse(input);
|
|
108
|
+
expect(ast.events).toHaveLength(4);
|
|
109
|
+
expect(ast.events[0]).toMatchObject({ kind: 'note', position: 'left', participantIds: ['A'], text: 'Note Left' });
|
|
110
|
+
expect(ast.events[1]).toMatchObject({ kind: 'note', position: 'right', participantIds: ['A'], text: 'Note Right' });
|
|
111
|
+
expect(ast.events[2]).toMatchObject({ kind: 'note', position: 'over', participantIds: ['A'], text: 'Note Over' });
|
|
112
|
+
expect(ast.events[3]).toMatchObject({ kind: 'note', position: 'over', participantIds: ['A', 'B'], text: 'Shared Note' });
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('Fragments', () => {
|
|
116
|
+
it('should parse alt/else/end', () => {
|
|
117
|
+
const input = `
|
|
118
|
+
@startuml
|
|
119
|
+
alt success
|
|
120
|
+
A -> B : OK
|
|
121
|
+
else failure
|
|
122
|
+
A -> B : Fail
|
|
123
|
+
end
|
|
124
|
+
@enduml
|
|
125
|
+
`;
|
|
126
|
+
const ast = parse(input);
|
|
127
|
+
expect(ast.events).toHaveLength(1);
|
|
128
|
+
const fragment = ast.events[0];
|
|
129
|
+
expect(fragment.kind).toBe('fragment');
|
|
130
|
+
expect(fragment.operator).toBe('alt');
|
|
131
|
+
expect(fragment.branches).toHaveLength(2);
|
|
132
|
+
expect(fragment.branches[0].condition).toBe('success');
|
|
133
|
+
expect(fragment.branches[0].events).toHaveLength(1);
|
|
134
|
+
expect(fragment.branches[1].condition).toBe('failure');
|
|
135
|
+
expect(fragment.branches[1].events).toHaveLength(1);
|
|
136
|
+
});
|
|
137
|
+
it('should parse opt/loop', () => {
|
|
138
|
+
const input = `
|
|
139
|
+
@startuml
|
|
140
|
+
opt maybe
|
|
141
|
+
A -> B : Maybe
|
|
142
|
+
end
|
|
143
|
+
loop forever
|
|
144
|
+
A -> B : Repeat
|
|
145
|
+
end
|
|
146
|
+
@enduml
|
|
147
|
+
`;
|
|
148
|
+
const ast = parse(input);
|
|
149
|
+
expect(ast.events).toHaveLength(2);
|
|
150
|
+
expect(ast.events[0].kind).toBe('fragment');
|
|
151
|
+
expect(ast.events[0].operator).toBe('opt');
|
|
152
|
+
expect(ast.events[1].kind).toBe('fragment');
|
|
153
|
+
expect(ast.events[1].operator).toBe('loop');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe('Grouping', () => {
|
|
157
|
+
it('should parse box with participants', () => {
|
|
158
|
+
const input = `
|
|
159
|
+
@startuml
|
|
160
|
+
box "Internal Service" #LightBlue
|
|
161
|
+
participant A
|
|
162
|
+
participant B
|
|
163
|
+
end box
|
|
164
|
+
participant C
|
|
165
|
+
@enduml
|
|
166
|
+
`;
|
|
167
|
+
const ast = parse(input);
|
|
168
|
+
expect(ast.groups).toHaveLength(1);
|
|
169
|
+
const box = ast.groups[0];
|
|
170
|
+
expect(box.kind).toBe('group');
|
|
171
|
+
expect(box.name).toBe('Internal Service');
|
|
172
|
+
expect(box.style?.backgroundColor).toBe('#LightBlue');
|
|
173
|
+
expect(box.participantIds).toEqual(['A', 'B']);
|
|
174
|
+
expect(ast.participants).toHaveLength(3);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe('Edge Cases', () => {
|
|
178
|
+
it('should ignore single line comments', () => {
|
|
179
|
+
const input = `
|
|
180
|
+
@startuml
|
|
181
|
+
' This is a comment
|
|
182
|
+
A -> B : Hello ' Another comment? No, this is part of string usually?
|
|
183
|
+
' Comment line
|
|
184
|
+
@enduml
|
|
185
|
+
`;
|
|
186
|
+
const ast = parse(input);
|
|
187
|
+
expect(ast.events).toHaveLength(1);
|
|
188
|
+
expect(ast.events[0]).toMatchObject({ text: "Hello ' Another comment? No, this is part of string usually?" });
|
|
189
|
+
});
|
|
190
|
+
it('should parse nested fragments', () => {
|
|
191
|
+
const input = `
|
|
192
|
+
@startuml
|
|
193
|
+
alt outer
|
|
194
|
+
A -> B : Outer
|
|
195
|
+
loop inner
|
|
196
|
+
A -> B : Inner
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
@enduml
|
|
200
|
+
`;
|
|
201
|
+
const ast = parse(input);
|
|
202
|
+
expect(ast.events).toHaveLength(1);
|
|
203
|
+
const outer = ast.events[0];
|
|
204
|
+
expect(outer.operator).toBe('alt');
|
|
205
|
+
const outerEvents = outer.branches[0].events;
|
|
206
|
+
expect(outerEvents).toHaveLength(2);
|
|
207
|
+
expect(outerEvents[0].kind).toBe('message');
|
|
208
|
+
const inner = outerEvents[1];
|
|
209
|
+
expect(inner.kind).toBe('fragment');
|
|
210
|
+
expect(inner.operator).toBe('loop');
|
|
211
|
+
expect(inner.branches[0].events).toHaveLength(1);
|
|
212
|
+
});
|
|
213
|
+
it('should parse multi-line notes', () => {
|
|
214
|
+
const input = `
|
|
215
|
+
@startuml
|
|
216
|
+
note left of A
|
|
217
|
+
Line 1
|
|
218
|
+
Line 2
|
|
219
|
+
end note
|
|
220
|
+
@enduml
|
|
221
|
+
`;
|
|
222
|
+
const ast = parse(input);
|
|
223
|
+
expect(ast.events).toHaveLength(1);
|
|
224
|
+
expect(ast.events[0]).toMatchObject({
|
|
225
|
+
kind: 'note',
|
|
226
|
+
position: 'left',
|
|
227
|
+
participantIds: ['A'],
|
|
228
|
+
// text should contain lines. trim() might remove leading/trailing.
|
|
229
|
+
// We probably want to preserve internal newlines.
|
|
230
|
+
});
|
|
231
|
+
const text = ast.events[0].text;
|
|
232
|
+
expect(text).toContain('Line 1');
|
|
233
|
+
expect(text).toContain('Line 2');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type TokenType = 'START_UML' | 'END_UML' | 'NEWLINE' | 'EOF' | 'UNKNOWN' | 'IDENTIFIER' | 'STRING' | 'TITLE' | 'PARTICIPANT' | 'ACTOR' | 'DATABASE' | 'ACTIVATE' | 'DEACTIVATE' | 'NOTE' | 'OF' | 'LEFT' | 'RIGHT' | 'OVER' | 'ALT' | 'OPT' | 'LOOP' | 'ELSE' | 'END' | 'BOX' | 'AS' | 'ARROW' | 'COLON' | 'COMMA';
|
|
2
|
+
export interface Token {
|
|
3
|
+
type: TokenType;
|
|
4
|
+
literal: string;
|
|
5
|
+
line: number;
|
|
6
|
+
column: number;
|
|
7
|
+
start: number;
|
|
8
|
+
end: number;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { TransformationEngine } from './engine';
|
|
3
3
|
describe('TransformationEngine (Pipeline Integration)', () => {
|
|
4
4
|
const createAst = (participants, events) => ({
|
|
@@ -38,4 +38,15 @@ describe('TransformationEngine (Pipeline Integration)', () => {
|
|
|
38
38
|
// 3. Cleaner (Unused) should have removed C and D
|
|
39
39
|
expect(result.participants.map(p => p.id).sort()).toEqual(['A0', 'B0']);
|
|
40
40
|
});
|
|
41
|
+
it('handles unknown actions gracefully (identity)', () => {
|
|
42
|
+
const root = createAst([pA], [msgAB]);
|
|
43
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
44
|
+
// @ts-ignore - purposefully passing invalid action
|
|
45
|
+
const result = new TransformationEngine().transform(root, [{ action: 'invalid' }]);
|
|
46
|
+
expect(result).toEqual(root); // Logic remains identity, but object reference changes due to CoW cleaners
|
|
47
|
+
// Actually structure cleaner typically returns new object.
|
|
48
|
+
// But main point is it didn't crash.
|
|
49
|
+
expect(warnSpy).toHaveBeenCalledWith('Unknown action: invalid');
|
|
50
|
+
warnSpy.mockRestore();
|
|
51
|
+
});
|
|
41
52
|
});
|
|
@@ -50,4 +50,21 @@ describe('Matcher', () => {
|
|
|
50
50
|
expect(matcher.matchMessage(msg, { kind: 'message', to: 'db' })).toBe(false);
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
|
+
describe('Branch Matching', () => {
|
|
54
|
+
const branch = { id: 'b1', condition: 'valid', events: [] };
|
|
55
|
+
it('matches by operator', () => {
|
|
56
|
+
// Operator matching is against the PARENT fragment's operator
|
|
57
|
+
expect(matcher.matchBranch(branch, { kind: 'fragment', operator: 'alt' }, 'alt')).toBe(true);
|
|
58
|
+
expect(matcher.matchBranch(branch, { kind: 'fragment', operator: 'loop' }, 'alt')).toBe(false);
|
|
59
|
+
expect(matcher.matchBranch(branch, { kind: 'fragment', operator: ['alt', 'opt'] }, 'alt')).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
it('matches by condition', () => {
|
|
62
|
+
expect(matcher.matchBranch(branch, { kind: 'fragment', condition: 'valid' }, 'alt')).toBe(true);
|
|
63
|
+
expect(matcher.matchBranch(branch, { kind: 'fragment', condition: 'invalid' }, 'alt')).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
it('returns false if branch has no condition but selector requires one', () => {
|
|
66
|
+
const noCondBranch = { id: 'b2', events: [] };
|
|
67
|
+
expect(matcher.matchBranch(noCondBranch, { kind: 'fragment', condition: 'needed' }, 'alt')).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
53
70
|
});
|
|
@@ -2,6 +2,33 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
import { Walker } from './walker';
|
|
3
3
|
class IdentityWalker extends Walker {
|
|
4
4
|
}
|
|
5
|
+
class FilteringWalker extends Walker {
|
|
6
|
+
// Removes messages starting with 'DROP'
|
|
7
|
+
visitEvent(node) {
|
|
8
|
+
if (node.kind === 'message' && node.text.startsWith('DROP')) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
return super.visitEvent(node);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
class MutatingWalker extends Walker {
|
|
15
|
+
// Uppercases message text
|
|
16
|
+
visitEvent(node) {
|
|
17
|
+
if (node.kind === 'message') {
|
|
18
|
+
return [{ ...node, text: node.text.toUpperCase() }];
|
|
19
|
+
}
|
|
20
|
+
return super.visitEvent(node);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
class ExpandingWalker extends Walker {
|
|
24
|
+
// Duplicates messages
|
|
25
|
+
visitEvent(node) {
|
|
26
|
+
if (node.kind === 'message') {
|
|
27
|
+
return [node, { ...node, id: node.id + '_copy', text: node.text + ' (Copy)' }];
|
|
28
|
+
}
|
|
29
|
+
return super.visitEvent(node);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
5
32
|
describe('Walker (Base Traversal)', () => {
|
|
6
33
|
const createAst = (events) => ({
|
|
7
34
|
kind: 'root',
|
|
@@ -11,7 +38,7 @@ describe('Walker (Base Traversal)', () => {
|
|
|
11
38
|
events
|
|
12
39
|
});
|
|
13
40
|
const msg = {
|
|
14
|
-
kind: 'message', id: 'm1', text: '
|
|
41
|
+
kind: 'message', id: 'm1', text: 'hello', from: 'A', to: 'B',
|
|
15
42
|
type: 'sync', style: { line: 'solid', head: 'arrow' }
|
|
16
43
|
};
|
|
17
44
|
it('returns events as is by default', () => {
|
|
@@ -20,7 +47,7 @@ describe('Walker (Base Traversal)', () => {
|
|
|
20
47
|
expect(result.events).toHaveLength(1);
|
|
21
48
|
expect(result.events[0].id).toBe('m1');
|
|
22
49
|
});
|
|
23
|
-
it('recursively traverses fragments (Deep
|
|
50
|
+
it('recursively traverses fragments (Deep Identity)', () => {
|
|
24
51
|
const fragment = {
|
|
25
52
|
kind: 'fragment', id: 'f1', operator: 'alt',
|
|
26
53
|
branches: [
|
|
@@ -33,7 +60,7 @@ describe('Walker (Base Traversal)', () => {
|
|
|
33
60
|
expect(resFrag.branches[0].events).toHaveLength(1);
|
|
34
61
|
expect(resFrag.branches[0].events[0].id).toBe('m1');
|
|
35
62
|
});
|
|
36
|
-
it('does not mutate original AST
|
|
63
|
+
it('does not mutate original AST', () => {
|
|
37
64
|
const fragment = {
|
|
38
65
|
kind: 'fragment', id: 'f1', operator: 'alt',
|
|
39
66
|
branches: [
|
|
@@ -41,8 +68,44 @@ describe('Walker (Base Traversal)', () => {
|
|
|
41
68
|
]
|
|
42
69
|
};
|
|
43
70
|
const root = createAst([fragment]);
|
|
44
|
-
const result = new IdentityWalker().transform(root);
|
|
71
|
+
const result = new IdentityWalker().transform(root);
|
|
45
72
|
expect(result).not.toBe(root); // Root object is new
|
|
46
73
|
expect(result.events).not.toBe(root.events); // Events array is new (map)
|
|
47
74
|
});
|
|
75
|
+
describe('Subclass Behaviors', () => {
|
|
76
|
+
it('supports filtering (removing nodes)', () => {
|
|
77
|
+
const dropMsg = { ...msg, id: 'm2', text: 'DROP me' };
|
|
78
|
+
const root = createAst([msg, dropMsg]);
|
|
79
|
+
const result = new FilteringWalker().transform(root);
|
|
80
|
+
expect(result.events).toHaveLength(1);
|
|
81
|
+
expect(result.events[0].id).toBe('m1');
|
|
82
|
+
});
|
|
83
|
+
it('supports mutation (modifying nodes)', () => {
|
|
84
|
+
const root = createAst([msg]);
|
|
85
|
+
const result = new MutatingWalker().transform(root);
|
|
86
|
+
expect(result.events[0].text).toBe('HELLO');
|
|
87
|
+
});
|
|
88
|
+
it('supports expansion (1->N nodes)', () => {
|
|
89
|
+
const root = createAst([msg]);
|
|
90
|
+
const result = new ExpandingWalker().transform(root);
|
|
91
|
+
expect(result.events).toHaveLength(2);
|
|
92
|
+
expect(result.events[0].text).toBe('hello');
|
|
93
|
+
expect(result.events[1].text).toBe('hello (Copy)');
|
|
94
|
+
});
|
|
95
|
+
it('handles nested mutations/filtering', () => {
|
|
96
|
+
const dropMsg = { ...msg, id: 'm2', text: 'DROP nested' };
|
|
97
|
+
const fragment = {
|
|
98
|
+
kind: 'fragment', id: 'f1', operator: 'loop',
|
|
99
|
+
branches: [
|
|
100
|
+
{ id: 'b1', events: [msg, dropMsg] }
|
|
101
|
+
]
|
|
102
|
+
};
|
|
103
|
+
const root = createAst([fragment]);
|
|
104
|
+
// Should filter inside the fragment
|
|
105
|
+
const result = new FilteringWalker().transform(root);
|
|
106
|
+
const resFrag = result.events[0];
|
|
107
|
+
expect(resFrag.branches[0].events).toHaveLength(1);
|
|
108
|
+
expect(resFrag.branches[0].events[0].text).toBe('hello');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
48
111
|
});
|