@blac/core 2.0.0-rc-13 → 2.0.0-rc-14
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.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +522 -297
- package/dist/index.es.js +1833 -1550
- package/dist/index.es.js.map +1 -1
- package/package.json +6 -7
- package/src/Blac.ts +38 -71
- package/src/Bloc.ts +8 -9
- package/src/BlocBase.ts +246 -540
- package/src/Cubit.ts +1 -16
- package/src/__tests__/Blac.config.test.ts +10 -17
- package/src/__tests__/Bloc.event.test.ts +27 -52
- package/src/__tests__/BlocBase.subscription.test.ts +349 -0
- package/src/__tests__/Cubit.test.ts +39 -50
- package/src/__tests__/memory-leaks.test.ts +383 -0
- package/src/__tests__/plugins.test.ts +107 -97
- package/src/adapter/BlacAdapter.ts +347 -207
- package/src/adapter/ProxyFactory.ts +196 -255
- package/src/adapter/__tests__/BlacAdapter.test.ts +71 -77
- package/src/adapter/__tests__/ProxyFactory.test.ts +1 -1
- package/src/adapter/index.ts +0 -1
- package/src/errors/BlacError.ts +69 -0
- package/src/errors/ErrorManager.ts +101 -0
- package/src/errors/handleError.ts +114 -0
- package/src/errors/index.ts +13 -0
- package/src/events.ts +7 -1
- package/src/index.ts +7 -1
- package/src/lifecycle/BlocLifecycle.ts +156 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/plugins/BlocPluginRegistry.ts +71 -51
- package/src/plugins/SystemPluginRegistry.ts +63 -13
- package/src/plugins/__tests__/BlocPluginRegistry.test.ts +450 -0
- package/src/plugins/__tests__/SystemPluginRegistry.test.ts +404 -0
- package/src/plugins/types.ts +32 -3
- package/src/subscription/README.md +132 -0
- package/src/subscription/SubscriptionManager.ts +395 -0
- package/src/subscription/__tests__/SubscriptionManager.test.ts +355 -0
- package/src/subscription/index.ts +2 -0
- package/src/subscription/types.ts +90 -0
- package/src/testing.ts +31 -37
- package/src/types.ts +4 -21
- package/src/utils/BatchingManager.ts +59 -0
- package/src/utils/RerenderLogger.ts +265 -0
- package/src/utils/__tests__/generateInstanceId.test.ts +104 -0
- package/src/utils/__tests__/shallowEqual.test.ts +171 -0
- package/src/utils/__tests__/uuid.test.ts +144 -0
- package/src/utils/generateInstanceId.ts +54 -0
- package/src/utils/shallowEqual.ts +1 -1
- package/README.md +0 -329
- package/src/BlacObserver.ts +0 -189
- package/src/__tests__/BlocBase.lifecycle.test.ts +0 -441
- package/src/__tests__/props.test.ts +0 -256
- package/src/adapter/ConsumerTracker.ts +0 -241
- package/src/adapter/__tests__/ConsumerTracker.test.ts +0 -562
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { BlocPluginRegistry } from '../BlocPluginRegistry';
|
|
3
|
+
import { BlocPlugin } from '../types';
|
|
4
|
+
|
|
5
|
+
// Mock bloc for testing
|
|
6
|
+
class MockBloc {
|
|
7
|
+
state = { count: 0 };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Sample plugins for testing
|
|
11
|
+
const createTestPlugin = (
|
|
12
|
+
name: string,
|
|
13
|
+
overrides?: Partial<BlocPlugin<any, any>>,
|
|
14
|
+
): BlocPlugin<any, any> => ({
|
|
15
|
+
name,
|
|
16
|
+
version: '1.0.0',
|
|
17
|
+
...overrides,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('BlocPluginRegistry', () => {
|
|
21
|
+
let registry: BlocPluginRegistry<any, any>;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
registry = new BlocPluginRegistry();
|
|
25
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('Plugin Management', () => {
|
|
29
|
+
it('should add plugins successfully', () => {
|
|
30
|
+
const plugin = createTestPlugin('test-plugin');
|
|
31
|
+
|
|
32
|
+
registry.add(plugin);
|
|
33
|
+
|
|
34
|
+
expect(registry.get('test-plugin')).toBe(plugin);
|
|
35
|
+
expect(registry.getAll()).toContain(plugin);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should throw error when adding duplicate plugin', () => {
|
|
39
|
+
const plugin = createTestPlugin('test-plugin');
|
|
40
|
+
|
|
41
|
+
registry.add(plugin);
|
|
42
|
+
|
|
43
|
+
expect(() => registry.add(plugin)).toThrow(
|
|
44
|
+
"Plugin 'test-plugin' is already registered",
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should maintain execution order', () => {
|
|
49
|
+
const plugin1 = createTestPlugin('plugin-1');
|
|
50
|
+
const plugin2 = createTestPlugin('plugin-2');
|
|
51
|
+
const plugin3 = createTestPlugin('plugin-3');
|
|
52
|
+
|
|
53
|
+
registry.add(plugin1);
|
|
54
|
+
registry.add(plugin2);
|
|
55
|
+
registry.add(plugin3);
|
|
56
|
+
|
|
57
|
+
const all = registry.getAll();
|
|
58
|
+
expect(all[0]).toBe(plugin1);
|
|
59
|
+
expect(all[1]).toBe(plugin2);
|
|
60
|
+
expect(all[2]).toBe(plugin3);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should remove plugins', () => {
|
|
64
|
+
const plugin = createTestPlugin('test-plugin');
|
|
65
|
+
|
|
66
|
+
registry.add(plugin);
|
|
67
|
+
expect(registry.get('test-plugin')).toBe(plugin);
|
|
68
|
+
|
|
69
|
+
const removed = registry.remove('test-plugin');
|
|
70
|
+
expect(removed).toBe(true);
|
|
71
|
+
expect(registry.get('test-plugin')).toBeUndefined();
|
|
72
|
+
expect(registry.getAll()).not.toContain(plugin);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return false when removing non-existent plugin', () => {
|
|
76
|
+
const removed = registry.remove('non-existent');
|
|
77
|
+
expect(removed).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should clear all plugins', () => {
|
|
81
|
+
registry.add(createTestPlugin('plugin-1'));
|
|
82
|
+
registry.add(createTestPlugin('plugin-2'));
|
|
83
|
+
registry.add(createTestPlugin('plugin-3'));
|
|
84
|
+
|
|
85
|
+
expect(registry.getAll().length).toBe(3);
|
|
86
|
+
|
|
87
|
+
registry.clear();
|
|
88
|
+
|
|
89
|
+
expect(registry.getAll().length).toBe(0);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('Capability Validation', () => {
|
|
94
|
+
it('should validate transformState requires readState', () => {
|
|
95
|
+
const plugin = createTestPlugin('test-plugin', {
|
|
96
|
+
capabilities: {
|
|
97
|
+
readState: false,
|
|
98
|
+
transformState: true,
|
|
99
|
+
interceptEvents: false,
|
|
100
|
+
persistData: false,
|
|
101
|
+
accessMetadata: false,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(() => registry.add(plugin)).toThrow(
|
|
106
|
+
"Plugin 'test-plugin': transformState requires readState capability",
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should validate interceptEvents requires readState', () => {
|
|
111
|
+
const plugin = createTestPlugin('test-plugin', {
|
|
112
|
+
capabilities: {
|
|
113
|
+
readState: false,
|
|
114
|
+
transformState: false,
|
|
115
|
+
interceptEvents: true,
|
|
116
|
+
persistData: false,
|
|
117
|
+
accessMetadata: false,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(() => registry.add(plugin)).toThrow(
|
|
122
|
+
"Plugin 'test-plugin': interceptEvents requires readState capability",
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should allow valid capability combinations', () => {
|
|
127
|
+
const plugin = createTestPlugin('test-plugin', {
|
|
128
|
+
capabilities: {
|
|
129
|
+
readState: true,
|
|
130
|
+
transformState: true,
|
|
131
|
+
interceptEvents: true,
|
|
132
|
+
persistData: false,
|
|
133
|
+
accessMetadata: false,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(() => registry.add(plugin)).not.toThrow();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('Lifecycle Hooks', () => {
|
|
142
|
+
it('should call onAttach for all plugins', () => {
|
|
143
|
+
const onAttach1 = vi.fn();
|
|
144
|
+
const onAttach2 = vi.fn();
|
|
145
|
+
|
|
146
|
+
registry.add(createTestPlugin('plugin-1', { onAttach: onAttach1 }));
|
|
147
|
+
registry.add(createTestPlugin('plugin-2', { onAttach: onAttach2 }));
|
|
148
|
+
|
|
149
|
+
const bloc = new MockBloc();
|
|
150
|
+
registry.attach(bloc);
|
|
151
|
+
|
|
152
|
+
expect(onAttach1).toHaveBeenCalledWith(bloc);
|
|
153
|
+
expect(onAttach2).toHaveBeenCalledWith(bloc);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should throw error when attaching twice', () => {
|
|
157
|
+
registry.add(createTestPlugin('plugin-1'));
|
|
158
|
+
|
|
159
|
+
const bloc = new MockBloc();
|
|
160
|
+
registry.attach(bloc);
|
|
161
|
+
|
|
162
|
+
expect(() => registry.attach(bloc)).toThrow(
|
|
163
|
+
'Plugins are already attached',
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should remove plugin if onAttach fails', () => {
|
|
168
|
+
const failingPlugin = createTestPlugin('failing-plugin', {
|
|
169
|
+
onAttach: () => {
|
|
170
|
+
throw new Error('Attach failed');
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
registry.add(failingPlugin);
|
|
175
|
+
registry.add(createTestPlugin('good-plugin'));
|
|
176
|
+
|
|
177
|
+
const bloc = new MockBloc();
|
|
178
|
+
registry.attach(bloc);
|
|
179
|
+
|
|
180
|
+
expect(registry.get('failing-plugin')).toBeUndefined();
|
|
181
|
+
expect(registry.get('good-plugin')).toBeDefined();
|
|
182
|
+
expect(console.error).toHaveBeenCalled();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should call onDetach when removing attached plugin', () => {
|
|
186
|
+
const onDetach = vi.fn();
|
|
187
|
+
const plugin = createTestPlugin('test-plugin', { onDetach });
|
|
188
|
+
|
|
189
|
+
registry.add(plugin);
|
|
190
|
+
registry.attach(new MockBloc());
|
|
191
|
+
registry.remove('test-plugin');
|
|
192
|
+
|
|
193
|
+
expect(onDetach).toHaveBeenCalled();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should call onDetach for all plugins when clearing', () => {
|
|
197
|
+
const onDetach1 = vi.fn();
|
|
198
|
+
const onDetach2 = vi.fn();
|
|
199
|
+
|
|
200
|
+
registry.add(createTestPlugin('plugin-1', { onDetach: onDetach1 }));
|
|
201
|
+
registry.add(createTestPlugin('plugin-2', { onDetach: onDetach2 }));
|
|
202
|
+
|
|
203
|
+
registry.attach(new MockBloc());
|
|
204
|
+
registry.clear();
|
|
205
|
+
|
|
206
|
+
expect(onDetach1).toHaveBeenCalled();
|
|
207
|
+
expect(onDetach2).toHaveBeenCalled();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('State Transformation', () => {
|
|
212
|
+
it('should transform state through all plugins', () => {
|
|
213
|
+
const plugin1 = createTestPlugin('plugin-1', {
|
|
214
|
+
capabilities: {
|
|
215
|
+
readState: true,
|
|
216
|
+
transformState: true,
|
|
217
|
+
interceptEvents: false,
|
|
218
|
+
persistData: false,
|
|
219
|
+
accessMetadata: false,
|
|
220
|
+
},
|
|
221
|
+
transformState: (prev, next) => ({ ...next, modified1: true }),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const plugin2 = createTestPlugin('plugin-2', {
|
|
225
|
+
capabilities: {
|
|
226
|
+
readState: true,
|
|
227
|
+
transformState: true,
|
|
228
|
+
interceptEvents: false,
|
|
229
|
+
persistData: false,
|
|
230
|
+
accessMetadata: false,
|
|
231
|
+
},
|
|
232
|
+
transformState: (prev, next) => ({ ...next, modified2: true }),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
registry.add(plugin1);
|
|
236
|
+
registry.add(plugin2);
|
|
237
|
+
|
|
238
|
+
const result = registry.transformState({ count: 0 }, { count: 1 });
|
|
239
|
+
|
|
240
|
+
expect(result).toEqual({
|
|
241
|
+
count: 1,
|
|
242
|
+
modified1: true,
|
|
243
|
+
modified2: true,
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should skip transformation for plugins without capability', () => {
|
|
248
|
+
const plugin = createTestPlugin('plugin-1', {
|
|
249
|
+
capabilities: {
|
|
250
|
+
readState: true,
|
|
251
|
+
transformState: false,
|
|
252
|
+
interceptEvents: false,
|
|
253
|
+
persistData: false,
|
|
254
|
+
accessMetadata: false,
|
|
255
|
+
},
|
|
256
|
+
transformState: vi.fn(),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
registry.add(plugin);
|
|
260
|
+
|
|
261
|
+
registry.transformState({ count: 0 }, { count: 1 });
|
|
262
|
+
|
|
263
|
+
expect(plugin.transformState).not.toHaveBeenCalled();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should continue on transformation error', () => {
|
|
267
|
+
const plugin1 = createTestPlugin('plugin-1', {
|
|
268
|
+
transformState: () => {
|
|
269
|
+
throw new Error('Transform failed');
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const plugin2 = createTestPlugin('plugin-2', {
|
|
274
|
+
transformState: (prev, next) => ({ ...next, modified: true }),
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
registry.add(plugin1);
|
|
278
|
+
registry.add(plugin2);
|
|
279
|
+
|
|
280
|
+
const result = registry.transformState({ count: 0 }, { count: 1 });
|
|
281
|
+
|
|
282
|
+
expect(result).toEqual({ count: 1, modified: true });
|
|
283
|
+
expect(console.error).toHaveBeenCalled();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('Event Transformation', () => {
|
|
288
|
+
it('should transform event through all plugins', () => {
|
|
289
|
+
const plugin1 = createTestPlugin('plugin-1', {
|
|
290
|
+
capabilities: {
|
|
291
|
+
readState: true,
|
|
292
|
+
transformState: false,
|
|
293
|
+
interceptEvents: true,
|
|
294
|
+
persistData: false,
|
|
295
|
+
accessMetadata: false,
|
|
296
|
+
},
|
|
297
|
+
transformEvent: (event) => ({ ...event, modified1: true }),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const plugin2 = createTestPlugin('plugin-2', {
|
|
301
|
+
capabilities: {
|
|
302
|
+
readState: true,
|
|
303
|
+
transformState: false,
|
|
304
|
+
interceptEvents: true,
|
|
305
|
+
persistData: false,
|
|
306
|
+
accessMetadata: false,
|
|
307
|
+
},
|
|
308
|
+
transformEvent: (event) => ({ ...event, modified2: true }),
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
registry.add(plugin1);
|
|
312
|
+
registry.add(plugin2);
|
|
313
|
+
|
|
314
|
+
const result = registry.transformEvent({ type: 'TEST' });
|
|
315
|
+
|
|
316
|
+
expect(result).toEqual({
|
|
317
|
+
type: 'TEST',
|
|
318
|
+
modified1: true,
|
|
319
|
+
modified2: true,
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should stop transformation chain if event becomes null', () => {
|
|
324
|
+
const plugin1 = createTestPlugin('plugin-1', {
|
|
325
|
+
transformEvent: () => null,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const plugin2 = createTestPlugin('plugin-2', {
|
|
329
|
+
transformEvent: vi.fn(),
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
registry.add(plugin1);
|
|
333
|
+
registry.add(plugin2);
|
|
334
|
+
|
|
335
|
+
const result = registry.transformEvent({ type: 'TEST' });
|
|
336
|
+
|
|
337
|
+
expect(result).toBeNull();
|
|
338
|
+
expect(plugin2.transformEvent).not.toHaveBeenCalled();
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe('Notifications', () => {
|
|
343
|
+
it('should notify plugins of state changes', () => {
|
|
344
|
+
const onStateChange1 = vi.fn();
|
|
345
|
+
const onStateChange2 = vi.fn();
|
|
346
|
+
|
|
347
|
+
registry.add(
|
|
348
|
+
createTestPlugin('plugin-1', { onStateChange: onStateChange1 }),
|
|
349
|
+
);
|
|
350
|
+
registry.add(
|
|
351
|
+
createTestPlugin('plugin-2', { onStateChange: onStateChange2 }),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const prevState = { count: 0 };
|
|
355
|
+
const currState = { count: 1 };
|
|
356
|
+
|
|
357
|
+
registry.notifyStateChange(prevState, currState);
|
|
358
|
+
|
|
359
|
+
expect(onStateChange1).toHaveBeenCalledWith(prevState, currState);
|
|
360
|
+
expect(onStateChange2).toHaveBeenCalledWith(prevState, currState);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('should notify plugins of events', () => {
|
|
364
|
+
const onEvent1 = vi.fn();
|
|
365
|
+
const onEvent2 = vi.fn();
|
|
366
|
+
|
|
367
|
+
registry.add(createTestPlugin('plugin-1', { onEvent: onEvent1 }));
|
|
368
|
+
registry.add(createTestPlugin('plugin-2', { onEvent: onEvent2 }));
|
|
369
|
+
|
|
370
|
+
const event = { type: 'TEST_EVENT' };
|
|
371
|
+
|
|
372
|
+
registry.notifyEvent(event);
|
|
373
|
+
|
|
374
|
+
expect(onEvent1).toHaveBeenCalledWith(event);
|
|
375
|
+
expect(onEvent2).toHaveBeenCalledWith(event);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should notify plugins of errors', () => {
|
|
379
|
+
const onError1 = vi.fn();
|
|
380
|
+
const onError2 = vi.fn();
|
|
381
|
+
|
|
382
|
+
registry.add(createTestPlugin('plugin-1', { onError: onError1 }));
|
|
383
|
+
registry.add(createTestPlugin('plugin-2', { onError: onError2 }));
|
|
384
|
+
|
|
385
|
+
const error = new Error('Test error');
|
|
386
|
+
const context = {
|
|
387
|
+
phase: 'state-change' as const,
|
|
388
|
+
operation: 'test-operation',
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
registry.notifyError(error, context);
|
|
392
|
+
|
|
393
|
+
expect(onError1).toHaveBeenCalledWith(error, context);
|
|
394
|
+
expect(onError2).toHaveBeenCalledWith(error, context);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('should continue notifying on plugin errors', () => {
|
|
398
|
+
const onStateChange1 = vi.fn(() => {
|
|
399
|
+
throw new Error('Plugin error');
|
|
400
|
+
});
|
|
401
|
+
const onStateChange2 = vi.fn();
|
|
402
|
+
|
|
403
|
+
registry.add(
|
|
404
|
+
createTestPlugin('plugin-1', { onStateChange: onStateChange1 }),
|
|
405
|
+
);
|
|
406
|
+
registry.add(
|
|
407
|
+
createTestPlugin('plugin-2', { onStateChange: onStateChange2 }),
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
const prevState = { count: 0 };
|
|
411
|
+
const currState = { count: 1 };
|
|
412
|
+
|
|
413
|
+
registry.notifyStateChange(prevState, currState);
|
|
414
|
+
|
|
415
|
+
expect(onStateChange2).toHaveBeenCalled();
|
|
416
|
+
expect(console.error).toHaveBeenCalled();
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
describe('Capability Checks', () => {
|
|
421
|
+
it('should respect readState capability for notifications', () => {
|
|
422
|
+
const plugin = createTestPlugin('plugin-1', {
|
|
423
|
+
capabilities: {
|
|
424
|
+
readState: false,
|
|
425
|
+
transformState: false,
|
|
426
|
+
interceptEvents: false,
|
|
427
|
+
persistData: false,
|
|
428
|
+
accessMetadata: false,
|
|
429
|
+
},
|
|
430
|
+
onStateChange: vi.fn(),
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
registry.add(plugin);
|
|
434
|
+
registry.notifyStateChange({ count: 0 }, { count: 1 });
|
|
435
|
+
|
|
436
|
+
expect(plugin.onStateChange).not.toHaveBeenCalled();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should use default capabilities when not specified', () => {
|
|
440
|
+
const plugin = createTestPlugin('plugin-1', {
|
|
441
|
+
onStateChange: vi.fn(),
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
registry.add(plugin);
|
|
445
|
+
registry.notifyStateChange({ count: 0 }, { count: 1 });
|
|
446
|
+
|
|
447
|
+
expect(plugin.onStateChange).toHaveBeenCalled();
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
});
|