@blac/react 2.0.0-rc-14 → 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/index.cjs +13 -0
- package/dist/index.d.ts +4 -58
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/useBloc.d.ts +20 -0
- package/dist/useBloc.d.ts.map +1 -0
- package/dist/useBloc.js +175 -0
- package/dist/useBloc.js.map +1 -0
- package/dist/useExternalBlocStore.d.ts +31 -0
- package/dist/useExternalBlocStore.d.ts.map +1 -0
- package/dist/useExternalBlocStore.js +93 -0
- package/dist/useExternalBlocStore.js.map +1 -0
- package/package.json +24 -14
- package/dist/index.cjs.js +0 -3
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.es.js +0 -140
- package/dist/index.es.js.map +0 -1
- package/src/__tests__/rerenderLogging.test.tsx +0 -257
- package/src/__tests__/useBloc.disposal.test.tsx +0 -377
- package/src/__tests__/useBloc.instanceChange.test.tsx +0 -200
- package/src/__tests__/useBloc.props.test.tsx +0 -435
- package/src/__tests__/useBloc.proxyConfig.test.tsx +0 -228
- package/src/__tests__/useBloc.staticProps.test.tsx +0 -297
- package/src/__tests__/useBloc.tracking.test.tsx +0 -202
- package/src/index.ts +0 -3
- package/src/useBloc.ts +0 -227
- package/src/useExternalBlocStore.ts +0 -145
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Blac,
|
|
3
|
-
Bloc,
|
|
4
|
-
BlocConstructorParams,
|
|
5
|
-
Cubit,
|
|
6
|
-
PropsUpdated,
|
|
7
|
-
} from '@blac/core';
|
|
8
|
-
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
9
|
-
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
10
|
-
import useBloc from '../useBloc';
|
|
11
|
-
|
|
12
|
-
// Test components
|
|
13
|
-
interface SearchProps {
|
|
14
|
-
query: string;
|
|
15
|
-
limit?: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface SearchState {
|
|
19
|
-
results: string[];
|
|
20
|
-
loading: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
class SearchBloc extends Bloc<SearchState, PropsUpdated<SearchProps>> {
|
|
24
|
-
props: BlocConstructorParams<typeof SearchBloc>;
|
|
25
|
-
|
|
26
|
-
constructor(props?: SearchProps) {
|
|
27
|
-
// Initialize with props
|
|
28
|
-
super({
|
|
29
|
-
results: props ? [`Search: ${props.query}`] : [],
|
|
30
|
-
loading: false,
|
|
31
|
-
});
|
|
32
|
-
this.props = props;
|
|
33
|
-
|
|
34
|
-
this.on(PropsUpdated<SearchProps>, (event, emit) => {
|
|
35
|
-
emit({ results: [`Search: ${event.props.query}`], loading: false });
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface CounterProps {
|
|
41
|
-
step: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface CounterState {
|
|
45
|
-
count: number;
|
|
46
|
-
stepSize: number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
class CounterCubit extends Cubit<CounterState> {
|
|
50
|
-
constructor(props?: CounterProps) {
|
|
51
|
-
super({ count: 0, stepSize: props?.step ?? 1 });
|
|
52
|
-
this.props = props || null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
props: CounterProps | null = null;
|
|
56
|
-
|
|
57
|
-
protected onPropsChanged(
|
|
58
|
-
oldProps: CounterProps | undefined,
|
|
59
|
-
newProps: CounterProps,
|
|
60
|
-
): void {
|
|
61
|
-
if (oldProps?.step !== newProps.step) {
|
|
62
|
-
this.emit({ ...this.state, stepSize: newProps.step });
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
increment = () => {
|
|
67
|
-
const step = this.props?.step ?? 1;
|
|
68
|
-
this.emit({
|
|
69
|
-
...this.state,
|
|
70
|
-
count: this.state.count + step,
|
|
71
|
-
stepSize: step,
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
describe('useBloc props integration', () => {
|
|
77
|
-
beforeEach(() => {
|
|
78
|
-
Blac.resetInstance();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('Basic props functionality', () => {
|
|
82
|
-
it('should pass props to Bloc via adapter', async () => {
|
|
83
|
-
const { result } = renderHook(
|
|
84
|
-
({ query }) => useBloc(SearchBloc, { staticProps: { query } }),
|
|
85
|
-
{ initialProps: { query: 'initial' } },
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
await act(async () => {
|
|
89
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const [state] = result.current;
|
|
93
|
-
expect(state.results).toEqual(['Search: initial']);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should create new instance when staticProps change', async () => {
|
|
97
|
-
const { result, rerender } = renderHook(
|
|
98
|
-
({ query }) => useBloc(SearchBloc, { staticProps: { query } }),
|
|
99
|
-
{ initialProps: { query: 'initial' } },
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
await act(async () => {
|
|
103
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const firstInstance = result.current[1];
|
|
107
|
-
expect(result.current[0].results).toEqual(['Search: initial']);
|
|
108
|
-
|
|
109
|
-
rerender({ query: 'updated' });
|
|
110
|
-
|
|
111
|
-
await act(async () => {
|
|
112
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const secondInstance = result.current[1];
|
|
116
|
-
expect(secondInstance).not.toBe(firstInstance); // New instance created
|
|
117
|
-
expect(result.current[0].results).toEqual(['Search: updated']);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should work with Cubit props', async () => {
|
|
121
|
-
const { result } = renderHook(
|
|
122
|
-
({ step }) => {
|
|
123
|
-
const [state, cubit] = useBloc(CounterCubit, {
|
|
124
|
-
staticProps: { step },
|
|
125
|
-
});
|
|
126
|
-
// Access count to ensure it's tracked
|
|
127
|
-
void state.count;
|
|
128
|
-
return [state, cubit];
|
|
129
|
-
},
|
|
130
|
-
{ initialProps: { step: 1 } },
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
// Wait for props to be set
|
|
134
|
-
await act(async () => {
|
|
135
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const [state, cubit] = result.current as [CounterState, CounterCubit];
|
|
139
|
-
expect(state.stepSize).toBe(1);
|
|
140
|
-
|
|
141
|
-
act(() => {
|
|
142
|
-
cubit.increment();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// Wait for React to re-render with new state
|
|
146
|
-
await waitFor(() => {
|
|
147
|
-
expect((result.current[0] as CounterState).count).toBe(1);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should create new Cubit instance when staticProps change', async () => {
|
|
152
|
-
const { result, rerender } = renderHook(
|
|
153
|
-
({ step }) => {
|
|
154
|
-
const [state, cubit] = useBloc(CounterCubit, {
|
|
155
|
-
staticProps: { step },
|
|
156
|
-
});
|
|
157
|
-
// Access count to ensure it's tracked
|
|
158
|
-
void state.count;
|
|
159
|
-
return [state, cubit];
|
|
160
|
-
},
|
|
161
|
-
{ initialProps: { step: 1 } },
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
// Wait for initial props to be set
|
|
165
|
-
await act(async () => {
|
|
166
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const firstInstance = result.current[1];
|
|
170
|
-
expect((result.current[0] as CounterState).stepSize).toBe(1);
|
|
171
|
-
|
|
172
|
-
rerender({ step: 5 });
|
|
173
|
-
|
|
174
|
-
// Wait for new instance to be created
|
|
175
|
-
await act(async () => {
|
|
176
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const secondInstance = result.current[1];
|
|
180
|
-
expect(secondInstance).not.toBe(firstInstance); // New instance
|
|
181
|
-
expect((result.current[0] as CounterState).stepSize).toBe(5);
|
|
182
|
-
expect((result.current[0] as CounterState).count).toBe(0); // New instance starts at 0
|
|
183
|
-
|
|
184
|
-
act(() => {
|
|
185
|
-
(result.current[1] as CounterCubit).increment();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Wait for React to re-render with new state
|
|
189
|
-
await waitFor(() => {
|
|
190
|
-
expect((result.current[0] as CounterState).count).toBe(5);
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
describe('Instance sharing', () => {
|
|
196
|
-
it('should share instance when using same instanceId', async () => {
|
|
197
|
-
// First hook creates instance with explicit id
|
|
198
|
-
const { result: result1 } = renderHook(() =>
|
|
199
|
-
useBloc(SearchBloc, {
|
|
200
|
-
staticProps: { query: 'first' },
|
|
201
|
-
instanceId: 'shared-search',
|
|
202
|
-
}),
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
// Second hook uses same instanceId - gets same instance
|
|
206
|
-
const { result: result2 } = renderHook(() =>
|
|
207
|
-
useBloc(SearchBloc, {
|
|
208
|
-
staticProps: { query: 'second' }, // Different props, but same instanceId
|
|
209
|
-
instanceId: 'shared-search',
|
|
210
|
-
}),
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
await act(async () => {
|
|
214
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// Both should share the same instance (check by ID)
|
|
218
|
-
expect(result1.current[1]._id).toBe('shared-search');
|
|
219
|
-
expect(result2.current[1]._id).toBe('shared-search');
|
|
220
|
-
|
|
221
|
-
// Both see the state from the first instance (query: 'first')
|
|
222
|
-
expect(result1.current[0].results).toEqual(['Search: first']);
|
|
223
|
-
expect(result2.current[0].results).toEqual(['Search: first']);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('should allow consumers without staticProps to share instance', async () => {
|
|
227
|
-
// First consumer with props and explicit id
|
|
228
|
-
const { result: firstResult } = renderHook(() =>
|
|
229
|
-
useBloc(SearchBloc, {
|
|
230
|
-
staticProps: { query: 'test' },
|
|
231
|
-
instanceId: 'search-instance',
|
|
232
|
-
}),
|
|
233
|
-
);
|
|
234
|
-
|
|
235
|
-
// Second consumer without props but same instanceId
|
|
236
|
-
const { result: secondResult } = renderHook(() =>
|
|
237
|
-
useBloc(SearchBloc, { instanceId: 'search-instance' }),
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
await act(async () => {
|
|
241
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// Same instance (check by ID)
|
|
245
|
-
expect(firstResult.current[1]._id).toBe('search-instance');
|
|
246
|
-
expect(secondResult.current[1]._id).toBe('search-instance');
|
|
247
|
-
|
|
248
|
-
// Both see the same state
|
|
249
|
-
expect(secondResult.current[0].results).toEqual(['Search: test']);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('should create independent instances with different staticProps', async () => {
|
|
253
|
-
const { result: result1, unmount: unmount1 } = renderHook(() =>
|
|
254
|
-
useBloc(SearchBloc, { staticProps: { query: 'first' } }),
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
await act(async () => {
|
|
258
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
expect(result1.current[0].results).toEqual(['Search: first']);
|
|
262
|
-
const instance1Id = result1.current[1]._id;
|
|
263
|
-
|
|
264
|
-
// Create second instance with different props
|
|
265
|
-
const { result: result2 } = renderHook(() =>
|
|
266
|
-
useBloc(SearchBloc, { staticProps: { query: 'second' } }),
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
await act(async () => {
|
|
270
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
expect(result2.current[0].results).toEqual(['Search: second']);
|
|
274
|
-
const instance2Id = result2.current[1]._id;
|
|
275
|
-
|
|
276
|
-
// Different instances with different auto-generated IDs
|
|
277
|
-
expect(instance1Id).not.toBe(instance2Id);
|
|
278
|
-
expect(result1.current[1]).not.toBe(result2.current[1]);
|
|
279
|
-
|
|
280
|
-
// Unmount first - second is unaffected
|
|
281
|
-
unmount1();
|
|
282
|
-
|
|
283
|
-
expect(result2.current[0].results).toEqual(['Search: second']);
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
describe('Props with other options', () => {
|
|
288
|
-
it('should work with key option', async () => {
|
|
289
|
-
const { result } = renderHook(() =>
|
|
290
|
-
useBloc(SearchBloc, {
|
|
291
|
-
instanceId: 'custom-search',
|
|
292
|
-
staticProps: { query: 'test' },
|
|
293
|
-
}),
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
await act(async () => {
|
|
297
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
expect(result.current[0].results).toEqual(['Search: test']);
|
|
301
|
-
expect(result.current[1]._id).toBe('custom-search');
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it('should work with lifecycle hooks', async () => {
|
|
305
|
-
const onMount = vi.fn();
|
|
306
|
-
const onUnmount = vi.fn();
|
|
307
|
-
|
|
308
|
-
const { result, unmount } = renderHook(() =>
|
|
309
|
-
useBloc(SearchBloc, {
|
|
310
|
-
staticProps: { query: 'test' },
|
|
311
|
-
onMount,
|
|
312
|
-
onUnmount,
|
|
313
|
-
}),
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
expect(onMount).toHaveBeenCalledWith(result.current[1]);
|
|
317
|
-
|
|
318
|
-
unmount();
|
|
319
|
-
|
|
320
|
-
expect(onUnmount).toHaveBeenCalledWith(result.current[1]);
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('should work with manual dependencies', () => {
|
|
324
|
-
const { result, rerender } = renderHook(
|
|
325
|
-
({ step }) =>
|
|
326
|
-
useBloc(CounterCubit, {
|
|
327
|
-
staticProps: { step },
|
|
328
|
-
dependencies: (cubit) => [cubit.state.count],
|
|
329
|
-
}),
|
|
330
|
-
{ initialProps: { step: 1 } },
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
const [, cubit] = result.current as [CounterState, CounterCubit];
|
|
334
|
-
|
|
335
|
-
// Increment should trigger re-render (count is a dependency)
|
|
336
|
-
act(() => {
|
|
337
|
-
cubit.increment();
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
expect((result.current[0] as CounterState).count).toBe(1);
|
|
341
|
-
|
|
342
|
-
// Changing props creates new instance
|
|
343
|
-
rerender({ step: 2 });
|
|
344
|
-
|
|
345
|
-
// New instance has different state
|
|
346
|
-
const [newState, newCubit] = result.current as [
|
|
347
|
-
CounterState,
|
|
348
|
-
CounterCubit,
|
|
349
|
-
];
|
|
350
|
-
expect(newCubit).not.toBe(cubit); // New instance
|
|
351
|
-
expect(newState.stepSize).toBe(2);
|
|
352
|
-
expect(newState.count).toBe(0); // New instance starts at 0
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
describe('Edge cases', () => {
|
|
357
|
-
it('should handle undefined props', async () => {
|
|
358
|
-
const { result } = renderHook(() => {
|
|
359
|
-
const [state, cubit] = useBloc(CounterCubit, {
|
|
360
|
-
staticProps: undefined,
|
|
361
|
-
});
|
|
362
|
-
// Access count to ensure it's tracked
|
|
363
|
-
void state.count;
|
|
364
|
-
return [state, cubit];
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
// Wait for any effects
|
|
368
|
-
await act(async () => {
|
|
369
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
const [state, cubit] = result.current as [CounterState, CounterCubit];
|
|
373
|
-
expect(state.stepSize).toBe(1);
|
|
374
|
-
|
|
375
|
-
act(() => {
|
|
376
|
-
cubit.increment();
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
// Wait for React to re-render with new state
|
|
380
|
-
await waitFor(() => {
|
|
381
|
-
expect((result.current[0] as CounterState).count).toBe(1); // Uses default step
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
it('should not re-render for unchanged props', () => {
|
|
386
|
-
let renderCount = 0;
|
|
387
|
-
|
|
388
|
-
const { rerender } = renderHook(
|
|
389
|
-
({ query }) => {
|
|
390
|
-
renderCount++;
|
|
391
|
-
return useBloc(SearchBloc, { staticProps: { query } });
|
|
392
|
-
},
|
|
393
|
-
{ initialProps: { query: 'test' } },
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
const initialRenderCount = renderCount;
|
|
397
|
-
|
|
398
|
-
// Rerender with same props
|
|
399
|
-
rerender({ query: 'test' });
|
|
400
|
-
|
|
401
|
-
// Should not cause additional renders beyond React's normal behavior
|
|
402
|
-
expect(renderCount).toBe(initialRenderCount + 1);
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
it('should handle rapid props updates', async () => {
|
|
406
|
-
const { result, rerender } = renderHook(
|
|
407
|
-
({ query }) => useBloc(SearchBloc, { staticProps: { query } }),
|
|
408
|
-
{ initialProps: { query: 'initial' } },
|
|
409
|
-
);
|
|
410
|
-
|
|
411
|
-
// Wait for initial render
|
|
412
|
-
await act(async () => {
|
|
413
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
const firstInstance = result.current[1];
|
|
417
|
-
|
|
418
|
-
// Rapid updates - each creates a new instance
|
|
419
|
-
await act(async () => {
|
|
420
|
-
rerender({ query: 'update1' });
|
|
421
|
-
rerender({ query: 'update2' });
|
|
422
|
-
rerender({ query: 'update3' });
|
|
423
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
const lastInstance = result.current[1];
|
|
427
|
-
|
|
428
|
-
// Different instance after updates
|
|
429
|
-
expect(lastInstance).not.toBe(firstInstance);
|
|
430
|
-
|
|
431
|
-
// Should have the latest value
|
|
432
|
-
expect(result.current[0].results).toEqual(['Search: update3']);
|
|
433
|
-
});
|
|
434
|
-
});
|
|
435
|
-
});
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { Blac, Cubit } from '@blac/core';
|
|
2
|
-
import { renderHook, act } from '@testing-library/react';
|
|
3
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
4
|
-
import useBloc from '../useBloc';
|
|
5
|
-
|
|
6
|
-
interface TestState {
|
|
7
|
-
count: number;
|
|
8
|
-
nested: {
|
|
9
|
-
value: string;
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
class TestCubit extends Cubit<TestState> {
|
|
14
|
-
constructor() {
|
|
15
|
-
super({ count: 0, nested: { value: 'initial' } });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
increment = () => {
|
|
19
|
-
this.emit({ ...this.state, count: this.state.count + 1 });
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
updateNested = () => {
|
|
23
|
-
this.emit({ ...this.state, nested: { value: 'updated' } });
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
describe('useBloc with proxy tracking config', () => {
|
|
28
|
-
const originalConfig = { ...Blac.config };
|
|
29
|
-
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
Blac.resetInstance();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
afterEach(() => {
|
|
35
|
-
Blac.setConfig(originalConfig);
|
|
36
|
-
Blac.resetInstance();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('when proxyDependencyTracking is enabled (default)', () => {
|
|
40
|
-
beforeEach(() => {
|
|
41
|
-
Blac.setConfig({ proxyDependencyTracking: true });
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should only re-render when accessed properties change', () => {
|
|
45
|
-
const { result } = renderHook(() => useBloc(TestCubit));
|
|
46
|
-
const renderSpy = vi.fn();
|
|
47
|
-
|
|
48
|
-
// Access only count in render
|
|
49
|
-
renderHook(() => {
|
|
50
|
-
const [state] = useBloc(TestCubit);
|
|
51
|
-
renderSpy(state.count);
|
|
52
|
-
return state.count;
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
56
|
-
expect(renderSpy).toHaveBeenCalledWith(0);
|
|
57
|
-
|
|
58
|
-
// Update nested value (not accessed)
|
|
59
|
-
act(() => {
|
|
60
|
-
result.current[1].updateNested();
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// Should not re-render since nested.value wasn't accessed
|
|
64
|
-
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
65
|
-
|
|
66
|
-
// Update count (accessed property)
|
|
67
|
-
act(() => {
|
|
68
|
-
result.current[1].increment();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Should re-render since count was accessed
|
|
72
|
-
expect(renderSpy).toHaveBeenCalledTimes(2);
|
|
73
|
-
expect(renderSpy).toHaveBeenLastCalledWith(1);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe('when proxyDependencyTracking is disabled', () => {
|
|
78
|
-
beforeEach(() => {
|
|
79
|
-
Blac.setConfig({ proxyDependencyTracking: false });
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should re-render on any state change', () => {
|
|
83
|
-
const { result } = renderHook(() => useBloc(TestCubit));
|
|
84
|
-
const renderSpy = vi.fn();
|
|
85
|
-
|
|
86
|
-
// Access only count in render
|
|
87
|
-
renderHook(() => {
|
|
88
|
-
const [state] = useBloc(TestCubit);
|
|
89
|
-
renderSpy(state.count);
|
|
90
|
-
return state.count;
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
94
|
-
expect(renderSpy).toHaveBeenCalledWith(0);
|
|
95
|
-
|
|
96
|
-
// Update nested value (not accessed)
|
|
97
|
-
act(() => {
|
|
98
|
-
result.current[1].updateNested();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// Should re-render even though nested.value wasn't accessed
|
|
102
|
-
expect(renderSpy).toHaveBeenCalledTimes(2);
|
|
103
|
-
expect(renderSpy).toHaveBeenLastCalledWith(0); // count didn't change
|
|
104
|
-
|
|
105
|
-
// Update count
|
|
106
|
-
act(() => {
|
|
107
|
-
result.current[1].increment();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// Should re-render
|
|
111
|
-
expect(renderSpy).toHaveBeenCalledTimes(3);
|
|
112
|
-
expect(renderSpy).toHaveBeenLastCalledWith(1);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('should still respect manual dependencies when provided', () => {
|
|
116
|
-
const renderSpy = vi.fn();
|
|
117
|
-
|
|
118
|
-
const { result } = renderHook(() =>
|
|
119
|
-
useBloc(TestCubit, {
|
|
120
|
-
dependencies: (bloc) => [bloc.state.count],
|
|
121
|
-
}),
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
// Create another hook that tracks renders
|
|
125
|
-
renderHook(() => {
|
|
126
|
-
const [state] = useBloc(TestCubit, {
|
|
127
|
-
dependencies: (bloc) => [bloc.state.count],
|
|
128
|
-
});
|
|
129
|
-
renderSpy(state);
|
|
130
|
-
return state;
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
134
|
-
|
|
135
|
-
// Update nested value
|
|
136
|
-
act(() => {
|
|
137
|
-
result.current[1].updateNested();
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Should not re-render since dependencies only include count
|
|
141
|
-
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
142
|
-
|
|
143
|
-
// Update count
|
|
144
|
-
act(() => {
|
|
145
|
-
result.current[1].increment();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// Should re-render since count is in dependencies
|
|
149
|
-
expect(renderSpy).toHaveBeenCalledTimes(2);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
describe('runtime config changes', () => {
|
|
154
|
-
it('should respect config changes for new hooks', () => {
|
|
155
|
-
// Start with proxy tracking enabled
|
|
156
|
-
Blac.setConfig({ proxyDependencyTracking: true });
|
|
157
|
-
|
|
158
|
-
// Create a new cubit instance for this test to avoid interference
|
|
159
|
-
class TestCubit2 extends Cubit<TestState> {
|
|
160
|
-
constructor() {
|
|
161
|
-
super({ count: 0, nested: { value: 'initial' } });
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
increment = () => {
|
|
165
|
-
this.emit({ ...this.state, count: this.state.count + 1 });
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
updateNested = () => {
|
|
169
|
-
this.emit({ ...this.state, nested: { value: 'updated' } });
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const renderSpy1 = vi.fn();
|
|
174
|
-
|
|
175
|
-
const { result: result1 } = renderHook(() => {
|
|
176
|
-
const [state, bloc] = useBloc(TestCubit2);
|
|
177
|
-
renderSpy1(state.count);
|
|
178
|
-
return [state, bloc] as const;
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
expect(renderSpy1).toHaveBeenCalledTimes(1);
|
|
182
|
-
|
|
183
|
-
// Update nested value
|
|
184
|
-
act(() => {
|
|
185
|
-
result1.current[1].updateNested();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Should not re-render since nested.value wasn't accessed (proxy tracking enabled)
|
|
189
|
-
expect(renderSpy1).toHaveBeenCalledTimes(1);
|
|
190
|
-
|
|
191
|
-
// Change config for future hooks
|
|
192
|
-
Blac.setConfig({ proxyDependencyTracking: false });
|
|
193
|
-
|
|
194
|
-
// Create new cubit class to ensure fresh instance
|
|
195
|
-
class TestCubit3 extends Cubit<TestState> {
|
|
196
|
-
constructor() {
|
|
197
|
-
super({ count: 0, nested: { value: 'initial' } });
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
increment = () => {
|
|
201
|
-
this.emit({ ...this.state, count: this.state.count + 1 });
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
updateNested = () => {
|
|
205
|
-
this.emit({ ...this.state, nested: { value: 'updated' } });
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Create new hook after config change
|
|
210
|
-
const renderSpy2 = vi.fn();
|
|
211
|
-
const { result: result2 } = renderHook(() => {
|
|
212
|
-
const [state, bloc] = useBloc(TestCubit3);
|
|
213
|
-
renderSpy2(state.count);
|
|
214
|
-
return [state, bloc] as const;
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
expect(renderSpy2).toHaveBeenCalledTimes(1);
|
|
218
|
-
|
|
219
|
-
// Update nested value on the new cubit
|
|
220
|
-
act(() => {
|
|
221
|
-
result2.current[1].updateNested();
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
// Should re-render even though nested.value wasn't accessed (proxy tracking disabled)
|
|
225
|
-
expect(renderSpy2).toHaveBeenCalledTimes(2);
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
});
|