@blac/core 2.0.0-rc-7 → 2.0.0-rc-10
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/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +194 -7
- package/dist/index.es.js +1096 -563
- package/dist/index.es.js.map +1 -1
- package/package.json +5 -4
- package/src/Blac.ts +204 -99
- package/src/BlacObserver.ts +62 -25
- package/src/Bloc.ts +166 -149
- package/src/BlocBase.ts +269 -85
- package/src/Cubit.ts +1 -1
- package/src/__tests__/Bloc.event.test.ts +419 -0
- package/src/__tests__/BlocBase.lifecycle.test.ts +441 -0
- package/src/__tests__/Cubit.test.ts +473 -0
- package/src/adapter/BlacAdapter.ts +262 -0
- package/src/adapter/ConsumerTracker.ts +241 -0
- package/src/adapter/ProxyFactory.ts +308 -0
- package/src/adapter/__tests__/BlacAdapter.test.ts +561 -0
- package/src/adapter/__tests__/ConsumerTracker.test.ts +562 -0
- package/src/adapter/__tests__/ProxyFactory.test.ts +655 -0
- package/src/adapter/index.ts +3 -0
- package/src/index.ts +2 -1
- package/src/testing.ts +60 -47
- package/src/types.ts +21 -6
- package/src/utils/uuid.ts +4 -4
- package/src/BlacEvent.ts +0 -5
package/src/testing.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { BlocEventConstraint } from './types';
|
|
|
9
9
|
*/
|
|
10
10
|
export class BlocTest {
|
|
11
11
|
private static originalInstance: Blac;
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
/**
|
|
14
14
|
* Sets up a clean test environment
|
|
15
15
|
*/
|
|
@@ -18,7 +18,7 @@ export class BlocTest {
|
|
|
18
18
|
Blac.resetInstance();
|
|
19
19
|
Blac.enableLog = false; // Disable logging in tests by default
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
/**
|
|
23
23
|
* Tears down the test environment and restores original state
|
|
24
24
|
*/
|
|
@@ -27,7 +27,7 @@ export class BlocTest {
|
|
|
27
27
|
// Note: Cannot restore original instance due to singleton pattern
|
|
28
28
|
// Tests should use setUp/tearDown properly to manage state
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
/**
|
|
32
32
|
* Creates a test bloc with automatic cleanup
|
|
33
33
|
*/
|
|
@@ -39,31 +39,35 @@ export class BlocTest {
|
|
|
39
39
|
Blac.activateBloc(bloc);
|
|
40
40
|
return bloc;
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
/**
|
|
44
44
|
* Waits for a bloc to emit a specific state
|
|
45
45
|
*/
|
|
46
46
|
static async waitForState<T extends BlocBase<S>, S>(
|
|
47
47
|
bloc: T,
|
|
48
48
|
predicate: (state: S) => boolean,
|
|
49
|
-
timeout = 5000
|
|
49
|
+
timeout = 5000,
|
|
50
50
|
): Promise<S> {
|
|
51
51
|
return new Promise<S>((resolve, reject) => {
|
|
52
52
|
const timeoutId = setTimeout(() => {
|
|
53
|
-
reject(
|
|
53
|
+
reject(
|
|
54
|
+
new Error(
|
|
55
|
+
`Timeout waiting for state matching predicate after ${timeout}ms`,
|
|
56
|
+
),
|
|
57
|
+
);
|
|
54
58
|
}, timeout);
|
|
55
|
-
|
|
59
|
+
|
|
56
60
|
const unsubscribe = bloc._observer.subscribe({
|
|
57
|
-
id: `test-waiter-${
|
|
61
|
+
id: `test-waiter-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
58
62
|
fn: (newState: S) => {
|
|
59
63
|
if (predicate(newState)) {
|
|
60
64
|
clearTimeout(timeoutId);
|
|
61
65
|
unsubscribe();
|
|
62
66
|
resolve(newState);
|
|
63
67
|
}
|
|
64
|
-
}
|
|
68
|
+
},
|
|
65
69
|
});
|
|
66
|
-
|
|
70
|
+
|
|
67
71
|
// Check current state immediately
|
|
68
72
|
if (predicate(bloc.state)) {
|
|
69
73
|
clearTimeout(timeoutId);
|
|
@@ -72,53 +76,57 @@ export class BlocTest {
|
|
|
72
76
|
}
|
|
73
77
|
});
|
|
74
78
|
}
|
|
75
|
-
|
|
79
|
+
|
|
76
80
|
/**
|
|
77
81
|
* Expects a bloc to emit specific states in order
|
|
78
82
|
*/
|
|
79
83
|
static async expectStates<T extends BlocBase<S>, S>(
|
|
80
84
|
bloc: T,
|
|
81
85
|
expectedStates: S[],
|
|
82
|
-
timeout = 5000
|
|
86
|
+
timeout = 5000,
|
|
83
87
|
): Promise<void> {
|
|
84
88
|
const receivedStates: S[] = [];
|
|
85
|
-
|
|
89
|
+
|
|
86
90
|
return new Promise<void>((resolve, reject) => {
|
|
87
91
|
const timeoutId = setTimeout(() => {
|
|
88
|
-
reject(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
reject(
|
|
93
|
+
new Error(
|
|
94
|
+
`Timeout waiting for states. Expected: ${JSON.stringify(expectedStates)}, ` +
|
|
95
|
+
`Received: ${JSON.stringify(receivedStates)}`,
|
|
96
|
+
),
|
|
97
|
+
);
|
|
92
98
|
}, timeout);
|
|
93
|
-
|
|
99
|
+
|
|
94
100
|
const unsubscribe = bloc._observer.subscribe({
|
|
95
101
|
id: `test-expecter-${crypto.randomUUID()}`,
|
|
96
102
|
fn: (newState: S) => {
|
|
97
103
|
receivedStates.push(newState);
|
|
98
|
-
|
|
104
|
+
|
|
99
105
|
// Check if we have all expected states
|
|
100
106
|
if (receivedStates.length === expectedStates.length) {
|
|
101
107
|
clearTimeout(timeoutId);
|
|
102
108
|
unsubscribe();
|
|
103
|
-
|
|
109
|
+
|
|
104
110
|
// Verify all states match using deep equality
|
|
105
111
|
for (let i = 0; i < expectedStates.length; i++) {
|
|
106
112
|
const expected = expectedStates[i];
|
|
107
113
|
const received = receivedStates[i];
|
|
108
|
-
|
|
114
|
+
|
|
109
115
|
// Use JSON comparison for deep equality
|
|
110
116
|
if (JSON.stringify(expected) !== JSON.stringify(received)) {
|
|
111
|
-
reject(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
reject(
|
|
118
|
+
new Error(
|
|
119
|
+
`State mismatch at index ${i}. Expected: ${JSON.stringify(expected)}, ` +
|
|
120
|
+
`Received: ${JSON.stringify(received)}`,
|
|
121
|
+
),
|
|
122
|
+
);
|
|
115
123
|
return;
|
|
116
124
|
}
|
|
117
125
|
}
|
|
118
|
-
|
|
126
|
+
|
|
119
127
|
resolve();
|
|
120
128
|
}
|
|
121
|
-
}
|
|
129
|
+
},
|
|
122
130
|
});
|
|
123
131
|
});
|
|
124
132
|
}
|
|
@@ -127,31 +135,37 @@ export class BlocTest {
|
|
|
127
135
|
/**
|
|
128
136
|
* Mock Bloc for testing
|
|
129
137
|
*/
|
|
130
|
-
export class MockBloc<
|
|
131
|
-
|
|
132
|
-
|
|
138
|
+
export class MockBloc<
|
|
139
|
+
S,
|
|
140
|
+
A extends BlocEventConstraint = BlocEventConstraint,
|
|
141
|
+
> extends Bloc<S, A> {
|
|
142
|
+
private mockHandlers = new Map<
|
|
143
|
+
string,
|
|
144
|
+
(event: A, emit: (newState: S) => void) => void | Promise<void>
|
|
145
|
+
>();
|
|
146
|
+
|
|
133
147
|
constructor(initialState: S) {
|
|
134
148
|
super(initialState);
|
|
135
149
|
}
|
|
136
|
-
|
|
150
|
+
|
|
137
151
|
/**
|
|
138
152
|
* Mock an event handler for testing
|
|
139
153
|
*/
|
|
140
154
|
mockEventHandler<E extends A>(
|
|
141
155
|
eventConstructor: new (...args: any[]) => E,
|
|
142
|
-
handler: (event: E, emit: (newState: S) => void) => void | Promise<void
|
|
156
|
+
handler: (event: E, emit: (newState: S) => void) => void | Promise<void>,
|
|
143
157
|
): void {
|
|
144
158
|
// Use the on method to register the mock handler
|
|
145
159
|
this.on(eventConstructor, handler);
|
|
146
160
|
}
|
|
147
|
-
|
|
161
|
+
|
|
148
162
|
/**
|
|
149
163
|
* Get the number of registered handlers
|
|
150
164
|
*/
|
|
151
165
|
getHandlerCount(): number {
|
|
152
166
|
return this.eventHandlers.size;
|
|
153
167
|
}
|
|
154
|
-
|
|
168
|
+
|
|
155
169
|
/**
|
|
156
170
|
* Check if a handler is registered for an event type
|
|
157
171
|
*/
|
|
@@ -165,12 +179,12 @@ export class MockBloc<S, A extends BlocEventConstraint = BlocEventConstraint> ex
|
|
|
165
179
|
*/
|
|
166
180
|
export class MockCubit<S> extends Cubit<S> {
|
|
167
181
|
private stateHistory: S[] = [];
|
|
168
|
-
|
|
182
|
+
|
|
169
183
|
constructor(initialState: S) {
|
|
170
184
|
super(initialState);
|
|
171
185
|
this.stateHistory.push(initialState);
|
|
172
186
|
}
|
|
173
|
-
|
|
187
|
+
|
|
174
188
|
/**
|
|
175
189
|
* Override emit to track state history
|
|
176
190
|
*/
|
|
@@ -178,14 +192,14 @@ export class MockCubit<S> extends Cubit<S> {
|
|
|
178
192
|
this.stateHistory.push(newState);
|
|
179
193
|
super.emit(newState);
|
|
180
194
|
}
|
|
181
|
-
|
|
195
|
+
|
|
182
196
|
/**
|
|
183
197
|
* Get the history of all states
|
|
184
198
|
*/
|
|
185
199
|
getStateHistory(): S[] {
|
|
186
200
|
return [...this.stateHistory];
|
|
187
201
|
}
|
|
188
|
-
|
|
202
|
+
|
|
189
203
|
/**
|
|
190
204
|
* Clear state history
|
|
191
205
|
*/
|
|
@@ -199,11 +213,11 @@ export class MockCubit<S> extends Cubit<S> {
|
|
|
199
213
|
*/
|
|
200
214
|
export class MemoryLeakDetector {
|
|
201
215
|
private initialStats: ReturnType<typeof Blac.getMemoryStats>;
|
|
202
|
-
|
|
216
|
+
|
|
203
217
|
constructor() {
|
|
204
218
|
this.initialStats = Blac.getMemoryStats();
|
|
205
219
|
}
|
|
206
|
-
|
|
220
|
+
|
|
207
221
|
/**
|
|
208
222
|
* Check for memory leaks and return a report
|
|
209
223
|
*/
|
|
@@ -213,12 +227,11 @@ export class MemoryLeakDetector {
|
|
|
213
227
|
stats: ReturnType<typeof Blac.getMemoryStats>;
|
|
214
228
|
} {
|
|
215
229
|
const currentStats = Blac.getMemoryStats();
|
|
216
|
-
const hasLeaks =
|
|
230
|
+
const hasLeaks =
|
|
217
231
|
currentStats.registeredBlocs > this.initialStats.registeredBlocs ||
|
|
218
232
|
currentStats.isolatedBlocs > this.initialStats.isolatedBlocs ||
|
|
219
|
-
currentStats.keepAliveBlocs > this.initialStats.keepAliveBlocs
|
|
220
|
-
|
|
221
|
-
|
|
233
|
+
currentStats.keepAliveBlocs > this.initialStats.keepAliveBlocs;
|
|
234
|
+
|
|
222
235
|
const report = `
|
|
223
236
|
Memory Leak Detection Report:
|
|
224
237
|
- Initial registered blocs: ${this.initialStats.registeredBlocs}
|
|
@@ -229,11 +242,11 @@ Memory Leak Detection Report:
|
|
|
229
242
|
- Current keep-alive blocs: ${currentStats.keepAliveBlocs}
|
|
230
243
|
- Potential leaks detected: ${hasLeaks ? 'YES' : 'NO'}
|
|
231
244
|
`.trim();
|
|
232
|
-
|
|
245
|
+
|
|
233
246
|
return {
|
|
234
247
|
hasLeaks,
|
|
235
248
|
report,
|
|
236
|
-
stats: currentStats
|
|
249
|
+
stats: currentStats,
|
|
237
250
|
};
|
|
238
251
|
}
|
|
239
|
-
}
|
|
252
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -20,7 +20,10 @@ export type BlocBaseAbstract =
|
|
|
20
20
|
* Represents a constructor type for a Bloc that can take any parameters
|
|
21
21
|
* @template B - The type of the Bloc instance
|
|
22
22
|
*/
|
|
23
|
-
export type BlocConstructor<B> = new (...args: any) => B
|
|
23
|
+
export type BlocConstructor<B> = (new (...args: any) => B) & {
|
|
24
|
+
isolated?: boolean;
|
|
25
|
+
keepAlive?: boolean;
|
|
26
|
+
};
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
29
|
* Extracts the state type from a BlocBase instance
|
|
@@ -71,15 +74,27 @@ export type BlocEventConstraint = BlocEvent & object;
|
|
|
71
74
|
* Error boundary interface for Bloc error handling
|
|
72
75
|
*/
|
|
73
76
|
export interface BlocErrorBoundary<S, A extends BlocEventConstraint> {
|
|
74
|
-
onError: (
|
|
77
|
+
onError: (
|
|
78
|
+
error: Error,
|
|
79
|
+
event: A,
|
|
80
|
+
currentState: S,
|
|
81
|
+
bloc: { name: string; id: string },
|
|
82
|
+
) => void | Promise<void>;
|
|
75
83
|
shouldRethrow?: (error: Error, event: A) => boolean;
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
/**
|
|
79
87
|
* Function type for determining dependencies that trigger re-renders
|
|
88
|
+
* Similar to React's useEffect dependency array - if any dependency changes, a re-render is triggered
|
|
80
89
|
* @template S The state type
|
|
81
|
-
* @
|
|
90
|
+
* @template I The bloc instance type
|
|
91
|
+
* @param currentState The current state
|
|
92
|
+
* @param previousState The previous state (undefined on first call)
|
|
93
|
+
* @param instance The bloc instance
|
|
94
|
+
* @returns Array of dependencies - if any dependency changes, a re-render is triggered
|
|
82
95
|
*/
|
|
83
|
-
export type BlocHookDependencyArrayFn<S> = (
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
export type BlocHookDependencyArrayFn<S, I = any> = (
|
|
97
|
+
currentState: S,
|
|
98
|
+
previousState: S | undefined,
|
|
99
|
+
instance: I,
|
|
100
|
+
) => unknown[];
|
package/src/utils/uuid.ts
CHANGED
|
@@ -14,9 +14,9 @@ export function generateUUID(): string {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
// Fallback implementation for React Native/Hermes and older environments
|
|
17
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
18
|
-
const r = Math.random() * 16 | 0;
|
|
19
|
-
const v = c === 'x' ? r : (r & 0x3 | 0x8
|
|
17
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
18
|
+
const r = (Math.random() * 16) | 0;
|
|
19
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
20
20
|
return v.toString(16);
|
|
21
21
|
});
|
|
22
|
-
}
|
|
22
|
+
}
|