@blac/react 2.0.0-rc-16 → 2.0.0-rc.1
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/{src/index.ts → dist/index.d.ts} +1 -0
- 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 +173 -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 +26 -10
- 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/useBloc.ts +0 -227
- package/src/useExternalBlocStore.ts +0 -145
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
-
import { act } from '@testing-library/react';
|
|
4
|
-
import { Cubit, Blac } from '@blac/core';
|
|
5
|
-
import useBloc from '../useBloc';
|
|
6
|
-
import React from 'react';
|
|
7
|
-
|
|
8
|
-
interface TestState {
|
|
9
|
-
counter: number;
|
|
10
|
-
text: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
class SharedTestCubit extends Cubit<TestState> {
|
|
14
|
-
// Not isolated - should be shared across components
|
|
15
|
-
constructor() {
|
|
16
|
-
super({ counter: 0, text: 'initial' });
|
|
17
|
-
console.log('[SharedTestCubit] Created');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
increment = () => {
|
|
21
|
-
console.log(
|
|
22
|
-
'[SharedTestCubit] increment called, current state:',
|
|
23
|
-
this.state,
|
|
24
|
-
);
|
|
25
|
-
console.log(
|
|
26
|
-
'[SharedTestCubit] disposal state on increment:',
|
|
27
|
-
(this as any)._disposalState,
|
|
28
|
-
);
|
|
29
|
-
this.emit({ ...this.state, counter: this.state.counter + 1 });
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
updateText = (text: string) => {
|
|
33
|
-
console.log(
|
|
34
|
-
'[SharedTestCubit] updateText called with:',
|
|
35
|
-
text,
|
|
36
|
-
'current state:',
|
|
37
|
-
this.state,
|
|
38
|
-
);
|
|
39
|
-
console.log(
|
|
40
|
-
'[SharedTestCubit] disposal state:',
|
|
41
|
-
(this as any)._disposalState,
|
|
42
|
-
);
|
|
43
|
-
this.emit({ ...this.state, text });
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
async dispose() {
|
|
47
|
-
console.log('[SharedTestCubit] Disposing');
|
|
48
|
-
await super.dispose();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
describe('useBloc disposal issues', () => {
|
|
53
|
-
afterEach(() => {
|
|
54
|
-
// Clean up any existing blocs after each test
|
|
55
|
-
const existingBloc = Blac.getBloc(SharedTestCubit);
|
|
56
|
-
if (existingBloc) {
|
|
57
|
-
existingBloc.dispose();
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should not dispose shared bloc when switching between components', async () => {
|
|
62
|
-
// Track disposal errors
|
|
63
|
-
const disposalErrors: string[] = [];
|
|
64
|
-
const originalError = console.error;
|
|
65
|
-
console.error = (...args: any[]) => {
|
|
66
|
-
const message = args[0];
|
|
67
|
-
if (
|
|
68
|
-
typeof message === 'string' &&
|
|
69
|
-
message.includes('Attempted state update')
|
|
70
|
-
) {
|
|
71
|
-
disposalErrors.push(message);
|
|
72
|
-
}
|
|
73
|
-
originalError(...args);
|
|
74
|
-
};
|
|
75
|
-
// Component A uses the shared bloc
|
|
76
|
-
const ComponentA = () => {
|
|
77
|
-
const [state, cubit] = useBloc(SharedTestCubit);
|
|
78
|
-
return (
|
|
79
|
-
<div>
|
|
80
|
-
<span data-testid="component-a-counter">{state.counter}</span>
|
|
81
|
-
<button onClick={cubit.increment}>Increment A</button>
|
|
82
|
-
</div>
|
|
83
|
-
);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
// Component B uses the same shared bloc with dependencies
|
|
87
|
-
const ComponentB = () => {
|
|
88
|
-
const [state, cubit] = useBloc(SharedTestCubit, {
|
|
89
|
-
dependencies: (bloc) => [bloc.state.text],
|
|
90
|
-
});
|
|
91
|
-
return (
|
|
92
|
-
<div>
|
|
93
|
-
<span data-testid="component-b-text">{state.text}</span>
|
|
94
|
-
<span data-testid="component-b-counter">{state.counter}</span>
|
|
95
|
-
<button onClick={() => cubit.updateText('updated')}>
|
|
96
|
-
Update Text B
|
|
97
|
-
</button>
|
|
98
|
-
</div>
|
|
99
|
-
);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
// App component that switches between A and B
|
|
103
|
-
const App = () => {
|
|
104
|
-
const [showA, setShowA] = React.useState(true);
|
|
105
|
-
return (
|
|
106
|
-
<div>
|
|
107
|
-
<button onClick={() => setShowA(!showA)}>Toggle Component</button>
|
|
108
|
-
{showA ? <ComponentA /> : <ComponentB />}
|
|
109
|
-
</div>
|
|
110
|
-
);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const { rerender } = render(<App />);
|
|
114
|
-
|
|
115
|
-
// Initially showing Component A
|
|
116
|
-
expect(screen.getByTestId('component-a-counter')).toHaveTextContent('0');
|
|
117
|
-
|
|
118
|
-
// Increment counter in Component A
|
|
119
|
-
act(() => {
|
|
120
|
-
screen.getByText('Increment A').click();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Wait for state to update
|
|
124
|
-
await waitFor(() => {
|
|
125
|
-
expect(screen.getByTestId('component-a-counter')).toHaveTextContent('1');
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// Switch to Component B
|
|
129
|
-
act(() => {
|
|
130
|
-
screen.getByText('Toggle Component').click();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// Wait a bit to ensure Component B is mounted
|
|
134
|
-
await act(async () => {
|
|
135
|
-
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// Component B should see the updated counter from Component A
|
|
139
|
-
await waitFor(() => {
|
|
140
|
-
expect(screen.getByTestId('component-b-counter')).toHaveTextContent('1');
|
|
141
|
-
expect(screen.getByTestId('component-b-text')).toHaveTextContent(
|
|
142
|
-
'initial',
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Try to update text in Component B
|
|
147
|
-
act(() => {
|
|
148
|
-
screen.getByText('Update Text B').click();
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Wait a moment to see if state updates
|
|
152
|
-
await act(async () => {
|
|
153
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// Check the actual text value
|
|
157
|
-
const textElement = screen.getByTestId('component-b-text');
|
|
158
|
-
const actualText = textElement.textContent;
|
|
159
|
-
console.log('Text after update attempt:', actualText);
|
|
160
|
-
|
|
161
|
-
// Should successfully update without disposal errors
|
|
162
|
-
expect(actualText).toBe('updated');
|
|
163
|
-
|
|
164
|
-
// Check if any disposal errors were logged
|
|
165
|
-
if (disposalErrors.length > 0) {
|
|
166
|
-
console.log('Disposal errors found:', disposalErrors);
|
|
167
|
-
}
|
|
168
|
-
expect(disposalErrors).toHaveLength(0);
|
|
169
|
-
|
|
170
|
-
// Switch back to Component A
|
|
171
|
-
act(() => {
|
|
172
|
-
screen.getByText('Toggle Component').click();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// Component A should still see the counter as 1
|
|
176
|
-
await waitFor(() => {
|
|
177
|
-
expect(screen.getByTestId('component-a-counter')).toHaveTextContent('1');
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// Restore console.error
|
|
181
|
-
console.error = originalError;
|
|
182
|
-
|
|
183
|
-
// Cleanup handled by afterEach
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('should handle rapid component switching without disposal issues', async () => {
|
|
187
|
-
let disposalErrorLogged = false;
|
|
188
|
-
const originalError = console.error;
|
|
189
|
-
console.error = (message: string) => {
|
|
190
|
-
if (message.includes('Attempted state update on disposed bloc')) {
|
|
191
|
-
disposalErrorLogged = true;
|
|
192
|
-
}
|
|
193
|
-
originalError(message);
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
const ComponentWithTimer = () => {
|
|
197
|
-
const [state, cubit] = useBloc(SharedTestCubit);
|
|
198
|
-
|
|
199
|
-
React.useEffect(() => {
|
|
200
|
-
// Simulate async operation that might complete after unmount
|
|
201
|
-
const timer = setTimeout(() => {
|
|
202
|
-
cubit.increment();
|
|
203
|
-
}, 50);
|
|
204
|
-
|
|
205
|
-
return () => clearTimeout(timer);
|
|
206
|
-
}, [cubit]);
|
|
207
|
-
|
|
208
|
-
return <span data-testid="timer-counter">{state.counter}</span>;
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const App = () => {
|
|
212
|
-
const [show, setShow] = React.useState(true);
|
|
213
|
-
return (
|
|
214
|
-
<div>
|
|
215
|
-
<button onClick={() => setShow(!show)}>Toggle</button>
|
|
216
|
-
{show && <ComponentWithTimer />}
|
|
217
|
-
</div>
|
|
218
|
-
);
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
render(<App />);
|
|
222
|
-
|
|
223
|
-
// Rapidly toggle the component
|
|
224
|
-
act(() => {
|
|
225
|
-
screen.getByText('Toggle').click(); // Hide
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
await act(async () => {
|
|
229
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
230
|
-
screen.getByText('Toggle').click(); // Show again
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// Wait for potential timer to fire
|
|
234
|
-
await act(async () => {
|
|
235
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// Should not have logged disposal error
|
|
239
|
-
expect(disposalErrorLogged).toBe(false);
|
|
240
|
-
|
|
241
|
-
// Restore console.error
|
|
242
|
-
console.error = originalError;
|
|
243
|
-
|
|
244
|
-
// Cleanup handled by afterEach
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it('should not dispose bloc when switching quickly between components', async () => {
|
|
248
|
-
// Use a unique instance ID for this test to avoid state contamination
|
|
249
|
-
const testInstanceId = 'test-demo-app-issue';
|
|
250
|
-
|
|
251
|
-
const errorMessages: string[] = [];
|
|
252
|
-
const originalError = console.error;
|
|
253
|
-
console.error = (message: string, ...args: any[]) => {
|
|
254
|
-
if (
|
|
255
|
-
typeof message === 'string' &&
|
|
256
|
-
message.includes('Attempted state update')
|
|
257
|
-
) {
|
|
258
|
-
errorMessages.push(message);
|
|
259
|
-
}
|
|
260
|
-
originalError(message, ...args);
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
// Component using automatic proxy tracking (like DependencyTrackingDemo)
|
|
264
|
-
const ProxyTrackingComponent = () => {
|
|
265
|
-
const [state, cubit] = useBloc(SharedTestCubit, {
|
|
266
|
-
instanceId: testInstanceId,
|
|
267
|
-
});
|
|
268
|
-
// Access only counter, not text
|
|
269
|
-
return (
|
|
270
|
-
<div>
|
|
271
|
-
<span data-testid="proxy-counter">{state.counter}</span>
|
|
272
|
-
<button onClick={cubit.increment}>Increment</button>
|
|
273
|
-
</div>
|
|
274
|
-
);
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
// Component using manual dependencies (like CustomSelectorDemo)
|
|
278
|
-
const ManualDependencyComponent = () => {
|
|
279
|
-
const [state, cubit] = useBloc(SharedTestCubit, {
|
|
280
|
-
instanceId: testInstanceId,
|
|
281
|
-
dependencies: (bloc) => [bloc.state.text],
|
|
282
|
-
});
|
|
283
|
-
return (
|
|
284
|
-
<div>
|
|
285
|
-
<span data-testid="manual-text">{state.text}</span>
|
|
286
|
-
<span data-testid="manual-counter">{state.counter}</span>
|
|
287
|
-
<button onClick={() => cubit.updateText('updated')}>
|
|
288
|
-
Update Text
|
|
289
|
-
</button>
|
|
290
|
-
</div>
|
|
291
|
-
);
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
// Simulate the demo app behavior
|
|
295
|
-
const DemoApp = () => {
|
|
296
|
-
const [showProxy, setShowProxy] = React.useState(false);
|
|
297
|
-
const [showManual, setShowManual] = React.useState(false);
|
|
298
|
-
|
|
299
|
-
return (
|
|
300
|
-
<div>
|
|
301
|
-
<button onClick={() => setShowProxy(!showProxy)}>
|
|
302
|
-
Toggle Proxy Tracking
|
|
303
|
-
</button>
|
|
304
|
-
<button onClick={() => setShowManual(!showManual)}>
|
|
305
|
-
Toggle Manual Dependencies
|
|
306
|
-
</button>
|
|
307
|
-
{showProxy && <ProxyTrackingComponent />}
|
|
308
|
-
{showManual && <ManualDependencyComponent />}
|
|
309
|
-
</div>
|
|
310
|
-
);
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
render(<DemoApp />);
|
|
314
|
-
|
|
315
|
-
// Open proxy tracking component
|
|
316
|
-
act(() => {
|
|
317
|
-
screen.getByText('Toggle Proxy Tracking').click();
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
expect(screen.getByTestId('proxy-counter')).toHaveTextContent('0');
|
|
321
|
-
|
|
322
|
-
// Increment counter
|
|
323
|
-
act(() => {
|
|
324
|
-
screen.getByText('Increment').click();
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
await waitFor(() => {
|
|
328
|
-
expect(screen.getByTestId('proxy-counter')).toHaveTextContent('1');
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
// Close proxy tracking component
|
|
332
|
-
act(() => {
|
|
333
|
-
screen.getByText('Toggle Proxy Tracking').click();
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
// Switch quickly without waiting - disposal should be cancelled
|
|
337
|
-
// Don't wait here - switch immediately
|
|
338
|
-
|
|
339
|
-
// Open manual dependency component
|
|
340
|
-
act(() => {
|
|
341
|
-
screen.getByText('Toggle Manual Dependencies').click();
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// Should see the incremented state because disposal was cancelled
|
|
345
|
-
expect(screen.getByTestId('manual-counter')).toHaveTextContent('1');
|
|
346
|
-
expect(screen.getByTestId('manual-text')).toHaveTextContent('initial');
|
|
347
|
-
|
|
348
|
-
// Try to update text
|
|
349
|
-
act(() => {
|
|
350
|
-
screen.getByText('Update Text').click();
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
// Wait for state update
|
|
354
|
-
await waitFor(() => {
|
|
355
|
-
expect(screen.getByTestId('manual-text')).toHaveTextContent('updated');
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
// Check for disposal errors
|
|
359
|
-
const disposalErrors = errorMessages.filter(
|
|
360
|
-
(msg) =>
|
|
361
|
-
msg.includes('Attempted state update on disposed bloc') ||
|
|
362
|
-
msg.includes('Attempted state update on disposal_requested bloc'),
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
expect(disposalErrors).toHaveLength(0);
|
|
366
|
-
expect(screen.getByTestId('manual-text')).toHaveTextContent('updated');
|
|
367
|
-
|
|
368
|
-
// Restore console.error
|
|
369
|
-
console.error = originalError;
|
|
370
|
-
|
|
371
|
-
// Clean up test instance
|
|
372
|
-
const testBloc = Blac.getBloc(SharedTestCubit, { id: testInstanceId });
|
|
373
|
-
if (testBloc) {
|
|
374
|
-
await testBloc.dispose();
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
});
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { act, renderHook } from '@testing-library/react';
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
-
import { BlocConstructorParams, Cubit } from '@blac/core';
|
|
4
|
-
import useBloc from '../useBloc';
|
|
5
|
-
|
|
6
|
-
interface CounterProps {
|
|
7
|
-
initialValue: number;
|
|
8
|
-
step?: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
class CounterCubit extends Cubit<number> {
|
|
12
|
-
props: BlocConstructorParams<typeof CounterCubit>;
|
|
13
|
-
|
|
14
|
-
constructor(props?: CounterProps) {
|
|
15
|
-
super(props?.initialValue ?? 0);
|
|
16
|
-
this.props = props;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
increment = () => {
|
|
20
|
-
const step = this.props?.step ?? 1;
|
|
21
|
-
this.emit(this.state + step);
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
describe('useBloc instance changes', () => {
|
|
26
|
-
describe('when instanceId changes', () => {
|
|
27
|
-
it('should use a different bloc instance', async () => {
|
|
28
|
-
const { result, rerender } = renderHook(
|
|
29
|
-
({ instanceId }) =>
|
|
30
|
-
useBloc(CounterCubit, {
|
|
31
|
-
instanceId,
|
|
32
|
-
staticProps: { initialValue: 0 },
|
|
33
|
-
}),
|
|
34
|
-
{ initialProps: { instanceId: 'counter-1' } },
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
// Wait for initial render
|
|
38
|
-
await act(async () => {
|
|
39
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const firstInstance = result.current[1];
|
|
43
|
-
expect(firstInstance._id).toBe('counter-1');
|
|
44
|
-
expect(firstInstance.props).toEqual({ initialValue: 0 });
|
|
45
|
-
|
|
46
|
-
// Change instanceId
|
|
47
|
-
rerender({ instanceId: 'counter-2' });
|
|
48
|
-
|
|
49
|
-
const secondInstance = result.current[1];
|
|
50
|
-
expect(secondInstance._id).toBe('counter-2');
|
|
51
|
-
expect(secondInstance).not.toBe(firstInstance);
|
|
52
|
-
expect(result.current[0]).toBe(0); // New instance starts at 0
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('when staticProps change', () => {
|
|
57
|
-
it('should use a different bloc instance when staticProps change', async () => {
|
|
58
|
-
const { result, rerender } = renderHook(
|
|
59
|
-
({ initialValue }) =>
|
|
60
|
-
useBloc(CounterCubit, {
|
|
61
|
-
staticProps: { initialValue },
|
|
62
|
-
}),
|
|
63
|
-
{ initialProps: { initialValue: 10 } },
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
// Wait for initial render
|
|
67
|
-
await act(async () => {
|
|
68
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const firstInstance = result.current[1];
|
|
72
|
-
expect(result.current[0]).toBe(10);
|
|
73
|
-
expect(firstInstance._id).toBe('initialValue:10');
|
|
74
|
-
|
|
75
|
-
// Change staticProps
|
|
76
|
-
rerender({ initialValue: 20 });
|
|
77
|
-
|
|
78
|
-
const secondInstance = result.current[1];
|
|
79
|
-
expect(secondInstance).not.toBe(firstInstance);
|
|
80
|
-
expect(result.current[0]).toBe(20); // New instance with new initial value
|
|
81
|
-
expect(secondInstance._id).toBe('initialValue:20');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('should use same instance when non-primitive staticProps values change', () => {
|
|
85
|
-
const { result, rerender } = renderHook(
|
|
86
|
-
({ config }) =>
|
|
87
|
-
useBloc(CounterCubit, {
|
|
88
|
-
staticProps: {
|
|
89
|
-
initialValue: 5,
|
|
90
|
-
config, // This will be ignored for instance ID
|
|
91
|
-
} as any,
|
|
92
|
-
}),
|
|
93
|
-
{ initialProps: { config: { debug: true } } },
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
const firstInstance = result.current[1];
|
|
97
|
-
expect(firstInstance._id).toBe('initialValue:5');
|
|
98
|
-
|
|
99
|
-
// Change non-primitive prop
|
|
100
|
-
rerender({ config: { debug: false } });
|
|
101
|
-
|
|
102
|
-
const secondInstance = result.current[1];
|
|
103
|
-
expect(secondInstance).toBe(firstInstance); // Same instance
|
|
104
|
-
expect(secondInstance._id).toBe('initialValue:5');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should change instance when primitive staticProps change', () => {
|
|
108
|
-
const { result, rerender } = renderHook(
|
|
109
|
-
({ step }) =>
|
|
110
|
-
useBloc(CounterCubit, {
|
|
111
|
-
staticProps: { initialValue: 0, step },
|
|
112
|
-
}),
|
|
113
|
-
{ initialProps: { step: 1 } },
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
const firstInstance = result.current[1];
|
|
117
|
-
expect(firstInstance._id).toBe('initialValue:0|step:1');
|
|
118
|
-
|
|
119
|
-
// Change primitive prop
|
|
120
|
-
rerender({ step: 2 });
|
|
121
|
-
|
|
122
|
-
const secondInstance = result.current[1];
|
|
123
|
-
expect(secondInstance).not.toBe(firstInstance); // Different instance
|
|
124
|
-
expect(secondInstance._id).toBe('initialValue:0|step:2');
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe('when using both instanceId and staticProps', () => {
|
|
129
|
-
it('should use explicit instanceId over generated one', () => {
|
|
130
|
-
const { result } = renderHook(() =>
|
|
131
|
-
useBloc(CounterCubit, {
|
|
132
|
-
instanceId: 'my-counter',
|
|
133
|
-
staticProps: { initialValue: 100 },
|
|
134
|
-
}),
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
expect(result.current[1]._id).toBe('my-counter');
|
|
138
|
-
expect(result.current[0]).toBe(100);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should change instance when instanceId changes even if staticProps are same', () => {
|
|
142
|
-
const { result, rerender } = renderHook(
|
|
143
|
-
({ instanceId }) =>
|
|
144
|
-
useBloc(CounterCubit, {
|
|
145
|
-
instanceId,
|
|
146
|
-
staticProps: { initialValue: 50 },
|
|
147
|
-
}),
|
|
148
|
-
{ initialProps: { instanceId: 'counter-a' } },
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
const firstInstance = result.current[1];
|
|
152
|
-
|
|
153
|
-
// Change instanceId
|
|
154
|
-
rerender({ instanceId: 'counter-b' });
|
|
155
|
-
|
|
156
|
-
const secondInstance = result.current[1];
|
|
157
|
-
expect(secondInstance).not.toBe(firstInstance);
|
|
158
|
-
expect(secondInstance._id).toBe('counter-b');
|
|
159
|
-
expect(result.current[0]).toBe(50); // Same initial value, different instance
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
describe('lifecycle hooks', () => {
|
|
164
|
-
it('should call onMount/onUnmount when instance changes', () => {
|
|
165
|
-
const onMount1 = vi.fn();
|
|
166
|
-
const onUnmount1 = vi.fn();
|
|
167
|
-
const onMount2 = vi.fn();
|
|
168
|
-
const onUnmount2 = vi.fn();
|
|
169
|
-
|
|
170
|
-
const { rerender, unmount } = renderHook(
|
|
171
|
-
({ instanceId, onMount, onUnmount }) =>
|
|
172
|
-
useBloc(CounterCubit, { instanceId, onMount, onUnmount }),
|
|
173
|
-
{
|
|
174
|
-
initialProps: {
|
|
175
|
-
instanceId: 'counter-1',
|
|
176
|
-
onMount: onMount1,
|
|
177
|
-
onUnmount: onUnmount1,
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
expect(onMount1).toHaveBeenCalledTimes(1);
|
|
183
|
-
expect(onUnmount1).not.toHaveBeenCalled();
|
|
184
|
-
|
|
185
|
-
// Change instance
|
|
186
|
-
rerender({
|
|
187
|
-
instanceId: 'counter-2',
|
|
188
|
-
onMount: onMount2,
|
|
189
|
-
onUnmount: onUnmount2,
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
expect(onUnmount1).toHaveBeenCalledTimes(1);
|
|
193
|
-
expect(onMount2).toHaveBeenCalledTimes(1);
|
|
194
|
-
expect(onUnmount2).not.toHaveBeenCalled();
|
|
195
|
-
|
|
196
|
-
unmount();
|
|
197
|
-
expect(onUnmount2).toHaveBeenCalledTimes(1);
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
});
|