@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.
@@ -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
- });
@@ -1,202 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { renderHook, act } from '@testing-library/react';
3
- import { Cubit, Blac } from '@blac/core';
4
- import useBloc from '../useBloc';
5
-
6
- interface TestState {
7
- count: number;
8
- name: string;
9
- unused: string;
10
- }
11
-
12
- class TestCubit extends Cubit<TestState> {
13
- static isolated = true; // Make it isolated to avoid test interference
14
-
15
- constructor() {
16
- super({ count: 0, name: 'Alice', unused: 'not-used' });
17
- }
18
-
19
- increment = () => {
20
- this.emit({ ...this.state, count: this.state.count + 1 });
21
- };
22
-
23
- changeName = (name: string) => {
24
- this.emit({ ...this.state, name });
25
- };
26
-
27
- changeUnused = (value: string) => {
28
- this.emit({ ...this.state, unused: value });
29
- };
30
- }
31
-
32
- describe('useBloc dependency tracking', () => {
33
- it('should only track properties accessed during current render', () => {
34
- let renderCount = 0;
35
- let accessedProperties: string[] = [];
36
-
37
- const { result, rerender } = renderHook(() => {
38
- const [state, cubit] = useBloc(TestCubit);
39
- renderCount++;
40
-
41
- // Reset tracked properties for this render
42
- accessedProperties = [];
43
-
44
- // First few renders: access both count and name
45
- if (renderCount <= 2) {
46
- // Access count and name
47
- void state.count;
48
- void state.name;
49
- accessedProperties = ['count', 'name'];
50
- } else {
51
- // Later renders: only access count
52
- void state.count;
53
- accessedProperties = ['count'];
54
- }
55
-
56
- return { state, cubit, accessedProperties };
57
- });
58
-
59
- // Initial render - accessing count and name
60
- expect(renderCount).toBe(1);
61
- expect(result.current.accessedProperties).toEqual(['count', 'name']);
62
-
63
- // Change name - should trigger re-render since we're tracking it
64
- act(() => {
65
- result.current.cubit.changeName('Bob');
66
- });
67
- expect(renderCount).toBe(2);
68
- expect(result.current.state.name).toBe('Bob');
69
-
70
- // Force a re-render (this will be render #3, where we stop accessing name)
71
- rerender();
72
- expect(renderCount).toBe(3);
73
- expect(result.current.accessedProperties).toEqual(['count']); // Only accessing count now
74
-
75
- // Change name again - should NOT trigger re-render since we're no longer tracking it
76
- const prevRenderCount = renderCount;
77
- act(() => {
78
- result.current.cubit.changeName('Charlie');
79
- });
80
-
81
- // Render count should not increase
82
- expect(renderCount).toBe(prevRenderCount);
83
- // But state should still be updated if we check it
84
- expect(result.current.cubit.state.name).toBe('Charlie');
85
-
86
- // Change count - should trigger re-render since we're still tracking it
87
- act(() => {
88
- result.current.cubit.increment();
89
- });
90
- expect(renderCount).toBe(prevRenderCount + 1);
91
- expect(result.current.state.count).toBe(1);
92
- });
93
-
94
- it('should not re-render when non-accessed properties change', () => {
95
- let renderCount = 0;
96
-
97
- const { result } = renderHook(() => {
98
- const [state, cubit] = useBloc(TestCubit);
99
- renderCount++;
100
-
101
- // Only access count, never access unused
102
- void state.count;
103
-
104
- return { state, cubit };
105
- });
106
-
107
- expect(renderCount).toBe(1);
108
-
109
- // Change unused property - should NOT trigger re-render
110
- act(() => {
111
- result.current.cubit.changeUnused('changed');
112
- });
113
-
114
- // Should not re-render
115
- expect(renderCount).toBe(1);
116
-
117
- // The component shouldn't re-render, so we can't check the state through result.current
118
- // This is the expected behavior - component doesn't see changes to untracked properties
119
- });
120
-
121
- it('should track nested property access correctly', () => {
122
- interface NestedState {
123
- user: {
124
- profile: {
125
- name: string;
126
- age: number;
127
- };
128
- };
129
- settings: {
130
- theme: string;
131
- };
132
- }
133
-
134
- class NestedCubit extends Cubit<NestedState> {
135
- static isolated = true;
136
-
137
- constructor() {
138
- super({
139
- user: {
140
- profile: {
141
- name: 'Alice',
142
- age: 25,
143
- },
144
- },
145
- settings: {
146
- theme: 'light',
147
- },
148
- });
149
- }
150
-
151
- updateName = (name: string) => {
152
- this.emit({
153
- ...this.state,
154
- user: {
155
- ...this.state.user,
156
- profile: {
157
- ...this.state.user.profile,
158
- name,
159
- },
160
- },
161
- });
162
- };
163
-
164
- updateTheme = (theme: string) => {
165
- this.emit({
166
- ...this.state,
167
- settings: {
168
- ...this.state.settings,
169
- theme,
170
- },
171
- });
172
- };
173
- }
174
-
175
- let renderCount = 0;
176
-
177
- const { result } = renderHook(() => {
178
- const [state, cubit] = useBloc(NestedCubit);
179
- renderCount++;
180
-
181
- // Only access user.profile.name
182
- void state.user.profile.name;
183
-
184
- return { state, cubit };
185
- });
186
-
187
- expect(renderCount).toBe(1);
188
-
189
- // Change theme - should NOT trigger re-render
190
- act(() => {
191
- result.current.cubit.updateTheme('dark');
192
- });
193
- expect(renderCount).toBe(1);
194
-
195
- // Change name - should trigger re-render
196
- act(() => {
197
- result.current.cubit.updateName('Bob');
198
- });
199
- expect(renderCount).toBe(2);
200
- expect(result.current.state.user.profile.name).toBe('Bob');
201
- });
202
- });
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export { default as useBloc } from './useBloc';
2
- export { default as useBlocNext } from './useBloc';
3
- export { default as useExternalBlocStore } from './useExternalBlocStore';