@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,436 @@
|
|
1
|
+
/**
|
2
|
+
* LineHeightOptimization Test Suite
|
3
|
+
* Comprehensive tests for line height optimization system
|
4
|
+
*/
|
5
|
+
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
7
|
+
import { LineHeightOptimization, LineHeightConfig } from '../LineHeightOptimization';
|
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('LineHeightOptimization', () => {
|
17
|
+
let lineHeightOptimization: LineHeightOptimization;
|
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 = 'This is a test paragraph with some content to test line height optimization.';
|
27
|
+
testElement.style.fontSize = '16px';
|
28
|
+
document.body.appendChild(testElement);
|
29
|
+
|
30
|
+
lineHeightOptimization = new LineHeightOptimization();
|
31
|
+
|
32
|
+
// Mock getComputedStyle
|
33
|
+
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
34
|
+
fontSize: '16px',
|
35
|
+
fontFamily: 'Arial, sans-serif'
|
36
|
+
} as CSSStyleDeclaration);
|
37
|
+
|
38
|
+
// Mock getBoundingClientRect
|
39
|
+
vi.spyOn(testElement, 'getBoundingClientRect').mockReturnValue({
|
40
|
+
width: 400,
|
41
|
+
height: 100,
|
42
|
+
top: 0,
|
43
|
+
left: 0,
|
44
|
+
bottom: 100,
|
45
|
+
right: 400,
|
46
|
+
x: 0,
|
47
|
+
y: 0,
|
48
|
+
toJSON: () => ({})
|
49
|
+
} as DOMRect);
|
50
|
+
});
|
51
|
+
|
52
|
+
afterEach(() => {
|
53
|
+
lineHeightOptimization.destroy();
|
54
|
+
document.body.innerHTML = '';
|
55
|
+
vi.restoreAllMocks();
|
56
|
+
});
|
57
|
+
|
58
|
+
describe('Basic Line Height Optimization', () => {
|
59
|
+
it('should optimize line height with default settings', () => {
|
60
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement);
|
61
|
+
|
62
|
+
expect(result.lineHeight).toBeGreaterThan(1);
|
63
|
+
expect(result.lineHeightCSS).toBeTruthy();
|
64
|
+
expect(result.reasoning).toBeInstanceOf(Array);
|
65
|
+
expect(result.reasoning.length).toBeGreaterThan(0);
|
66
|
+
expect(testElement.style.lineHeight).toBe(result.lineHeightCSS);
|
67
|
+
});
|
68
|
+
|
69
|
+
it('should apply different density settings', () => {
|
70
|
+
const compactResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
71
|
+
density: 'compact'
|
72
|
+
});
|
73
|
+
|
74
|
+
const spaciousResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
75
|
+
density: 'spacious'
|
76
|
+
});
|
77
|
+
|
78
|
+
expect(compactResult.lineHeight).toBeLessThan(spaciousResult.lineHeight);
|
79
|
+
});
|
80
|
+
|
81
|
+
it('should add debugging attributes', () => {
|
82
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement);
|
83
|
+
|
84
|
+
expect(testElement.getAttribute('data-proteus-line-height')).toBe(
|
85
|
+
result.lineHeight.toString()
|
86
|
+
);
|
87
|
+
expect(testElement.getAttribute('data-proteus-line-height-reasoning')).toBeTruthy();
|
88
|
+
});
|
89
|
+
});
|
90
|
+
|
91
|
+
describe('Content Type Adjustments', () => {
|
92
|
+
it('should adjust for heading content', () => {
|
93
|
+
const headingResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
94
|
+
contentType: 'heading'
|
95
|
+
});
|
96
|
+
|
97
|
+
const bodyResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
98
|
+
contentType: 'body'
|
99
|
+
});
|
100
|
+
|
101
|
+
expect(headingResult.lineHeight).toBeLessThan(bodyResult.lineHeight);
|
102
|
+
expect(headingResult.adjustments).toContain(
|
103
|
+
expect.stringContaining('Content type (heading)')
|
104
|
+
);
|
105
|
+
});
|
106
|
+
|
107
|
+
it('should adjust for code content', () => {
|
108
|
+
const codeResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
109
|
+
contentType: 'code'
|
110
|
+
});
|
111
|
+
|
112
|
+
const bodyResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
113
|
+
contentType: 'body'
|
114
|
+
});
|
115
|
+
|
116
|
+
expect(codeResult.lineHeight).toBeGreaterThan(bodyResult.lineHeight);
|
117
|
+
expect(codeResult.adjustments).toContain(
|
118
|
+
expect.stringContaining('Content type (code)')
|
119
|
+
);
|
120
|
+
});
|
121
|
+
|
122
|
+
it('should adjust for caption content', () => {
|
123
|
+
const captionResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
124
|
+
contentType: 'caption'
|
125
|
+
});
|
126
|
+
|
127
|
+
const bodyResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
128
|
+
contentType: 'body'
|
129
|
+
});
|
130
|
+
|
131
|
+
expect(captionResult.lineHeight).toBeLessThan(bodyResult.lineHeight);
|
132
|
+
});
|
133
|
+
});
|
134
|
+
|
135
|
+
describe('Language-Specific Adjustments', () => {
|
136
|
+
it('should adjust for Chinese language', () => {
|
137
|
+
const chineseResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
138
|
+
language: 'zh-CN'
|
139
|
+
});
|
140
|
+
|
141
|
+
const englishResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
142
|
+
language: 'en'
|
143
|
+
});
|
144
|
+
|
145
|
+
expect(chineseResult.lineHeight).toBeGreaterThan(englishResult.lineHeight);
|
146
|
+
expect(chineseResult.adjustments).toContain(
|
147
|
+
expect.stringContaining('Language (zh)')
|
148
|
+
);
|
149
|
+
});
|
150
|
+
|
151
|
+
it('should adjust for Japanese language', () => {
|
152
|
+
const japaneseResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
153
|
+
language: 'ja'
|
154
|
+
});
|
155
|
+
|
156
|
+
const englishResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
157
|
+
language: 'en'
|
158
|
+
});
|
159
|
+
|
160
|
+
expect(japaneseResult.lineHeight).toBeGreaterThan(englishResult.lineHeight);
|
161
|
+
});
|
162
|
+
|
163
|
+
it('should handle unknown languages gracefully', () => {
|
164
|
+
const unknownResult = lineHeightOptimization.optimizeLineHeight(testElement, {
|
165
|
+
language: 'unknown-lang'
|
166
|
+
});
|
167
|
+
|
168
|
+
expect(unknownResult.lineHeight).toBeGreaterThan(0);
|
169
|
+
expect(unknownResult.adjustments).not.toContain(
|
170
|
+
expect.stringContaining('Language (unknown-lang)')
|
171
|
+
);
|
172
|
+
});
|
173
|
+
});
|
174
|
+
|
175
|
+
describe('Line Length Adjustments', () => {
|
176
|
+
it('should adjust for very short lines', () => {
|
177
|
+
// Mock narrow container
|
178
|
+
vi.spyOn(testElement, 'getBoundingClientRect').mockReturnValue({
|
179
|
+
width: 100, // Very narrow
|
180
|
+
height: 100,
|
181
|
+
top: 0,
|
182
|
+
left: 0,
|
183
|
+
bottom: 100,
|
184
|
+
right: 100,
|
185
|
+
x: 0,
|
186
|
+
y: 0,
|
187
|
+
toJSON: () => ({})
|
188
|
+
} as DOMRect);
|
189
|
+
|
190
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement);
|
191
|
+
|
192
|
+
expect(result.adjustments).toContain(
|
193
|
+
expect.stringContaining('Line length')
|
194
|
+
);
|
195
|
+
});
|
196
|
+
|
197
|
+
it('should adjust for very long lines', () => {
|
198
|
+
// Mock very wide container
|
199
|
+
vi.spyOn(testElement, 'getBoundingClientRect').mockReturnValue({
|
200
|
+
width: 1200, // Very wide
|
201
|
+
height: 100,
|
202
|
+
top: 0,
|
203
|
+
left: 0,
|
204
|
+
bottom: 100,
|
205
|
+
right: 1200,
|
206
|
+
x: 0,
|
207
|
+
y: 0,
|
208
|
+
toJSON: () => ({})
|
209
|
+
} as DOMRect);
|
210
|
+
|
211
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement);
|
212
|
+
|
213
|
+
expect(result.adjustments).toContain(
|
214
|
+
expect.stringContaining('Line length')
|
215
|
+
);
|
216
|
+
});
|
217
|
+
});
|
218
|
+
|
219
|
+
describe('Font Size Adjustments', () => {
|
220
|
+
it('should adjust for small font sizes', () => {
|
221
|
+
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
222
|
+
fontSize: '10px', // Very small
|
223
|
+
fontFamily: 'Arial, sans-serif'
|
224
|
+
} as CSSStyleDeclaration);
|
225
|
+
|
226
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement);
|
227
|
+
|
228
|
+
expect(result.adjustments).toContain(
|
229
|
+
expect.stringContaining('Font size (10px)')
|
230
|
+
);
|
231
|
+
});
|
232
|
+
|
233
|
+
it('should adjust for large font sizes', () => {
|
234
|
+
vi.spyOn(window, 'getComputedStyle').mockReturnValue({
|
235
|
+
fontSize: '32px', // Very large
|
236
|
+
fontFamily: 'Arial, sans-serif'
|
237
|
+
} as CSSStyleDeclaration);
|
238
|
+
|
239
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement);
|
240
|
+
|
241
|
+
expect(result.adjustments).toContain(
|
242
|
+
expect.stringContaining('Font size (32px)')
|
243
|
+
);
|
244
|
+
});
|
245
|
+
});
|
246
|
+
|
247
|
+
describe('Accessibility Compliance', () => {
|
248
|
+
it('should enforce WCAG AA compliance', () => {
|
249
|
+
const config: LineHeightConfig = {
|
250
|
+
density: 'compact', // Would normally result in low line height
|
251
|
+
accessibility: 'AA',
|
252
|
+
enforceAccessibility: true
|
253
|
+
};
|
254
|
+
|
255
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement, config);
|
256
|
+
|
257
|
+
expect(result.lineHeight).toBeGreaterThanOrEqual(1.5); // WCAG AA minimum
|
258
|
+
expect(result.accessibilityCompliant).toBe(true);
|
259
|
+
});
|
260
|
+
|
261
|
+
it('should enforce WCAG AAA compliance', () => {
|
262
|
+
const config: LineHeightConfig = {
|
263
|
+
density: 'compact',
|
264
|
+
accessibility: 'AAA',
|
265
|
+
enforceAccessibility: true
|
266
|
+
};
|
267
|
+
|
268
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement, config);
|
269
|
+
|
270
|
+
expect(result.lineHeight).toBeGreaterThanOrEqual(1.6); // WCAG AAA minimum
|
271
|
+
expect(result.accessibilityCompliant).toBe(true);
|
272
|
+
});
|
273
|
+
|
274
|
+
it('should allow bypassing accessibility constraints', () => {
|
275
|
+
const config: LineHeightConfig = {
|
276
|
+
density: 'compact',
|
277
|
+
accessibility: 'AA',
|
278
|
+
enforceAccessibility: false
|
279
|
+
};
|
280
|
+
|
281
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement, config);
|
282
|
+
|
283
|
+
// Should not enforce minimum if disabled
|
284
|
+
expect(result.lineHeight).toBeLessThan(1.5);
|
285
|
+
});
|
286
|
+
|
287
|
+
it('should mark non-compliant results', () => {
|
288
|
+
const config: LineHeightConfig = {
|
289
|
+
density: 'compact',
|
290
|
+
accessibility: 'AAA',
|
291
|
+
enforceAccessibility: true
|
292
|
+
};
|
293
|
+
|
294
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement, config);
|
295
|
+
|
296
|
+
if (result.lineHeight < 1.6) {
|
297
|
+
expect(result.accessibilityCompliant).toBe(false);
|
298
|
+
} else {
|
299
|
+
expect(result.accessibilityCompliant).toBe(true);
|
300
|
+
}
|
301
|
+
});
|
302
|
+
});
|
303
|
+
|
304
|
+
describe('User Preferences', () => {
|
305
|
+
it('should respect user line spacing preferences', () => {
|
306
|
+
// Mock CSS custom property for user preferences
|
307
|
+
vi.spyOn(window, 'getComputedStyle').mockImplementation((element) => {
|
308
|
+
if (element === document.documentElement) {
|
309
|
+
return {
|
310
|
+
getPropertyValue: (prop: string) => {
|
311
|
+
if (prop === '--user-line-spacing') return '1.2';
|
312
|
+
return '';
|
313
|
+
}
|
314
|
+
} as CSSStyleDeclaration;
|
315
|
+
}
|
316
|
+
return {
|
317
|
+
fontSize: '16px',
|
318
|
+
fontFamily: 'Arial, sans-serif'
|
319
|
+
} as CSSStyleDeclaration;
|
320
|
+
});
|
321
|
+
|
322
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement, {
|
323
|
+
respectUserPreferences: true
|
324
|
+
});
|
325
|
+
|
326
|
+
expect(result.adjustments).toContain(
|
327
|
+
expect.stringContaining('User preferences')
|
328
|
+
);
|
329
|
+
});
|
330
|
+
|
331
|
+
it('should handle missing user preferences gracefully', () => {
|
332
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement, {
|
333
|
+
respectUserPreferences: true
|
334
|
+
});
|
335
|
+
|
336
|
+
expect(result.lineHeight).toBeGreaterThan(0);
|
337
|
+
});
|
338
|
+
});
|
339
|
+
|
340
|
+
describe('Element Management', () => {
|
341
|
+
it('should update line height when element changes', () => {
|
342
|
+
const config: LineHeightConfig = {
|
343
|
+
density: 'comfortable'
|
344
|
+
};
|
345
|
+
|
346
|
+
lineHeightOptimization.optimizeLineHeight(testElement, config);
|
347
|
+
const initialLineHeight = testElement.style.lineHeight;
|
348
|
+
|
349
|
+
// Change element size
|
350
|
+
vi.spyOn(testElement, 'getBoundingClientRect').mockReturnValue({
|
351
|
+
width: 800, // Much wider
|
352
|
+
height: 100,
|
353
|
+
top: 0,
|
354
|
+
left: 0,
|
355
|
+
bottom: 100,
|
356
|
+
right: 800,
|
357
|
+
x: 0,
|
358
|
+
y: 0,
|
359
|
+
toJSON: () => ({})
|
360
|
+
} as DOMRect);
|
361
|
+
|
362
|
+
lineHeightOptimization.updateLineHeight(testElement);
|
363
|
+
|
364
|
+
expect(testElement.style.lineHeight).not.toBe(initialLineHeight);
|
365
|
+
});
|
366
|
+
|
367
|
+
it('should remove optimization', () => {
|
368
|
+
lineHeightOptimization.optimizeLineHeight(testElement);
|
369
|
+
|
370
|
+
expect(testElement.style.lineHeight).toBeTruthy();
|
371
|
+
expect(testElement.getAttribute('data-proteus-line-height')).toBeTruthy();
|
372
|
+
|
373
|
+
lineHeightOptimization.removeOptimization(testElement);
|
374
|
+
|
375
|
+
expect(testElement.style.lineHeight).toBeFalsy();
|
376
|
+
expect(testElement.getAttribute('data-proteus-line-height')).toBeNull();
|
377
|
+
});
|
378
|
+
|
379
|
+
it('should handle elements without optimization', () => {
|
380
|
+
expect(() => {
|
381
|
+
lineHeightOptimization.removeOptimization(testElement);
|
382
|
+
}).not.toThrow();
|
383
|
+
|
384
|
+
expect(() => {
|
385
|
+
lineHeightOptimization.updateLineHeight(testElement);
|
386
|
+
}).not.toThrow();
|
387
|
+
});
|
388
|
+
});
|
389
|
+
|
390
|
+
describe('Error Handling', () => {
|
391
|
+
it('should handle errors gracefully', () => {
|
392
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
393
|
+
|
394
|
+
// Force an error by providing invalid config
|
395
|
+
const config = null as any;
|
396
|
+
|
397
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement, config);
|
398
|
+
|
399
|
+
expect(result.lineHeight).toBeGreaterThan(0);
|
400
|
+
expect(result.reasoning).toContain('Error occurred, using default');
|
401
|
+
expect(consoleSpy).toHaveBeenCalled();
|
402
|
+
|
403
|
+
consoleSpy.mockRestore();
|
404
|
+
});
|
405
|
+
|
406
|
+
it('should clean up resources on destroy', () => {
|
407
|
+
lineHeightOptimization.optimizeLineHeight(testElement);
|
408
|
+
|
409
|
+
expect(() => {
|
410
|
+
lineHeightOptimization.destroy();
|
411
|
+
}).not.toThrow();
|
412
|
+
});
|
413
|
+
});
|
414
|
+
|
415
|
+
describe('Precision and Rounding', () => {
|
416
|
+
it('should round line height to reasonable precision', () => {
|
417
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement);
|
418
|
+
|
419
|
+
// Should be rounded to 3 decimal places
|
420
|
+
const decimalPlaces = (result.lineHeight.toString().split('.')[1] || '').length;
|
421
|
+
expect(decimalPlaces).toBeLessThanOrEqual(3);
|
422
|
+
});
|
423
|
+
|
424
|
+
it('should provide detailed reasoning', () => {
|
425
|
+
const result = lineHeightOptimization.optimizeLineHeight(testElement, {
|
426
|
+
density: 'comfortable',
|
427
|
+
contentType: 'body',
|
428
|
+
language: 'en',
|
429
|
+
accessibility: 'AA'
|
430
|
+
});
|
431
|
+
|
432
|
+
expect(result.reasoning.length).toBeGreaterThan(1);
|
433
|
+
expect(result.reasoning[0]).toContain('comfortable density');
|
434
|
+
});
|
435
|
+
});
|
436
|
+
});
|
@@ -0,0 +1,173 @@
|
|
1
|
+
/**
|
2
|
+
* ProteusJS Logger
|
3
|
+
* Centralized logging system with configurable levels and production-safe output
|
4
|
+
*/
|
5
|
+
|
6
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
7
|
+
|
8
|
+
export interface LoggerConfig {
|
9
|
+
level: LogLevel;
|
10
|
+
prefix: string;
|
11
|
+
enableInProduction: boolean;
|
12
|
+
enableTimestamps: boolean;
|
13
|
+
enableStackTrace: boolean;
|
14
|
+
}
|
15
|
+
|
16
|
+
export class Logger {
|
17
|
+
private static instance: Logger;
|
18
|
+
private config: LoggerConfig;
|
19
|
+
private readonly levels: Record<LogLevel, number> = {
|
20
|
+
debug: 0,
|
21
|
+
info: 1,
|
22
|
+
warn: 2,
|
23
|
+
error: 3,
|
24
|
+
silent: 4
|
25
|
+
};
|
26
|
+
|
27
|
+
private constructor(config: Partial<LoggerConfig> = {}) {
|
28
|
+
this.config = {
|
29
|
+
level: 'warn',
|
30
|
+
prefix: 'ProteusJS',
|
31
|
+
enableInProduction: false,
|
32
|
+
enableTimestamps: false,
|
33
|
+
enableStackTrace: false,
|
34
|
+
...config
|
35
|
+
};
|
36
|
+
}
|
37
|
+
|
38
|
+
public static getInstance(config?: Partial<LoggerConfig>): Logger {
|
39
|
+
if (!Logger.instance) {
|
40
|
+
Logger.instance = new Logger(config);
|
41
|
+
}
|
42
|
+
return Logger.instance;
|
43
|
+
}
|
44
|
+
|
45
|
+
public static configure(config: Partial<LoggerConfig>): void {
|
46
|
+
if (Logger.instance) {
|
47
|
+
Logger.instance.config = { ...Logger.instance.config, ...config };
|
48
|
+
} else {
|
49
|
+
Logger.instance = new Logger(config);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
private shouldLog(level: LogLevel): boolean {
|
54
|
+
// Don't log in production unless explicitly enabled
|
55
|
+
if (process.env['NODE_ENV'] === 'production' && !this.config.enableInProduction) {
|
56
|
+
return level === 'error'; // Only errors in production
|
57
|
+
}
|
58
|
+
|
59
|
+
return this.levels[level] >= this.levels[this.config.level];
|
60
|
+
}
|
61
|
+
|
62
|
+
private formatMessage(level: LogLevel, message: string, ...args: unknown[]): [string, ...unknown[]] {
|
63
|
+
let formattedMessage = message;
|
64
|
+
|
65
|
+
// Add prefix
|
66
|
+
if (this.config.prefix) {
|
67
|
+
formattedMessage = `${this.config.prefix}: ${formattedMessage}`;
|
68
|
+
}
|
69
|
+
|
70
|
+
// Add timestamp
|
71
|
+
if (this.config.enableTimestamps) {
|
72
|
+
const timestamp = new Date().toISOString();
|
73
|
+
formattedMessage = `[${timestamp}] ${formattedMessage}`;
|
74
|
+
}
|
75
|
+
|
76
|
+
// Add log level
|
77
|
+
const levelPrefix = level.toUpperCase().padEnd(5);
|
78
|
+
formattedMessage = `[${levelPrefix}] ${formattedMessage}`;
|
79
|
+
|
80
|
+
return [formattedMessage, ...args];
|
81
|
+
}
|
82
|
+
|
83
|
+
public debug(message: string, ...args: unknown[]): void {
|
84
|
+
if (!this.shouldLog('debug')) return;
|
85
|
+
|
86
|
+
const [formattedMessage, ...formattedArgs] = this.formatMessage('debug', message, ...args);
|
87
|
+
console.debug(formattedMessage, ...formattedArgs);
|
88
|
+
|
89
|
+
if (this.config.enableStackTrace) {
|
90
|
+
console.trace();
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
public info(message: string, ...args: unknown[]): void {
|
95
|
+
if (!this.shouldLog('info')) return;
|
96
|
+
|
97
|
+
const [formattedMessage, ...formattedArgs] = this.formatMessage('info', message, ...args);
|
98
|
+
console.info(formattedMessage, ...formattedArgs);
|
99
|
+
}
|
100
|
+
|
101
|
+
public warn(message: string, ...args: unknown[]): void {
|
102
|
+
if (!this.shouldLog('warn')) return;
|
103
|
+
|
104
|
+
const [formattedMessage, ...formattedArgs] = this.formatMessage('warn', message, ...args);
|
105
|
+
console.warn(formattedMessage, ...formattedArgs);
|
106
|
+
}
|
107
|
+
|
108
|
+
public error(message: string, error?: Error | unknown, ...args: unknown[]): void {
|
109
|
+
if (!this.shouldLog('error')) return;
|
110
|
+
|
111
|
+
const [formattedMessage, ...formattedArgs] = this.formatMessage('error', message, ...args);
|
112
|
+
|
113
|
+
if (error instanceof Error) {
|
114
|
+
console.error(formattedMessage, error, ...formattedArgs);
|
115
|
+
if (this.config.enableStackTrace && error.stack) {
|
116
|
+
console.error('Stack trace:', error.stack);
|
117
|
+
}
|
118
|
+
} else if (error) {
|
119
|
+
console.error(formattedMessage, error, ...formattedArgs);
|
120
|
+
} else {
|
121
|
+
console.error(formattedMessage, ...formattedArgs);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
public group(label: string): void {
|
126
|
+
if (!this.shouldLog('info')) return;
|
127
|
+
console.group(`${this.config.prefix}: ${label}`);
|
128
|
+
}
|
129
|
+
|
130
|
+
public groupEnd(): void {
|
131
|
+
if (!this.shouldLog('info')) return;
|
132
|
+
console.groupEnd();
|
133
|
+
}
|
134
|
+
|
135
|
+
public time(label: string): void {
|
136
|
+
if (!this.shouldLog('debug')) return;
|
137
|
+
console.time(`${this.config.prefix}: ${label}`);
|
138
|
+
}
|
139
|
+
|
140
|
+
public timeEnd(label: string): void {
|
141
|
+
if (!this.shouldLog('debug')) return;
|
142
|
+
console.timeEnd(`${this.config.prefix}: ${label}`);
|
143
|
+
}
|
144
|
+
|
145
|
+
public setLevel(level: LogLevel): void {
|
146
|
+
this.config.level = level;
|
147
|
+
}
|
148
|
+
|
149
|
+
public getLevel(): LogLevel {
|
150
|
+
return this.config.level;
|
151
|
+
}
|
152
|
+
|
153
|
+
public isEnabled(level: LogLevel): boolean {
|
154
|
+
return this.shouldLog(level);
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
// Create default logger instance
|
159
|
+
export const logger = Logger.getInstance({
|
160
|
+
level: process.env['NODE_ENV'] === 'development' ? 'debug' : 'warn',
|
161
|
+
prefix: 'ProteusJS',
|
162
|
+
enableInProduction: false,
|
163
|
+
enableTimestamps: process.env['NODE_ENV'] === 'development',
|
164
|
+
enableStackTrace: false
|
165
|
+
});
|
166
|
+
|
167
|
+
// Convenience functions
|
168
|
+
export const debug = (message: string, ...args: unknown[]): void => logger.debug(message, ...args);
|
169
|
+
export const info = (message: string, ...args: unknown[]): void => logger.info(message, ...args);
|
170
|
+
export const warn = (message: string, ...args: unknown[]): void => logger.warn(message, ...args);
|
171
|
+
export const error = (message: string, error?: Error | unknown, ...args: unknown[]): void => logger.error(message, error, ...args);
|
172
|
+
|
173
|
+
export default logger;
|