@blac/core 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.
Files changed (157) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/Blac.d.ts +246 -0
  3. package/dist/Blac.d.ts.map +1 -0
  4. package/dist/Blac.js +624 -0
  5. package/dist/Blac.js.map +1 -0
  6. package/dist/Bloc.d.ts +41 -0
  7. package/dist/Bloc.d.ts.map +1 -0
  8. package/dist/Bloc.js +140 -0
  9. package/dist/Bloc.js.map +1 -0
  10. package/dist/BlocBase.d.ts +110 -0
  11. package/dist/BlocBase.d.ts.map +1 -0
  12. package/dist/BlocBase.js +287 -0
  13. package/dist/BlocBase.js.map +1 -0
  14. package/dist/Cubit.d.ts +25 -0
  15. package/dist/Cubit.d.ts.map +1 -0
  16. package/dist/Cubit.js +64 -0
  17. package/dist/Cubit.js.map +1 -0
  18. package/dist/adapter/BlacAdapter.d.ts +67 -0
  19. package/dist/adapter/BlacAdapter.d.ts.map +1 -0
  20. package/dist/adapter/BlacAdapter.js +263 -0
  21. package/dist/adapter/BlacAdapter.js.map +1 -0
  22. package/dist/adapter/ProxyFactory.d.ts +50 -0
  23. package/dist/adapter/ProxyFactory.d.ts.map +1 -0
  24. package/dist/adapter/ProxyFactory.js +150 -0
  25. package/dist/adapter/ProxyFactory.js.map +1 -0
  26. package/{src/adapter/index.ts → dist/adapter/index.d.ts} +1 -0
  27. package/dist/adapter/index.d.ts.map +1 -0
  28. package/dist/adapter/index.js +19 -0
  29. package/dist/adapter/index.js.map +1 -0
  30. package/dist/errors/BlacError.d.ts +38 -0
  31. package/dist/errors/BlacError.d.ts.map +1 -0
  32. package/dist/errors/BlacError.js +55 -0
  33. package/dist/errors/BlacError.js.map +1 -0
  34. package/dist/errors/ErrorManager.d.ts +39 -0
  35. package/dist/errors/ErrorManager.d.ts.map +1 -0
  36. package/dist/errors/ErrorManager.js +91 -0
  37. package/dist/errors/ErrorManager.js.map +1 -0
  38. package/dist/errors/handleError.d.ts +24 -0
  39. package/dist/errors/handleError.d.ts.map +1 -0
  40. package/dist/errors/handleError.js +62 -0
  41. package/dist/errors/handleError.js.map +1 -0
  42. package/dist/errors/index.d.ts +5 -0
  43. package/dist/errors/index.d.ts.map +1 -0
  44. package/dist/errors/index.js +13 -0
  45. package/dist/errors/index.js.map +1 -0
  46. package/{src/events.ts → dist/events.d.ts} +4 -2
  47. package/dist/events.d.ts.map +1 -0
  48. package/dist/events.js +17 -0
  49. package/dist/events.js.map +1 -0
  50. package/dist/index.cjs +38 -0
  51. package/{src/index.ts → dist/index.d.ts} +1 -10
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +38 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/lifecycle/BlocLifecycle.d.ts +51 -0
  56. package/dist/lifecycle/BlocLifecycle.d.ts.map +1 -0
  57. package/dist/lifecycle/BlocLifecycle.js +123 -0
  58. package/dist/lifecycle/BlocLifecycle.js.map +1 -0
  59. package/dist/lifecycle/index.d.ts +2 -0
  60. package/dist/lifecycle/index.d.ts.map +1 -0
  61. package/dist/lifecycle/index.js +18 -0
  62. package/dist/lifecycle/index.js.map +1 -0
  63. package/dist/plugins/BlocPluginRegistry.d.ts +58 -0
  64. package/dist/plugins/BlocPluginRegistry.d.ts.map +1 -0
  65. package/dist/plugins/BlocPluginRegistry.js +201 -0
  66. package/dist/plugins/BlocPluginRegistry.js.map +1 -0
  67. package/dist/plugins/SystemPluginRegistry.d.ts +79 -0
  68. package/dist/plugins/SystemPluginRegistry.d.ts.map +1 -0
  69. package/dist/plugins/SystemPluginRegistry.js +184 -0
  70. package/dist/plugins/SystemPluginRegistry.js.map +1 -0
  71. package/{src/plugins/index.ts → dist/plugins/index.d.ts} +1 -5
  72. package/dist/plugins/index.d.ts.map +1 -0
  73. package/dist/plugins/index.js +26 -0
  74. package/dist/plugins/index.js.map +1 -0
  75. package/dist/plugins/types.d.ts +103 -0
  76. package/dist/plugins/types.d.ts.map +1 -0
  77. package/dist/plugins/types.js +3 -0
  78. package/dist/plugins/types.js.map +1 -0
  79. package/dist/subscription/SubscriptionManager.d.ts +67 -0
  80. package/dist/subscription/SubscriptionManager.d.ts.map +1 -0
  81. package/dist/subscription/SubscriptionManager.js +317 -0
  82. package/dist/subscription/SubscriptionManager.js.map +1 -0
  83. package/{src/subscription/index.ts → dist/subscription/index.d.ts} +1 -0
  84. package/dist/subscription/index.d.ts.map +1 -0
  85. package/dist/subscription/index.js +19 -0
  86. package/dist/subscription/index.js.map +1 -0
  87. package/dist/subscription/types.d.ts +65 -0
  88. package/dist/subscription/types.d.ts.map +1 -0
  89. package/dist/subscription/types.js +3 -0
  90. package/dist/subscription/types.js.map +1 -0
  91. package/dist/testing.d.ts +33 -0
  92. package/dist/testing.d.ts.map +1 -0
  93. package/dist/testing.js +68 -0
  94. package/dist/testing.js.map +1 -0
  95. package/{src/types.ts → dist/types.d.ts} +11 -28
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +3 -0
  98. package/dist/types.js.map +1 -0
  99. package/dist/utils/BatchingManager.d.ts +28 -0
  100. package/dist/utils/BatchingManager.d.ts.map +1 -0
  101. package/dist/utils/BatchingManager.js +48 -0
  102. package/dist/utils/BatchingManager.js.map +1 -0
  103. package/dist/utils/RerenderLogger.d.ts +60 -0
  104. package/dist/utils/RerenderLogger.d.ts.map +1 -0
  105. package/dist/utils/RerenderLogger.js +205 -0
  106. package/dist/utils/RerenderLogger.js.map +1 -0
  107. package/dist/utils/generateInstanceId.d.ts +11 -0
  108. package/dist/utils/generateInstanceId.d.ts.map +1 -0
  109. package/dist/utils/generateInstanceId.js +50 -0
  110. package/dist/utils/generateInstanceId.js.map +1 -0
  111. package/dist/utils/shallowEqual.d.ts +9 -0
  112. package/dist/utils/shallowEqual.d.ts.map +1 -0
  113. package/dist/utils/shallowEqual.js +35 -0
  114. package/dist/utils/shallowEqual.js.map +1 -0
  115. package/dist/utils/uuid.d.ts +10 -0
  116. package/dist/utils/uuid.d.ts.map +1 -0
  117. package/dist/utils/uuid.js +24 -0
  118. package/dist/utils/uuid.js.map +1 -0
  119. package/package.json +19 -6
  120. package/src/Blac.ts +0 -802
  121. package/src/Bloc.ts +0 -180
  122. package/src/BlocBase.ts +0 -398
  123. package/src/Cubit.ts +0 -68
  124. package/src/__tests__/Blac.config.test.ts +0 -79
  125. package/src/__tests__/Bloc.event.test.ts +0 -394
  126. package/src/__tests__/BlocBase.subscription.test.ts +0 -349
  127. package/src/__tests__/Cubit.test.ts +0 -462
  128. package/src/__tests__/memory-leaks.test.ts +0 -383
  129. package/src/__tests__/plugins.test.ts +0 -464
  130. package/src/adapter/BlacAdapter.ts +0 -467
  131. package/src/adapter/ProxyFactory.ts +0 -249
  132. package/src/adapter/__tests__/BlacAdapter.test.ts +0 -555
  133. package/src/adapter/__tests__/ProxyFactory.test.ts +0 -655
  134. package/src/errors/BlacError.ts +0 -69
  135. package/src/errors/ErrorManager.ts +0 -101
  136. package/src/errors/handleError.ts +0 -114
  137. package/src/errors/index.ts +0 -13
  138. package/src/lifecycle/BlocLifecycle.ts +0 -156
  139. package/src/lifecycle/index.ts +0 -1
  140. package/src/plugins/BlocPluginRegistry.ts +0 -240
  141. package/src/plugins/SystemPluginRegistry.ts +0 -242
  142. package/src/plugins/__tests__/BlocPluginRegistry.test.ts +0 -450
  143. package/src/plugins/__tests__/SystemPluginRegistry.test.ts +0 -404
  144. package/src/plugins/types.ts +0 -129
  145. package/src/subscription/README.md +0 -132
  146. package/src/subscription/SubscriptionManager.ts +0 -395
  147. package/src/subscription/__tests__/SubscriptionManager.test.ts +0 -355
  148. package/src/subscription/types.ts +0 -90
  149. package/src/testing.ts +0 -246
  150. package/src/utils/BatchingManager.ts +0 -59
  151. package/src/utils/RerenderLogger.ts +0 -265
  152. package/src/utils/__tests__/generateInstanceId.test.ts +0 -104
  153. package/src/utils/__tests__/shallowEqual.test.ts +0 -171
  154. package/src/utils/__tests__/uuid.test.ts +0 -144
  155. package/src/utils/generateInstanceId.ts +0 -54
  156. package/src/utils/shallowEqual.ts +0 -40
  157. package/src/utils/uuid.ts +0 -22
