@blac/core 2.0.0-rc-16 → 2.0.0-rc.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/dist/.tsbuildinfo +1 -0
- package/dist/Blac.d.ts +246 -0
- package/dist/Blac.d.ts.map +1 -0
- package/dist/Blac.js +625 -0
- package/dist/Blac.js.map +1 -0
- package/dist/Bloc.d.ts +41 -0
- package/dist/Bloc.d.ts.map +1 -0
- package/dist/Bloc.js +140 -0
- package/dist/Bloc.js.map +1 -0
- package/dist/BlocBase.d.ts +110 -0
- package/dist/BlocBase.d.ts.map +1 -0
- package/dist/BlocBase.js +287 -0
- package/dist/BlocBase.js.map +1 -0
- package/dist/Cubit.d.ts +25 -0
- package/dist/Cubit.d.ts.map +1 -0
- package/dist/Cubit.js +64 -0
- package/dist/Cubit.js.map +1 -0
- package/dist/adapter/BlacAdapter.d.ts +71 -0
- package/dist/adapter/BlacAdapter.d.ts.map +1 -0
- package/dist/adapter/BlacAdapter.js +373 -0
- package/dist/adapter/BlacAdapter.js.map +1 -0
- package/dist/adapter/ProxyFactory.d.ts +50 -0
- package/dist/adapter/ProxyFactory.d.ts.map +1 -0
- package/dist/adapter/ProxyFactory.js +150 -0
- package/dist/adapter/ProxyFactory.js.map +1 -0
- package/{src/adapter/index.ts → dist/adapter/index.d.ts} +1 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +19 -0
- package/dist/adapter/index.js.map +1 -0
- package/dist/errors/BlacError.d.ts +38 -0
- package/dist/errors/BlacError.d.ts.map +1 -0
- package/dist/errors/BlacError.js +55 -0
- package/dist/errors/BlacError.js.map +1 -0
- package/dist/errors/ErrorManager.d.ts +39 -0
- package/dist/errors/ErrorManager.d.ts.map +1 -0
- package/dist/errors/ErrorManager.js +91 -0
- package/dist/errors/ErrorManager.js.map +1 -0
- package/dist/errors/handleError.d.ts +24 -0
- package/dist/errors/handleError.d.ts.map +1 -0
- package/dist/errors/handleError.js +62 -0
- package/dist/errors/handleError.js.map +1 -0
- package/dist/errors/index.d.ts +5 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +13 -0
- package/dist/errors/index.js.map +1 -0
- package/{src/events.ts → dist/events.d.ts} +4 -2
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +17 -0
- package/dist/events.js.map +1 -0
- package/dist/index.cjs +38 -0
- package/{src/index.ts → dist/index.d.ts} +1 -10
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle/BlocLifecycle.d.ts +51 -0
- package/dist/lifecycle/BlocLifecycle.d.ts.map +1 -0
- package/dist/lifecycle/BlocLifecycle.js +123 -0
- package/dist/lifecycle/BlocLifecycle.js.map +1 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.d.ts.map +1 -0
- package/dist/lifecycle/index.js +18 -0
- package/dist/lifecycle/index.js.map +1 -0
- package/dist/plugins/BlocPluginRegistry.d.ts +58 -0
- package/dist/plugins/BlocPluginRegistry.d.ts.map +1 -0
- package/dist/plugins/BlocPluginRegistry.js +201 -0
- package/dist/plugins/BlocPluginRegistry.js.map +1 -0
- package/dist/plugins/SystemPluginRegistry.d.ts +79 -0
- package/dist/plugins/SystemPluginRegistry.d.ts.map +1 -0
- package/dist/plugins/SystemPluginRegistry.js +184 -0
- package/dist/plugins/SystemPluginRegistry.js.map +1 -0
- package/{src/plugins/index.ts → dist/plugins/index.d.ts} +1 -5
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +26 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/types.d.ts +103 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +3 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/subscription/SubscriptionManager.d.ts +67 -0
- package/dist/subscription/SubscriptionManager.d.ts.map +1 -0
- package/dist/subscription/SubscriptionManager.js +317 -0
- package/dist/subscription/SubscriptionManager.js.map +1 -0
- package/{src/subscription/index.ts → dist/subscription/index.d.ts} +1 -0
- package/dist/subscription/index.d.ts.map +1 -0
- package/dist/subscription/index.js +19 -0
- package/dist/subscription/index.js.map +1 -0
- package/dist/subscription/types.d.ts +65 -0
- package/dist/subscription/types.d.ts.map +1 -0
- package/dist/subscription/types.js +3 -0
- package/dist/subscription/types.js.map +1 -0
- package/dist/testing.d.ts +85 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +186 -0
- package/dist/testing.js.map +1 -0
- package/{src/types.ts → dist/types.d.ts} +11 -28
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/BatchingManager.d.ts +28 -0
- package/dist/utils/BatchingManager.d.ts.map +1 -0
- package/dist/utils/BatchingManager.js +48 -0
- package/dist/utils/BatchingManager.js.map +1 -0
- package/dist/utils/RerenderLogger.d.ts +60 -0
- package/dist/utils/RerenderLogger.d.ts.map +1 -0
- package/dist/utils/RerenderLogger.js +205 -0
- package/dist/utils/RerenderLogger.js.map +1 -0
- package/dist/utils/generateInstanceId.d.ts +11 -0
- package/dist/utils/generateInstanceId.d.ts.map +1 -0
- package/dist/utils/generateInstanceId.js +50 -0
- package/dist/utils/generateInstanceId.js.map +1 -0
- package/dist/utils/shallowEqual.d.ts +9 -0
- package/dist/utils/shallowEqual.d.ts.map +1 -0
- package/dist/utils/shallowEqual.js +35 -0
- package/dist/utils/shallowEqual.js.map +1 -0
- package/dist/utils/uuid.d.ts +10 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/dist/utils/uuid.js +24 -0
- package/dist/utils/uuid.js.map +1 -0
- package/package.json +17 -6
- package/src/Blac.ts +0 -802
- package/src/Bloc.ts +0 -180
- package/src/BlocBase.ts +0 -398
- package/src/Cubit.ts +0 -68
- package/src/__tests__/Blac.config.test.ts +0 -79
- package/src/__tests__/Bloc.event.test.ts +0 -394
- package/src/__tests__/BlocBase.subscription.test.ts +0 -349
- package/src/__tests__/Cubit.test.ts +0 -462
- package/src/__tests__/memory-leaks.test.ts +0 -383
- package/src/__tests__/plugins.test.ts +0 -464
- package/src/adapter/BlacAdapter.ts +0 -467
- package/src/adapter/ProxyFactory.ts +0 -249
- package/src/adapter/__tests__/BlacAdapter.test.ts +0 -555
- package/src/adapter/__tests__/ProxyFactory.test.ts +0 -655
- package/src/errors/BlacError.ts +0 -69
- package/src/errors/ErrorManager.ts +0 -101
- package/src/errors/handleError.ts +0 -114
- package/src/errors/index.ts +0 -13
- package/src/lifecycle/BlocLifecycle.ts +0 -156
- package/src/lifecycle/index.ts +0 -1
- package/src/plugins/BlocPluginRegistry.ts +0 -240
- package/src/plugins/SystemPluginRegistry.ts +0 -242
- package/src/plugins/__tests__/BlocPluginRegistry.test.ts +0 -450
- package/src/plugins/__tests__/SystemPluginRegistry.test.ts +0 -404
- package/src/plugins/types.ts +0 -129
- package/src/subscription/README.md +0 -132
- package/src/subscription/SubscriptionManager.ts +0 -395
- package/src/subscription/__tests__/SubscriptionManager.test.ts +0 -355
- package/src/subscription/types.ts +0 -90
- package/src/testing.ts +0 -246
- package/src/utils/BatchingManager.ts +0 -59
- package/src/utils/RerenderLogger.ts +0 -265
- package/src/utils/__tests__/generateInstanceId.test.ts +0 -104
- package/src/utils/__tests__/shallowEqual.test.ts +0 -171
- package/src/utils/__tests__/uuid.test.ts +0 -144
- package/src/utils/generateInstanceId.ts +0 -54
- package/src/utils/shallowEqual.ts +0 -40
- package/src/utils/uuid.ts +0 -22
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { SubscriptionManager } from '../SubscriptionManager';
|
|
3
|
-
import { BlocBase } from '../../BlocBase';
|
|
4
|
-
import { Cubit } from '../../Cubit';
|
|
5
|
-
|
|
6
|
-
class TestCubit extends Cubit<{ count: number; nested: { value: string } }> {
|
|
7
|
-
increment = () => {
|
|
8
|
-
this.emit({ ...this.state, count: this.state.count + 1 });
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
updateNested = (value: string) => {
|
|
12
|
-
this.emit({ ...this.state, nested: { value } });
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
describe('SubscriptionManager', () => {
|
|
17
|
-
let cubit: TestCubit;
|
|
18
|
-
let manager: SubscriptionManager<{
|
|
19
|
-
count: number;
|
|
20
|
-
nested: { value: string };
|
|
21
|
-
}>;
|
|
22
|
-
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
cubit = new TestCubit({ count: 0, nested: { value: 'initial' } });
|
|
25
|
-
manager = new SubscriptionManager(cubit);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe('subscribe', () => {
|
|
29
|
-
it('should create a subscription and return unsubscribe function', () => {
|
|
30
|
-
const notify = vi.fn();
|
|
31
|
-
const unsubscribe = manager.subscribe({
|
|
32
|
-
type: 'observer',
|
|
33
|
-
notify,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
expect(manager.size).toBe(1);
|
|
37
|
-
|
|
38
|
-
unsubscribe();
|
|
39
|
-
expect(manager.size).toBe(0);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should initialize selector value on subscription', () => {
|
|
43
|
-
const notify = vi.fn();
|
|
44
|
-
const selector = vi.fn((state) => state.count);
|
|
45
|
-
|
|
46
|
-
manager.subscribe({
|
|
47
|
-
type: 'consumer',
|
|
48
|
-
selector,
|
|
49
|
-
notify,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
expect(selector).toHaveBeenCalledWith(cubit.state, cubit);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should handle selector errors gracefully', () => {
|
|
56
|
-
const notify = vi.fn();
|
|
57
|
-
const selector = vi.fn(() => {
|
|
58
|
-
throw new Error('Selector error');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Should not throw
|
|
62
|
-
expect(() => {
|
|
63
|
-
manager.subscribe({
|
|
64
|
-
type: 'observer',
|
|
65
|
-
selector,
|
|
66
|
-
notify,
|
|
67
|
-
});
|
|
68
|
-
}).not.toThrow();
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe('notify', () => {
|
|
73
|
-
it('should notify subscriptions without selectors', () => {
|
|
74
|
-
const notify = vi.fn();
|
|
75
|
-
manager.subscribe({
|
|
76
|
-
type: 'observer',
|
|
77
|
-
notify,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const oldState = { count: 0, nested: { value: 'old' } };
|
|
81
|
-
const newState = { count: 1, nested: { value: 'new' } };
|
|
82
|
-
|
|
83
|
-
manager.notify(newState, oldState);
|
|
84
|
-
|
|
85
|
-
expect(notify).toHaveBeenCalledWith(newState, oldState, undefined);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should use selector for change detection', () => {
|
|
89
|
-
const notify = vi.fn();
|
|
90
|
-
const selector = (state: any) => state.count;
|
|
91
|
-
|
|
92
|
-
manager.subscribe({
|
|
93
|
-
type: 'consumer',
|
|
94
|
-
selector,
|
|
95
|
-
notify,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Same count, should not notify
|
|
99
|
-
manager.notify(
|
|
100
|
-
{ count: 0, nested: { value: 'new' } },
|
|
101
|
-
{ count: 0, nested: { value: 'old' } },
|
|
102
|
-
);
|
|
103
|
-
expect(notify).not.toHaveBeenCalled();
|
|
104
|
-
|
|
105
|
-
// Different count, should notify
|
|
106
|
-
manager.notify(
|
|
107
|
-
{ count: 1, nested: { value: 'new' } },
|
|
108
|
-
{ count: 0, nested: { value: 'old' } },
|
|
109
|
-
);
|
|
110
|
-
expect(notify).toHaveBeenCalledWith(1, 0, undefined);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should use custom equality function', () => {
|
|
114
|
-
const notify = vi.fn();
|
|
115
|
-
const selector = (state: any) => state.nested;
|
|
116
|
-
const equalityFn = (a: any, b: any) => a.value === b.value;
|
|
117
|
-
|
|
118
|
-
manager.subscribe({
|
|
119
|
-
type: 'observer',
|
|
120
|
-
selector,
|
|
121
|
-
equalityFn,
|
|
122
|
-
notify,
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// First notification to establish baseline
|
|
126
|
-
manager.notify(
|
|
127
|
-
{ count: 0, nested: { value: 'initial' } },
|
|
128
|
-
{ count: 0, nested: { value: 'initial' } },
|
|
129
|
-
);
|
|
130
|
-
notify.mockClear();
|
|
131
|
-
|
|
132
|
-
// Same nested value, should not notify
|
|
133
|
-
manager.notify(
|
|
134
|
-
{ count: 1, nested: { value: 'initial' } },
|
|
135
|
-
{ count: 0, nested: { value: 'initial' } },
|
|
136
|
-
);
|
|
137
|
-
expect(notify).not.toHaveBeenCalled();
|
|
138
|
-
|
|
139
|
-
// Different nested value, should notify
|
|
140
|
-
manager.notify(
|
|
141
|
-
{ count: 1, nested: { value: 'new' } },
|
|
142
|
-
{ count: 0, nested: { value: 'initial' } },
|
|
143
|
-
);
|
|
144
|
-
expect(notify).toHaveBeenCalled();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('should respect priority order', () => {
|
|
148
|
-
const callOrder: string[] = [];
|
|
149
|
-
|
|
150
|
-
manager.subscribe({
|
|
151
|
-
type: 'observer',
|
|
152
|
-
priority: 1,
|
|
153
|
-
notify: () => callOrder.push('low'),
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
manager.subscribe({
|
|
157
|
-
type: 'observer',
|
|
158
|
-
priority: 10,
|
|
159
|
-
notify: () => callOrder.push('high'),
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
manager.subscribe({
|
|
163
|
-
type: 'observer',
|
|
164
|
-
priority: 5,
|
|
165
|
-
notify: () => callOrder.push('medium'),
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
manager.notify(
|
|
169
|
-
{ count: 1, nested: { value: 'new' } },
|
|
170
|
-
{ count: 0, nested: { value: 'old' } },
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
expect(callOrder).toEqual(['high', 'medium', 'low']);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should handle notify errors gracefully', () => {
|
|
177
|
-
const goodNotify = vi.fn();
|
|
178
|
-
const badNotify = vi.fn(() => {
|
|
179
|
-
throw new Error('Notify error');
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
manager.subscribe({ type: 'observer', notify: badNotify });
|
|
183
|
-
manager.subscribe({ type: 'observer', notify: goodNotify });
|
|
184
|
-
|
|
185
|
-
// Should not throw and should call good notify
|
|
186
|
-
expect(() => {
|
|
187
|
-
manager.notify(
|
|
188
|
-
{ count: 1, nested: { value: 'new' } },
|
|
189
|
-
{ count: 0, nested: { value: 'old' } },
|
|
190
|
-
);
|
|
191
|
-
}).not.toThrow();
|
|
192
|
-
|
|
193
|
-
expect(goodNotify).toHaveBeenCalled();
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('trackAccess', () => {
|
|
198
|
-
it('should track path dependencies', () => {
|
|
199
|
-
const notify = vi.fn();
|
|
200
|
-
const unsubscribe = manager.subscribe({
|
|
201
|
-
type: 'consumer',
|
|
202
|
-
notify,
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// Get subscription ID (hacky but works for testing)
|
|
206
|
-
const subscriptionId = Array.from(
|
|
207
|
-
(manager as any).subscriptions.keys(),
|
|
208
|
-
)[0] as string;
|
|
209
|
-
|
|
210
|
-
manager.trackAccess(subscriptionId, 'count');
|
|
211
|
-
manager.trackAccess(subscriptionId, 'nested.value');
|
|
212
|
-
|
|
213
|
-
const subscription = manager.getSubscription(subscriptionId);
|
|
214
|
-
expect(subscription?.dependencies).toContain('count');
|
|
215
|
-
expect(subscription?.dependencies).toContain('nested.value');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('should update metadata on access', () => {
|
|
219
|
-
const notify = vi.fn();
|
|
220
|
-
manager.subscribe({ type: 'consumer', notify });
|
|
221
|
-
|
|
222
|
-
const subscriptionId = Array.from(
|
|
223
|
-
(manager as any).subscriptions.keys(),
|
|
224
|
-
)[0] as string;
|
|
225
|
-
|
|
226
|
-
manager.trackAccess(subscriptionId, 'count');
|
|
227
|
-
|
|
228
|
-
const subscription = manager.getSubscription(subscriptionId);
|
|
229
|
-
expect(subscription?.metadata?.accessCount).toBe(1);
|
|
230
|
-
expect(subscription?.metadata?.lastAccessTime).toBeGreaterThan(0);
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
describe('shouldNotifyForPaths', () => {
|
|
235
|
-
it('should return true for direct path match', () => {
|
|
236
|
-
const notify = vi.fn();
|
|
237
|
-
manager.subscribe({ type: 'consumer', notify });
|
|
238
|
-
|
|
239
|
-
const subscriptionId = Array.from(
|
|
240
|
-
(manager as any).subscriptions.keys(),
|
|
241
|
-
)[0] as string;
|
|
242
|
-
manager.trackAccess(subscriptionId, 'count');
|
|
243
|
-
|
|
244
|
-
const changedPaths = new Set(['count']);
|
|
245
|
-
expect(manager.shouldNotifyForPaths(subscriptionId, changedPaths)).toBe(
|
|
246
|
-
true,
|
|
247
|
-
);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('should return true for nested path changes', () => {
|
|
251
|
-
const notify = vi.fn();
|
|
252
|
-
manager.subscribe({ type: 'consumer', notify });
|
|
253
|
-
|
|
254
|
-
const subscriptionId = Array.from(
|
|
255
|
-
(manager as any).subscriptions.keys(),
|
|
256
|
-
)[0] as string;
|
|
257
|
-
manager.trackAccess(subscriptionId, 'nested');
|
|
258
|
-
|
|
259
|
-
const changedPaths = new Set(['nested.value']);
|
|
260
|
-
expect(manager.shouldNotifyForPaths(subscriptionId, changedPaths)).toBe(
|
|
261
|
-
true,
|
|
262
|
-
);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it('should return false for unrelated path changes', () => {
|
|
266
|
-
const notify = vi.fn();
|
|
267
|
-
manager.subscribe({ type: 'consumer', notify });
|
|
268
|
-
|
|
269
|
-
const subscriptionId = Array.from(
|
|
270
|
-
(manager as any).subscriptions.keys(),
|
|
271
|
-
)[0] as string;
|
|
272
|
-
manager.trackAccess(subscriptionId, 'count');
|
|
273
|
-
|
|
274
|
-
const changedPaths = new Set(['nested.value']);
|
|
275
|
-
expect(manager.shouldNotifyForPaths(subscriptionId, changedPaths)).toBe(
|
|
276
|
-
false,
|
|
277
|
-
);
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
describe('weak reference handling', () => {
|
|
282
|
-
it('should clean up subscriptions with dead weak references', async () => {
|
|
283
|
-
let obj: any = { id: 'test' };
|
|
284
|
-
const weakRef = new WeakRef(obj);
|
|
285
|
-
const notify = vi.fn();
|
|
286
|
-
|
|
287
|
-
manager.subscribe({
|
|
288
|
-
type: 'consumer',
|
|
289
|
-
weakRef,
|
|
290
|
-
notify,
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
expect(manager.size).toBe(1);
|
|
294
|
-
|
|
295
|
-
// Clear strong reference
|
|
296
|
-
obj = null;
|
|
297
|
-
|
|
298
|
-
// Force garbage collection (this is platform-specific and may not work in all environments)
|
|
299
|
-
if ((global as any).gc) {
|
|
300
|
-
(global as any).gc();
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Trigger notification which should clean up dead refs
|
|
304
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
305
|
-
manager.notify(
|
|
306
|
-
{ count: 1, nested: { value: 'new' } },
|
|
307
|
-
{ count: 0, nested: { value: 'old' } },
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
// Wait for microtask
|
|
311
|
-
await new Promise((resolve) => queueMicrotask(resolve));
|
|
312
|
-
|
|
313
|
-
// Note: This test may not reliably pass in all environments due to GC timing
|
|
314
|
-
// In production, the cleanup will happen eventually
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
describe('getStats', () => {
|
|
319
|
-
it('should return correct statistics', () => {
|
|
320
|
-
manager.subscribe({ type: 'consumer', notify: vi.fn() });
|
|
321
|
-
manager.subscribe({ type: 'observer', notify: vi.fn() });
|
|
322
|
-
manager.subscribe({ type: 'observer', notify: vi.fn() });
|
|
323
|
-
|
|
324
|
-
const stats = manager.getStats();
|
|
325
|
-
|
|
326
|
-
expect(stats.activeSubscriptions).toBe(3);
|
|
327
|
-
expect(stats.consumerCount).toBe(1);
|
|
328
|
-
expect(stats.observerCount).toBe(2);
|
|
329
|
-
expect(stats.totalNotifications).toBe(0);
|
|
330
|
-
|
|
331
|
-
// Trigger a notification
|
|
332
|
-
manager.notify(
|
|
333
|
-
{ count: 1, nested: { value: 'new' } },
|
|
334
|
-
{ count: 0, nested: { value: 'old' } },
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
const updatedStats = manager.getStats();
|
|
338
|
-
expect(updatedStats.totalNotifications).toBe(3); // All 3 subscriptions notified
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
describe('clear', () => {
|
|
343
|
-
it('should remove all subscriptions', () => {
|
|
344
|
-
manager.subscribe({ type: 'consumer', notify: vi.fn() });
|
|
345
|
-
manager.subscribe({ type: 'observer', notify: vi.fn() });
|
|
346
|
-
|
|
347
|
-
expect(manager.size).toBe(2);
|
|
348
|
-
|
|
349
|
-
manager.clear();
|
|
350
|
-
|
|
351
|
-
expect(manager.size).toBe(0);
|
|
352
|
-
expect(manager.hasSubscriptions).toBe(false);
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
});
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { BlocBase } from '../BlocBase';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Unified subscription interface that replaces the dual consumer/observer system
|
|
5
|
-
*/
|
|
6
|
-
export interface Subscription<S = unknown> {
|
|
7
|
-
/** Unique identifier for the subscription */
|
|
8
|
-
id: string;
|
|
9
|
-
|
|
10
|
-
/** Type of subscription for backward compatibility */
|
|
11
|
-
type: 'consumer' | 'observer';
|
|
12
|
-
|
|
13
|
-
/** Optional selector function to derive values from state */
|
|
14
|
-
selector?: (state: S, bloc?: BlocBase<S>) => unknown;
|
|
15
|
-
|
|
16
|
-
/** Custom equality function for selector results (defaults to Object.is) */
|
|
17
|
-
equalityFn?: (prev: unknown, next: unknown) => boolean;
|
|
18
|
-
|
|
19
|
-
/** Notification callback when state changes */
|
|
20
|
-
notify: (value: unknown, oldValue?: unknown, action?: unknown) => void;
|
|
21
|
-
|
|
22
|
-
/** Priority for notification order (higher = earlier) */
|
|
23
|
-
priority?: number;
|
|
24
|
-
|
|
25
|
-
/** WeakRef to the subscriber for automatic cleanup (React components) */
|
|
26
|
-
weakRef?: WeakRef<object>;
|
|
27
|
-
|
|
28
|
-
/** Last computed selector value for comparison */
|
|
29
|
-
lastValue?: unknown;
|
|
30
|
-
|
|
31
|
-
/** Tracked state path dependencies */
|
|
32
|
-
dependencies?: Set<string>;
|
|
33
|
-
|
|
34
|
-
/** Metadata for tracking and debugging */
|
|
35
|
-
metadata?: SubscriptionMetadata;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface SubscriptionMetadata {
|
|
39
|
-
/** Timestamp of last notification */
|
|
40
|
-
lastNotified?: number;
|
|
41
|
-
|
|
42
|
-
/** Whether the subscriber has rendered (React-specific) */
|
|
43
|
-
hasRendered?: boolean;
|
|
44
|
-
|
|
45
|
-
/** Number of times this subscription accessed state */
|
|
46
|
-
accessCount?: number;
|
|
47
|
-
|
|
48
|
-
/** First access timestamp */
|
|
49
|
-
firstAccessTime?: number;
|
|
50
|
-
|
|
51
|
-
/** Last access timestamp */
|
|
52
|
-
lastAccessTime?: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface SubscriptionOptions<S> {
|
|
56
|
-
/** Type of subscription */
|
|
57
|
-
type: 'consumer' | 'observer';
|
|
58
|
-
|
|
59
|
-
/** Optional selector function */
|
|
60
|
-
selector?: (state: S, bloc?: BlocBase<S>) => unknown;
|
|
61
|
-
|
|
62
|
-
/** Custom equality function */
|
|
63
|
-
equalityFn?: (prev: unknown, next: unknown) => boolean;
|
|
64
|
-
|
|
65
|
-
/** Notification callback */
|
|
66
|
-
notify: (value: unknown, oldValue?: unknown, action?: unknown) => void;
|
|
67
|
-
|
|
68
|
-
/** Priority for notifications */
|
|
69
|
-
priority?: number;
|
|
70
|
-
|
|
71
|
-
/** WeakRef to subscriber */
|
|
72
|
-
weakRef?: WeakRef<object>;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface SubscriptionManagerStats {
|
|
76
|
-
/** Total number of active subscriptions */
|
|
77
|
-
activeSubscriptions: number;
|
|
78
|
-
|
|
79
|
-
/** Number of consumer subscriptions */
|
|
80
|
-
consumerCount: number;
|
|
81
|
-
|
|
82
|
-
/** Number of observer subscriptions */
|
|
83
|
-
observerCount: number;
|
|
84
|
-
|
|
85
|
-
/** Total notifications sent */
|
|
86
|
-
totalNotifications: number;
|
|
87
|
-
|
|
88
|
-
/** Number of tracked dependencies */
|
|
89
|
-
trackedDependencies: number;
|
|
90
|
-
}
|
package/src/testing.ts
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
import { Blac } from './Blac';
|
|
2
|
-
import { Bloc } from './Bloc';
|
|
3
|
-
import { BlocBase } from './BlocBase';
|
|
4
|
-
import { Cubit } from './Cubit';
|
|
5
|
-
import { BlocEventConstraint } from './types';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Test utilities for Blac state management
|
|
9
|
-
*/
|
|
10
|
-
export class BlocTest {
|
|
11
|
-
private static originalInstance: Blac;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Sets up a clean test environment
|
|
15
|
-
*/
|
|
16
|
-
static setUp(): void {
|
|
17
|
-
this.originalInstance = Blac.instance;
|
|
18
|
-
Blac.resetInstance();
|
|
19
|
-
Blac.enableLog = false; // Disable logging in tests by default
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Tears down the test environment and restores original state
|
|
24
|
-
*/
|
|
25
|
-
static tearDown(): void {
|
|
26
|
-
Blac.resetInstance();
|
|
27
|
-
// Note: Cannot restore original instance due to singleton pattern
|
|
28
|
-
// Tests should use setUp/tearDown properly to manage state
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Creates a test bloc with automatic cleanup
|
|
33
|
-
*/
|
|
34
|
-
static createBloc<T extends BlocBase<any>>(
|
|
35
|
-
BlocClass: new (...args: any[]) => T,
|
|
36
|
-
...args: any[]
|
|
37
|
-
): T {
|
|
38
|
-
const bloc = new BlocClass(...args);
|
|
39
|
-
Blac.activateBloc(bloc);
|
|
40
|
-
return bloc;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Waits for a bloc to emit a specific state
|
|
45
|
-
*/
|
|
46
|
-
static async waitForState<T extends BlocBase<S>, S>(
|
|
47
|
-
bloc: T,
|
|
48
|
-
predicate: (state: S) => boolean,
|
|
49
|
-
timeout = 5000,
|
|
50
|
-
): Promise<S> {
|
|
51
|
-
return new Promise<S>((resolve, reject) => {
|
|
52
|
-
const timeoutId = setTimeout(() => {
|
|
53
|
-
reject(
|
|
54
|
-
new Error(
|
|
55
|
-
`Timeout waiting for state matching predicate after ${timeout}ms`,
|
|
56
|
-
),
|
|
57
|
-
);
|
|
58
|
-
}, timeout);
|
|
59
|
-
|
|
60
|
-
const unsubscribe = bloc.subscribe((newState: S) => {
|
|
61
|
-
if (predicate(newState)) {
|
|
62
|
-
clearTimeout(timeoutId);
|
|
63
|
-
unsubscribe();
|
|
64
|
-
resolve(newState);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// Check current state immediately
|
|
69
|
-
if (predicate(bloc.state)) {
|
|
70
|
-
clearTimeout(timeoutId);
|
|
71
|
-
unsubscribe();
|
|
72
|
-
resolve(bloc.state);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Expects a bloc to emit specific states in order
|
|
79
|
-
*/
|
|
80
|
-
static async expectStates<T extends BlocBase<S>, S>(
|
|
81
|
-
bloc: T,
|
|
82
|
-
expectedStates: S[],
|
|
83
|
-
timeout = 5000,
|
|
84
|
-
): Promise<void> {
|
|
85
|
-
const receivedStates: S[] = [];
|
|
86
|
-
|
|
87
|
-
return new Promise<void>((resolve, reject) => {
|
|
88
|
-
const timeoutId = setTimeout(() => {
|
|
89
|
-
reject(
|
|
90
|
-
new Error(
|
|
91
|
-
`Timeout waiting for states. Expected: ${JSON.stringify(expectedStates)}, ` +
|
|
92
|
-
`Received: ${JSON.stringify(receivedStates)}`,
|
|
93
|
-
),
|
|
94
|
-
);
|
|
95
|
-
}, timeout);
|
|
96
|
-
|
|
97
|
-
const unsubscribe = bloc.subscribe((newState: S) => {
|
|
98
|
-
receivedStates.push(newState);
|
|
99
|
-
|
|
100
|
-
// Check if we have all expected states
|
|
101
|
-
if (receivedStates.length === expectedStates.length) {
|
|
102
|
-
clearTimeout(timeoutId);
|
|
103
|
-
unsubscribe();
|
|
104
|
-
|
|
105
|
-
// Verify all states match using deep equality
|
|
106
|
-
for (let i = 0; i < expectedStates.length; i++) {
|
|
107
|
-
const expected = expectedStates[i];
|
|
108
|
-
const received = receivedStates[i];
|
|
109
|
-
|
|
110
|
-
// Use JSON comparison for deep equality
|
|
111
|
-
if (JSON.stringify(expected) !== JSON.stringify(received)) {
|
|
112
|
-
reject(
|
|
113
|
-
new Error(
|
|
114
|
-
`State mismatch at index ${i}. Expected: ${JSON.stringify(expected)}, ` +
|
|
115
|
-
`Received: ${JSON.stringify(received)}`,
|
|
116
|
-
),
|
|
117
|
-
);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
resolve();
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Mock Bloc for testing
|
|
131
|
-
*/
|
|
132
|
-
export class MockBloc<
|
|
133
|
-
S,
|
|
134
|
-
A extends BlocEventConstraint = BlocEventConstraint,
|
|
135
|
-
> extends Bloc<S, A> {
|
|
136
|
-
private mockHandlers = new Map<
|
|
137
|
-
string,
|
|
138
|
-
(event: A, emit: (newState: S) => void) => void | Promise<void>
|
|
139
|
-
>();
|
|
140
|
-
|
|
141
|
-
constructor(initialState: S) {
|
|
142
|
-
super(initialState);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Mock an event handler for testing
|
|
147
|
-
*/
|
|
148
|
-
mockEventHandler<E extends A>(
|
|
149
|
-
eventConstructor: new (...args: any[]) => E,
|
|
150
|
-
handler: (event: E, emit: (newState: S) => void) => void | Promise<void>,
|
|
151
|
-
): void {
|
|
152
|
-
// Use the on method to register the mock handler
|
|
153
|
-
this.on(eventConstructor, handler);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Get the number of registered handlers
|
|
158
|
-
*/
|
|
159
|
-
getHandlerCount(): number {
|
|
160
|
-
return this.eventHandlers.size;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Check if a handler is registered for an event type
|
|
165
|
-
*/
|
|
166
|
-
hasHandler(eventConstructor: new (...args: any[]) => A): boolean {
|
|
167
|
-
return this.eventHandlers.has(eventConstructor);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Mock Cubit for testing
|
|
173
|
-
*/
|
|
174
|
-
export class MockCubit<S> extends Cubit<S> {
|
|
175
|
-
private stateHistory: S[] = [];
|
|
176
|
-
|
|
177
|
-
constructor(initialState: S) {
|
|
178
|
-
super(initialState);
|
|
179
|
-
this.stateHistory.push(initialState);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Override emit to track state history
|
|
184
|
-
*/
|
|
185
|
-
emit(newState: S): void {
|
|
186
|
-
this.stateHistory.push(newState);
|
|
187
|
-
super.emit(newState);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Get the history of all states
|
|
192
|
-
*/
|
|
193
|
-
getStateHistory(): S[] {
|
|
194
|
-
return [...this.stateHistory];
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Clear state history
|
|
199
|
-
*/
|
|
200
|
-
clearStateHistory(): void {
|
|
201
|
-
this.stateHistory = [this.state];
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Memory leak detector for tests
|
|
207
|
-
*/
|
|
208
|
-
export class MemoryLeakDetector {
|
|
209
|
-
private initialStats: ReturnType<typeof Blac.getMemoryStats>;
|
|
210
|
-
|
|
211
|
-
constructor() {
|
|
212
|
-
this.initialStats = Blac.getMemoryStats();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Check for memory leaks and return a report
|
|
217
|
-
*/
|
|
218
|
-
checkForLeaks(): {
|
|
219
|
-
hasLeaks: boolean;
|
|
220
|
-
report: string;
|
|
221
|
-
stats: ReturnType<typeof Blac.getMemoryStats>;
|
|
222
|
-
} {
|
|
223
|
-
const currentStats = Blac.getMemoryStats();
|
|
224
|
-
const hasLeaks =
|
|
225
|
-
currentStats.registeredBlocs > this.initialStats.registeredBlocs ||
|
|
226
|
-
currentStats.isolatedBlocs > this.initialStats.isolatedBlocs ||
|
|
227
|
-
currentStats.keepAliveBlocs > this.initialStats.keepAliveBlocs;
|
|
228
|
-
|
|
229
|
-
const report = `
|
|
230
|
-
Memory Leak Detection Report:
|
|
231
|
-
- Initial registered blocs: ${this.initialStats.registeredBlocs}
|
|
232
|
-
- Current registered blocs: ${currentStats.registeredBlocs}
|
|
233
|
-
- Initial isolated blocs: ${this.initialStats.isolatedBlocs}
|
|
234
|
-
- Current isolated blocs: ${currentStats.isolatedBlocs}
|
|
235
|
-
- Initial keep-alive blocs: ${this.initialStats.keepAliveBlocs}
|
|
236
|
-
- Current keep-alive blocs: ${currentStats.keepAliveBlocs}
|
|
237
|
-
- Potential leaks detected: ${hasLeaks ? 'YES' : 'NO'}
|
|
238
|
-
`.trim();
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
hasLeaks,
|
|
242
|
-
report,
|
|
243
|
-
stats: currentStats,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
}
|