@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.
- package/API.md +438 -0
- package/FEATURES.md +286 -0
- package/LICENSE +21 -0
- package/README.md +645 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/proteus.cjs.js +16014 -0
- package/dist/proteus.cjs.js.map +1 -0
- package/dist/proteus.d.ts +3018 -0
- package/dist/proteus.esm.js +16005 -0
- package/dist/proteus.esm.js.map +1 -0
- package/dist/proteus.esm.min.js +8 -0
- package/dist/proteus.esm.min.js.map +1 -0
- package/dist/proteus.js +16020 -0
- package/dist/proteus.js.map +1 -0
- package/dist/proteus.min.js +8 -0
- package/dist/proteus.min.js.map +1 -0
- package/package.json +98 -0
- package/src/__tests__/mvp-integration.test.ts +518 -0
- package/src/accessibility/AccessibilityEngine.ts +2106 -0
- package/src/accessibility/ScreenReaderSupport.ts +444 -0
- package/src/accessibility/__tests__/ScreenReaderSupport.test.ts +435 -0
- package/src/animations/FLIPAnimationSystem.ts +491 -0
- package/src/compatibility/BrowserCompatibility.ts +1076 -0
- package/src/containers/BreakpointSystem.ts +347 -0
- package/src/containers/ContainerBreakpoints.ts +726 -0
- package/src/containers/ContainerManager.ts +370 -0
- package/src/containers/ContainerUnits.ts +336 -0
- package/src/containers/ContextIsolation.ts +394 -0
- package/src/containers/ElementQueries.ts +411 -0
- package/src/containers/SmartContainer.ts +536 -0
- package/src/containers/SmartContainers.ts +376 -0
- package/src/containers/__tests__/ContainerBreakpoints.test.ts +411 -0
- package/src/containers/__tests__/SmartContainers.test.ts +281 -0
- package/src/content/ResponsiveImages.ts +570 -0
- package/src/core/EventSystem.ts +147 -0
- package/src/core/MemoryManager.ts +321 -0
- package/src/core/PerformanceMonitor.ts +238 -0
- package/src/core/PluginSystem.ts +275 -0
- package/src/core/ProteusJS.test.ts +164 -0
- package/src/core/ProteusJS.ts +962 -0
- package/src/developer/PerformanceProfiler.ts +567 -0
- package/src/developer/VisualDebuggingTools.ts +656 -0
- package/src/developer/ZeroConfigSystem.ts +593 -0
- package/src/index.ts +35 -0
- package/src/integration.test.ts +227 -0
- package/src/layout/AdaptiveGrid.ts +429 -0
- package/src/layout/ContentReordering.ts +532 -0
- package/src/layout/FlexboxEnhancer.ts +406 -0
- package/src/layout/FlowLayout.ts +545 -0
- package/src/layout/SpacingSystem.ts +512 -0
- package/src/observers/IntersectionObserverPolyfill.ts +289 -0
- package/src/observers/ObserverManager.ts +299 -0
- package/src/observers/ResizeObserverPolyfill.ts +179 -0
- package/src/performance/BatchDOMOperations.ts +519 -0
- package/src/performance/CSSOptimizationEngine.ts +646 -0
- package/src/performance/CacheOptimizationSystem.ts +601 -0
- package/src/performance/EfficientEventHandler.ts +740 -0
- package/src/performance/LazyEvaluationSystem.ts +532 -0
- package/src/performance/MemoryManagementSystem.ts +497 -0
- package/src/performance/PerformanceMonitor.ts +931 -0
- package/src/performance/__tests__/BatchDOMOperations.test.ts +309 -0
- package/src/performance/__tests__/EfficientEventHandler.test.ts +268 -0
- package/src/performance/__tests__/PerformanceMonitor.test.ts +422 -0
- package/src/polyfills/BrowserPolyfills.ts +586 -0
- package/src/polyfills/__tests__/BrowserPolyfills.test.ts +328 -0
- package/src/test/setup.ts +115 -0
- package/src/theming/SmartThemeSystem.ts +591 -0
- package/src/types/index.ts +134 -0
- package/src/typography/ClampScaling.ts +356 -0
- package/src/typography/FluidTypography.ts +759 -0
- package/src/typography/LineHeightOptimization.ts +430 -0
- package/src/typography/LineHeightOptimizer.ts +326 -0
- package/src/typography/TextFitting.ts +355 -0
- package/src/typography/TypographicScale.ts +428 -0
- package/src/typography/VerticalRhythm.ts +369 -0
- package/src/typography/__tests__/FluidTypography.test.ts +432 -0
- package/src/typography/__tests__/LineHeightOptimization.test.ts +436 -0
- package/src/utils/Logger.ts +173 -0
- package/src/utils/debounce.ts +259 -0
- package/src/utils/performance.ts +371 -0
- package/src/utils/support.ts +106 -0
- package/src/utils/version.ts +24 -0
@@ -0,0 +1,411 @@
|
|
1
|
+
/**
|
2
|
+
* ContainerBreakpoints Test Suite
|
3
|
+
* Comprehensive tests for breakpoint management system
|
4
|
+
*/
|
5
|
+
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
7
|
+
import { ContainerBreakpoints, BreakpointMap } from '../ContainerBreakpoints';
|
8
|
+
|
9
|
+
// Mock ResizeObserver
|
10
|
+
global.ResizeObserver = vi.fn().mockImplementation((callback) => ({
|
11
|
+
observe: vi.fn(),
|
12
|
+
unobserve: vi.fn(),
|
13
|
+
disconnect: vi.fn(),
|
14
|
+
}));
|
15
|
+
|
16
|
+
describe('ContainerBreakpoints', () => {
|
17
|
+
let containerBreakpoints: ContainerBreakpoints;
|
18
|
+
let testElement: HTMLElement;
|
19
|
+
let mockCallback: ReturnType<typeof vi.fn>;
|
20
|
+
|
21
|
+
beforeEach(() => {
|
22
|
+
// Clean up DOM
|
23
|
+
document.body.innerHTML = '';
|
24
|
+
|
25
|
+
// Create test element
|
26
|
+
testElement = document.createElement('div');
|
27
|
+
testElement.id = 'test-element';
|
28
|
+
testElement.style.width = '400px';
|
29
|
+
testElement.style.height = '300px';
|
30
|
+
document.body.appendChild(testElement);
|
31
|
+
|
32
|
+
containerBreakpoints = new ContainerBreakpoints();
|
33
|
+
mockCallback = vi.fn();
|
34
|
+
|
35
|
+
// Mock getBoundingClientRect
|
36
|
+
vi.spyOn(testElement, 'getBoundingClientRect').mockReturnValue({
|
37
|
+
width: 400,
|
38
|
+
height: 300,
|
39
|
+
top: 0,
|
40
|
+
left: 0,
|
41
|
+
bottom: 300,
|
42
|
+
right: 400,
|
43
|
+
x: 0,
|
44
|
+
y: 0,
|
45
|
+
toJSON: () => ({})
|
46
|
+
} as DOMRect);
|
47
|
+
});
|
48
|
+
|
49
|
+
afterEach(() => {
|
50
|
+
containerBreakpoints.destroy();
|
51
|
+
document.body.innerHTML = '';
|
52
|
+
vi.restoreAllMocks();
|
53
|
+
});
|
54
|
+
|
55
|
+
describe('Breakpoint Registration', () => {
|
56
|
+
it('should register breakpoints successfully', () => {
|
57
|
+
const breakpoints: BreakpointMap = {
|
58
|
+
sm: '300px',
|
59
|
+
md: '500px',
|
60
|
+
lg: '800px'
|
61
|
+
};
|
62
|
+
|
63
|
+
const id = containerBreakpoints.register(testElement, breakpoints, mockCallback);
|
64
|
+
|
65
|
+
expect(id).toBeTruthy();
|
66
|
+
expect(id).toMatch(/^proteus-breakpoint-\d+-\d+$/);
|
67
|
+
});
|
68
|
+
|
69
|
+
it('should handle invalid breakpoint values gracefully', () => {
|
70
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
71
|
+
|
72
|
+
const breakpoints: BreakpointMap = {
|
73
|
+
invalid: 'not-a-valid-value',
|
74
|
+
valid: '300px'
|
75
|
+
};
|
76
|
+
|
77
|
+
const id = containerBreakpoints.register(testElement, breakpoints);
|
78
|
+
|
79
|
+
expect(id).toBeTruthy();
|
80
|
+
expect(consoleSpy).toHaveBeenCalled();
|
81
|
+
|
82
|
+
consoleSpy.mockRestore();
|
83
|
+
});
|
84
|
+
|
85
|
+
it('should register multiple breakpoint sets', () => {
|
86
|
+
const element1 = document.createElement('div');
|
87
|
+
const element2 = document.createElement('div');
|
88
|
+
document.body.appendChild(element1);
|
89
|
+
document.body.appendChild(element2);
|
90
|
+
|
91
|
+
const registrations = [
|
92
|
+
{
|
93
|
+
element: element1,
|
94
|
+
breakpoints: { sm: '300px', md: '500px' }
|
95
|
+
},
|
96
|
+
{
|
97
|
+
element: element2,
|
98
|
+
breakpoints: { xs: '200px', lg: '800px' }
|
99
|
+
}
|
100
|
+
];
|
101
|
+
|
102
|
+
const ids = containerBreakpoints.registerMultiple(registrations);
|
103
|
+
|
104
|
+
expect(ids).toHaveLength(2);
|
105
|
+
expect(ids.every(id => id.length > 0)).toBe(true);
|
106
|
+
});
|
107
|
+
});
|
108
|
+
|
109
|
+
describe('Breakpoint Detection', () => {
|
110
|
+
it('should detect correct breakpoint for current width', () => {
|
111
|
+
const breakpoints: BreakpointMap = {
|
112
|
+
sm: '300px',
|
113
|
+
md: '500px',
|
114
|
+
lg: '800px'
|
115
|
+
};
|
116
|
+
|
117
|
+
const id = containerBreakpoints.register(testElement, breakpoints, mockCallback);
|
118
|
+
|
119
|
+
// Element width is 400px, should be 'sm' (300px <= 400px < 500px)
|
120
|
+
const currentBreakpoint = containerBreakpoints.getCurrentBreakpoint(id);
|
121
|
+
expect(currentBreakpoint).toBe('sm');
|
122
|
+
});
|
123
|
+
|
124
|
+
it('should call callback when breakpoint changes', () => {
|
125
|
+
const breakpoints: BreakpointMap = {
|
126
|
+
sm: '300px',
|
127
|
+
md: '500px',
|
128
|
+
lg: '800px'
|
129
|
+
};
|
130
|
+
|
131
|
+
containerBreakpoints.register(testElement, breakpoints, mockCallback);
|
132
|
+
|
133
|
+
// Initial callback should be called
|
134
|
+
expect(mockCallback).toHaveBeenCalledWith(
|
135
|
+
'sm',
|
136
|
+
expect.objectContaining({
|
137
|
+
width: 400,
|
138
|
+
height: 300,
|
139
|
+
breakpoint: 'sm',
|
140
|
+
element: testElement
|
141
|
+
})
|
142
|
+
);
|
143
|
+
});
|
144
|
+
|
145
|
+
it('should update breakpoint when element resizes', () => {
|
146
|
+
const breakpoints: BreakpointMap = {
|
147
|
+
sm: '300px',
|
148
|
+
md: '500px',
|
149
|
+
lg: '800px'
|
150
|
+
};
|
151
|
+
|
152
|
+
const id = containerBreakpoints.register(testElement, breakpoints, mockCallback);
|
153
|
+
|
154
|
+
// Simulate resize to 600px width
|
155
|
+
vi.spyOn(testElement, 'getBoundingClientRect').mockReturnValue({
|
156
|
+
width: 600,
|
157
|
+
height: 300,
|
158
|
+
top: 0,
|
159
|
+
left: 0,
|
160
|
+
bottom: 300,
|
161
|
+
right: 600,
|
162
|
+
x: 0,
|
163
|
+
y: 0,
|
164
|
+
toJSON: () => ({})
|
165
|
+
} as DOMRect);
|
166
|
+
|
167
|
+
containerBreakpoints.updateElement(testElement);
|
168
|
+
|
169
|
+
const currentBreakpoint = containerBreakpoints.getCurrentBreakpoint(id);
|
170
|
+
expect(currentBreakpoint).toBe('md');
|
171
|
+
});
|
172
|
+
});
|
173
|
+
|
174
|
+
describe('CSS Class Management', () => {
|
175
|
+
it('should add breakpoint CSS classes', () => {
|
176
|
+
const breakpoints: BreakpointMap = {
|
177
|
+
sm: '300px',
|
178
|
+
md: '500px'
|
179
|
+
};
|
180
|
+
|
181
|
+
containerBreakpoints.register(testElement, breakpoints);
|
182
|
+
|
183
|
+
expect(testElement.classList.contains('proteus-sm')).toBe(true);
|
184
|
+
expect(testElement.getAttribute('data-proteus-breakpoint')).toBe('sm');
|
185
|
+
});
|
186
|
+
|
187
|
+
it('should update CSS classes when breakpoint changes', () => {
|
188
|
+
const breakpoints: BreakpointMap = {
|
189
|
+
sm: '300px',
|
190
|
+
md: '500px'
|
191
|
+
};
|
192
|
+
|
193
|
+
containerBreakpoints.register(testElement, breakpoints);
|
194
|
+
|
195
|
+
// Initially should have 'sm' class
|
196
|
+
expect(testElement.classList.contains('proteus-sm')).toBe(true);
|
197
|
+
|
198
|
+
// Simulate resize to trigger 'md' breakpoint
|
199
|
+
vi.spyOn(testElement, 'getBoundingClientRect').mockReturnValue({
|
200
|
+
width: 600,
|
201
|
+
height: 300,
|
202
|
+
top: 0,
|
203
|
+
left: 0,
|
204
|
+
bottom: 300,
|
205
|
+
right: 600,
|
206
|
+
x: 0,
|
207
|
+
y: 0,
|
208
|
+
toJSON: () => ({})
|
209
|
+
} as DOMRect);
|
210
|
+
|
211
|
+
containerBreakpoints.updateElement(testElement);
|
212
|
+
|
213
|
+
expect(testElement.classList.contains('proteus-sm')).toBe(false);
|
214
|
+
expect(testElement.classList.contains('proteus-md')).toBe(true);
|
215
|
+
expect(testElement.getAttribute('data-proteus-breakpoint')).toBe('md');
|
216
|
+
});
|
217
|
+
|
218
|
+
it('should remove CSS classes when unregistering', () => {
|
219
|
+
const breakpoints: BreakpointMap = {
|
220
|
+
sm: '300px',
|
221
|
+
md: '500px'
|
222
|
+
};
|
223
|
+
|
224
|
+
const id = containerBreakpoints.register(testElement, breakpoints);
|
225
|
+
|
226
|
+
expect(testElement.classList.contains('proteus-sm')).toBe(true);
|
227
|
+
|
228
|
+
containerBreakpoints.unregister(id);
|
229
|
+
|
230
|
+
expect(testElement.classList.contains('proteus-sm')).toBe(false);
|
231
|
+
expect(testElement.hasAttribute('data-proteus-breakpoint')).toBe(false);
|
232
|
+
});
|
233
|
+
});
|
234
|
+
|
235
|
+
describe('Event Dispatching', () => {
|
236
|
+
it('should dispatch custom events on breakpoint change', () => {
|
237
|
+
const eventSpy = vi.fn();
|
238
|
+
testElement.addEventListener('proteus:breakpoint-change', eventSpy);
|
239
|
+
|
240
|
+
const breakpoints: BreakpointMap = {
|
241
|
+
sm: '300px',
|
242
|
+
md: '500px'
|
243
|
+
};
|
244
|
+
|
245
|
+
containerBreakpoints.register(testElement, breakpoints);
|
246
|
+
|
247
|
+
expect(eventSpy).toHaveBeenCalledWith(
|
248
|
+
expect.objectContaining({
|
249
|
+
type: 'proteus:breakpoint-change',
|
250
|
+
detail: expect.objectContaining({
|
251
|
+
breakpoint: 'sm',
|
252
|
+
width: 400,
|
253
|
+
height: 300
|
254
|
+
})
|
255
|
+
})
|
256
|
+
);
|
257
|
+
});
|
258
|
+
});
|
259
|
+
|
260
|
+
describe('Breakpoint Value Parsing', () => {
|
261
|
+
it('should parse pixel values correctly', () => {
|
262
|
+
const breakpoints: BreakpointMap = {
|
263
|
+
sm: '300px',
|
264
|
+
md: '500px'
|
265
|
+
};
|
266
|
+
|
267
|
+
const id = containerBreakpoints.register(testElement, breakpoints);
|
268
|
+
const config = containerBreakpoints['breakpoints'].get(id);
|
269
|
+
|
270
|
+
expect(config?.parsedBreakpoints).toEqual([
|
271
|
+
{ name: 'sm', value: 300, unit: 'px', originalValue: '300px' },
|
272
|
+
{ name: 'md', value: 500, unit: 'px', originalValue: '500px' }
|
273
|
+
]);
|
274
|
+
});
|
275
|
+
|
276
|
+
it('should parse em values correctly', () => {
|
277
|
+
const breakpoints: BreakpointMap = {
|
278
|
+
sm: '20em',
|
279
|
+
md: '30em'
|
280
|
+
};
|
281
|
+
|
282
|
+
const id = containerBreakpoints.register(testElement, breakpoints);
|
283
|
+
const config = containerBreakpoints['breakpoints'].get(id);
|
284
|
+
|
285
|
+
// em values should be converted to pixels (assuming 16px base)
|
286
|
+
expect(config?.parsedBreakpoints[0].value).toBe(320); // 20 * 16
|
287
|
+
expect(config?.parsedBreakpoints[1].value).toBe(480); // 30 * 16
|
288
|
+
});
|
289
|
+
|
290
|
+
it('should handle unitless values as pixels', () => {
|
291
|
+
const breakpoints: BreakpointMap = {
|
292
|
+
sm: '300',
|
293
|
+
md: '500'
|
294
|
+
};
|
295
|
+
|
296
|
+
const id = containerBreakpoints.register(testElement, breakpoints);
|
297
|
+
const config = containerBreakpoints['breakpoints'].get(id);
|
298
|
+
|
299
|
+
expect(config?.parsedBreakpoints[0].value).toBe(300);
|
300
|
+
expect(config?.parsedBreakpoints[0].unit).toBe('px');
|
301
|
+
});
|
302
|
+
});
|
303
|
+
|
304
|
+
describe('Management and Metrics', () => {
|
305
|
+
it('should provide performance metrics', () => {
|
306
|
+
const breakpoints: BreakpointMap = {
|
307
|
+
sm: '300px',
|
308
|
+
md: '500px',
|
309
|
+
lg: '800px'
|
310
|
+
};
|
311
|
+
|
312
|
+
containerBreakpoints.register(testElement, breakpoints);
|
313
|
+
|
314
|
+
const metrics = containerBreakpoints.getMetrics();
|
315
|
+
|
316
|
+
expect(metrics.totalRegistrations).toBe(1);
|
317
|
+
expect(metrics.activeElements).toBe(1);
|
318
|
+
expect(metrics.averageBreakpoints).toBe(3);
|
319
|
+
expect(metrics.breakpointDistribution).toEqual({
|
320
|
+
sm: 1,
|
321
|
+
md: 1,
|
322
|
+
lg: 1
|
323
|
+
});
|
324
|
+
});
|
325
|
+
|
326
|
+
it('should get all active breakpoints', () => {
|
327
|
+
const breakpoints: BreakpointMap = {
|
328
|
+
sm: '300px',
|
329
|
+
md: '500px'
|
330
|
+
};
|
331
|
+
|
332
|
+
containerBreakpoints.register(testElement, breakpoints);
|
333
|
+
|
334
|
+
const activeBreakpoints = containerBreakpoints.getAllActiveBreakpoints();
|
335
|
+
|
336
|
+
expect(activeBreakpoints).toHaveLength(1);
|
337
|
+
expect(activeBreakpoints[0]).toEqual(
|
338
|
+
expect.objectContaining({
|
339
|
+
element: testElement,
|
340
|
+
breakpoint: 'sm',
|
341
|
+
width: 400,
|
342
|
+
height: 300
|
343
|
+
})
|
344
|
+
);
|
345
|
+
});
|
346
|
+
|
347
|
+
it('should unregister all breakpoints for an element', () => {
|
348
|
+
const breakpoints1: BreakpointMap = { sm: '300px' };
|
349
|
+
const breakpoints2: BreakpointMap = { md: '500px' };
|
350
|
+
|
351
|
+
containerBreakpoints.register(testElement, breakpoints1);
|
352
|
+
containerBreakpoints.register(testElement, breakpoints2);
|
353
|
+
|
354
|
+
expect(containerBreakpoints.getMetrics().totalRegistrations).toBe(2);
|
355
|
+
|
356
|
+
containerBreakpoints.unregisterElement(testElement);
|
357
|
+
|
358
|
+
expect(containerBreakpoints.getMetrics().totalRegistrations).toBe(0);
|
359
|
+
});
|
360
|
+
|
361
|
+
it('should update all breakpoints at once', () => {
|
362
|
+
const element1 = document.createElement('div');
|
363
|
+
const element2 = document.createElement('div');
|
364
|
+
document.body.appendChild(element1);
|
365
|
+
document.body.appendChild(element2);
|
366
|
+
|
367
|
+
const callback1 = vi.fn();
|
368
|
+
const callback2 = vi.fn();
|
369
|
+
|
370
|
+
containerBreakpoints.register(element1, { sm: '300px' }, callback1);
|
371
|
+
containerBreakpoints.register(element2, { md: '500px' }, callback2);
|
372
|
+
|
373
|
+
// Clear previous calls
|
374
|
+
callback1.mockClear();
|
375
|
+
callback2.mockClear();
|
376
|
+
|
377
|
+
containerBreakpoints.updateAll();
|
378
|
+
|
379
|
+
// Both callbacks should be called during update
|
380
|
+
expect(callback1).toHaveBeenCalled();
|
381
|
+
expect(callback2).toHaveBeenCalled();
|
382
|
+
});
|
383
|
+
});
|
384
|
+
|
385
|
+
describe('Error Handling', () => {
|
386
|
+
it('should handle cleanup properly', () => {
|
387
|
+
const breakpoints: BreakpointMap = {
|
388
|
+
sm: '300px',
|
389
|
+
md: '500px'
|
390
|
+
};
|
391
|
+
|
392
|
+
containerBreakpoints.register(testElement, breakpoints);
|
393
|
+
|
394
|
+
expect(() => {
|
395
|
+
containerBreakpoints.destroy();
|
396
|
+
}).not.toThrow();
|
397
|
+
|
398
|
+
// CSS classes should be removed
|
399
|
+
expect(testElement.classList.contains('proteus-sm')).toBe(false);
|
400
|
+
});
|
401
|
+
|
402
|
+
it('should handle invalid registration IDs gracefully', () => {
|
403
|
+
const currentBreakpoint = containerBreakpoints.getCurrentBreakpoint('invalid-id');
|
404
|
+
expect(currentBreakpoint).toBeNull();
|
405
|
+
|
406
|
+
expect(() => {
|
407
|
+
containerBreakpoints.unregister('invalid-id');
|
408
|
+
}).not.toThrow();
|
409
|
+
});
|
410
|
+
});
|
411
|
+
});
|
@@ -0,0 +1,281 @@
|
|
1
|
+
/**
|
2
|
+
* SmartContainers Test Suite
|
3
|
+
* Comprehensive tests for container detection and management
|
4
|
+
*/
|
5
|
+
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
7
|
+
import { SmartContainers, ContainerInfo } from '../SmartContainers';
|
8
|
+
|
9
|
+
// Mock ResizeObserver
|
10
|
+
global.ResizeObserver = vi.fn().mockImplementation((callback) => ({
|
11
|
+
observe: vi.fn(),
|
12
|
+
unobserve: vi.fn(),
|
13
|
+
disconnect: vi.fn(),
|
14
|
+
}));
|
15
|
+
|
16
|
+
// Mock MutationObserver
|
17
|
+
global.MutationObserver = vi.fn().mockImplementation((callback) => ({
|
18
|
+
observe: vi.fn(),
|
19
|
+
disconnect: vi.fn(),
|
20
|
+
}));
|
21
|
+
|
22
|
+
describe('SmartContainers', () => {
|
23
|
+
let smartContainers: SmartContainers;
|
24
|
+
let testContainer: HTMLElement;
|
25
|
+
|
26
|
+
beforeEach(() => {
|
27
|
+
// Clean up DOM
|
28
|
+
document.body.innerHTML = '';
|
29
|
+
|
30
|
+
// Create test container
|
31
|
+
testContainer = document.createElement('div');
|
32
|
+
testContainer.id = 'test-container';
|
33
|
+
testContainer.style.width = '400px';
|
34
|
+
testContainer.style.height = '300px';
|
35
|
+
testContainer.style.display = 'block';
|
36
|
+
document.body.appendChild(testContainer);
|
37
|
+
|
38
|
+
smartContainers = new SmartContainers();
|
39
|
+
});
|
40
|
+
|
41
|
+
afterEach(() => {
|
42
|
+
smartContainers.destroy();
|
43
|
+
document.body.innerHTML = '';
|
44
|
+
});
|
45
|
+
|
46
|
+
describe('Container Detection', () => {
|
47
|
+
it('should detect basic block containers', async () => {
|
48
|
+
const containers = await smartContainers.detectContainers();
|
49
|
+
|
50
|
+
expect(containers.length).toBeGreaterThan(0);
|
51
|
+
const testContainerInfo = containers.find(c => c.element === testContainer);
|
52
|
+
expect(testContainerInfo).toBeDefined();
|
53
|
+
expect(testContainerInfo?.type).toBe('block');
|
54
|
+
});
|
55
|
+
|
56
|
+
it('should detect grid containers', async () => {
|
57
|
+
testContainer.style.display = 'grid';
|
58
|
+
|
59
|
+
const containers = await smartContainers.detectContainers();
|
60
|
+
const gridContainer = containers.find(c => c.element === testContainer);
|
61
|
+
|
62
|
+
expect(gridContainer?.type).toBe('grid');
|
63
|
+
expect(gridContainer?.confidence).toBeGreaterThan(0.4);
|
64
|
+
});
|
65
|
+
|
66
|
+
it('should detect flex containers', async () => {
|
67
|
+
testContainer.style.display = 'flex';
|
68
|
+
|
69
|
+
const containers = await smartContainers.detectContainers();
|
70
|
+
const flexContainer = containers.find(c => c.element === testContainer);
|
71
|
+
|
72
|
+
expect(flexContainer?.type).toBe('flex');
|
73
|
+
expect(flexContainer?.confidence).toBeGreaterThan(0.3);
|
74
|
+
});
|
75
|
+
|
76
|
+
it('should exclude small elements', async () => {
|
77
|
+
const smallElement = document.createElement('div');
|
78
|
+
smallElement.style.width = '10px';
|
79
|
+
smallElement.style.height = '10px';
|
80
|
+
document.body.appendChild(smallElement);
|
81
|
+
|
82
|
+
const containers = await smartContainers.detectContainers();
|
83
|
+
const smallContainer = containers.find(c => c.element === smallElement);
|
84
|
+
|
85
|
+
expect(smallContainer).toBeUndefined();
|
86
|
+
});
|
87
|
+
|
88
|
+
it('should respect confidence threshold', async () => {
|
89
|
+
const containers = await smartContainers.detectContainers({ minConfidence: 0.8 });
|
90
|
+
|
91
|
+
// Should have fewer containers with high confidence threshold
|
92
|
+
expect(containers.length).toBeLessThanOrEqual(
|
93
|
+
(await smartContainers.detectContainers({ minConfidence: 0.1 })).length
|
94
|
+
);
|
95
|
+
});
|
96
|
+
});
|
97
|
+
|
98
|
+
describe('Container Units', () => {
|
99
|
+
beforeEach(() => {
|
100
|
+
// Mock getBoundingClientRect
|
101
|
+
vi.spyOn(testContainer, 'getBoundingClientRect').mockReturnValue({
|
102
|
+
width: 400,
|
103
|
+
height: 300,
|
104
|
+
top: 0,
|
105
|
+
left: 0,
|
106
|
+
bottom: 300,
|
107
|
+
right: 400,
|
108
|
+
x: 0,
|
109
|
+
y: 0,
|
110
|
+
toJSON: () => ({})
|
111
|
+
} as DOMRect);
|
112
|
+
});
|
113
|
+
|
114
|
+
it('should calculate container width units (cw)', () => {
|
115
|
+
const unit = smartContainers.calculateContainerUnit(testContainer, 'cw');
|
116
|
+
expect(unit).toBe(4); // 400px / 100 = 4px per cw
|
117
|
+
});
|
118
|
+
|
119
|
+
it('should calculate container height units (ch)', () => {
|
120
|
+
const unit = smartContainers.calculateContainerUnit(testContainer, 'ch');
|
121
|
+
expect(unit).toBe(3); // 300px / 100 = 3px per ch
|
122
|
+
});
|
123
|
+
|
124
|
+
it('should calculate container minimum units (cmin)', () => {
|
125
|
+
const unit = smartContainers.calculateContainerUnit(testContainer, 'cmin');
|
126
|
+
expect(unit).toBe(3); // min(400, 300) / 100 = 3px per cmin
|
127
|
+
});
|
128
|
+
|
129
|
+
it('should calculate container maximum units (cmax)', () => {
|
130
|
+
const unit = smartContainers.calculateContainerUnit(testContainer, 'cmax');
|
131
|
+
expect(unit).toBe(4); // max(400, 300) / 100 = 4px per cmax
|
132
|
+
});
|
133
|
+
|
134
|
+
it('should handle unknown units gracefully', () => {
|
135
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
136
|
+
const unit = smartContainers.calculateContainerUnit(testContainer, 'unknown');
|
137
|
+
|
138
|
+
expect(unit).toBe(0);
|
139
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
140
|
+
expect.stringContaining('Unknown container unit "unknown"')
|
141
|
+
);
|
142
|
+
|
143
|
+
consoleSpy.mockRestore();
|
144
|
+
});
|
145
|
+
});
|
146
|
+
|
147
|
+
describe('Container Management', () => {
|
148
|
+
it('should track container information', async () => {
|
149
|
+
await smartContainers.detectContainers();
|
150
|
+
|
151
|
+
const containerInfo = smartContainers.getContainerInfo(testContainer);
|
152
|
+
expect(containerInfo).toBeDefined();
|
153
|
+
expect(containerInfo?.element).toBe(testContainer);
|
154
|
+
});
|
155
|
+
|
156
|
+
it('should update container information', async () => {
|
157
|
+
await smartContainers.detectContainers();
|
158
|
+
|
159
|
+
// Change container style
|
160
|
+
testContainer.style.display = 'grid';
|
161
|
+
|
162
|
+
const updatedInfo = smartContainers.updateContainer(testContainer);
|
163
|
+
expect(updatedInfo?.type).toBe('grid');
|
164
|
+
});
|
165
|
+
|
166
|
+
it('should start and stop monitoring', () => {
|
167
|
+
smartContainers.startMonitoring();
|
168
|
+
expect(smartContainers['isActive']).toBe(true);
|
169
|
+
|
170
|
+
smartContainers.stopMonitoring();
|
171
|
+
expect(smartContainers['isActive']).toBe(false);
|
172
|
+
});
|
173
|
+
});
|
174
|
+
|
175
|
+
describe('Responsive Content Detection', () => {
|
176
|
+
it('should detect responsive images', async () => {
|
177
|
+
const img = document.createElement('img');
|
178
|
+
img.srcset = 'image-320w.jpg 320w, image-640w.jpg 640w';
|
179
|
+
testContainer.appendChild(img);
|
180
|
+
|
181
|
+
const containers = await smartContainers.detectContainers();
|
182
|
+
const containerInfo = containers.find(c => c.element === testContainer);
|
183
|
+
|
184
|
+
expect(containerInfo?.hasResponsiveContent).toBe(true);
|
185
|
+
});
|
186
|
+
|
187
|
+
it('should detect responsive videos', async () => {
|
188
|
+
const iframe = document.createElement('iframe');
|
189
|
+
iframe.src = 'https://www.youtube.com/embed/test';
|
190
|
+
testContainer.appendChild(iframe);
|
191
|
+
|
192
|
+
const containers = await smartContainers.detectContainers();
|
193
|
+
const containerInfo = containers.find(c => c.element === testContainer);
|
194
|
+
|
195
|
+
expect(containerInfo?.hasResponsiveContent).toBe(true);
|
196
|
+
});
|
197
|
+
|
198
|
+
it('should detect grid/flex children', async () => {
|
199
|
+
const child = document.createElement('div');
|
200
|
+
child.style.display = 'grid';
|
201
|
+
testContainer.appendChild(child);
|
202
|
+
|
203
|
+
const containers = await smartContainers.detectContainers();
|
204
|
+
const containerInfo = containers.find(c => c.element === testContainer);
|
205
|
+
|
206
|
+
expect(containerInfo?.hasResponsiveContent).toBe(true);
|
207
|
+
});
|
208
|
+
});
|
209
|
+
|
210
|
+
describe('Metrics and Analysis', () => {
|
211
|
+
it('should provide performance metrics', async () => {
|
212
|
+
await smartContainers.detectContainers();
|
213
|
+
|
214
|
+
const metrics = smartContainers.getMetrics();
|
215
|
+
|
216
|
+
expect(metrics.totalContainers).toBeGreaterThan(0);
|
217
|
+
expect(metrics.averageConfidence).toBeGreaterThan(0);
|
218
|
+
expect(metrics.typeDistribution).toBeDefined();
|
219
|
+
});
|
220
|
+
|
221
|
+
it('should filter containers by type', async () => {
|
222
|
+
// Create different container types
|
223
|
+
const gridContainer = document.createElement('div');
|
224
|
+
gridContainer.style.display = 'grid';
|
225
|
+
gridContainer.style.width = '200px';
|
226
|
+
gridContainer.style.height = '200px';
|
227
|
+
document.body.appendChild(gridContainer);
|
228
|
+
|
229
|
+
await smartContainers.detectContainers();
|
230
|
+
|
231
|
+
const gridContainers = smartContainers.getContainersByType('grid');
|
232
|
+
const blockContainers = smartContainers.getContainersByType('block');
|
233
|
+
|
234
|
+
expect(gridContainers.length).toBeGreaterThan(0);
|
235
|
+
expect(blockContainers.length).toBeGreaterThan(0);
|
236
|
+
expect(gridContainers.every(c => c.type === 'grid')).toBe(true);
|
237
|
+
expect(blockContainers.every(c => c.type === 'block')).toBe(true);
|
238
|
+
});
|
239
|
+
|
240
|
+
it('should filter high-confidence containers', async () => {
|
241
|
+
await smartContainers.detectContainers();
|
242
|
+
|
243
|
+
const highConfidenceContainers = smartContainers.getHighConfidenceContainers(0.5);
|
244
|
+
|
245
|
+
expect(highConfidenceContainers.every(c => c.confidence >= 0.5)).toBe(true);
|
246
|
+
});
|
247
|
+
|
248
|
+
it('should refresh containers', async () => {
|
249
|
+
const initialContainers = await smartContainers.detectContainers();
|
250
|
+
|
251
|
+
// Add new container
|
252
|
+
const newContainer = document.createElement('div');
|
253
|
+
newContainer.style.width = '300px';
|
254
|
+
newContainer.style.height = '200px';
|
255
|
+
newContainer.style.display = 'flex';
|
256
|
+
document.body.appendChild(newContainer);
|
257
|
+
|
258
|
+
const refreshedContainers = await smartContainers.refreshContainers();
|
259
|
+
|
260
|
+
expect(refreshedContainers.length).toBeGreaterThan(initialContainers.length);
|
261
|
+
});
|
262
|
+
});
|
263
|
+
|
264
|
+
describe('Error Handling', () => {
|
265
|
+
it('should handle missing elements gracefully', () => {
|
266
|
+
const nonExistentElement = document.createElement('div');
|
267
|
+
|
268
|
+
const containerInfo = smartContainers.getContainerInfo(nonExistentElement);
|
269
|
+
expect(containerInfo).toBeNull();
|
270
|
+
|
271
|
+
const updatedInfo = smartContainers.updateContainer(nonExistentElement);
|
272
|
+
expect(updatedInfo).toBeNull();
|
273
|
+
});
|
274
|
+
|
275
|
+
it('should handle cleanup properly', () => {
|
276
|
+
expect(() => {
|
277
|
+
smartContainers.destroy();
|
278
|
+
}).not.toThrow();
|
279
|
+
});
|
280
|
+
});
|
281
|
+
});
|