@sc4rfurryx/proteusjs 1.0.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.
Files changed (82) hide show
  1. package/API.md +438 -0
  2. package/FEATURES.md +286 -0
  3. package/LICENSE +21 -0
  4. package/README.md +645 -0
  5. package/dist/.tsbuildinfo +1 -0
  6. package/dist/proteus.cjs.js +16014 -0
  7. package/dist/proteus.cjs.js.map +1 -0
  8. package/dist/proteus.d.ts +3018 -0
  9. package/dist/proteus.esm.js +16005 -0
  10. package/dist/proteus.esm.js.map +1 -0
  11. package/dist/proteus.esm.min.js +8 -0
  12. package/dist/proteus.esm.min.js.map +1 -0
  13. package/dist/proteus.js +16020 -0
  14. package/dist/proteus.js.map +1 -0
  15. package/dist/proteus.min.js +8 -0
  16. package/dist/proteus.min.js.map +1 -0
  17. package/package.json +98 -0
  18. package/src/__tests__/mvp-integration.test.ts +518 -0
  19. package/src/accessibility/AccessibilityEngine.ts +2106 -0
  20. package/src/accessibility/ScreenReaderSupport.ts +444 -0
  21. package/src/accessibility/__tests__/ScreenReaderSupport.test.ts +435 -0
  22. package/src/animations/FLIPAnimationSystem.ts +491 -0
  23. package/src/compatibility/BrowserCompatibility.ts +1076 -0
  24. package/src/containers/BreakpointSystem.ts +347 -0
  25. package/src/containers/ContainerBreakpoints.ts +726 -0
  26. package/src/containers/ContainerManager.ts +370 -0
  27. package/src/containers/ContainerUnits.ts +336 -0
  28. package/src/containers/ContextIsolation.ts +394 -0
  29. package/src/containers/ElementQueries.ts +411 -0
  30. package/src/containers/SmartContainer.ts +536 -0
  31. package/src/containers/SmartContainers.ts +376 -0
  32. package/src/containers/__tests__/ContainerBreakpoints.test.ts +411 -0
  33. package/src/containers/__tests__/SmartContainers.test.ts +281 -0
  34. package/src/content/ResponsiveImages.ts +570 -0
  35. package/src/core/EventSystem.ts +147 -0
  36. package/src/core/MemoryManager.ts +321 -0
  37. package/src/core/PerformanceMonitor.ts +238 -0
  38. package/src/core/PluginSystem.ts +275 -0
  39. package/src/core/ProteusJS.test.ts +164 -0
  40. package/src/core/ProteusJS.ts +962 -0
  41. package/src/developer/PerformanceProfiler.ts +567 -0
  42. package/src/developer/VisualDebuggingTools.ts +656 -0
  43. package/src/developer/ZeroConfigSystem.ts +593 -0
  44. package/src/index.ts +35 -0
  45. package/src/integration.test.ts +227 -0
  46. package/src/layout/AdaptiveGrid.ts +429 -0
  47. package/src/layout/ContentReordering.ts +532 -0
  48. package/src/layout/FlexboxEnhancer.ts +406 -0
  49. package/src/layout/FlowLayout.ts +545 -0
  50. package/src/layout/SpacingSystem.ts +512 -0
  51. package/src/observers/IntersectionObserverPolyfill.ts +289 -0
  52. package/src/observers/ObserverManager.ts +299 -0
  53. package/src/observers/ResizeObserverPolyfill.ts +179 -0
  54. package/src/performance/BatchDOMOperations.ts +519 -0
  55. package/src/performance/CSSOptimizationEngine.ts +646 -0
  56. package/src/performance/CacheOptimizationSystem.ts +601 -0
  57. package/src/performance/EfficientEventHandler.ts +740 -0
  58. package/src/performance/LazyEvaluationSystem.ts +532 -0
  59. package/src/performance/MemoryManagementSystem.ts +497 -0
  60. package/src/performance/PerformanceMonitor.ts +931 -0
  61. package/src/performance/__tests__/BatchDOMOperations.test.ts +309 -0
  62. package/src/performance/__tests__/EfficientEventHandler.test.ts +268 -0
  63. package/src/performance/__tests__/PerformanceMonitor.test.ts +422 -0
  64. package/src/polyfills/BrowserPolyfills.ts +586 -0
  65. package/src/polyfills/__tests__/BrowserPolyfills.test.ts +328 -0
  66. package/src/test/setup.ts +115 -0
  67. package/src/theming/SmartThemeSystem.ts +591 -0
  68. package/src/types/index.ts +134 -0
  69. package/src/typography/ClampScaling.ts +356 -0
  70. package/src/typography/FluidTypography.ts +759 -0
  71. package/src/typography/LineHeightOptimization.ts +430 -0
  72. package/src/typography/LineHeightOptimizer.ts +326 -0
  73. package/src/typography/TextFitting.ts +355 -0
  74. package/src/typography/TypographicScale.ts +428 -0
  75. package/src/typography/VerticalRhythm.ts +369 -0
  76. package/src/typography/__tests__/FluidTypography.test.ts +432 -0
  77. package/src/typography/__tests__/LineHeightOptimization.test.ts +436 -0
  78. package/src/utils/Logger.ts +173 -0
  79. package/src/utils/debounce.ts +259 -0
  80. package/src/utils/performance.ts +371 -0
  81. package/src/utils/support.ts +106 -0
  82. package/src/utils/version.ts +24 -0
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Tests for Batch DOM Operations
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
6
+ import { BatchDOMOperations } from '../BatchDOMOperations';
7
+
8
+ // Mock requestAnimationFrame
9
+ global.requestAnimationFrame = vi.fn((callback) => {
10
+ setTimeout(callback, 16);
11
+ return 1;
12
+ });
13
+
14
+ global.cancelAnimationFrame = vi.fn();
15
+
16
+ describe('BatchDOMOperations', () => {
17
+ let batchDOM: BatchDOMOperations;
18
+ let testElement: HTMLElement;
19
+
20
+ beforeEach(() => {
21
+ batchDOM = new BatchDOMOperations({
22
+ maxBatchSize: 10,
23
+ frameTimeLimit: 16,
24
+ separateReadWrite: true,
25
+ autoFlush: false // Disable for testing
26
+ });
27
+
28
+ testElement = document.createElement('div');
29
+ testElement.style.width = '100px';
30
+ testElement.style.height = '100px';
31
+ document.body.appendChild(testElement);
32
+ });
33
+
34
+ afterEach(() => {
35
+ batchDOM.destroy();
36
+ document.body.removeChild(testElement);
37
+ vi.clearAllMocks();
38
+ });
39
+
40
+ describe('Read Operations', () => {
41
+ it('should queue and execute read operations', async () => {
42
+ const result = await batchDOM.queueRead(
43
+ testElement,
44
+ () => testElement.offsetWidth
45
+ );
46
+
47
+ expect(result).toBe(100);
48
+ });
49
+
50
+ it('should batch multiple reads efficiently', async () => {
51
+ const reads = [
52
+ batchDOM.queueRead(testElement, () => testElement.offsetWidth),
53
+ batchDOM.queueRead(testElement, () => testElement.offsetHeight),
54
+ batchDOM.queueRead(testElement, () => testElement.offsetTop)
55
+ ];
56
+
57
+ const results = await Promise.all(reads);
58
+
59
+ expect(results[0]).toBe(100); // width
60
+ expect(results[1]).toBe(100); // height
61
+ expect(typeof results[2]).toBe('number'); // top
62
+ });
63
+
64
+ it('should handle read operation errors', async () => {
65
+ await expect(
66
+ batchDOM.queueRead(testElement, () => {
67
+ throw new Error('Test error');
68
+ })
69
+ ).rejects.toThrow('Test error');
70
+ });
71
+ });
72
+
73
+ describe('Write Operations', () => {
74
+ it('should queue and execute write operations', async () => {
75
+ await batchDOM.queueWrite(
76
+ testElement,
77
+ () => {
78
+ testElement.style.backgroundColor = 'red';
79
+ }
80
+ );
81
+
82
+ expect(testElement.style.backgroundColor).toBe('red');
83
+ });
84
+
85
+ it('should batch multiple writes efficiently', async () => {
86
+ const writes = [
87
+ batchDOM.queueWrite(testElement, () => {
88
+ testElement.style.color = 'blue';
89
+ }),
90
+ batchDOM.queueWrite(testElement, () => {
91
+ testElement.style.fontSize = '20px';
92
+ })
93
+ ];
94
+
95
+ await Promise.all(writes);
96
+
97
+ expect(testElement.style.color).toBe('blue');
98
+ expect(testElement.style.fontSize).toBe('20px');
99
+ });
100
+ });
101
+
102
+ describe('Batch Style Operations', () => {
103
+ it('should batch multiple style changes', async () => {
104
+ await batchDOM.batchStyles(testElement, {
105
+ 'background-color': 'green',
106
+ 'border': '1px solid black',
107
+ 'padding': '10px'
108
+ });
109
+
110
+ expect(testElement.style.backgroundColor).toBe('green');
111
+ expect(testElement.style.border).toBe('1px solid black');
112
+ expect(testElement.style.padding).toBe('10px');
113
+ });
114
+ });
115
+
116
+ describe('Batch Class Operations', () => {
117
+ it('should batch class changes', async () => {
118
+ testElement.className = 'initial-class';
119
+
120
+ await batchDOM.batchClasses(testElement, {
121
+ add: ['new-class', 'another-class'],
122
+ remove: ['initial-class'],
123
+ toggle: ['toggle-class']
124
+ });
125
+
126
+ expect(testElement.classList.contains('new-class')).toBe(true);
127
+ expect(testElement.classList.contains('another-class')).toBe(true);
128
+ expect(testElement.classList.contains('initial-class')).toBe(false);
129
+ expect(testElement.classList.contains('toggle-class')).toBe(true);
130
+ });
131
+ });
132
+
133
+ describe('Batch Attribute Operations', () => {
134
+ it('should batch attribute changes', async () => {
135
+ await batchDOM.batchAttributes(testElement, {
136
+ 'data-test': 'value',
137
+ 'aria-label': 'Test element',
138
+ 'title': null // Should remove
139
+ });
140
+
141
+ expect(testElement.getAttribute('data-test')).toBe('value');
142
+ expect(testElement.getAttribute('aria-label')).toBe('Test element');
143
+ expect(testElement.hasAttribute('title')).toBe(false);
144
+ });
145
+ });
146
+
147
+ describe('Batch Read Operations', () => {
148
+ it('should batch multiple property reads', async () => {
149
+ const results = await batchDOM.batchReads(testElement, {
150
+ width: () => testElement.offsetWidth,
151
+ height: () => testElement.offsetHeight,
152
+ tagName: () => testElement.tagName
153
+ });
154
+
155
+ expect(results.width).toBe(100);
156
+ expect(results.height).toBe(100);
157
+ expect(results.tagName).toBe('DIV');
158
+ });
159
+ });
160
+
161
+ describe('Element Measurement', () => {
162
+ it('should measure element dimensions', async () => {
163
+ const measurements = await batchDOM.measureElement(
164
+ testElement,
165
+ ['width', 'height', 'top', 'left']
166
+ );
167
+
168
+ expect(measurements.width).toBe(100);
169
+ expect(measurements.height).toBe(100);
170
+ expect(typeof measurements.top).toBe('number');
171
+ expect(typeof measurements.left).toBe('number');
172
+ });
173
+
174
+ it('should measure only requested dimensions', async () => {
175
+ const measurements = await batchDOM.measureElement(
176
+ testElement,
177
+ ['width']
178
+ );
179
+
180
+ expect(measurements.width).toBe(100);
181
+ expect(measurements.height).toBeUndefined();
182
+ });
183
+ });
184
+
185
+ describe('Priority Handling', () => {
186
+ it('should respect operation priorities', async () => {
187
+ const executionOrder: string[] = [];
188
+
189
+ // Add operations with different priorities
190
+ const lowPriority = batchDOM.queueWrite(testElement, () => {
191
+ executionOrder.push('low');
192
+ }, 'low');
193
+
194
+ const highPriority = batchDOM.queueWrite(testElement, () => {
195
+ executionOrder.push('high');
196
+ }, 'high');
197
+
198
+ const normalPriority = batchDOM.queueWrite(testElement, () => {
199
+ executionOrder.push('normal');
200
+ }, 'normal');
201
+
202
+ await Promise.all([lowPriority, highPriority, normalPriority]);
203
+
204
+ // High priority should execute first
205
+ expect(executionOrder[0]).toBe('high');
206
+ });
207
+ });
208
+
209
+ describe('Read/Write Separation', () => {
210
+ it('should separate reads and writes to prevent layout thrashing', async () => {
211
+ const executionOrder: string[] = [];
212
+
213
+ // Mix reads and writes
214
+ const read1 = batchDOM.queueRead(testElement, () => {
215
+ executionOrder.push('read1');
216
+ return testElement.offsetWidth;
217
+ });
218
+
219
+ const write1 = batchDOM.queueWrite(testElement, () => {
220
+ executionOrder.push('write1');
221
+ testElement.style.width = '200px';
222
+ });
223
+
224
+ const read2 = batchDOM.queueRead(testElement, () => {
225
+ executionOrder.push('read2');
226
+ return testElement.offsetHeight;
227
+ });
228
+
229
+ const write2 = batchDOM.queueWrite(testElement, () => {
230
+ executionOrder.push('write2');
231
+ testElement.style.height = '200px';
232
+ });
233
+
234
+ await Promise.all([read1, write1, read2, write2]);
235
+
236
+ // All reads should come before writes
237
+ const readIndices = executionOrder
238
+ .map((op, index) => op.startsWith('read') ? index : -1)
239
+ .filter(index => index !== -1);
240
+
241
+ const writeIndices = executionOrder
242
+ .map((op, index) => op.startsWith('write') ? index : -1)
243
+ .filter(index => index !== -1);
244
+
245
+ const lastReadIndex = Math.max(...readIndices);
246
+ const firstWriteIndex = Math.min(...writeIndices);
247
+
248
+ expect(lastReadIndex).toBeLessThan(firstWriteIndex);
249
+ });
250
+ });
251
+
252
+ describe('Performance Metrics', () => {
253
+ it('should track performance metrics', async () => {
254
+ const initialMetrics = batchDOM.getMetrics();
255
+
256
+ await batchDOM.queueRead(testElement, () => testElement.offsetWidth);
257
+ await batchDOM.queueWrite(testElement, () => {
258
+ testElement.style.color = 'red';
259
+ });
260
+
261
+ const updatedMetrics = batchDOM.getMetrics();
262
+
263
+ expect(updatedMetrics.totalOperations).toBeGreaterThan(initialMetrics.totalOperations);
264
+ expect(updatedMetrics.readOperations).toBeGreaterThan(initialMetrics.readOperations);
265
+ expect(updatedMetrics.writeOperations).toBeGreaterThan(initialMetrics.writeOperations);
266
+ });
267
+ });
268
+
269
+ describe('Flush Operations', () => {
270
+ it('should flush all queued operations', async () => {
271
+ let executed = false;
272
+
273
+ // Queue operation without auto-flush
274
+ batchDOM.queueWrite(testElement, () => {
275
+ executed = true;
276
+ });
277
+
278
+ // Should not be executed yet
279
+ expect(executed).toBe(false);
280
+
281
+ // Flush manually
282
+ await batchDOM.flush();
283
+
284
+ // Should be executed now
285
+ expect(executed).toBe(true);
286
+ });
287
+ });
288
+
289
+ describe('Cleanup', () => {
290
+ it('should clear all queues', () => {
291
+ batchDOM.queueRead(testElement, () => testElement.offsetWidth);
292
+ batchDOM.queueWrite(testElement, () => {
293
+ testElement.style.color = 'red';
294
+ });
295
+
296
+ const metricsBeforeClear = batchDOM.getMetrics();
297
+ expect(metricsBeforeClear.totalOperations).toBeGreaterThan(0);
298
+
299
+ batchDOM.clear();
300
+
301
+ // Queues should be empty after clear
302
+ // Note: We can't directly test queue size, but we can test that no new operations are processed
303
+ });
304
+
305
+ it('should destroy properly', () => {
306
+ expect(() => batchDOM.destroy()).not.toThrow();
307
+ });
308
+ });
309
+ });
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Tests for Efficient Event Handler
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
6
+ import { EfficientEventHandler } from '../EfficientEventHandler';
7
+
8
+ // Mock ResizeObserver and IntersectionObserver
9
+ global.ResizeObserver = vi.fn().mockImplementation((callback) => ({
10
+ observe: vi.fn(),
11
+ unobserve: vi.fn(),
12
+ disconnect: vi.fn(),
13
+ }));
14
+
15
+ global.IntersectionObserver = vi.fn().mockImplementation((callback) => ({
16
+ observe: vi.fn(),
17
+ unobserve: vi.fn(),
18
+ disconnect: vi.fn(),
19
+ }));
20
+
21
+ // Mock requestAnimationFrame
22
+ global.requestAnimationFrame = vi.fn((callback) => {
23
+ setTimeout(callback, 16);
24
+ return 1;
25
+ });
26
+
27
+ global.cancelAnimationFrame = vi.fn();
28
+
29
+ describe('EfficientEventHandler', () => {
30
+ let eventHandler: EfficientEventHandler;
31
+ let testElement: HTMLElement;
32
+
33
+ beforeEach(() => {
34
+ eventHandler = new EfficientEventHandler({
35
+ debounceDelay: 10,
36
+ throttleDelay: 10,
37
+ useRAF: false, // Use timer for testing
38
+ batchOperations: true,
39
+ performanceTarget: 16
40
+ });
41
+
42
+ testElement = document.createElement('div');
43
+ document.body.appendChild(testElement);
44
+ });
45
+
46
+ afterEach(() => {
47
+ eventHandler.destroy();
48
+ document.body.removeChild(testElement);
49
+ vi.clearAllMocks();
50
+ });
51
+
52
+ describe('Initialization', () => {
53
+ it('should initialize without errors', () => {
54
+ expect(() => eventHandler.initialize()).not.toThrow();
55
+ });
56
+
57
+ it('should setup observers when initialized', () => {
58
+ eventHandler.initialize();
59
+ expect(global.ResizeObserver).toHaveBeenCalled();
60
+ expect(global.IntersectionObserver).toHaveBeenCalled();
61
+ });
62
+ });
63
+
64
+ describe('Resize Observation', () => {
65
+ it('should register resize observation', () => {
66
+ eventHandler.initialize();
67
+
68
+ const callback = vi.fn();
69
+ const operationId = eventHandler.observeResize(testElement, callback);
70
+
71
+ expect(operationId).toBeDefined();
72
+ expect(typeof operationId).toBe('string');
73
+ });
74
+
75
+ it('should call resize callback when triggered', async () => {
76
+ eventHandler.initialize();
77
+
78
+ const callback = vi.fn();
79
+ eventHandler.observeResize(testElement, callback);
80
+
81
+ // Simulate resize
82
+ const resizeObserverInstance = (global.ResizeObserver as any).mock.instances[0];
83
+ const resizeCallback = (global.ResizeObserver as any).mock.calls[0][0];
84
+
85
+ const mockEntry = {
86
+ target: testElement,
87
+ contentRect: { width: 100, height: 100 },
88
+ borderBoxSize: [{ inlineSize: 100, blockSize: 100 }],
89
+ contentBoxSize: [{ inlineSize: 100, blockSize: 100 }],
90
+ devicePixelContentBoxSize: [{ inlineSize: 100, blockSize: 100 }]
91
+ };
92
+
93
+ resizeCallback([mockEntry]);
94
+
95
+ // Wait for processing
96
+ await new Promise(resolve => setTimeout(resolve, 50));
97
+
98
+ expect(callback).toHaveBeenCalled();
99
+ });
100
+ });
101
+
102
+ describe('Intersection Observation', () => {
103
+ it('should register intersection observation', () => {
104
+ eventHandler.initialize();
105
+
106
+ const callback = vi.fn();
107
+ const operationId = eventHandler.observeIntersection(testElement, callback);
108
+
109
+ expect(operationId).toBeDefined();
110
+ expect(typeof operationId).toBe('string');
111
+ });
112
+ });
113
+
114
+ describe('Debounced Events', () => {
115
+ it('should add debounced event listener', () => {
116
+ const callback = vi.fn();
117
+ const operationId = eventHandler.addDebouncedListener(testElement, 'click', callback);
118
+
119
+ expect(operationId).toBeDefined();
120
+ });
121
+
122
+ it('should debounce rapid events', async () => {
123
+ const callback = vi.fn();
124
+ eventHandler.addDebouncedListener(testElement, 'click', callback, 20);
125
+
126
+ // Trigger multiple events rapidly
127
+ testElement.click();
128
+ testElement.click();
129
+ testElement.click();
130
+
131
+ // Should not be called immediately
132
+ expect(callback).not.toHaveBeenCalled();
133
+
134
+ // Wait for debounce delay
135
+ await new Promise(resolve => setTimeout(resolve, 30));
136
+
137
+ // Should be called only once after debounce
138
+ expect(callback).toHaveBeenCalledTimes(1);
139
+ });
140
+ });
141
+
142
+ describe('Throttled Events', () => {
143
+ it('should add throttled event listener', () => {
144
+ const callback = vi.fn();
145
+ const operationId = eventHandler.addThrottledListener(testElement, 'scroll', callback);
146
+
147
+ expect(operationId).toBeDefined();
148
+ });
149
+ });
150
+
151
+ describe('Performance Metrics', () => {
152
+ it('should provide performance metrics', () => {
153
+ const metrics = eventHandler.getMetrics();
154
+
155
+ expect(metrics).toHaveProperty('frameTime');
156
+ expect(metrics).toHaveProperty('fps');
157
+ expect(metrics).toHaveProperty('operationsPerFrame');
158
+ expect(metrics).toHaveProperty('queueSize');
159
+ expect(metrics).toHaveProperty('droppedFrames');
160
+ expect(metrics).toHaveProperty('averageLatency');
161
+ });
162
+
163
+ it('should update metrics during operation', async () => {
164
+ eventHandler.initialize();
165
+
166
+ const callback = vi.fn();
167
+ eventHandler.observeResize(testElement, callback);
168
+
169
+ const initialMetrics = eventHandler.getMetrics();
170
+
171
+ // Trigger some operations
172
+ const resizeCallback = (global.ResizeObserver as any).mock.calls[0][0];
173
+ resizeCallback([{
174
+ target: testElement,
175
+ contentRect: { width: 100, height: 100 }
176
+ }]);
177
+
178
+ await new Promise(resolve => setTimeout(resolve, 50));
179
+
180
+ const updatedMetrics = eventHandler.getMetrics();
181
+ expect(updatedMetrics.queueSize).toBeGreaterThanOrEqual(0);
182
+ });
183
+ });
184
+
185
+ describe('Operation Management', () => {
186
+ it('should remove operations from queue', () => {
187
+ eventHandler.initialize();
188
+
189
+ const callback = vi.fn();
190
+ const operationId = eventHandler.observeResize(testElement, callback);
191
+
192
+ eventHandler.removeOperation(operationId);
193
+
194
+ // Operation should be removed from queue
195
+ const metrics = eventHandler.getMetrics();
196
+ expect(metrics.queueSize).toBe(0);
197
+ });
198
+
199
+ it('should flush all operations', async () => {
200
+ eventHandler.initialize();
201
+
202
+ const callback = vi.fn();
203
+ eventHandler.observeResize(testElement, callback);
204
+
205
+ // Add operation to queue
206
+ const resizeCallback = (global.ResizeObserver as any).mock.calls[0][0];
207
+ resizeCallback([{
208
+ target: testElement,
209
+ contentRect: { width: 100, height: 100 }
210
+ }]);
211
+
212
+ // Flush operations
213
+ eventHandler.flush();
214
+
215
+ await new Promise(resolve => setTimeout(resolve, 10));
216
+
217
+ expect(callback).toHaveBeenCalled();
218
+ });
219
+ });
220
+
221
+ describe('Cleanup', () => {
222
+ it('should clean up resources on destroy', () => {
223
+ eventHandler.initialize();
224
+
225
+ const resizeObserverInstance = (global.ResizeObserver as any).mock.instances[0];
226
+ const intersectionObserverInstance = (global.IntersectionObserver as any).mock.instances[0];
227
+
228
+ eventHandler.destroy();
229
+
230
+ expect(resizeObserverInstance.disconnect).toHaveBeenCalled();
231
+ expect(intersectionObserverInstance.disconnect).toHaveBeenCalled();
232
+ });
233
+ });
234
+
235
+ describe('Performance Budgeting', () => {
236
+ it('should respect performance budget', async () => {
237
+ const handler = new EfficientEventHandler({
238
+ performanceTarget: 1, // Very low budget
239
+ maxBatchSize: 1,
240
+ useRAF: false
241
+ });
242
+
243
+ handler.initialize();
244
+
245
+ const callbacks = Array.from({ length: 10 }, () => vi.fn());
246
+
247
+ // Add many operations
248
+ callbacks.forEach((callback, index) => {
249
+ handler.observeResize(testElement, callback);
250
+ });
251
+
252
+ // Trigger operations
253
+ const resizeCallback = (global.ResizeObserver as any).mock.calls[0][0];
254
+ resizeCallback(callbacks.map(() => ({
255
+ target: testElement,
256
+ contentRect: { width: 100, height: 100 }
257
+ })));
258
+
259
+ await new Promise(resolve => setTimeout(resolve, 50));
260
+
261
+ // Not all callbacks should be executed due to budget constraints
262
+ const executedCount = callbacks.filter(cb => cb.mock.calls.length > 0).length;
263
+ expect(executedCount).toBeLessThan(callbacks.length);
264
+
265
+ handler.destroy();
266
+ });
267
+ });
268
+ });