@scenetest/scenes 0.1.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/LICENSE +21 -0
- package/dist/__tests__/devices.test.d.ts +2 -0
- package/dist/__tests__/devices.test.d.ts.map +1 -0
- package/dist/__tests__/devices.test.js +117 -0
- package/dist/__tests__/devices.test.js.map +1 -0
- package/dist/__tests__/dsl.test.d.ts +2 -0
- package/dist/__tests__/dsl.test.d.ts.map +1 -0
- package/dist/__tests__/dsl.test.js +385 -0
- package/dist/__tests__/dsl.test.js.map +1 -0
- package/dist/__tests__/markdown-scene.test.d.ts +2 -0
- package/dist/__tests__/markdown-scene.test.d.ts.map +1 -0
- package/dist/__tests__/markdown-scene.test.js +508 -0
- package/dist/__tests__/markdown-scene.test.js.map +1 -0
- package/dist/__tests__/reactive.test.d.ts +2 -0
- package/dist/__tests__/reactive.test.d.ts.map +1 -0
- package/dist/__tests__/reactive.test.js +383 -0
- package/dist/__tests__/reactive.test.js.map +1 -0
- package/dist/__tests__/swarm.test.d.ts +2 -0
- package/dist/__tests__/swarm.test.d.ts.map +1 -0
- package/dist/__tests__/swarm.test.js +214 -0
- package/dist/__tests__/swarm.test.js.map +1 -0
- package/dist/actor.d.ts +104 -0
- package/dist/actor.d.ts.map +1 -0
- package/dist/actor.js +527 -0
- package/dist/actor.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +273 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +21 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +120 -0
- package/dist/config.js.map +1 -0
- package/dist/devices.d.ts +55 -0
- package/dist/devices.d.ts.map +1 -0
- package/dist/devices.js +167 -0
- package/dist/devices.js.map +1 -0
- package/dist/dsl.d.ts +99 -0
- package/dist/dsl.d.ts.map +1 -0
- package/dist/dsl.js +247 -0
- package/dist/dsl.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +9 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +27 -0
- package/dist/init.js.map +1 -0
- package/dist/loader.d.ts +2 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +10 -0
- package/dist/loader.js.map +1 -0
- package/dist/markdown-scene.d.ts +120 -0
- package/dist/markdown-scene.d.ts.map +1 -0
- package/dist/markdown-scene.js +452 -0
- package/dist/markdown-scene.js.map +1 -0
- package/dist/message-bus.d.ts +31 -0
- package/dist/message-bus.d.ts.map +1 -0
- package/dist/message-bus.js +74 -0
- package/dist/message-bus.js.map +1 -0
- package/dist/reactive.d.ts +267 -0
- package/dist/reactive.d.ts.map +1 -0
- package/dist/reactive.js +779 -0
- package/dist/reactive.js.map +1 -0
- package/dist/runner.d.ts +51 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +306 -0
- package/dist/runner.js.map +1 -0
- package/dist/scene.d.ts +40 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +110 -0
- package/dist/scene.js.map +1 -0
- package/dist/selectors.d.ts +57 -0
- package/dist/selectors.d.ts.map +1 -0
- package/dist/selectors.js +193 -0
- package/dist/selectors.js.map +1 -0
- package/dist/swarm.d.ts +64 -0
- package/dist/swarm.d.ts.map +1 -0
- package/dist/swarm.js +306 -0
- package/dist/swarm.js.map +1 -0
- package/dist/team-manager.d.ts +120 -0
- package/dist/team-manager.d.ts.map +1 -0
- package/dist/team-manager.js +267 -0
- package/dist/team-manager.js.map +1 -0
- package/dist/types.d.ts +653 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { ConcurrentActorHandleImpl, drainAll } from '../reactive.js';
|
|
3
|
+
import { MessageBus } from '../message-bus.js';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Helpers — minimal Playwright mocks
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
function mockLocator(visible = true) {
|
|
8
|
+
return {
|
|
9
|
+
waitFor: vi.fn().mockResolvedValue(undefined),
|
|
10
|
+
isVisible: vi.fn().mockResolvedValue(visible),
|
|
11
|
+
click: vi.fn().mockResolvedValue(undefined),
|
|
12
|
+
fill: vi.fn().mockResolvedValue(undefined),
|
|
13
|
+
check: vi.fn().mockResolvedValue(undefined),
|
|
14
|
+
selectOption: vi.fn().mockResolvedValue(undefined),
|
|
15
|
+
evaluate: vi.fn().mockResolvedValue(undefined),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Create a mock Page that stubs the methods ConcurrentActorHandleImpl uses */
|
|
19
|
+
function mockPage() {
|
|
20
|
+
const locator = mockLocator();
|
|
21
|
+
return {
|
|
22
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
23
|
+
getByText: vi.fn().mockReturnValue({ first: () => locator }),
|
|
24
|
+
locator: vi.fn().mockReturnValue(locator),
|
|
25
|
+
evaluate: vi.fn().mockResolvedValue(undefined),
|
|
26
|
+
// Used by resolveSelector — it calls page.locator(css)
|
|
27
|
+
// We need to handle the selector resolution chain
|
|
28
|
+
_locator: locator,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build a ConcurrentActorHandleImpl with mocked dependencies.
|
|
33
|
+
* Returns the actor plus shared timeline/warnings arrays for inspection.
|
|
34
|
+
*/
|
|
35
|
+
function createTestActor(role = 'user', overrides) {
|
|
36
|
+
const bus = overrides?.bus ?? new MessageBus();
|
|
37
|
+
const timeline = [];
|
|
38
|
+
const warnings = [];
|
|
39
|
+
const page = overrides?.page ?? mockPage();
|
|
40
|
+
const actor = new ConcurrentActorHandleImpl(role, { id: `${role}-1`, username: role, email: `${role}@test.com`, password: 'pass' }, page, bus, timeline, warnings,
|
|
41
|
+
/* actionTimeout */ 5000,
|
|
42
|
+
/* warnAfter */ 60000 // high so warnings don't fire during tests
|
|
43
|
+
);
|
|
44
|
+
return { actor, bus, timeline, warnings, page };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a ConcurrentActorHandleImpl with deferred page (null).
|
|
48
|
+
* Simulates how flow() creates actors before browser init.
|
|
49
|
+
*/
|
|
50
|
+
function createDeferredActor(role = 'user') {
|
|
51
|
+
const bus = new MessageBus();
|
|
52
|
+
const timeline = [];
|
|
53
|
+
const warnings = [];
|
|
54
|
+
const page = mockPage();
|
|
55
|
+
const actor = new ConcurrentActorHandleImpl(role, { id: `${role}-1`, username: role, email: `${role}@test.com`, password: 'pass' }, null, // deferred — page set later via _setPage
|
|
56
|
+
bus, timeline, warnings, 5000, 60000);
|
|
57
|
+
return { actor, bus, timeline, warnings, page };
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Tests
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
describe('ConcurrentActorHandleImpl', () => {
|
|
63
|
+
describe('queueing (declaration phase)', () => {
|
|
64
|
+
it('queues actions without executing', () => {
|
|
65
|
+
const { actor, page } = createTestActor();
|
|
66
|
+
actor.openTo('/login');
|
|
67
|
+
actor.see('form').typeInto('email', 'a@b.com').click('submit');
|
|
68
|
+
expect(actor.pending).toBe(4);
|
|
69
|
+
// Nothing should have been called on the page yet
|
|
70
|
+
expect(page.goto).not.toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
it('chains fluently — every method returns the same actor', () => {
|
|
73
|
+
const { actor } = createTestActor();
|
|
74
|
+
const returned = actor
|
|
75
|
+
.openTo('/')
|
|
76
|
+
.see('nav')
|
|
77
|
+
.click('login')
|
|
78
|
+
.typeInto('email', 'x')
|
|
79
|
+
.check('remember')
|
|
80
|
+
.select('role', 'admin')
|
|
81
|
+
.wait(100)
|
|
82
|
+
.emit('done')
|
|
83
|
+
.waitFor('other')
|
|
84
|
+
.notSee('spinner')
|
|
85
|
+
.seeText('Welcome')
|
|
86
|
+
.seeToast('saved')
|
|
87
|
+
.scrollToBottom()
|
|
88
|
+
.up('header')
|
|
89
|
+
.prev()
|
|
90
|
+
.do(async () => { });
|
|
91
|
+
expect(returned).toBe(actor);
|
|
92
|
+
expect(actor.pending).toBe(16);
|
|
93
|
+
});
|
|
94
|
+
it('starts with zero pending actions', () => {
|
|
95
|
+
const { actor } = createTestActor();
|
|
96
|
+
expect(actor.pending).toBe(0);
|
|
97
|
+
});
|
|
98
|
+
it('forwards config properties', () => {
|
|
99
|
+
const { actor } = createTestActor('alice');
|
|
100
|
+
expect(actor.role).toBe('alice');
|
|
101
|
+
expect(actor.id).toBe('alice-1');
|
|
102
|
+
expect(actor.username).toBe('alice');
|
|
103
|
+
expect(actor.email).toBe('alice@test.com');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('drain (execution phase)', () => {
|
|
107
|
+
it('executes queued actions in order', async () => {
|
|
108
|
+
const order = [];
|
|
109
|
+
const { actor } = createTestActor();
|
|
110
|
+
actor.do(async () => { order.push('first'); });
|
|
111
|
+
actor.do(async () => { order.push('second'); });
|
|
112
|
+
actor.do(async () => { order.push('third'); });
|
|
113
|
+
await actor.drain();
|
|
114
|
+
expect(order).toEqual(['first', 'second', 'third']);
|
|
115
|
+
});
|
|
116
|
+
it('clears the queue after draining', async () => {
|
|
117
|
+
const { actor } = createTestActor();
|
|
118
|
+
actor.do(async () => { });
|
|
119
|
+
actor.do(async () => { });
|
|
120
|
+
expect(actor.pending).toBe(2);
|
|
121
|
+
await actor.drain();
|
|
122
|
+
expect(actor.pending).toBe(0);
|
|
123
|
+
});
|
|
124
|
+
it('records timeline entries', async () => {
|
|
125
|
+
const { actor, timeline } = createTestActor();
|
|
126
|
+
actor.do(async () => { });
|
|
127
|
+
actor.do(async () => { });
|
|
128
|
+
await actor.drain();
|
|
129
|
+
expect(timeline).toHaveLength(2);
|
|
130
|
+
expect(timeline[0].action).toBe('do');
|
|
131
|
+
expect(timeline[0].actor).toBe('user');
|
|
132
|
+
expect(timeline[0].duration).toBeDefined();
|
|
133
|
+
});
|
|
134
|
+
it('records errors in timeline entries', async () => {
|
|
135
|
+
const { actor, timeline } = createTestActor();
|
|
136
|
+
actor.do(async () => {
|
|
137
|
+
throw new Error('boom');
|
|
138
|
+
});
|
|
139
|
+
await expect(actor.drain()).rejects.toThrow('boom');
|
|
140
|
+
expect(timeline).toHaveLength(1);
|
|
141
|
+
expect(timeline[0].error).toBe('boom');
|
|
142
|
+
});
|
|
143
|
+
it('throws if drain called while already draining', async () => {
|
|
144
|
+
const { actor } = createTestActor();
|
|
145
|
+
// Queue a slow action
|
|
146
|
+
actor.do(async () => {
|
|
147
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
148
|
+
});
|
|
149
|
+
const p1 = actor.drain();
|
|
150
|
+
await expect(actor.drain()).rejects.toThrow('already draining');
|
|
151
|
+
await p1;
|
|
152
|
+
});
|
|
153
|
+
it('stops execution when aborted', async () => {
|
|
154
|
+
const order = [];
|
|
155
|
+
const { actor } = createTestActor();
|
|
156
|
+
actor.do(async () => {
|
|
157
|
+
order.push('first');
|
|
158
|
+
// Abort mid-drain
|
|
159
|
+
actor.abort('peer failed');
|
|
160
|
+
});
|
|
161
|
+
actor.do(async () => {
|
|
162
|
+
order.push('second'); // should NOT execute
|
|
163
|
+
});
|
|
164
|
+
await expect(actor.drain()).rejects.toThrow('aborted');
|
|
165
|
+
expect(order).toEqual(['first']);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe('coordination', () => {
|
|
169
|
+
it('emit() pushes message to bus during drain', async () => {
|
|
170
|
+
const bus = new MessageBus();
|
|
171
|
+
const { actor } = createTestActor('alice', { bus });
|
|
172
|
+
actor.emit('hello');
|
|
173
|
+
expect(bus.hasEmitted('hello')).toBe(false);
|
|
174
|
+
await actor.drain();
|
|
175
|
+
expect(bus.hasEmitted('hello')).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
it('waitFor() blocks until message arrives', async () => {
|
|
178
|
+
const bus = new MessageBus();
|
|
179
|
+
const { actor } = createTestActor('bob', { bus });
|
|
180
|
+
const order = [];
|
|
181
|
+
actor.do(async () => { order.push('before-wait'); });
|
|
182
|
+
actor.waitFor('ready');
|
|
183
|
+
actor.do(async () => { order.push('after-wait'); });
|
|
184
|
+
// Start draining — will block at waitFor
|
|
185
|
+
const drainPromise = actor.drain();
|
|
186
|
+
// Give drain a tick to reach the waitFor
|
|
187
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
188
|
+
expect(order).toEqual(['before-wait']);
|
|
189
|
+
// Emit the message — unblocks waitFor
|
|
190
|
+
bus.emit('ready');
|
|
191
|
+
await drainPromise;
|
|
192
|
+
expect(order).toEqual(['before-wait', 'after-wait']);
|
|
193
|
+
});
|
|
194
|
+
it('waitFor() resolves immediately if message already emitted (sticky)', async () => {
|
|
195
|
+
const bus = new MessageBus();
|
|
196
|
+
bus.emit('already-sent');
|
|
197
|
+
const { actor } = createTestActor('bob', { bus });
|
|
198
|
+
const order = [];
|
|
199
|
+
actor.waitFor('already-sent');
|
|
200
|
+
actor.do(async () => { order.push('after'); });
|
|
201
|
+
await actor.drain();
|
|
202
|
+
expect(order).toEqual(['after']);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
describe('if() — conditional monitors', () => {
|
|
206
|
+
it('captures sub-actions during declaration without affecting main queue', () => {
|
|
207
|
+
const { actor } = createTestActor();
|
|
208
|
+
actor.do(async () => { });
|
|
209
|
+
actor.if('modal', a => {
|
|
210
|
+
a.do(async () => { });
|
|
211
|
+
a.do(async () => { });
|
|
212
|
+
});
|
|
213
|
+
actor.do(async () => { });
|
|
214
|
+
// Main queue should have 2 actions (the two do() calls), not 4
|
|
215
|
+
expect(actor.pending).toBe(2);
|
|
216
|
+
});
|
|
217
|
+
it('fires sub-actions inline when selector is visible during an action', async () => {
|
|
218
|
+
const { actor } = createTestActor();
|
|
219
|
+
const order = [];
|
|
220
|
+
// Register a conditional monitor
|
|
221
|
+
actor.if('modal', a => {
|
|
222
|
+
a.do(async () => { order.push('dismiss-modal'); });
|
|
223
|
+
});
|
|
224
|
+
// Queue a slow action during which the monitor should fire
|
|
225
|
+
actor.do(async () => {
|
|
226
|
+
order.push('action-start');
|
|
227
|
+
// Simulate a slow action
|
|
228
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
229
|
+
order.push('action-end');
|
|
230
|
+
});
|
|
231
|
+
actor.do(async () => { order.push('after'); });
|
|
232
|
+
// We need the monitor to actually detect "modal" as visible.
|
|
233
|
+
// Since we're using mocks, the resolveSelector won't find anything,
|
|
234
|
+
// so the monitor won't fire. This test verifies the flow completes
|
|
235
|
+
// without the monitor firing (no visible selector).
|
|
236
|
+
await actor.drain();
|
|
237
|
+
expect(order).toEqual(['action-start', 'action-end', 'after']);
|
|
238
|
+
});
|
|
239
|
+
it('does not affect drain when monitor never triggers', async () => {
|
|
240
|
+
const { actor } = createTestActor();
|
|
241
|
+
const order = [];
|
|
242
|
+
actor.if('nonexistent', a => {
|
|
243
|
+
a.do(async () => { order.push('should-not-run'); });
|
|
244
|
+
});
|
|
245
|
+
actor.do(async () => { order.push('normal'); });
|
|
246
|
+
await actor.drain();
|
|
247
|
+
// Sub-actions should NOT have run
|
|
248
|
+
expect(order).toEqual(['normal']);
|
|
249
|
+
});
|
|
250
|
+
it('queue-swap restores main queue even if callback throws', () => {
|
|
251
|
+
const { actor } = createTestActor();
|
|
252
|
+
actor.do(async () => { });
|
|
253
|
+
expect(() => {
|
|
254
|
+
actor.if('modal', () => {
|
|
255
|
+
throw new Error('callback error');
|
|
256
|
+
});
|
|
257
|
+
}).toThrow('callback error');
|
|
258
|
+
// Main queue should still be intact
|
|
259
|
+
expect(actor.pending).toBe(1);
|
|
260
|
+
});
|
|
261
|
+
it('supports chained sub-actions inside if()', () => {
|
|
262
|
+
const { actor } = createTestActor();
|
|
263
|
+
actor.if('modal', a => {
|
|
264
|
+
a.click('dismiss').wait(100).click('confirm');
|
|
265
|
+
});
|
|
266
|
+
// Main queue unaffected
|
|
267
|
+
expect(actor.pending).toBe(0);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
describe('deferred page initialization', () => {
|
|
272
|
+
it('allows queueing actions before page is set', () => {
|
|
273
|
+
const { actor } = createDeferredActor();
|
|
274
|
+
actor.openTo('/login');
|
|
275
|
+
actor.see('form').typeInto('email', 'a@b.com').click('submit');
|
|
276
|
+
expect(actor.pending).toBe(4);
|
|
277
|
+
});
|
|
278
|
+
it('exposes config properties before page is set', () => {
|
|
279
|
+
const { actor } = createDeferredActor('alice');
|
|
280
|
+
expect(actor.role).toBe('alice');
|
|
281
|
+
expect(actor.id).toBe('alice-1');
|
|
282
|
+
expect(actor.email).toBe('alice@test.com');
|
|
283
|
+
});
|
|
284
|
+
it('throws when accessing page before initialization', () => {
|
|
285
|
+
const { actor } = createDeferredActor();
|
|
286
|
+
expect(() => actor.page).toThrow('page not initialized');
|
|
287
|
+
});
|
|
288
|
+
it('throws when draining without page', async () => {
|
|
289
|
+
const { actor } = createDeferredActor();
|
|
290
|
+
actor.do(async () => { });
|
|
291
|
+
await expect(actor.drain()).rejects.toThrow('page not initialized');
|
|
292
|
+
});
|
|
293
|
+
it('drains successfully after _setPage', async () => {
|
|
294
|
+
const { actor, page } = createDeferredActor();
|
|
295
|
+
const order = [];
|
|
296
|
+
actor.do(async () => { order.push('first'); });
|
|
297
|
+
actor.do(async () => { order.push('second'); });
|
|
298
|
+
// Set page — simulates flow runner phase 2
|
|
299
|
+
actor._setPage(page);
|
|
300
|
+
await actor.drain();
|
|
301
|
+
expect(order).toEqual(['first', 'second']);
|
|
302
|
+
});
|
|
303
|
+
it('page getter works after _setPage', () => {
|
|
304
|
+
const { actor, page } = createDeferredActor();
|
|
305
|
+
actor._setPage(page);
|
|
306
|
+
expect(() => actor.page).not.toThrow();
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
describe('drainAll', () => {
|
|
310
|
+
it('drains multiple actors concurrently', async () => {
|
|
311
|
+
const events = [];
|
|
312
|
+
const { actor: alice } = createTestActor('alice');
|
|
313
|
+
const { actor: bob } = createTestActor('bob');
|
|
314
|
+
alice.do(async () => {
|
|
315
|
+
events.push('alice-start');
|
|
316
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
317
|
+
events.push('alice-end');
|
|
318
|
+
});
|
|
319
|
+
bob.do(async () => {
|
|
320
|
+
events.push('bob-start');
|
|
321
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
322
|
+
events.push('bob-end');
|
|
323
|
+
});
|
|
324
|
+
await drainAll([alice, bob]);
|
|
325
|
+
// Both should have started before either finished
|
|
326
|
+
// (bob finishes first because it's shorter)
|
|
327
|
+
expect(events.indexOf('alice-start')).toBeLessThan(events.indexOf('alice-end'));
|
|
328
|
+
expect(events.indexOf('bob-start')).toBeLessThan(events.indexOf('bob-end'));
|
|
329
|
+
// Both started (we don't assert start ordering because it's nondeterministic,
|
|
330
|
+
// but both must have completed)
|
|
331
|
+
expect(events).toContain('alice-end');
|
|
332
|
+
expect(events).toContain('bob-end');
|
|
333
|
+
});
|
|
334
|
+
it('aborts other actors when one fails', async () => {
|
|
335
|
+
const { actor: alice } = createTestActor('alice');
|
|
336
|
+
const { actor: bob } = createTestActor('bob');
|
|
337
|
+
alice.do(async () => {
|
|
338
|
+
throw new Error('alice broke');
|
|
339
|
+
});
|
|
340
|
+
bob.do(async () => {
|
|
341
|
+
// Slow action — should get aborted
|
|
342
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
343
|
+
});
|
|
344
|
+
bob.do(async () => {
|
|
345
|
+
// Should never execute
|
|
346
|
+
});
|
|
347
|
+
await expect(drainAll([alice, bob])).rejects.toThrow('alice broke');
|
|
348
|
+
// Bob should have been aborted
|
|
349
|
+
expect(bob.aborted).toBe(true);
|
|
350
|
+
});
|
|
351
|
+
it('handles empty actor list', async () => {
|
|
352
|
+
await drainAll([]);
|
|
353
|
+
// Should not throw
|
|
354
|
+
});
|
|
355
|
+
it('handles single actor', async () => {
|
|
356
|
+
const { actor } = createTestActor();
|
|
357
|
+
const order = [];
|
|
358
|
+
actor.do(async () => { order.push('only'); });
|
|
359
|
+
await drainAll([actor]);
|
|
360
|
+
expect(order).toEqual(['only']);
|
|
361
|
+
});
|
|
362
|
+
it('cross-actor coordination via message bus', async () => {
|
|
363
|
+
const bus = new MessageBus();
|
|
364
|
+
const { actor: alice } = createTestActor('alice', { bus });
|
|
365
|
+
const { actor: bob } = createTestActor('bob', { bus });
|
|
366
|
+
const events = [];
|
|
367
|
+
// Alice does work then signals
|
|
368
|
+
alice.do(async () => {
|
|
369
|
+
events.push('alice-work');
|
|
370
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
371
|
+
});
|
|
372
|
+
alice.emit('alice-done');
|
|
373
|
+
// Bob waits for alice then proceeds
|
|
374
|
+
bob.waitFor('alice-done');
|
|
375
|
+
bob.do(async () => {
|
|
376
|
+
events.push('bob-after-alice');
|
|
377
|
+
});
|
|
378
|
+
await drainAll([alice, bob]);
|
|
379
|
+
// Bob must have waited for alice
|
|
380
|
+
expect(events.indexOf('alice-work')).toBeLessThan(events.indexOf('bob-after-alice'));
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
//# sourceMappingURL=reactive.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reactive.test.js","sourceRoot":"","sources":["../../src/__tests__/reactive.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swarm.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/swarm.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { SwarmTrigger, resolveSwarmConfig } from '../swarm.js';
|
|
3
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
4
|
+
function makeSceneReport(name, status = 'completed', assertions = [{ result: true }]) {
|
|
5
|
+
return {
|
|
6
|
+
name,
|
|
7
|
+
file: `scenes/${name}.spec.ts`,
|
|
8
|
+
status,
|
|
9
|
+
teamIndex: 0,
|
|
10
|
+
actors: {},
|
|
11
|
+
assertions: assertions.map((a) => ({
|
|
12
|
+
type: a.result ? 'pass' : 'fail',
|
|
13
|
+
description: 'test',
|
|
14
|
+
result: a.result,
|
|
15
|
+
timestamp: Date.now(),
|
|
16
|
+
})),
|
|
17
|
+
warnings: [],
|
|
18
|
+
timeline: [],
|
|
19
|
+
duration: 100,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function makeRunReport(scenes) {
|
|
23
|
+
return {
|
|
24
|
+
timestamp: new Date().toISOString(),
|
|
25
|
+
duration: 1000,
|
|
26
|
+
scenes,
|
|
27
|
+
summary: {
|
|
28
|
+
scenes: scenes.length,
|
|
29
|
+
completed: scenes.filter((s) => s.status === 'completed').length,
|
|
30
|
+
failed: scenes.filter((s) => s.status !== 'completed').length,
|
|
31
|
+
assertions: {
|
|
32
|
+
total: scenes.reduce((sum, s) => sum + s.assertions.length, 0),
|
|
33
|
+
passed: scenes.reduce((sum, s) => sum + s.assertions.filter((a) => a.result).length, 0),
|
|
34
|
+
failed: scenes.reduce((sum, s) => sum + s.assertions.filter((a) => !a.result).length, 0),
|
|
35
|
+
},
|
|
36
|
+
warnings: 0,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
41
|
+
describe('resolveSwarmConfig', () => {
|
|
42
|
+
it('provides defaults when no config given', () => {
|
|
43
|
+
const resolved = resolveSwarmConfig();
|
|
44
|
+
expect(resolved.failureThreshold).toBe(5);
|
|
45
|
+
expect(resolved.windowSize).toBe(3);
|
|
46
|
+
expect(resolved.concurrency).toBe(Infinity);
|
|
47
|
+
expect(resolved.repeats).toBe(3);
|
|
48
|
+
expect(resolved.auto).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
it('merges partial config with defaults', () => {
|
|
51
|
+
const resolved = resolveSwarmConfig({ failureThreshold: 10, concurrency: 2 });
|
|
52
|
+
expect(resolved.failureThreshold).toBe(10);
|
|
53
|
+
expect(resolved.concurrency).toBe(2);
|
|
54
|
+
expect(resolved.windowSize).toBe(3); // default
|
|
55
|
+
expect(resolved.repeats).toBe(3); // default
|
|
56
|
+
expect(resolved.auto).toBe(true); // default
|
|
57
|
+
});
|
|
58
|
+
it('overrides all defaults when full config given', () => {
|
|
59
|
+
const config = {
|
|
60
|
+
failureThreshold: 2,
|
|
61
|
+
windowSize: 5,
|
|
62
|
+
concurrency: 4,
|
|
63
|
+
repeats: 10,
|
|
64
|
+
auto: false,
|
|
65
|
+
};
|
|
66
|
+
const resolved = resolveSwarmConfig(config);
|
|
67
|
+
expect(resolved).toEqual(config);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('SwarmTrigger', () => {
|
|
71
|
+
describe('recordRun', () => {
|
|
72
|
+
it('tracks scene outcomes across runs', () => {
|
|
73
|
+
const trigger = new SwarmTrigger({ failureThreshold: 3, windowSize: 5 });
|
|
74
|
+
// Run 1: scene passes
|
|
75
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login')]));
|
|
76
|
+
const record = trigger.getRecord('login');
|
|
77
|
+
expect(record).toBeDefined();
|
|
78
|
+
expect(record.history).toEqual([true]);
|
|
79
|
+
expect(record.consecutiveFailures).toBe(0);
|
|
80
|
+
});
|
|
81
|
+
it('counts consecutive failures', () => {
|
|
82
|
+
const trigger = new SwarmTrigger({ failureThreshold: 3, windowSize: 5 });
|
|
83
|
+
// Passes
|
|
84
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login')]));
|
|
85
|
+
expect(trigger.getRecord('login').consecutiveFailures).toBe(0);
|
|
86
|
+
// Fails
|
|
87
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
88
|
+
expect(trigger.getRecord('login').consecutiveFailures).toBe(1);
|
|
89
|
+
// Fails again
|
|
90
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
91
|
+
expect(trigger.getRecord('login').consecutiveFailures).toBe(2);
|
|
92
|
+
});
|
|
93
|
+
it('resets consecutive count on pass', () => {
|
|
94
|
+
const trigger = new SwarmTrigger({ failureThreshold: 5, windowSize: 5 });
|
|
95
|
+
// Two failures
|
|
96
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
97
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
98
|
+
expect(trigger.getRecord('login').consecutiveFailures).toBe(2);
|
|
99
|
+
// Pass breaks the streak
|
|
100
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login')]));
|
|
101
|
+
expect(trigger.getRecord('login').consecutiveFailures).toBe(0);
|
|
102
|
+
});
|
|
103
|
+
it('treats completed scenes with failed assertions as failures', () => {
|
|
104
|
+
const trigger = new SwarmTrigger({ failureThreshold: 3, windowSize: 5 });
|
|
105
|
+
// Scene completes but has a failed assertion
|
|
106
|
+
const scene = makeSceneReport('login', 'completed', [{ result: false }]);
|
|
107
|
+
trigger.recordRun(makeRunReport([scene]));
|
|
108
|
+
expect(trigger.getRecord('login').consecutiveFailures).toBe(1);
|
|
109
|
+
});
|
|
110
|
+
it('trims history to window size', () => {
|
|
111
|
+
const trigger = new SwarmTrigger({ failureThreshold: 5, windowSize: 3 });
|
|
112
|
+
// Record 5 runs
|
|
113
|
+
for (let i = 0; i < 5; i++) {
|
|
114
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login')]));
|
|
115
|
+
}
|
|
116
|
+
// History should be trimmed to 3
|
|
117
|
+
expect(trigger.getRecord('login').history).toHaveLength(3);
|
|
118
|
+
});
|
|
119
|
+
it('tracks multiple scenes independently', () => {
|
|
120
|
+
const trigger = new SwarmTrigger({ failureThreshold: 3, windowSize: 5 });
|
|
121
|
+
trigger.recordRun(makeRunReport([
|
|
122
|
+
makeSceneReport('login'),
|
|
123
|
+
makeSceneReport('profile', 'failed'),
|
|
124
|
+
]));
|
|
125
|
+
expect(trigger.getRecord('login').consecutiveFailures).toBe(0);
|
|
126
|
+
expect(trigger.getRecord('profile').consecutiveFailures).toBe(1);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('shouldTrigger', () => {
|
|
130
|
+
it('does not trigger when failures are below threshold', () => {
|
|
131
|
+
const trigger = new SwarmTrigger({ failureThreshold: 3, windowSize: 5 });
|
|
132
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
133
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
134
|
+
expect(trigger.shouldTrigger()).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
it('triggers when consecutive failures reach threshold', () => {
|
|
137
|
+
const trigger = new SwarmTrigger({ failureThreshold: 3, windowSize: 5 });
|
|
138
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
139
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
140
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
141
|
+
expect(trigger.shouldTrigger()).toEqual(['login']);
|
|
142
|
+
});
|
|
143
|
+
it('returns multiple triggered scenes', () => {
|
|
144
|
+
const trigger = new SwarmTrigger({ failureThreshold: 2, windowSize: 5 });
|
|
145
|
+
trigger.recordRun(makeRunReport([
|
|
146
|
+
makeSceneReport('login', 'failed'),
|
|
147
|
+
makeSceneReport('profile', 'failed'),
|
|
148
|
+
]));
|
|
149
|
+
trigger.recordRun(makeRunReport([
|
|
150
|
+
makeSceneReport('login', 'failed'),
|
|
151
|
+
makeSceneReport('profile', 'failed'),
|
|
152
|
+
]));
|
|
153
|
+
const triggered = trigger.shouldTrigger();
|
|
154
|
+
expect(triggered).toContain('login');
|
|
155
|
+
expect(triggered).toContain('profile');
|
|
156
|
+
expect(triggered).toHaveLength(2);
|
|
157
|
+
});
|
|
158
|
+
it('does not trigger when auto is disabled', () => {
|
|
159
|
+
const trigger = new SwarmTrigger({ failureThreshold: 1, auto: false });
|
|
160
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
161
|
+
expect(trigger.shouldTrigger()).toEqual([]);
|
|
162
|
+
});
|
|
163
|
+
it('only triggers for scenes that exceed threshold, not all', () => {
|
|
164
|
+
const trigger = new SwarmTrigger({ failureThreshold: 2, windowSize: 5 });
|
|
165
|
+
// login fails twice, profile fails once
|
|
166
|
+
trigger.recordRun(makeRunReport([
|
|
167
|
+
makeSceneReport('login', 'failed'),
|
|
168
|
+
makeSceneReport('profile', 'failed'),
|
|
169
|
+
]));
|
|
170
|
+
trigger.recordRun(makeRunReport([
|
|
171
|
+
makeSceneReport('login', 'failed'),
|
|
172
|
+
makeSceneReport('profile'), // passes this time
|
|
173
|
+
]));
|
|
174
|
+
expect(trigger.shouldTrigger()).toEqual(['login']);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe('clear', () => {
|
|
178
|
+
it('resets all tracking data', () => {
|
|
179
|
+
const trigger = new SwarmTrigger({ failureThreshold: 1 });
|
|
180
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
181
|
+
expect(trigger.shouldTrigger()).toEqual(['login']);
|
|
182
|
+
trigger.clear();
|
|
183
|
+
expect(trigger.shouldTrigger()).toEqual([]);
|
|
184
|
+
expect(trigger.getRecord('login')).toBeUndefined();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
describe('threshold edge cases', () => {
|
|
188
|
+
it('threshold of 1 triggers on first failure', () => {
|
|
189
|
+
const trigger = new SwarmTrigger({ failureThreshold: 1, windowSize: 3 });
|
|
190
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
191
|
+
expect(trigger.shouldTrigger()).toEqual(['login']);
|
|
192
|
+
});
|
|
193
|
+
it('a pass after threshold no longer triggers', () => {
|
|
194
|
+
const trigger = new SwarmTrigger({ failureThreshold: 2, windowSize: 5 });
|
|
195
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
196
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
197
|
+
expect(trigger.shouldTrigger()).toEqual(['login']);
|
|
198
|
+
// Now it passes
|
|
199
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login')]));
|
|
200
|
+
expect(trigger.shouldTrigger()).toEqual([]);
|
|
201
|
+
});
|
|
202
|
+
it('interleaved pass/fail does not trigger', () => {
|
|
203
|
+
const trigger = new SwarmTrigger({ failureThreshold: 3, windowSize: 10 });
|
|
204
|
+
// fail, pass, fail, pass, fail — never 3 consecutive
|
|
205
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
206
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login')]));
|
|
207
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
208
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login')]));
|
|
209
|
+
trigger.recordRun(makeRunReport([makeSceneReport('login', 'failed')]));
|
|
210
|
+
expect(trigger.shouldTrigger()).toEqual([]);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
//# sourceMappingURL=swarm.test.js.map
|