mtrl-addons 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorrules +117 -0
- package/AI.md +241 -0
- package/build.js +170 -0
- package/bun.lock +792 -0
- package/index.ts +7 -0
- package/package.json +10 -17
- package/scripts/analyze-orphaned-functions.ts +387 -0
- package/src/components/index.ts +45 -0
- package/src/components/list/api.ts +314 -0
- package/src/components/list/config.ts +352 -0
- package/src/components/list/constants.ts +56 -0
- package/src/components/list/features/api.ts +428 -0
- package/src/components/list/features/index.ts +31 -0
- package/src/components/list/features/list-manager.ts +502 -0
- package/src/components/list/features.ts +112 -0
- package/src/components/list/index.ts +39 -0
- package/src/components/list/list.ts +234 -0
- package/src/components/list/types.ts +513 -0
- package/src/core/collection/base-collection.ts +100 -0
- package/src/core/collection/collection-composer.ts +178 -0
- package/src/core/collection/collection.ts +745 -0
- package/src/core/collection/constants.ts +172 -0
- package/src/core/collection/events.ts +428 -0
- package/src/core/collection/features/api/loading.ts +279 -0
- package/src/core/collection/features/operations/data-operations.ts +147 -0
- package/src/core/collection/index.ts +104 -0
- package/src/core/collection/state.ts +497 -0
- package/src/core/collection/types.ts +404 -0
- package/src/core/compose/features/collection.ts +119 -0
- package/src/core/compose/features/index.ts +39 -0
- package/src/core/compose/features/performance.ts +161 -0
- package/src/core/compose/features/selection.ts +213 -0
- package/src/core/compose/features/styling.ts +108 -0
- package/src/core/compose/index.ts +31 -0
- package/src/core/index.ts +167 -0
- package/src/core/layout/config.ts +102 -0
- package/src/core/layout/index.ts +168 -0
- package/src/core/layout/jsx.ts +174 -0
- package/src/core/layout/schema.ts +963 -0
- package/src/core/layout/types.ts +92 -0
- package/src/core/list-manager/api.ts +599 -0
- package/src/core/list-manager/config.ts +593 -0
- package/src/core/list-manager/constants.ts +268 -0
- package/src/core/list-manager/features/api.ts +58 -0
- package/src/core/list-manager/features/collection/collection.ts +705 -0
- package/src/core/list-manager/features/collection/index.ts +17 -0
- package/src/core/list-manager/features/viewport/constants.ts +42 -0
- package/src/core/list-manager/features/viewport/index.ts +16 -0
- package/src/core/list-manager/features/viewport/item-size.ts +274 -0
- package/src/core/list-manager/features/viewport/loading.ts +263 -0
- package/src/core/list-manager/features/viewport/placeholders.ts +281 -0
- package/src/core/list-manager/features/viewport/rendering.ts +575 -0
- package/src/core/list-manager/features/viewport/scrollbar.ts +495 -0
- package/src/core/list-manager/features/viewport/scrolling.ts +795 -0
- package/src/core/list-manager/features/viewport/template.ts +220 -0
- package/src/core/list-manager/features/viewport/viewport.ts +654 -0
- package/src/core/list-manager/features/viewport/virtual.ts +309 -0
- package/src/core/list-manager/index.ts +279 -0
- package/src/core/list-manager/list-manager.ts +206 -0
- package/src/core/list-manager/types.ts +439 -0
- package/src/core/list-manager/utils/calculations.ts +290 -0
- package/src/core/list-manager/utils/range-calculator.ts +349 -0
- package/src/core/list-manager/utils/speed-tracker.ts +273 -0
- package/src/index.ts +17 -0
- package/src/styles/components/_list.scss +244 -0
- package/src/styles/index.scss +12 -0
- package/src/types/mtrl.d.ts +6 -0
- package/test/benchmarks/layout/advanced.test.ts +656 -0
- package/test/benchmarks/layout/comparison.test.ts +519 -0
- package/test/benchmarks/layout/performance-comparison.test.ts +274 -0
- package/test/benchmarks/layout/real-components.test.ts +733 -0
- package/test/benchmarks/layout/simple.test.ts +321 -0
- package/test/benchmarks/layout/stress.test.ts +990 -0
- package/test/collection/basic.test.ts +304 -0
- package/test/components/list.test.ts +256 -0
- package/test/core/collection/collection.test.ts +394 -0
- package/test/core/collection/failed-ranges.test.ts +270 -0
- package/test/core/compose/features.test.ts +183 -0
- package/test/core/layout/layout.test.ts +201 -0
- package/test/core/list-manager/features/collection.test.ts +704 -0
- package/test/core/list-manager/features/viewport.test.ts +698 -0
- package/test/core/list-manager/list-manager.test.ts +593 -0
- package/test/core/list-manager/utils/calculations.test.ts +433 -0
- package/test/core/list-manager/utils/range-calculator.test.ts +569 -0
- package/test/core/list-manager/utils/speed-tracker.test.ts +530 -0
- package/test/utils/dom-helpers.ts +275 -0
- package/test/utils/performance-helpers.ts +392 -0
- package/tsconfig.build.json +14 -0
- package/tsconfig.json +20 -0
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -38
- package/dist/index.mjs +0 -8
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// test/utils/dom-helpers.ts - DOM Testing Utilities
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a test container element with scrolling capabilities
|
|
5
|
+
* @param height - Height of the container (default: 400px)
|
|
6
|
+
* @param width - Width of the container (default: 100%)
|
|
7
|
+
* @returns HTMLElement configured for testing
|
|
8
|
+
*/
|
|
9
|
+
export function createContainer(height = "400px", width = "100%"): HTMLElement {
|
|
10
|
+
const container = document.createElement("div");
|
|
11
|
+
container.style.height = height;
|
|
12
|
+
container.style.width = width;
|
|
13
|
+
container.style.overflow = "auto";
|
|
14
|
+
container.style.position = "relative";
|
|
15
|
+
container.className = "test-container";
|
|
16
|
+
document.body.appendChild(container);
|
|
17
|
+
return container;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Cleans up a test container by removing it from DOM
|
|
22
|
+
* @param container - Container to cleanup
|
|
23
|
+
*/
|
|
24
|
+
export function cleanupContainer(container: HTMLElement): void {
|
|
25
|
+
if (container && container.parentNode) {
|
|
26
|
+
container.parentNode.removeChild(container);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Simulates a scroll event on an element
|
|
32
|
+
* @param element - Element to scroll
|
|
33
|
+
* @param scrollTop - Scroll position
|
|
34
|
+
* @param scrollLeft - Horizontal scroll position (optional)
|
|
35
|
+
*/
|
|
36
|
+
export function simulateScroll(
|
|
37
|
+
element: HTMLElement,
|
|
38
|
+
scrollTop: number,
|
|
39
|
+
scrollLeft: number = 0
|
|
40
|
+
): void {
|
|
41
|
+
element.scrollTop = scrollTop;
|
|
42
|
+
element.scrollLeft = scrollLeft;
|
|
43
|
+
|
|
44
|
+
const scrollEvent = new Event("scroll", {
|
|
45
|
+
bubbles: true,
|
|
46
|
+
cancelable: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
element.dispatchEvent(scrollEvent);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Simulates a resize event on an element
|
|
54
|
+
* @param element - Element to resize
|
|
55
|
+
* @param width - New width
|
|
56
|
+
* @param height - New height
|
|
57
|
+
*/
|
|
58
|
+
export function simulateResize(
|
|
59
|
+
element: HTMLElement,
|
|
60
|
+
width: number,
|
|
61
|
+
height: number
|
|
62
|
+
): void {
|
|
63
|
+
element.style.width = `${width}px`;
|
|
64
|
+
element.style.height = `${height}px`;
|
|
65
|
+
|
|
66
|
+
const resizeEvent = new Event("resize", {
|
|
67
|
+
bubbles: true,
|
|
68
|
+
cancelable: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
window.dispatchEvent(resizeEvent);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Waits for the next animation frame
|
|
76
|
+
* @returns Promise that resolves after requestAnimationFrame
|
|
77
|
+
*/
|
|
78
|
+
export function waitForAnimationFrame(): Promise<void> {
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
requestAnimationFrame(() => resolve());
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Waits for multiple animation frames
|
|
86
|
+
* @param count - Number of frames to wait
|
|
87
|
+
* @returns Promise that resolves after the specified frames
|
|
88
|
+
*/
|
|
89
|
+
export function waitForAnimationFrames(count: number): Promise<void> {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
let remaining = count;
|
|
92
|
+
|
|
93
|
+
function frame() {
|
|
94
|
+
remaining--;
|
|
95
|
+
if (remaining <= 0) {
|
|
96
|
+
resolve();
|
|
97
|
+
} else {
|
|
98
|
+
requestAnimationFrame(frame);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
requestAnimationFrame(frame);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Waits for a specific amount of time
|
|
108
|
+
* @param ms - Milliseconds to wait
|
|
109
|
+
* @returns Promise that resolves after the timeout
|
|
110
|
+
*/
|
|
111
|
+
export function waitFor(ms: number): Promise<void> {
|
|
112
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Gets the computed style of an element
|
|
117
|
+
* @param element - Element to get style for
|
|
118
|
+
* @param property - CSS property to get
|
|
119
|
+
* @returns The computed style value
|
|
120
|
+
*/
|
|
121
|
+
export function getComputedStyleValue(
|
|
122
|
+
element: HTMLElement,
|
|
123
|
+
property: string
|
|
124
|
+
): string {
|
|
125
|
+
return window.getComputedStyle(element).getPropertyValue(property);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Checks if an element is visible in the viewport
|
|
130
|
+
* @param element - Element to check
|
|
131
|
+
* @param container - Container element (default: document.body)
|
|
132
|
+
* @returns Whether the element is visible
|
|
133
|
+
*/
|
|
134
|
+
export function isElementVisible(
|
|
135
|
+
element: HTMLElement,
|
|
136
|
+
container: HTMLElement = document.body
|
|
137
|
+
): boolean {
|
|
138
|
+
const rect = element.getBoundingClientRect();
|
|
139
|
+
const containerRect = container.getBoundingClientRect();
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
rect.top >= containerRect.top &&
|
|
143
|
+
rect.left >= containerRect.left &&
|
|
144
|
+
rect.bottom <= containerRect.bottom &&
|
|
145
|
+
rect.right <= containerRect.right
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Gets all elements within a visible range
|
|
151
|
+
* @param container - Container element
|
|
152
|
+
* @param selector - CSS selector for elements
|
|
153
|
+
* @returns Array of visible elements
|
|
154
|
+
*/
|
|
155
|
+
export function getVisibleElements(
|
|
156
|
+
container: HTMLElement,
|
|
157
|
+
selector: string = ".test-item"
|
|
158
|
+
): HTMLElement[] {
|
|
159
|
+
const elements = Array.from(
|
|
160
|
+
container.querySelectorAll(selector)
|
|
161
|
+
) as HTMLElement[];
|
|
162
|
+
return elements.filter((el) => isElementVisible(el, container));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Simulates a mouse event on an element
|
|
167
|
+
* @param element - Target element
|
|
168
|
+
* @param type - Event type (click, mousedown, etc.)
|
|
169
|
+
* @param options - Event options
|
|
170
|
+
*/
|
|
171
|
+
export function simulateMouseEvent(
|
|
172
|
+
element: HTMLElement,
|
|
173
|
+
type: string,
|
|
174
|
+
options: Partial<MouseEventInit> = {}
|
|
175
|
+
): void {
|
|
176
|
+
const event = new MouseEvent(type, {
|
|
177
|
+
bubbles: true,
|
|
178
|
+
cancelable: true,
|
|
179
|
+
...options,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
element.dispatchEvent(event);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Simulates a touch event on an element
|
|
187
|
+
* @param element - Target element
|
|
188
|
+
* @param type - Event type (touchstart, touchmove, etc.)
|
|
189
|
+
* @param touches - Touch points
|
|
190
|
+
*/
|
|
191
|
+
export function simulateTouchEvent(
|
|
192
|
+
element: HTMLElement,
|
|
193
|
+
type: string,
|
|
194
|
+
touches: Touch[] = []
|
|
195
|
+
): void {
|
|
196
|
+
const event = new TouchEvent(type, {
|
|
197
|
+
bubbles: true,
|
|
198
|
+
cancelable: true,
|
|
199
|
+
touches,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
element.dispatchEvent(event);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Creates a mock IntersectionObserver for testing
|
|
207
|
+
* @param callback - Callback function
|
|
208
|
+
* @returns Mock IntersectionObserver
|
|
209
|
+
*/
|
|
210
|
+
export function createMockIntersectionObserver(
|
|
211
|
+
callback: IntersectionObserverCallback
|
|
212
|
+
): any {
|
|
213
|
+
const observer = {
|
|
214
|
+
observe: () => {},
|
|
215
|
+
unobserve: () => {},
|
|
216
|
+
disconnect: () => {},
|
|
217
|
+
root: null,
|
|
218
|
+
rootMargin: "",
|
|
219
|
+
thresholds: [],
|
|
220
|
+
takeRecords: () => [],
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Store callback for manual triggering
|
|
224
|
+
(observer as any).callback = callback;
|
|
225
|
+
|
|
226
|
+
return observer;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Triggers intersection observer callback manually
|
|
231
|
+
* @param observer - Mock observer
|
|
232
|
+
* @param entries - Intersection entries
|
|
233
|
+
*/
|
|
234
|
+
export function triggerIntersectionObserver(
|
|
235
|
+
observer: any,
|
|
236
|
+
entries: IntersectionObserverEntry[]
|
|
237
|
+
): void {
|
|
238
|
+
if (observer.callback) {
|
|
239
|
+
observer.callback(entries, observer);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Creates a mock ResizeObserver for testing
|
|
245
|
+
* @param callback - Callback function
|
|
246
|
+
* @returns Mock ResizeObserver
|
|
247
|
+
*/
|
|
248
|
+
export function createMockResizeObserver(
|
|
249
|
+
callback: ResizeObserverCallback
|
|
250
|
+
): any {
|
|
251
|
+
const observer = {
|
|
252
|
+
observe: () => {},
|
|
253
|
+
unobserve: () => {},
|
|
254
|
+
disconnect: () => {},
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Store callback for manual triggering
|
|
258
|
+
(observer as any).callback = callback;
|
|
259
|
+
|
|
260
|
+
return observer;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Triggers resize observer callback manually
|
|
265
|
+
* @param observer - Mock observer
|
|
266
|
+
* @param entries - Resize entries
|
|
267
|
+
*/
|
|
268
|
+
export function triggerResizeObserver(
|
|
269
|
+
observer: any,
|
|
270
|
+
entries: ResizeObserverEntry[]
|
|
271
|
+
): void {
|
|
272
|
+
if (observer.callback) {
|
|
273
|
+
observer.callback(entries, observer);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
// test/utils/performance-helpers.ts - Performance Testing Utilities
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Measures the execution time of a function
|
|
5
|
+
* @param callback - Function to measure
|
|
6
|
+
* @returns Promise that resolves with the execution time in milliseconds
|
|
7
|
+
*/
|
|
8
|
+
export function measureExecutionTime(
|
|
9
|
+
callback: () => Promise<void> | void
|
|
10
|
+
): Promise<number> {
|
|
11
|
+
return new Promise(async (resolve) => {
|
|
12
|
+
const start = performance.now();
|
|
13
|
+
await callback();
|
|
14
|
+
const end = performance.now();
|
|
15
|
+
resolve(end - start);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Measures the time it takes to render content
|
|
21
|
+
* @param callback - Render function
|
|
22
|
+
* @returns Promise that resolves with render time in milliseconds
|
|
23
|
+
*/
|
|
24
|
+
export function measureRenderTime(
|
|
25
|
+
callback: () => Promise<void> | void
|
|
26
|
+
): Promise<number> {
|
|
27
|
+
return measureExecutionTime(callback);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a large dataset for performance testing
|
|
32
|
+
* @param count - Number of items to create
|
|
33
|
+
* @param template - Template function for creating items
|
|
34
|
+
* @returns Array of test items
|
|
35
|
+
*/
|
|
36
|
+
export function createLargeDataset<T>(
|
|
37
|
+
count: number = 10000,
|
|
38
|
+
template: (index: number) => T = (i) =>
|
|
39
|
+
({
|
|
40
|
+
id: i.toString(),
|
|
41
|
+
name: `Item ${i}`,
|
|
42
|
+
value: Math.random() * 1000,
|
|
43
|
+
} as T)
|
|
44
|
+
): T[] {
|
|
45
|
+
return Array.from({ length: count }, (_, i) => template(i));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates test items with varying sizes for dynamic height testing
|
|
50
|
+
* @param count - Number of items to create
|
|
51
|
+
* @returns Array of items with different content lengths
|
|
52
|
+
*/
|
|
53
|
+
export function createVariableSizeDataset(count: number = 1000) {
|
|
54
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
55
|
+
id: i.toString(),
|
|
56
|
+
name: `Item ${i}`,
|
|
57
|
+
description: "Lorem ipsum ".repeat(Math.floor(Math.random() * 10) + 1),
|
|
58
|
+
tags: Array.from(
|
|
59
|
+
{ length: Math.floor(Math.random() * 5) + 1 },
|
|
60
|
+
(_, j) => `tag${j}`
|
|
61
|
+
),
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Measures frame rate during a series of operations
|
|
67
|
+
* @param operations - Array of operations to perform
|
|
68
|
+
* @param duration - Duration to measure in milliseconds
|
|
69
|
+
* @returns Promise that resolves with average FPS
|
|
70
|
+
*/
|
|
71
|
+
export function measureFrameRate(
|
|
72
|
+
operations: (() => void)[],
|
|
73
|
+
duration: number = 1000
|
|
74
|
+
): Promise<number> {
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
let frameCount = 0;
|
|
77
|
+
let lastTime = performance.now();
|
|
78
|
+
const endTime = lastTime + duration;
|
|
79
|
+
let operationIndex = 0;
|
|
80
|
+
|
|
81
|
+
function frame() {
|
|
82
|
+
const currentTime = performance.now();
|
|
83
|
+
|
|
84
|
+
if (currentTime >= endTime) {
|
|
85
|
+
const fps = (frameCount / duration) * 1000;
|
|
86
|
+
resolve(fps);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Perform operation
|
|
91
|
+
if (operations.length > 0) {
|
|
92
|
+
operations[operationIndex % operations.length]();
|
|
93
|
+
operationIndex++;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
frameCount++;
|
|
97
|
+
requestAnimationFrame(frame);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
requestAnimationFrame(frame);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Measures scroll performance by simulating rapid scrolling
|
|
106
|
+
* @param container - Container element to scroll
|
|
107
|
+
* @param scrollDistance - Total distance to scroll
|
|
108
|
+
* @param scrollSteps - Number of scroll steps
|
|
109
|
+
* @returns Promise that resolves with performance metrics
|
|
110
|
+
*/
|
|
111
|
+
export function measureScrollPerformance(
|
|
112
|
+
container: HTMLElement,
|
|
113
|
+
scrollDistance: number = 10000,
|
|
114
|
+
scrollSteps: number = 100
|
|
115
|
+
): Promise<{
|
|
116
|
+
averageFrameTime: number;
|
|
117
|
+
maxFrameTime: number;
|
|
118
|
+
minFrameTime: number;
|
|
119
|
+
droppedFrames: number;
|
|
120
|
+
}> {
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
const frameTimes: number[] = [];
|
|
123
|
+
const stepSize = scrollDistance / scrollSteps;
|
|
124
|
+
let currentStep = 0;
|
|
125
|
+
let lastTime = performance.now();
|
|
126
|
+
const targetFrameTime = 16.67; // 60fps = 16.67ms per frame
|
|
127
|
+
|
|
128
|
+
function scrollStep() {
|
|
129
|
+
const currentTime = performance.now();
|
|
130
|
+
const frameTime = currentTime - lastTime;
|
|
131
|
+
frameTimes.push(frameTime);
|
|
132
|
+
|
|
133
|
+
if (currentStep >= scrollSteps) {
|
|
134
|
+
const averageFrameTime =
|
|
135
|
+
frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;
|
|
136
|
+
const maxFrameTime = Math.max(...frameTimes);
|
|
137
|
+
const minFrameTime = Math.min(...frameTimes);
|
|
138
|
+
const droppedFrames = frameTimes.filter(
|
|
139
|
+
(time) => time > targetFrameTime
|
|
140
|
+
).length;
|
|
141
|
+
|
|
142
|
+
resolve({
|
|
143
|
+
averageFrameTime,
|
|
144
|
+
maxFrameTime,
|
|
145
|
+
minFrameTime,
|
|
146
|
+
droppedFrames,
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Perform scroll
|
|
152
|
+
container.scrollTop = currentStep * stepSize;
|
|
153
|
+
const scrollEvent = new Event("scroll", { bubbles: true });
|
|
154
|
+
container.dispatchEvent(scrollEvent);
|
|
155
|
+
|
|
156
|
+
currentStep++;
|
|
157
|
+
lastTime = currentTime;
|
|
158
|
+
requestAnimationFrame(scrollStep);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
requestAnimationFrame(scrollStep);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Measures memory usage (approximate)
|
|
167
|
+
* @returns Memory usage information
|
|
168
|
+
*/
|
|
169
|
+
export function measureMemoryUsage(): {
|
|
170
|
+
heapUsed: number;
|
|
171
|
+
heapTotal: number;
|
|
172
|
+
external: number;
|
|
173
|
+
} {
|
|
174
|
+
// For browser environment, we can't access precise memory info
|
|
175
|
+
// This is a placeholder that would work in Node.js
|
|
176
|
+
if (typeof process !== "undefined" && process.memoryUsage) {
|
|
177
|
+
const usage = process.memoryUsage();
|
|
178
|
+
return {
|
|
179
|
+
heapUsed: usage.heapUsed,
|
|
180
|
+
heapTotal: usage.heapTotal,
|
|
181
|
+
external: usage.external,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Browser fallback - approximate values
|
|
186
|
+
return {
|
|
187
|
+
heapUsed: 0,
|
|
188
|
+
heapTotal: 0,
|
|
189
|
+
external: 0,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Measures DOM manipulation performance
|
|
195
|
+
* @param manipulationFn - Function that performs DOM manipulation
|
|
196
|
+
* @param iterations - Number of iterations to perform
|
|
197
|
+
* @returns Promise that resolves with performance metrics
|
|
198
|
+
*/
|
|
199
|
+
export function measureDOMPerformance(
|
|
200
|
+
manipulationFn: () => void,
|
|
201
|
+
iterations: number = 1000
|
|
202
|
+
): Promise<{
|
|
203
|
+
totalTime: number;
|
|
204
|
+
averageTime: number;
|
|
205
|
+
operationsPerSecond: number;
|
|
206
|
+
}> {
|
|
207
|
+
return new Promise((resolve) => {
|
|
208
|
+
const start = performance.now();
|
|
209
|
+
|
|
210
|
+
for (let i = 0; i < iterations; i++) {
|
|
211
|
+
manipulationFn();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const end = performance.now();
|
|
215
|
+
const totalTime = end - start;
|
|
216
|
+
const averageTime = totalTime / iterations;
|
|
217
|
+
const operationsPerSecond = (iterations / totalTime) * 1000;
|
|
218
|
+
|
|
219
|
+
resolve({
|
|
220
|
+
totalTime,
|
|
221
|
+
averageTime,
|
|
222
|
+
operationsPerSecond,
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Runs a performance benchmark and compares against baseline
|
|
229
|
+
* @param name - Name of the benchmark
|
|
230
|
+
* @param fn - Function to benchmark
|
|
231
|
+
* @param baseline - Baseline time in milliseconds
|
|
232
|
+
* @returns Promise that resolves with benchmark results
|
|
233
|
+
*/
|
|
234
|
+
export function runBenchmark(
|
|
235
|
+
name: string,
|
|
236
|
+
fn: () => Promise<void> | void,
|
|
237
|
+
baseline?: number
|
|
238
|
+
): Promise<{
|
|
239
|
+
name: string;
|
|
240
|
+
time: number;
|
|
241
|
+
baseline?: number;
|
|
242
|
+
ratio?: number;
|
|
243
|
+
passed: boolean;
|
|
244
|
+
}> {
|
|
245
|
+
return new Promise(async (resolve) => {
|
|
246
|
+
const time = await measureExecutionTime(fn);
|
|
247
|
+
const result = {
|
|
248
|
+
name,
|
|
249
|
+
time,
|
|
250
|
+
baseline,
|
|
251
|
+
ratio: baseline ? time / baseline : undefined,
|
|
252
|
+
passed: baseline ? time <= baseline : true,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
resolve(result);
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Stress tests a function with increasing load
|
|
261
|
+
* @param fn - Function to stress test
|
|
262
|
+
* @param startLoad - Starting load (e.g., number of items)
|
|
263
|
+
* @param maxLoad - Maximum load to test
|
|
264
|
+
* @param stepSize - Step size for increasing load
|
|
265
|
+
* @returns Promise that resolves with stress test results
|
|
266
|
+
*/
|
|
267
|
+
export function stressTest(
|
|
268
|
+
fn: (load: number) => Promise<void> | void,
|
|
269
|
+
startLoad: number = 100,
|
|
270
|
+
maxLoad: number = 10000,
|
|
271
|
+
stepSize: number = 100
|
|
272
|
+
): Promise<
|
|
273
|
+
{
|
|
274
|
+
load: number;
|
|
275
|
+
time: number;
|
|
276
|
+
success: boolean;
|
|
277
|
+
}[]
|
|
278
|
+
> {
|
|
279
|
+
return new Promise(async (resolve) => {
|
|
280
|
+
const results: { load: number; time: number; success: boolean }[] = [];
|
|
281
|
+
|
|
282
|
+
for (let load = startLoad; load <= maxLoad; load += stepSize) {
|
|
283
|
+
try {
|
|
284
|
+
const time = await measureExecutionTime(() => fn(load));
|
|
285
|
+
results.push({ load, time, success: true });
|
|
286
|
+
} catch (error) {
|
|
287
|
+
results.push({ load, time: -1, success: false });
|
|
288
|
+
break; // Stop on first failure
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
resolve(results);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Measures the performance of virtual scrolling
|
|
298
|
+
* @param container - Container element
|
|
299
|
+
* @param itemCount - Total number of items
|
|
300
|
+
* @param visibleItems - Number of visible items
|
|
301
|
+
* @param scrollTests - Number of scroll positions to test
|
|
302
|
+
* @returns Promise that resolves with virtual scroll performance metrics
|
|
303
|
+
*/
|
|
304
|
+
export function measureVirtualScrollPerformance(
|
|
305
|
+
container: HTMLElement,
|
|
306
|
+
itemCount: number = 10000,
|
|
307
|
+
visibleItems: number = 20,
|
|
308
|
+
scrollTests: number = 100
|
|
309
|
+
): Promise<{
|
|
310
|
+
renderTime: number;
|
|
311
|
+
scrollTime: number;
|
|
312
|
+
memoryEfficiency: number;
|
|
313
|
+
}> {
|
|
314
|
+
return new Promise(async (resolve) => {
|
|
315
|
+
// Measure initial render time
|
|
316
|
+
const renderTime = await measureRenderTime(async () => {
|
|
317
|
+
// This would be the virtual scroll render function
|
|
318
|
+
// Placeholder for now
|
|
319
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Measure scroll performance
|
|
323
|
+
const scrollTime = await measureScrollPerformance(
|
|
324
|
+
container,
|
|
325
|
+
5000,
|
|
326
|
+
scrollTests
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
// Calculate memory efficiency (visible items / total items)
|
|
330
|
+
const memoryEfficiency = visibleItems / itemCount;
|
|
331
|
+
|
|
332
|
+
resolve({
|
|
333
|
+
renderTime,
|
|
334
|
+
scrollTime: scrollTime.averageFrameTime,
|
|
335
|
+
memoryEfficiency,
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Creates a performance test suite
|
|
342
|
+
* @param tests - Array of test configurations
|
|
343
|
+
* @returns Promise that resolves with all test results
|
|
344
|
+
*/
|
|
345
|
+
export function runPerformanceTestSuite(
|
|
346
|
+
tests: {
|
|
347
|
+
name: string;
|
|
348
|
+
fn: () => Promise<void> | void;
|
|
349
|
+
baseline?: number;
|
|
350
|
+
timeout?: number;
|
|
351
|
+
}[]
|
|
352
|
+
): Promise<
|
|
353
|
+
{
|
|
354
|
+
name: string;
|
|
355
|
+
time: number;
|
|
356
|
+
baseline?: number;
|
|
357
|
+
passed: boolean;
|
|
358
|
+
error?: string;
|
|
359
|
+
}[]
|
|
360
|
+
> {
|
|
361
|
+
return new Promise(async (resolve) => {
|
|
362
|
+
const results: {
|
|
363
|
+
name: string;
|
|
364
|
+
time: number;
|
|
365
|
+
baseline?: number;
|
|
366
|
+
passed: boolean;
|
|
367
|
+
error?: string;
|
|
368
|
+
}[] = [];
|
|
369
|
+
|
|
370
|
+
for (const test of tests) {
|
|
371
|
+
try {
|
|
372
|
+
const benchmark = await runBenchmark(test.name, test.fn, test.baseline);
|
|
373
|
+
results.push({
|
|
374
|
+
name: benchmark.name,
|
|
375
|
+
time: benchmark.time,
|
|
376
|
+
baseline: benchmark.baseline,
|
|
377
|
+
passed: benchmark.passed,
|
|
378
|
+
});
|
|
379
|
+
} catch (error) {
|
|
380
|
+
results.push({
|
|
381
|
+
name: test.name,
|
|
382
|
+
time: -1,
|
|
383
|
+
baseline: test.baseline,
|
|
384
|
+
passed: false,
|
|
385
|
+
error: error instanceof Error ? error.message : String(error),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
resolve(results);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"emitDeclarationOnly": true,
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"noEmitOnError": false,
|
|
10
|
+
"typeRoots": ["./node_modules/@types", "./src/types"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*.ts", "src/types/**/*.d.ts"],
|
|
13
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts", "test/**/*"]
|
|
14
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"lib": ["ESNext", "DOM"],
|
|
13
|
+
"types": ["bun-types"],
|
|
14
|
+
"paths": {
|
|
15
|
+
"@/*": ["./src/*"]
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*.ts"],
|
|
19
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
20
|
+
}
|