@blac/react 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.
@@ -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
- });
@@ -1,297 +0,0 @@
1
- import { Blac, Cubit } from '@blac/core';
2
- import { renderHook } from '@testing-library/react';
3
- import { describe, expect, it, vi, beforeEach } from 'vitest';
4
- import useBloc from '../useBloc';
5
-
6
- // Test components
7
- interface UserDetailsProps {
8
- userId: string;
9
- page: number;
10
- }
11
-
12
- interface UserDetailsState {
13
- user: { id: string; name: string } | null;
14
- loading: boolean;
15
- page: number;
16
- }
17
-
18
- class UserDetailsCubit extends Cubit<UserDetailsState> {
19
- props: ConstructorParameters<typeof UserDetailsCubit>[0];
20
-
21
- constructor(props: UserDetailsProps) {
22
- super({ user: null, loading: false, page: props.page });
23
- this.props = props;
24
- }
25
-
26
- loadUser = () => {
27
- this.emit({ ...this.state, loading: true });
28
- // Simulate API call
29
- setTimeout(() => {
30
- this.emit({
31
- ...this.state,
32
- user: { id: this.props!.userId, name: `User ${this.props!.userId}` },
33
- loading: false,
34
- });
35
- }, 10);
36
- };
37
- }
38
-
39
- interface SearchProps {
40
- query: string;
41
- filters?: { category?: string; minPrice?: number; tags?: string[] };
42
- }
43
-
44
- interface SearchState {
45
- results: string[];
46
- loading: boolean;
47
- }
48
-
49
- class SearchCubit extends Cubit<SearchState> {
50
- constructor(props: SearchProps) {
51
- super({ results: [], loading: false });
52
- }
53
- }
54
-
55
- describe('useBloc staticProps integration', () => {
56
- beforeEach(() => {
57
- Blac.resetInstance();
58
- vi.clearAllMocks();
59
- });
60
-
61
- describe('staticProps with instanceId', () => {
62
- it('should use explicit instanceId when provided with staticProps', () => {
63
- const { result: result1 } = renderHook(() =>
64
- useBloc(UserDetailsCubit, {
65
- staticProps: { userId: 'user123', page: 1 },
66
- instanceId: 'user123',
67
- }),
68
- );
69
-
70
- const { result: result2 } = renderHook(() =>
71
- useBloc(UserDetailsCubit, {
72
- staticProps: { userId: 'user123', page: 2 }, // Different page
73
- instanceId: 'user123', // Same instanceId
74
- }),
75
- );
76
-
77
- // Both should have the same instanceId
78
- expect(result1.current[1]._id).toBe('user123');
79
- expect(result2.current[1]._id).toBe('user123');
80
-
81
- // Note: In React Testing Library, each renderHook creates a separate component tree,
82
- // so they won't share the same instance. In a real app, they would.
83
- expect(result1.current[1]._id).toBe(result2.current[1]._id);
84
- });
85
-
86
- it('should create different instances with different instanceIds', () => {
87
- const { result: result1 } = renderHook(() =>
88
- useBloc(UserDetailsCubit, {
89
- staticProps: { userId: 'user123', page: 1 },
90
- instanceId: 'user123',
91
- }),
92
- );
93
-
94
- const { result: result2 } = renderHook(() =>
95
- useBloc(UserDetailsCubit, {
96
- staticProps: { userId: 'user456', page: 1 },
97
- instanceId: 'user456',
98
- }),
99
- );
100
-
101
- // Should have different IDs
102
- expect(result1.current[1]._id).toBe('user123');
103
- expect(result2.current[1]._id).toBe('user456');
104
- expect(result1.current[1]._id).not.toBe(result2.current[1]._id);
105
- });
106
- });
107
-
108
- describe('staticProps without instanceId (auto-generation)', () => {
109
- it('should generate instanceId from primitive staticProps values', () => {
110
- const { result: result1 } = renderHook(() =>
111
- useBloc(SearchCubit, {
112
- staticProps: { query: 'test', filters: { category: 'books' } },
113
- }),
114
- );
115
-
116
- const { result: result2 } = renderHook(() =>
117
- useBloc(SearchCubit, {
118
- staticProps: { query: 'test', filters: { category: 'movies' } },
119
- }),
120
- );
121
-
122
- // Should have generated ID from the query primitive
123
- expect(result1.current[1]._id).toBe('query:test');
124
- expect(result2.current[1]._id).toBe('query:test');
125
-
126
- // They have the same generated ID
127
- expect(result1.current[1]._id).toBe(result2.current[1]._id);
128
- });
129
-
130
- it('should create different instances for different primitive values', () => {
131
- const { result: result1 } = renderHook(() =>
132
- useBloc(SearchCubit, {
133
- staticProps: { query: 'test1' },
134
- }),
135
- );
136
-
137
- const { result: result2 } = renderHook(() =>
138
- useBloc(SearchCubit, {
139
- staticProps: { query: 'test2' },
140
- }),
141
- );
142
-
143
- // Should have different generated IDs
144
- expect(result1.current[1]._id).toBe('query:test1');
145
- expect(result2.current[1]._id).toBe('query:test2');
146
- expect(result1.current[1]._id).not.toBe(result2.current[1]._id);
147
- });
148
-
149
- it('should ignore non-primitive values when generating instanceId', () => {
150
- const filters = { category: 'books', tags: ['fiction', 'bestseller'] };
151
-
152
- const { result: result1 } = renderHook(() =>
153
- useBloc(SearchCubit, {
154
- staticProps: { query: 'test', filters }, // filters has an array
155
- }),
156
- );
157
-
158
- const { result: result2 } = renderHook(() =>
159
- useBloc(SearchCubit, {
160
- staticProps: {
161
- query: 'test',
162
- filters: { ...filters, tags: ['different'] },
163
- },
164
- }),
165
- );
166
-
167
- // Both should generate the same ID (only 'query' is used)
168
- expect(result1.current[1]._id).toBe('query:test');
169
- expect(result2.current[1]._id).toBe('query:test');
170
- });
171
- });
172
-
173
- describe('InstanceId usage', () => {
174
- it('should use instanceId option', () => {
175
- const { result } = renderHook(() =>
176
- useBloc(UserDetailsCubit, {
177
- staticProps: { userId: 'user123', page: 1 },
178
- instanceId: 'custom-id',
179
- }),
180
- );
181
-
182
- expect(result.current[1]._id).toBe('custom-id');
183
- });
184
-
185
- it('should use instanceId option with static props', () => {
186
- const { result } = renderHook(() =>
187
- useBloc(UserDetailsCubit, {
188
- staticProps: { userId: 'user123', page: 1 },
189
- instanceId: 'user123',
190
- }),
191
- );
192
-
193
- const [state, bloc] = result.current;
194
- expect(state.page).toBe(1);
195
- expect(bloc).toBeDefined();
196
- expect(bloc._id).toBe('user123');
197
- });
198
-
199
- it('should use explicit instanceId when provided', () => {
200
- const { result } = renderHook(() =>
201
- useBloc(UserDetailsCubit, {
202
- staticProps: { userId: 'new', page: 2 },
203
- instanceId: 'new-id',
204
- }),
205
- );
206
-
207
- // Should use the instanceId
208
- expect(result.current[1]._id).toBe('new-id');
209
-
210
- // Should use staticProps
211
- expect(result.current[1].props).toEqual({ userId: 'new', page: 2 });
212
- });
213
- });
214
-
215
- describe('Complex scenarios', () => {
216
- it('should handle mixed primitive types in staticProps', () => {
217
- interface ConfigProps {
218
- enabled: boolean;
219
- maxRetries: number;
220
- apiKey: string;
221
- debugInfo?: { level: string };
222
- }
223
-
224
- class ConfigCubit extends Cubit<{ config: ConfigProps | null }> {
225
- constructor(props: ConfigProps) {
226
- super({ config: props });
227
- }
228
- }
229
-
230
- const { result: result1 } = renderHook(() =>
231
- useBloc(ConfigCubit, {
232
- staticProps: {
233
- enabled: true,
234
- maxRetries: 3,
235
- apiKey: 'abc123',
236
- debugInfo: { level: 'verbose' }, // This should be ignored
237
- },
238
- }),
239
- );
240
-
241
- const { result: result2 } = renderHook(() =>
242
- useBloc(ConfigCubit, {
243
- staticProps: {
244
- enabled: true,
245
- maxRetries: 3,
246
- apiKey: 'abc123',
247
- debugInfo: { level: 'quiet' }, // Different object, but should still match
248
- },
249
- }),
250
- );
251
-
252
- // Should generate the same ID from primitives
253
- const expectedId = 'apiKey:abc123|enabled:true|maxRetries:3';
254
- expect(result1.current[1]._id).toBe(expectedId);
255
- expect(result2.current[1]._id).toBe(expectedId);
256
- });
257
-
258
- it('should handle null and undefined in staticProps', () => {
259
- interface OptionalProps {
260
- required: string;
261
- optional?: string;
262
- nullable: string | null;
263
- }
264
-
265
- class OptionalCubit extends Cubit<{ data: any }> {
266
- constructor(props: OptionalProps) {
267
- super({ data: props });
268
- }
269
- }
270
-
271
- const { result: result1 } = renderHook(() =>
272
- useBloc(OptionalCubit, {
273
- staticProps: {
274
- required: 'test',
275
- optional: undefined,
276
- nullable: null,
277
- },
278
- }),
279
- );
280
-
281
- const { result: result2 } = renderHook(() =>
282
- useBloc(OptionalCubit, {
283
- staticProps: {
284
- required: 'test',
285
- optional: undefined,
286
- nullable: null,
287
- },
288
- }),
289
- );
290
-
291
- // Should generate the same ID including null/undefined
292
- const expectedId = 'nullable:null|optional:undefined|required:test';
293
- expect(result1.current[1]._id).toBe(expectedId);
294
- expect(result2.current[1]._id).toBe(expectedId);
295
- });
296
- });
297
- });