@blac/core 2.0.0-rc-4 → 2.0.0-rc-6

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/src/testing.ts ADDED
@@ -0,0 +1,239 @@
1
+ import { Blac } from './Blac';
2
+ import { Bloc } from './Bloc';
3
+ import { BlocBase } from './BlocBase';
4
+ import { Cubit } from './Cubit';
5
+ import { BlocEventConstraint } from './types';
6
+
7
+ /**
8
+ * Test utilities for Blac state management
9
+ */
10
+ export class BlocTest {
11
+ private static originalInstance: Blac;
12
+
13
+ /**
14
+ * Sets up a clean test environment
15
+ */
16
+ static setUp(): void {
17
+ this.originalInstance = Blac.instance;
18
+ Blac.resetInstance();
19
+ Blac.enableLog = false; // Disable logging in tests by default
20
+ }
21
+
22
+ /**
23
+ * Tears down the test environment and restores original state
24
+ */
25
+ static tearDown(): void {
26
+ Blac.resetInstance();
27
+ // Note: Cannot restore original instance due to singleton pattern
28
+ // Tests should use setUp/tearDown properly to manage state
29
+ }
30
+
31
+ /**
32
+ * Creates a test bloc with automatic cleanup
33
+ */
34
+ static createBloc<T extends BlocBase<any>>(
35
+ BlocClass: new (...args: any[]) => T,
36
+ ...args: any[]
37
+ ): T {
38
+ const bloc = new BlocClass(...args);
39
+ Blac.activateBloc(bloc);
40
+ return bloc;
41
+ }
42
+
43
+ /**
44
+ * Waits for a bloc to emit a specific state
45
+ */
46
+ static async waitForState<T extends BlocBase<S>, S>(
47
+ bloc: T,
48
+ predicate: (state: S) => boolean,
49
+ timeout = 5000
50
+ ): Promise<S> {
51
+ return new Promise<S>((resolve, reject) => {
52
+ const timeoutId = setTimeout(() => {
53
+ reject(new Error(`Timeout waiting for state matching predicate after ${timeout}ms`));
54
+ }, timeout);
55
+
56
+ const unsubscribe = bloc._observer.subscribe({
57
+ id: `test-waiter-${crypto.randomUUID()}`,
58
+ fn: (newState: S) => {
59
+ if (predicate(newState)) {
60
+ clearTimeout(timeoutId);
61
+ unsubscribe();
62
+ resolve(newState);
63
+ }
64
+ }
65
+ });
66
+
67
+ // Check current state immediately
68
+ if (predicate(bloc.state)) {
69
+ clearTimeout(timeoutId);
70
+ unsubscribe();
71
+ resolve(bloc.state);
72
+ }
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Expects a bloc to emit specific states in order
78
+ */
79
+ static async expectStates<T extends BlocBase<S>, S>(
80
+ bloc: T,
81
+ expectedStates: S[],
82
+ timeout = 5000
83
+ ): Promise<void> {
84
+ const receivedStates: S[] = [];
85
+
86
+ return new Promise<void>((resolve, reject) => {
87
+ const timeoutId = setTimeout(() => {
88
+ reject(new Error(
89
+ `Timeout waiting for states. Expected: ${JSON.stringify(expectedStates)}, ` +
90
+ `Received: ${JSON.stringify(receivedStates)}`
91
+ ));
92
+ }, timeout);
93
+
94
+ const unsubscribe = bloc._observer.subscribe({
95
+ id: `test-expecter-${crypto.randomUUID()}`,
96
+ fn: (newState: S) => {
97
+ receivedStates.push(newState);
98
+
99
+ // Check if we have all expected states
100
+ if (receivedStates.length === expectedStates.length) {
101
+ clearTimeout(timeoutId);
102
+ unsubscribe();
103
+
104
+ // Verify all states match using deep equality
105
+ for (let i = 0; i < expectedStates.length; i++) {
106
+ const expected = expectedStates[i];
107
+ const received = receivedStates[i];
108
+
109
+ // Use JSON comparison for deep equality
110
+ if (JSON.stringify(expected) !== JSON.stringify(received)) {
111
+ reject(new Error(
112
+ `State mismatch at index ${i}. Expected: ${JSON.stringify(expected)}, ` +
113
+ `Received: ${JSON.stringify(received)}`
114
+ ));
115
+ return;
116
+ }
117
+ }
118
+
119
+ resolve();
120
+ }
121
+ }
122
+ });
123
+ });
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Mock Bloc for testing
129
+ */
130
+ export class MockBloc<S, A extends BlocEventConstraint = BlocEventConstraint> extends Bloc<S, A> {
131
+ private mockHandlers = new Map<string, (event: A, emit: (newState: S) => void) => void | Promise<void>>();
132
+
133
+ constructor(initialState: S) {
134
+ super(initialState);
135
+ }
136
+
137
+ /**
138
+ * Mock an event handler for testing
139
+ */
140
+ mockEventHandler<E extends A>(
141
+ eventConstructor: new (...args: any[]) => E,
142
+ handler: (event: E, emit: (newState: S) => void) => void | Promise<void>
143
+ ): void {
144
+ // Use the on method to register the mock handler
145
+ this.on(eventConstructor, handler);
146
+ }
147
+
148
+ /**
149
+ * Get the number of registered handlers
150
+ */
151
+ getHandlerCount(): number {
152
+ return this.eventHandlers.size;
153
+ }
154
+
155
+ /**
156
+ * Check if a handler is registered for an event type
157
+ */
158
+ hasHandler(eventConstructor: new (...args: any[]) => A): boolean {
159
+ return this.eventHandlers.has(eventConstructor);
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Mock Cubit for testing
165
+ */
166
+ export class MockCubit<S> extends Cubit<S> {
167
+ private stateHistory: S[] = [];
168
+
169
+ constructor(initialState: S) {
170
+ super(initialState);
171
+ this.stateHistory.push(initialState);
172
+ }
173
+
174
+ /**
175
+ * Override emit to track state history
176
+ */
177
+ emit(newState: S): void {
178
+ this.stateHistory.push(newState);
179
+ super.emit(newState);
180
+ }
181
+
182
+ /**
183
+ * Get the history of all states
184
+ */
185
+ getStateHistory(): S[] {
186
+ return [...this.stateHistory];
187
+ }
188
+
189
+ /**
190
+ * Clear state history
191
+ */
192
+ clearStateHistory(): void {
193
+ this.stateHistory = [this.state];
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Memory leak detector for tests
199
+ */
200
+ export class MemoryLeakDetector {
201
+ private initialStats: ReturnType<typeof Blac.getMemoryStats>;
202
+
203
+ constructor() {
204
+ this.initialStats = Blac.getMemoryStats();
205
+ }
206
+
207
+ /**
208
+ * Check for memory leaks and return a report
209
+ */
210
+ checkForLeaks(): {
211
+ hasLeaks: boolean;
212
+ report: string;
213
+ stats: ReturnType<typeof Blac.getMemoryStats>;
214
+ } {
215
+ const currentStats = Blac.getMemoryStats();
216
+ const hasLeaks = (
217
+ currentStats.registeredBlocs > this.initialStats.registeredBlocs ||
218
+ currentStats.isolatedBlocs > this.initialStats.isolatedBlocs ||
219
+ currentStats.keepAliveBlocs > this.initialStats.keepAliveBlocs
220
+ );
221
+
222
+ const report = `
223
+ Memory Leak Detection Report:
224
+ - Initial registered blocs: ${this.initialStats.registeredBlocs}
225
+ - Current registered blocs: ${currentStats.registeredBlocs}
226
+ - Initial isolated blocs: ${this.initialStats.isolatedBlocs}
227
+ - Current isolated blocs: ${currentStats.isolatedBlocs}
228
+ - Initial keep-alive blocs: ${this.initialStats.keepAliveBlocs}
229
+ - Current keep-alive blocs: ${currentStats.keepAliveBlocs}
230
+ - Potential leaks detected: ${hasLeaks ? 'YES' : 'NO'}
231
+ `.trim();
232
+
233
+ return {
234
+ hasLeaks,
235
+ report,
236
+ stats: currentStats
237
+ };
238
+ }
239
+ }
package/src/types.ts CHANGED
@@ -54,8 +54,31 @@ export type BlocConstructorParameters<B extends BlocBase<any>> =
54
54
  BlocConstructor<B> extends new (...args: infer P) => B ? P : never;
55
55
 
56
56
  /**
57
- * Represents a function type for determining hook dependencies based on state changes
58
- * @template B - The BlocGeneric type
57
+ * Base interface for all Bloc events
58
+ * Events must be objects with a constructor to enable proper type matching
59
+ */
60
+ export interface BlocEvent {
61
+ readonly type?: string;
62
+ readonly timestamp?: number;
63
+ }
64
+
65
+ /**
66
+ * Enhanced constraint for Bloc events - must be objects with proper constructor
67
+ */
68
+ export type BlocEventConstraint = BlocEvent & object;
69
+
70
+ /**
71
+ * Error boundary interface for Bloc error handling
72
+ */
73
+ export interface BlocErrorBoundary<S, A extends BlocEventConstraint> {
74
+ onError: (error: Error, event: A, currentState: S, bloc: { name: string; id: string }) => void | Promise<void>;
75
+ shouldRethrow?: (error: Error, event: A) => boolean;
76
+ }
77
+
78
+ /**
79
+ * Function type for determining dependencies that trigger re-renders
80
+ * @template S The state type
81
+ * @returns Array of dependency arrays - if any dependency in any array changes, a re-render is triggered
59
82
  */
60
83
  export type BlocHookDependencyArrayFn<S> = (
61
84
  newState: S
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Cross-platform UUID v4 generator that works in all environments including React Native/Hermes
3
+ * Falls back to crypto.randomUUID() when available, otherwise uses Math.random()
4
+ */
5
+
6
+ /**
7
+ * Generates a UUID v4 string compatible with all JavaScript environments
8
+ * @returns A UUID v4 string in the format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
9
+ */
10
+ export function generateUUID(): string {
11
+ // Try to use crypto.randomUUID() if available (Node.js 16+, modern browsers)
12
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
13
+ return crypto.randomUUID();
14
+ }
15
+
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);
20
+ return v.toString(16);
21
+ });
22
+ }