@@ -1,242 +0,0 @@
1
- import {
2
- BlacPlugin,
3
- PluginRegistry,
4
- PluginMetrics,
5
- ErrorContext,
6
- AdapterMetadata,
7
- } from './types';
8
- import { BlocBase } from '../BlocBase';
9
- import { Bloc } from '../Bloc';
10
-
11
- /**
12
- * Registry for system-wide plugins
13
- */
14
- export class SystemPluginRegistry implements PluginRegistry<BlacPlugin> {
15
- private plugins = new Map<string, BlacPlugin>();
16
- private metrics = new Map<string, Map<string, PluginMetrics>>();
17
- private executionOrder: string[] = [];
18
-
19
- /**
20
- * Add a system plugin
21
- */
22
- add(plugin: BlacPlugin): void {
23
- if (this.plugins.has(plugin.name)) {
24
- throw new Error(`Plugin '${plugin.name}' is already registered`);
25
- }
26
-
27
- this.plugins.set(plugin.name, plugin);
28
- this.executionOrder.push(plugin.name);
29
- this.initializeMetrics(plugin.name);
30
- }
31
-
32
- /**
33
- * Remove a system plugin
34
- */
35
- remove(pluginName: string): boolean {
36
- const plugin = this.plugins.get(pluginName);
37
- if (!plugin) return false;
38
-
39
- this.plugins.delete(pluginName);
40
- this.metrics.delete(pluginName);
41
- this.executionOrder = this.executionOrder.filter(
42
- (name) => name !== pluginName,
43
- );
44
-
45
- return true;
46
- }
47
-
48
- /**
49
- * Get a plugin by name
50
- */
51
- get(pluginName: string): BlacPlugin | undefined {
52
- return this.plugins.get(pluginName);
53
- }
54
-
55
- /**
56
- * Get all plugins in execution order
57
- */
58
- getAll(): ReadonlyArray<BlacPlugin> {
59
- return this.executionOrder.map((name) => this.plugins.get(name)!);
60
- }
61
-
62
- /**
63
- * Clear all plugins
64
- */
65
- clear(): void {
66
- this.plugins.clear();
67
- this.metrics.clear();
68
- this.executionOrder = [];
69
- }
70
-
71
- /**
72
- * Execute a hook on all plugins
73
- */
74
- executeHook(
75
- hookName: keyof BlacPlugin,
76
- args: unknown[],
77
- errorHandler?: (error: Error, plugin: BlacPlugin) => void,
78
- ): void {
79
- for (const pluginName of this.executionOrder) {
80
- const plugin = this.plugins.get(pluginName)!;
81
- const hook = plugin[hookName] as Function | undefined;
82
-
83
- if (typeof hook !== 'function') continue;
84
-
85
- const startTime = performance.now();
86
-
87
- try {
88
- hook.apply(plugin, args);
89
- this.recordSuccess(pluginName, hookName as string, startTime);
90
- } catch (error) {
91
- this.recordError(pluginName, hookName as string, error as Error);
92
-
93
- if (errorHandler) {
94
- errorHandler(error as Error, plugin);
95
- } else {
96
- // Default: log and continue
97
- console.error(
98
- `Plugin '${pluginName}' error in hook '${hookName as string}':`,
99
- error,
100
- );
101
- }
102
- }
103
- }
104
- }
105
-
106
- /**
107
- * Bootstrap all plugins
108
- */
109
- bootstrap(): void {
110
- this.executeHook('beforeBootstrap', []);
111
- this.executeHook('afterBootstrap', []);
112
- }
113
-
114
- /**
115
- * Shutdown all plugins
116
- */
117
- shutdown(): void {
118
- this.executeHook('beforeShutdown', []);
119
- this.executeHook('afterShutdown', []);
120
- }
121
-
122
- /**
123
- * Notify plugins of bloc creation
124
- */
125
- notifyBlocCreated(bloc: BlocBase<any>): void {
126
- this.executeHook('onBlocCreated', [bloc]);
127
- }
128
-
129
- /**
130
- * Notify plugins of bloc disposal
131
- */
132
- notifyBlocDisposed(bloc: BlocBase<any>): void {
133
- this.executeHook('onBlocDisposed', [bloc]);
134
- }
135
-
136
- /**
137
- * Notify plugins of state change
138
- */
139
- notifyStateChanged(
140
- bloc: BlocBase<any>,
141
- previousState: any,
142
- currentState: any,
143
- ): void {
144
- this.executeHook('onStateChanged', [bloc, previousState, currentState]);
145
- }
146
-
147
- /**
148
- * Notify plugins of event addition
149
- */
150
- notifyEventAdded(bloc: Bloc<any, any>, event: any): void {
151
- this.executeHook('onEventAdded', [bloc, event]);
152
- }
153
-
154
- /**
155
- * Notify plugins of errors
156
- */
157
- notifyError(
158
- error: Error,
159
- bloc: BlocBase<unknown>,
160
- context: ErrorContext,
161
- ): void {
162
- this.executeHook('onError', [error, bloc, context], (hookError, plugin) => {
163
- // Double fault protection - if error handler fails, just log
164
- console.error(`Plugin '${plugin.name}' error handler failed:`, hookError);
165
- });
166
- }
167
-
168
- /**
169
- * Notify plugins of adapter lifecycle events
170
- */
171
- notifyAdapterCreated(adapter: any, metadata: AdapterMetadata): void {
172
- this.executeHook('onAdapterCreated', [adapter, metadata]);
173
- }
174
-
175
- notifyAdapterMount(adapter: any, metadata: AdapterMetadata): void {
176
- this.executeHook('onAdapterMount', [adapter, metadata]);
177
- }
178
-
179
- notifyAdapterUnmount(adapter: any, metadata: AdapterMetadata): void {
180
- this.executeHook('onAdapterUnmount', [adapter, metadata]);
181
- }
182
-
183
- notifyAdapterRender(adapter: any, metadata: AdapterMetadata): void {
184
- this.executeHook('onAdapterRender', [adapter, metadata]);
185
- }
186
-
187
- notifyAdapterDisposed(adapter: any, metadata: AdapterMetadata): void {
188
- this.executeHook('onAdapterDisposed', [adapter, metadata]);
189
- }
190
-
191
- /**
192
- * Get metrics for a plugin
193
- */
194
- getMetrics(pluginName: string): Map<string, PluginMetrics> | undefined {
195
- return this.metrics.get(pluginName);
196
- }
197
-
198
- private initializeMetrics(pluginName: string): void {
199
- this.metrics.set(pluginName, new Map());
200
- }
201
-
202
- private recordSuccess(
203
- pluginName: string,
204
- hookName: string,
205
- startTime: number,
206
- ): void {
207
- const pluginMetrics = this.metrics.get(pluginName)!;
208
- const hookMetrics = pluginMetrics.get(hookName) || {
209
- executionTime: 0,
210
- executionCount: 0,
211
- errorCount: 0,
212
- };
213
-
214
- const executionTime = performance.now() - startTime;
215
-
216
- pluginMetrics.set(hookName, {
217
- ...hookMetrics,
218
- executionTime: hookMetrics.executionTime + executionTime,
219
- executionCount: hookMetrics.executionCount + 1,
220
- lastExecutionTime: executionTime,
221
- });
222
- }
223
-
224
- private recordError(
225
- pluginName: string,
226
- hookName: string,
227
- error: Error,
228
- ): void {
229
- const pluginMetrics = this.metrics.get(pluginName)!;
230
- const hookMetrics = pluginMetrics.get(hookName) || {
231
- executionTime: 0,
232
- executionCount: 0,
233
- errorCount: 0,
234
- };
235
-
236
- pluginMetrics.set(hookName, {
237
- ...hookMetrics,
238
- errorCount: hookMetrics.errorCount + 1,
239
- lastError: error,
240
- });
241
- }
242
- }
@@ -1,450 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { BlocPluginRegistry } from '../BlocPluginRegistry';
3
- import { BlocPlugin } from '../types';
4
-
5
- // Mock bloc for testing
6
- class MockBloc {
7
- state = { count: 0 };
8
- }
9
-
10
- // Sample plugins for testing
11
- const createTestPlugin = (
12
- name: string,
13
- overrides?: Partial<BlocPlugin<any, any>>,
14
- ): BlocPlugin<any, any> => ({
15
- name,
16
- version: '1.0.0',
17
- ...overrides,
18
- });
19
-
20
- describe('BlocPluginRegistry', () => {
21
- let registry: BlocPluginRegistry<any, any>;
22
-
23
- beforeEach(() => {
24
- registry = new BlocPluginRegistry();
25
- vi.spyOn(console, 'error').mockImplementation(() => {});
26
- });
27
-
28
- describe('Plugin Management', () => {
29
- it('should add plugins successfully', () => {
30
- const plugin = createTestPlugin('test-plugin');
31
-
32
- registry.add(plugin);
33
-
34
- expect(registry.get('test-plugin')).toBe(plugin);
35
- expect(registry.getAll()).toContain(plugin);
36
- });
37
-
38
- it('should throw error when adding duplicate plugin', () => {
39
- const plugin = createTestPlugin('test-plugin');
40
-
41
- registry.add(plugin);
42
-
43
- expect(() => registry.add(plugin)).toThrow(
44
- "Plugin 'test-plugin' is already registered",
45
- );
46
- });
47
-
48
- it('should maintain execution order', () => {
49
- const plugin1 = createTestPlugin('plugin-1');
50
- const plugin2 = createTestPlugin('plugin-2');
51
- const plugin3 = createTestPlugin('plugin-3');
52
-
53
- registry.add(plugin1);
54
- registry.add(plugin2);
55
- registry.add(plugin3);
56
-
57
- const all = registry.getAll();
58
- expect(all[0]).toBe(plugin1);
59
- expect(all[1]).toBe(plugin2);
60
- expect(all[2]).toBe(plugin3);
61
- });
62
-
63
- it('should remove plugins', () => {
64
- const plugin = createTestPlugin('test-plugin');
65
-
66
- registry.add(plugin);
67
- expect(registry.get('test-plugin')).toBe(plugin);
68
-
69
- const removed = registry.remove('test-plugin');
70
- expect(removed).toBe(true);
71
- expect(registry.get('test-plugin')).toBeUndefined();
72
- expect(registry.getAll()).not.toContain(plugin);
73
- });
74
-
75
- it('should return false when removing non-existent plugin', () => {
76
- const removed = registry.remove('non-existent');
77
- expect(removed).toBe(false);
78
- });
79
-
80
- it('should clear all plugins', () => {
81
- registry.add(createTestPlugin('plugin-1'));
82
- registry.add(createTestPlugin('plugin-2'));
83
- registry.add(createTestPlugin('plugin-3'));
84
-
85
- expect(registry.getAll().length).toBe(3);
86
-
87
- registry.clear();
88
-
89
- expect(registry.getAll().length).toBe(0);
90
- });
91
- });
92
-
93
- describe('Capability Validation', () => {
94
- it('should validate transformState requires readState', () => {
95
- const plugin = createTestPlugin('test-plugin', {
96
- capabilities: {
97
- readState: false,
98
- transformState: true,
99
- interceptEvents: false,
100
- persistData: false,
101
- accessMetadata: false,
102
- },
103
- });
104
-
105
- expect(() => registry.add(plugin)).toThrow(
106
- "Plugin 'test-plugin': transformState requires readState capability",
107
- );
108
- });
109
-
110
- it('should validate interceptEvents requires readState', () => {
111
- const plugin = createTestPlugin('test-plugin', {
112
- capabilities: {
113
- readState: false,
114
- transformState: false,
115
- interceptEvents: true,
116
- persistData: false,
117
- accessMetadata: false,
118
- },
119
- });
120
-
121
- expect(() => registry.add(plugin)).toThrow(
122
- "Plugin 'test-plugin': interceptEvents requires readState capability",
123
- );
124
- });
125
-
126
- it('should allow valid capability combinations', () => {
127
- const plugin = createTestPlugin('test-plugin', {
128
- capabilities: {
129
- readState: true,
130
- transformState: true,
131
- interceptEvents: true,
132
- persistData: false,
133
- accessMetadata: false,
134
- },
135
- });
136
-
137
- expect(() => registry.add(plugin)).not.toThrow();
138
- });
139
- });
140
-
141
- describe('Lifecycle Hooks', () => {
142
- it('should call onAttach for all plugins', () => {
143
- const onAttach1 = vi.fn();
144
- const onAttach2 = vi.fn();
145
-
146
- registry.add(createTestPlugin('plugin-1', { onAttach: onAttach1 }));
147
- registry.add(createTestPlugin('plugin-2', { onAttach: onAttach2 }));
148
-
149
- const bloc = new MockBloc();
150
- registry.attach(bloc);
151
-
152
- expect(onAttach1).toHaveBeenCalledWith(bloc);
153
- expect(onAttach2).toHaveBeenCalledWith(bloc);
154
- });
155
-
156
- it('should throw error when attaching twice', () => {
157
- registry.add(createTestPlugin('plugin-1'));
158
-
159
- const bloc = new MockBloc();
160
- registry.attach(bloc);
161
-
162
- expect(() => registry.attach(bloc)).toThrow(
163
- 'Plugins are already attached',
164
- );
165
- });
166
-
167
- it('should remove plugin if onAttach fails', () => {
168
- const failingPlugin = createTestPlugin('failing-plugin', {
169
- onAttach: () => {
170
- throw new Error('Attach failed');
171
- },
172
- });
173
-
174
- registry.add(failingPlugin);
175
- registry.add(createTestPlugin('good-plugin'));
176
-
177
- const bloc = new MockBloc();
178
- registry.attach(bloc);
179
-
180
- expect(registry.get('failing-plugin')).toBeUndefined();
181
- expect(registry.get('good-plugin')).toBeDefined();
182
- expect(console.error).toHaveBeenCalled();
183
- });
184
-
185
- it('should call onDetach when removing attached plugin', () => {
186
- const onDetach = vi.fn();
187
- const plugin = createTestPlugin('test-plugin', { onDetach });
188
-
189
- registry.add(plugin);
190
- registry.attach(new MockBloc());
191
- registry.remove('test-plugin');
192
-
193
- expect(onDetach).toHaveBeenCalled();
194
- });
195
-
196
- it('should call onDetach for all plugins when clearing', () => {
197
- const onDetach1 = vi.fn();
198
- const onDetach2 = vi.fn();
199
-
200
- registry.add(createTestPlugin('plugin-1', { onDetach: onDetach1 }));
201
- registry.add(createTestPlugin('plugin-2', { onDetach: onDetach2 }));
202
-
203
- registry.attach(new MockBloc());
204
- registry.clear();
205
-
206
- expect(onDetach1).toHaveBeenCalled();
207
- expect(onDetach2).toHaveBeenCalled();
208
- });
209
- });
210
-
211
- describe('State Transformation', () => {
212
- it('should transform state through all plugins', () => {
213
- const plugin1 = createTestPlugin('plugin-1', {
214
- capabilities: {
215
- readState: true,
216
- transformState: true,
217
- interceptEvents: false,
218
- persistData: false,
219
- accessMetadata: false,
220
- },
221
- transformState: (prev, next) => ({ ...next, modified1: true }),
222
- });
223
-
224
- const plugin2 = createTestPlugin('plugin-2', {
225
- capabilities: {
226
- readState: true,
227
- transformState: true,
228
- interceptEvents: false,
229
- persistData: false,
230
- accessMetadata: false,
231
- },
232
- transformState: (prev, next) => ({ ...next, modified2: true }),
233
- });
234
-
235
- registry.add(plugin1);
236
- registry.add(plugin2);
237
-
238
- const result = registry.transformState({ count: 0 }, { count: 1 });
239
-
240
- expect(result).toEqual({
241
- count: 1,
242
- modified1: true,
243
- modified2: true,
244
- });
245
- });
246
-
247
- it('should skip transformation for plugins without capability', () => {
248
- const plugin = createTestPlugin('plugin-1', {
249
- capabilities: {
250
- readState: true,
251
- transformState: false,
252
- interceptEvents: false,
253
- persistData: false,
254
- accessMetadata: false,
255
- },
256
- transformState: vi.fn(),
257
- });
258
-
259
- registry.add(plugin);
260
-
261
- registry.transformState({ count: 0 }, { count: 1 });
262
-
263
- expect(plugin.transformState).not.toHaveBeenCalled();
264
- });
265
-
266
- it('should continue on transformation error', () => {
267
- const plugin1 = createTestPlugin('plugin-1', {
268
- transformState: () => {
269
- throw new Error('Transform failed');
270
- },
271
- });
272
-
273
- const plugin2 = createTestPlugin('plugin-2', {
274
- transformState: (prev, next) => ({ ...next, modified: true }),
275
- });
276
-
277
- registry.add(plugin1);
278
- registry.add(plugin2);
279
-
280
- const result = registry.transformState({ count: 0 }, { count: 1 });
281
-
282
- expect(result).toEqual({ count: 1, modified: true });
283
- expect(console.error).toHaveBeenCalled();
284
- });
285
- });
286
-
287
- describe('Event Transformation', () => {
288
- it('should transform event through all plugins', () => {
289
- const plugin1 = createTestPlugin('plugin-1', {
290
- capabilities: {
291
- readState: true,
292
- transformState: false,
293
- interceptEvents: true,
294
- persistData: false,
295
- accessMetadata: false,
296
- },
297
- transformEvent: (event) => ({ ...event, modified1: true }),
298
- });
299
-
300
- const plugin2 = createTestPlugin('plugin-2', {
301
- capabilities: {
302
- readState: true,
303
- transformState: false,
304
- interceptEvents: true,
305
- persistData: false,
306
- accessMetadata: false,
307
- },
308
- transformEvent: (event) => ({ ...event, modified2: true }),
309
- });
310
-
311
- registry.add(plugin1);
312
- registry.add(plugin2);
313
-
314
- const result = registry.transformEvent({ type: 'TEST' });
315
-
316
- expect(result).toEqual({
317
- type: 'TEST',
318
- modified1: true,
319
- modified2: true,
320
- });
321
- });
322
-
323
- it('should stop transformation chain if event becomes null', () => {
324
- const plugin1 = createTestPlugin('plugin-1', {
325
- transformEvent: () => null,
326
- });
327
-
328
- const plugin2 = createTestPlugin('plugin-2', {
329
- transformEvent: vi.fn(),
330
- });
331
-
332
- registry.add(plugin1);
333
- registry.add(plugin2);
334
-
335
- const result = registry.transformEvent({ type: 'TEST' });
336
-
337
- expect(result).toBeNull();
338
- expect(plugin2.transformEvent).not.toHaveBeenCalled();
339
- });
340
- });
341
-
342
- describe('Notifications', () => {
343
- it('should notify plugins of state changes', () => {
344
- const onStateChange1 = vi.fn();
345
- const onStateChange2 = vi.fn();
346
-
347
- registry.add(
348
- createTestPlugin('plugin-1', { onStateChange: onStateChange1 }),
349
- );
350
- registry.add(
351
- createTestPlugin('plugin-2', { onStateChange: onStateChange2 }),
352
- );
353
-
354
- const prevState = { count: 0 };
355
- const currState = { count: 1 };
356
-
357
- registry.notifyStateChange(prevState, currState);
358
-
359
- expect(onStateChange1).toHaveBeenCalledWith(prevState, currState);
360
- expect(onStateChange2).toHaveBeenCalledWith(prevState, currState);
361
- });
362
-
363
- it('should notify plugins of events', () => {
364
- const onEvent1 = vi.fn();
365
- const onEvent2 = vi.fn();
366
-
367
- registry.add(createTestPlugin('plugin-1', { onEvent: onEvent1 }));
368
- registry.add(createTestPlugin('plugin-2', { onEvent: onEvent2 }));
369
-
370
- const event = { type: 'TEST_EVENT' };
371
-
372
- registry.notifyEvent(event);
373
-
374
- expect(onEvent1).toHaveBeenCalledWith(event);
375
- expect(onEvent2).toHaveBeenCalledWith(event);
376
- });
377
-
378
- it('should notify plugins of errors', () => {
379
- const onError1 = vi.fn();
380
- const onError2 = vi.fn();
381
-
382
- registry.add(createTestPlugin('plugin-1', { onError: onError1 }));
383
- registry.add(createTestPlugin('plugin-2', { onError: onError2 }));
384
-
385
- const error = new Error('Test error');
386
- const context = {
387
- phase: 'state-change' as const,
388
- operation: 'test-operation',
389
- };
390
-
391
- registry.notifyError(error, context);
392
-
393
- expect(onError1).toHaveBeenCalledWith(error, context);
394
- expect(onError2).toHaveBeenCalledWith(error, context);
395
- });
396
-
397
- it('should continue notifying on plugin errors', () => {
398
- const onStateChange1 = vi.fn(() => {
399
- throw new Error('Plugin error');
400
- });
401
- const onStateChange2 = vi.fn();
402
-
403
- registry.add(
404
- createTestPlugin('plugin-1', { onStateChange: onStateChange1 }),
405
- );
406
- registry.add(
407
- createTestPlugin('plugin-2', { onStateChange: onStateChange2 }),
408
- );
409
-
410
- const prevState = { count: 0 };
411
- const currState = { count: 1 };
412
-
413
- registry.notifyStateChange(prevState, currState);
414
-
415
- expect(onStateChange2).toHaveBeenCalled();
416
- expect(console.error).toHaveBeenCalled();
417
- });
418
- });
419
-
420
- describe('Capability Checks', () => {
421
- it('should respect readState capability for notifications', () => {
422
- const plugin = createTestPlugin('plugin-1', {
423
- capabilities: {
424
- readState: false,
425
- transformState: false,
426
- interceptEvents: false,
427
- persistData: false,
428
- accessMetadata: false,
429
- },
430
- onStateChange: vi.fn(),
431
- });
432
-
433
- registry.add(plugin);
434
- registry.notifyStateChange({ count: 0 }, { count: 1 });
435
-
436
- expect(plugin.onStateChange).not.toHaveBeenCalled();
437
- });
438
-
439
- it('should use default capabilities when not specified', () => {
440
- const plugin = createTestPlugin('plugin-1', {
441
- onStateChange: vi.fn(),
442
- });
443
-
444
- registry.add(plugin);
445
- registry.notifyStateChange({ count: 0 }, { count: 1 });
446
-
447
- expect(plugin.onStateChange).toHaveBeenCalled();
448
- });
449
- });
450
- });