@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,432 @@
|
|
1
|
+
/**
|
2
|
+
* FluidTypography Test Suite
|
3
|
+
* Comprehensive tests for fluid typography system
|
4
|
+
*/
|
5
|
+
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
7
|
+
import { FluidTypography, FluidConfig, ContainerBasedConfig } from '../FluidTypography';
|
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('FluidTypography', () => {
|
17
|
+
let fluidTypography: FluidTypography;
|
18
|
+
let testElement: HTMLElement;
|
19
|
+
|
20
|
+
beforeEach(() => {
|
21
|
+
// Clean up DOM
|
22
|
+
document.body.innerHTML = '';
|
23
|
+
|
24
|
+
// Create test element
|
25
|
+
testElement = document.createElement('p');
|
26
|
+
testElement.textContent = 'Test typography content';
|
27
|
+
testElement.style.fontSize = '16px';
|
28
|
+
document.body.appendChild(testElement);
|
29
|
+
|
30
|
+
fluidTypography = new FluidTypography();
|
31
|
+
|
32
|
+
// Mock getComputedStyle
|
33
|
+
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
34
|
+
fontSize: '16px',
|
35
|
+
fontFamily: 'Arial, sans-serif',
|
36
|
+
fontWeight: 'normal',
|
37
|
+
fontStyle: 'normal'
|
38
|
+
} as CSSStyleDeclaration);
|
39
|
+
});
|
40
|
+
|
41
|
+
afterEach(() => {
|
42
|
+
fluidTypography.destroy();
|
43
|
+
document.body.innerHTML = '';
|
44
|
+
vi.restoreAllMocks();
|
45
|
+
});
|
46
|
+
|
47
|
+
describe('Fluid Scaling', () => {
|
48
|
+
it('should apply fluid scaling with clamp()', () => {
|
49
|
+
const config: FluidConfig = {
|
50
|
+
minSize: 14,
|
51
|
+
maxSize: 24,
|
52
|
+
minViewport: 320,
|
53
|
+
maxViewport: 1200
|
54
|
+
};
|
55
|
+
|
56
|
+
fluidTypography.applyFluidScaling(testElement, config);
|
57
|
+
|
58
|
+
expect(testElement.style.fontSize).toContain('clamp(');
|
59
|
+
expect(testElement.getAttribute('data-proteus-fluid')).toBe('true');
|
60
|
+
expect(testElement.getAttribute('data-proteus-min-size')).toBe('14');
|
61
|
+
expect(testElement.getAttribute('data-proteus-max-size')).toBe('24');
|
62
|
+
});
|
63
|
+
|
64
|
+
it('should enforce accessibility constraints', () => {
|
65
|
+
const config: FluidConfig = {
|
66
|
+
minSize: 10, // Below WCAG AA minimum
|
67
|
+
maxSize: 20,
|
68
|
+
accessibility: 'AA',
|
69
|
+
enforceAccessibility: true
|
70
|
+
};
|
71
|
+
|
72
|
+
fluidTypography.applyFluidScaling(testElement, config);
|
73
|
+
|
74
|
+
// Should be adjusted to meet WCAG AA minimum (14px)
|
75
|
+
expect(testElement.getAttribute('data-proteus-min-size')).toBe('14');
|
76
|
+
});
|
77
|
+
|
78
|
+
it('should respect user preferences', () => {
|
79
|
+
// Mock root font size to simulate user preference
|
80
|
+
vi.spyOn(window, 'getComputedStyle').mockImplementation((element) => {
|
81
|
+
if (element === document.documentElement) {
|
82
|
+
return { fontSize: '20px' } as CSSStyleDeclaration; // User increased font size
|
83
|
+
}
|
84
|
+
return {
|
85
|
+
fontSize: '16px',
|
86
|
+
fontFamily: 'Arial, sans-serif',
|
87
|
+
fontWeight: 'normal',
|
88
|
+
fontStyle: 'normal'
|
89
|
+
} as CSSStyleDeclaration;
|
90
|
+
});
|
91
|
+
|
92
|
+
const config: FluidConfig = {
|
93
|
+
minSize: 16,
|
94
|
+
maxSize: 24,
|
95
|
+
respectUserPreferences: true
|
96
|
+
};
|
97
|
+
|
98
|
+
fluidTypography.applyFluidScaling(testElement, config);
|
99
|
+
|
100
|
+
// Font sizes should be scaled by user preference (20/16 = 1.25)
|
101
|
+
expect(testElement.getAttribute('data-proteus-min-size')).toBe('20'); // 16 * 1.25
|
102
|
+
expect(testElement.getAttribute('data-proteus-max-size')).toBe('30'); // 24 * 1.25
|
103
|
+
});
|
104
|
+
|
105
|
+
it('should generate linear clamp values correctly', () => {
|
106
|
+
const config: FluidConfig = {
|
107
|
+
minSize: 16,
|
108
|
+
maxSize: 24,
|
109
|
+
minViewport: 320,
|
110
|
+
maxViewport: 1200,
|
111
|
+
scalingFunction: 'linear'
|
112
|
+
};
|
113
|
+
|
114
|
+
fluidTypography.applyFluidScaling(testElement, config);
|
115
|
+
|
116
|
+
const fontSize = testElement.style.fontSize;
|
117
|
+
expect(fontSize).toMatch(/clamp\(16px,.*,24px\)/);
|
118
|
+
});
|
119
|
+
|
120
|
+
it('should handle exponential scaling', () => {
|
121
|
+
const config: FluidConfig = {
|
122
|
+
minSize: 16,
|
123
|
+
maxSize: 32,
|
124
|
+
minViewport: 320,
|
125
|
+
maxViewport: 1200,
|
126
|
+
scalingFunction: 'exponential'
|
127
|
+
};
|
128
|
+
|
129
|
+
fluidTypography.applyFluidScaling(testElement, config);
|
130
|
+
|
131
|
+
const fontSize = testElement.style.fontSize;
|
132
|
+
expect(fontSize).toContain('clamp(');
|
133
|
+
expect(fontSize).toContain('16px');
|
134
|
+
expect(fontSize).toContain('32px');
|
135
|
+
});
|
136
|
+
});
|
137
|
+
|
138
|
+
describe('Container-Based Scaling', () => {
|
139
|
+
let containerElement: HTMLElement;
|
140
|
+
|
141
|
+
beforeEach(() => {
|
142
|
+
containerElement = document.createElement('div');
|
143
|
+
containerElement.style.width = '600px';
|
144
|
+
containerElement.style.height = '400px';
|
145
|
+
containerElement.appendChild(testElement);
|
146
|
+
document.body.appendChild(containerElement);
|
147
|
+
|
148
|
+
// Mock getBoundingClientRect for container
|
149
|
+
vi.spyOn(containerElement, 'getBoundingClientRect').mockReturnValue({
|
150
|
+
width: 600,
|
151
|
+
height: 400,
|
152
|
+
top: 0,
|
153
|
+
left: 0,
|
154
|
+
bottom: 400,
|
155
|
+
right: 600,
|
156
|
+
x: 0,
|
157
|
+
y: 0,
|
158
|
+
toJSON: () => ({})
|
159
|
+
} as DOMRect);
|
160
|
+
});
|
161
|
+
|
162
|
+
it('should apply container-based scaling', () => {
|
163
|
+
const config: ContainerBasedConfig = {
|
164
|
+
minSize: 14,
|
165
|
+
maxSize: 24,
|
166
|
+
containerElement,
|
167
|
+
minContainerWidth: 300,
|
168
|
+
maxContainerWidth: 800
|
169
|
+
};
|
170
|
+
|
171
|
+
fluidTypography.applyContainerBasedScaling(testElement, config);
|
172
|
+
|
173
|
+
// Container width is 600px, should scale between min and max
|
174
|
+
const fontSize = parseFloat(testElement.style.fontSize);
|
175
|
+
expect(fontSize).toBeGreaterThan(14);
|
176
|
+
expect(fontSize).toBeLessThan(24);
|
177
|
+
});
|
178
|
+
|
179
|
+
it('should find nearest container automatically', () => {
|
180
|
+
const config: ContainerBasedConfig = {
|
181
|
+
minSize: 14,
|
182
|
+
maxSize: 24,
|
183
|
+
minContainerWidth: 300,
|
184
|
+
maxContainerWidth: 800
|
185
|
+
};
|
186
|
+
|
187
|
+
fluidTypography.applyContainerBasedScaling(testElement, config);
|
188
|
+
|
189
|
+
// Should find the container element automatically
|
190
|
+
expect(testElement.style.fontSize).toBeTruthy();
|
191
|
+
});
|
192
|
+
|
193
|
+
it('should update on container resize', () => {
|
194
|
+
const config: ContainerBasedConfig = {
|
195
|
+
minSize: 14,
|
196
|
+
maxSize: 24,
|
197
|
+
containerElement,
|
198
|
+
minContainerWidth: 300,
|
199
|
+
maxContainerWidth: 800
|
200
|
+
};
|
201
|
+
|
202
|
+
fluidTypography.applyContainerBasedScaling(testElement, config);
|
203
|
+
|
204
|
+
const initialFontSize = testElement.style.fontSize;
|
205
|
+
|
206
|
+
// Simulate container resize
|
207
|
+
vi.spyOn(containerElement, 'getBoundingClientRect').mockReturnValue({
|
208
|
+
width: 400,
|
209
|
+
height: 400,
|
210
|
+
top: 0,
|
211
|
+
left: 0,
|
212
|
+
bottom: 400,
|
213
|
+
right: 400,
|
214
|
+
x: 0,
|
215
|
+
y: 0,
|
216
|
+
toJSON: () => ({})
|
217
|
+
} as DOMRect);
|
218
|
+
|
219
|
+
// Trigger resize update
|
220
|
+
fluidTypography['handleContainerResize'](containerElement);
|
221
|
+
|
222
|
+
expect(testElement.style.fontSize).not.toBe(initialFontSize);
|
223
|
+
});
|
224
|
+
});
|
225
|
+
|
226
|
+
describe('Text Fitting', () => {
|
227
|
+
beforeEach(() => {
|
228
|
+
// Mock getBoundingClientRect for text measurement
|
229
|
+
const mockGetBoundingClientRect = vi.fn();
|
230
|
+
Element.prototype.getBoundingClientRect = mockGetBoundingClientRect;
|
231
|
+
|
232
|
+
// Return different widths based on font size
|
233
|
+
mockGetBoundingClientRect.mockImplementation(function(this: Element) {
|
234
|
+
const fontSize = parseFloat(this.style.fontSize || '16');
|
235
|
+
const textLength = this.textContent?.length || 0;
|
236
|
+
return {
|
237
|
+
width: fontSize * textLength * 0.6, // Approximate character width
|
238
|
+
height: fontSize * 1.2,
|
239
|
+
top: 0,
|
240
|
+
left: 0,
|
241
|
+
bottom: fontSize * 1.2,
|
242
|
+
right: fontSize * textLength * 0.6,
|
243
|
+
x: 0,
|
244
|
+
y: 0,
|
245
|
+
toJSON: () => ({})
|
246
|
+
} as DOMRect;
|
247
|
+
});
|
248
|
+
});
|
249
|
+
|
250
|
+
it('should fit text to container width', () => {
|
251
|
+
const config = {
|
252
|
+
maxWidth: 200,
|
253
|
+
minSize: 12,
|
254
|
+
maxSize: 24
|
255
|
+
};
|
256
|
+
|
257
|
+
fluidTypography.fitTextToContainer(testElement, config);
|
258
|
+
|
259
|
+
const fontSize = parseFloat(testElement.style.fontSize);
|
260
|
+
expect(fontSize).toBeGreaterThanOrEqual(12);
|
261
|
+
expect(fontSize).toBeLessThanOrEqual(24);
|
262
|
+
});
|
263
|
+
|
264
|
+
it('should handle overflow settings', () => {
|
265
|
+
const config = {
|
266
|
+
maxWidth: 100,
|
267
|
+
minSize: 12,
|
268
|
+
maxSize: 24,
|
269
|
+
allowOverflow: false,
|
270
|
+
wordBreak: 'break-all' as const
|
271
|
+
};
|
272
|
+
|
273
|
+
fluidTypography.fitTextToContainer(testElement, config);
|
274
|
+
|
275
|
+
expect(testElement.style.overflow).toBe('hidden');
|
276
|
+
expect(testElement.style.textOverflow).toBe('ellipsis');
|
277
|
+
expect(testElement.style.wordBreak).toBe('break-all');
|
278
|
+
});
|
279
|
+
|
280
|
+
it('should calculate optimal text size correctly', () => {
|
281
|
+
testElement.textContent = 'Short text';
|
282
|
+
|
283
|
+
const config = {
|
284
|
+
maxWidth: 300,
|
285
|
+
minSize: 12,
|
286
|
+
maxSize: 48
|
287
|
+
};
|
288
|
+
|
289
|
+
fluidTypography.fitTextToContainer(testElement, config);
|
290
|
+
|
291
|
+
// Should find a size that fits within the width
|
292
|
+
const fontSize = parseFloat(testElement.style.fontSize);
|
293
|
+
expect(fontSize).toBeGreaterThan(12);
|
294
|
+
});
|
295
|
+
});
|
296
|
+
|
297
|
+
describe('Element Management', () => {
|
298
|
+
it('should remove fluid scaling', () => {
|
299
|
+
const config: FluidConfig = {
|
300
|
+
minSize: 14,
|
301
|
+
maxSize: 24
|
302
|
+
};
|
303
|
+
|
304
|
+
fluidTypography.applyFluidScaling(testElement, config);
|
305
|
+
|
306
|
+
expect(testElement.style.fontSize).toBeTruthy();
|
307
|
+
expect(testElement.getAttribute('data-proteus-fluid')).toBe('true');
|
308
|
+
|
309
|
+
fluidTypography.removeFluidScaling(testElement);
|
310
|
+
|
311
|
+
expect(testElement.style.fontSize).toBeFalsy();
|
312
|
+
expect(testElement.getAttribute('data-proteus-fluid')).toBeNull();
|
313
|
+
});
|
314
|
+
|
315
|
+
it('should handle elements without applied scaling', () => {
|
316
|
+
expect(() => {
|
317
|
+
fluidTypography.removeFluidScaling(testElement);
|
318
|
+
}).not.toThrow();
|
319
|
+
});
|
320
|
+
|
321
|
+
it('should clean up resources on destroy', () => {
|
322
|
+
const config: FluidConfig = {
|
323
|
+
minSize: 14,
|
324
|
+
maxSize: 24
|
325
|
+
};
|
326
|
+
|
327
|
+
fluidTypography.applyFluidScaling(testElement, config);
|
328
|
+
|
329
|
+
expect(() => {
|
330
|
+
fluidTypography.destroy();
|
331
|
+
}).not.toThrow();
|
332
|
+
});
|
333
|
+
});
|
334
|
+
|
335
|
+
describe('Error Handling', () => {
|
336
|
+
it('should handle missing container gracefully', () => {
|
337
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
338
|
+
|
339
|
+
// Remove element from DOM to simulate missing container
|
340
|
+
testElement.remove();
|
341
|
+
|
342
|
+
const config: ContainerBasedConfig = {
|
343
|
+
minSize: 14,
|
344
|
+
maxSize: 24
|
345
|
+
};
|
346
|
+
|
347
|
+
expect(() => {
|
348
|
+
fluidTypography.applyContainerBasedScaling(testElement, config);
|
349
|
+
}).not.toThrow();
|
350
|
+
|
351
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
352
|
+
expect.stringContaining('No container found')
|
353
|
+
);
|
354
|
+
|
355
|
+
consoleSpy.mockRestore();
|
356
|
+
});
|
357
|
+
|
358
|
+
it('should handle errors in scaling gracefully', () => {
|
359
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
360
|
+
|
361
|
+
// Force an error by providing invalid config
|
362
|
+
const config = null as any;
|
363
|
+
|
364
|
+
expect(() => {
|
365
|
+
fluidTypography.applyFluidScaling(testElement, config);
|
366
|
+
}).not.toThrow();
|
367
|
+
|
368
|
+
expect(consoleSpy).toHaveBeenCalled();
|
369
|
+
consoleSpy.mockRestore();
|
370
|
+
});
|
371
|
+
|
372
|
+
it('should handle empty text content', () => {
|
373
|
+
testElement.textContent = '';
|
374
|
+
|
375
|
+
const config = {
|
376
|
+
maxWidth: 200,
|
377
|
+
minSize: 12,
|
378
|
+
maxSize: 24
|
379
|
+
};
|
380
|
+
|
381
|
+
expect(() => {
|
382
|
+
fluidTypography.fitTextToContainer(testElement, config);
|
383
|
+
}).not.toThrow();
|
384
|
+
|
385
|
+
expect(parseFloat(testElement.style.fontSize)).toBe(12); // Should use minimum
|
386
|
+
});
|
387
|
+
});
|
388
|
+
|
389
|
+
describe('Accessibility Compliance', () => {
|
390
|
+
it('should enforce WCAG AA compliance', () => {
|
391
|
+
const config: FluidConfig = {
|
392
|
+
minSize: 10, // Below WCAG AA minimum
|
393
|
+
maxSize: 20,
|
394
|
+
accessibility: 'AA',
|
395
|
+
enforceAccessibility: true
|
396
|
+
};
|
397
|
+
|
398
|
+
fluidTypography.applyFluidScaling(testElement, config);
|
399
|
+
|
400
|
+
const minSize = parseFloat(testElement.getAttribute('data-proteus-min-size') || '0');
|
401
|
+
expect(minSize).toBeGreaterThanOrEqual(14); // WCAG AA minimum
|
402
|
+
});
|
403
|
+
|
404
|
+
it('should enforce WCAG AAA compliance', () => {
|
405
|
+
const config: FluidConfig = {
|
406
|
+
minSize: 12, // Below WCAG AAA minimum
|
407
|
+
maxSize: 20,
|
408
|
+
accessibility: 'AAA',
|
409
|
+
enforceAccessibility: true
|
410
|
+
};
|
411
|
+
|
412
|
+
fluidTypography.applyFluidScaling(testElement, config);
|
413
|
+
|
414
|
+
const minSize = parseFloat(testElement.getAttribute('data-proteus-min-size') || '0');
|
415
|
+
expect(minSize).toBeGreaterThanOrEqual(16); // WCAG AAA minimum
|
416
|
+
});
|
417
|
+
|
418
|
+
it('should allow bypassing accessibility constraints', () => {
|
419
|
+
const config: FluidConfig = {
|
420
|
+
minSize: 10,
|
421
|
+
maxSize: 20,
|
422
|
+
accessibility: 'AA',
|
423
|
+
enforceAccessibility: false
|
424
|
+
};
|
425
|
+
|
426
|
+
fluidTypography.applyFluidScaling(testElement, config);
|
427
|
+
|
428
|
+
const minSize = parseFloat(testElement.getAttribute('data-proteus-min-size') || '0');
|
429
|
+
expect(minSize).toBe(10); // Should not be enforced
|
430
|
+
});
|
431
|
+
});
|
432
|
+
});
|