mtrl-addons 0.1.2 → 0.2.2
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/AI.md +28 -230
- package/CLAUDE.md +882 -0
- package/build.js +253 -24
- package/package.json +14 -4
- package/scripts/debug/vlist-selection.ts +121 -0
- package/src/components/index.ts +5 -41
- package/src/components/{list → vlist}/config.ts +66 -95
- package/src/components/vlist/constants.ts +23 -0
- package/src/components/vlist/features/api.ts +626 -0
- package/src/components/vlist/features/index.ts +10 -0
- package/src/components/vlist/features/selection.ts +436 -0
- package/src/components/vlist/features/viewport.ts +59 -0
- package/src/components/vlist/index.ts +17 -0
- package/src/components/{list → vlist}/types.ts +242 -32
- package/src/components/vlist/vlist.ts +92 -0
- package/src/core/compose/features/gestures/index.ts +227 -0
- package/src/core/compose/features/gestures/longpress.ts +383 -0
- package/src/core/compose/features/gestures/pan.ts +424 -0
- package/src/core/compose/features/gestures/pinch.ts +475 -0
- package/src/core/compose/features/gestures/rotate.ts +485 -0
- package/src/core/compose/features/gestures/swipe.ts +492 -0
- package/src/core/compose/features/gestures/tap.ts +334 -0
- package/src/core/compose/features/index.ts +2 -38
- package/src/core/compose/index.ts +13 -29
- package/src/core/gestures/index.ts +31 -0
- package/src/core/gestures/longpress.ts +68 -0
- package/src/core/gestures/manager.ts +418 -0
- package/src/core/gestures/pan.ts +48 -0
- package/src/core/gestures/pinch.ts +58 -0
- package/src/core/gestures/rotate.ts +58 -0
- package/src/core/gestures/swipe.ts +66 -0
- package/src/core/gestures/tap.ts +45 -0
- package/src/core/gestures/types.ts +387 -0
- package/src/core/gestures/utils.ts +128 -0
- package/src/core/index.ts +27 -151
- package/src/core/layout/schema.ts +153 -72
- package/src/core/layout/types.ts +5 -2
- package/src/core/viewport/constants.ts +145 -0
- package/src/core/viewport/features/base.ts +73 -0
- package/src/core/viewport/features/collection.ts +1182 -0
- package/src/core/viewport/features/events.ts +130 -0
- package/src/core/viewport/features/index.ts +20 -0
- package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +31 -34
- package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
- package/src/core/viewport/features/momentum.ts +269 -0
- package/src/core/viewport/features/placeholders.ts +335 -0
- package/src/core/viewport/features/rendering.ts +962 -0
- package/src/core/viewport/features/scrollbar.ts +434 -0
- package/src/core/viewport/features/scrolling.ts +634 -0
- package/src/core/viewport/features/utils.ts +94 -0
- package/src/core/viewport/features/virtual.ts +525 -0
- package/src/core/viewport/index.ts +31 -0
- package/src/core/viewport/types.ts +133 -0
- package/src/core/viewport/utils/speed-tracker.ts +79 -0
- package/src/core/viewport/viewport.ts +265 -0
- package/src/index.ts +0 -7
- package/src/styles/components/_vlist.scss +352 -0
- package/src/styles/index.scss +1 -1
- package/test/components/vlist-selection.test.ts +240 -0
- package/test/components/vlist.test.ts +63 -0
- package/test/core/collection/adapter.test.ts +161 -0
- package/bun.lock +0 -792
- package/src/components/list/api.ts +0 -314
- package/src/components/list/constants.ts +0 -56
- package/src/components/list/features/api.ts +0 -428
- package/src/components/list/features/index.ts +0 -31
- package/src/components/list/features/list-manager.ts +0 -502
- package/src/components/list/index.ts +0 -39
- package/src/components/list/list.ts +0 -234
- package/src/core/collection/base-collection.ts +0 -100
- package/src/core/collection/collection-composer.ts +0 -178
- package/src/core/collection/collection.ts +0 -745
- package/src/core/collection/constants.ts +0 -172
- package/src/core/collection/events.ts +0 -428
- package/src/core/collection/features/api/loading.ts +0 -279
- package/src/core/collection/features/operations/data-operations.ts +0 -147
- package/src/core/collection/index.ts +0 -104
- package/src/core/collection/state.ts +0 -497
- package/src/core/collection/types.ts +0 -404
- package/src/core/compose/features/collection.ts +0 -119
- package/src/core/compose/features/selection.ts +0 -213
- package/src/core/compose/features/styling.ts +0 -108
- package/src/core/list-manager/api.ts +0 -599
- package/src/core/list-manager/config.ts +0 -593
- package/src/core/list-manager/constants.ts +0 -268
- package/src/core/list-manager/features/api.ts +0 -58
- package/src/core/list-manager/features/collection/collection.ts +0 -705
- package/src/core/list-manager/features/collection/index.ts +0 -17
- package/src/core/list-manager/features/viewport/constants.ts +0 -42
- package/src/core/list-manager/features/viewport/index.ts +0 -16
- package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
- package/src/core/list-manager/features/viewport/rendering.ts +0 -575
- package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
- package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
- package/src/core/list-manager/features/viewport/template.ts +0 -220
- package/src/core/list-manager/features/viewport/viewport.ts +0 -654
- package/src/core/list-manager/features/viewport/virtual.ts +0 -309
- package/src/core/list-manager/index.ts +0 -279
- package/src/core/list-manager/list-manager.ts +0 -206
- package/src/core/list-manager/types.ts +0 -439
- package/src/core/list-manager/utils/calculations.ts +0 -290
- package/src/core/list-manager/utils/range-calculator.ts +0 -349
- package/src/core/list-manager/utils/speed-tracker.ts +0 -273
- package/src/styles/components/_list.scss +0 -244
- package/src/types/mtrl.d.ts +0 -6
- package/test/components/list.test.ts +0 -256
- package/test/core/collection/failed-ranges.test.ts +0 -270
- package/test/core/compose/features.test.ts +0 -183
- package/test/core/list-manager/features/collection.test.ts +0 -704
- package/test/core/list-manager/features/viewport.test.ts +0 -698
- package/test/core/list-manager/list-manager.test.ts +0 -593
- package/test/core/list-manager/utils/calculations.test.ts +0 -433
- package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
- package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
- package/tsconfig.build.json +0 -23
- /package/src/components/{list → vlist}/features.ts +0 -0
- /package/src/core/{compose → viewport}/features/performance.ts +0 -0
|
@@ -1,593 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
describe,
|
|
3
|
-
it,
|
|
4
|
-
expect,
|
|
5
|
-
beforeEach,
|
|
6
|
-
afterEach,
|
|
7
|
-
mock,
|
|
8
|
-
spyOn,
|
|
9
|
-
} from "bun:test";
|
|
10
|
-
import {
|
|
11
|
-
createListManager,
|
|
12
|
-
ListManagerImpl,
|
|
13
|
-
} from "../../../src/core/list-manager/list-manager";
|
|
14
|
-
import { ListManagerEvents } from "../../../src/core/list-manager/types";
|
|
15
|
-
import type {
|
|
16
|
-
ListManagerConfig,
|
|
17
|
-
ListManagerConfigUpdate,
|
|
18
|
-
} from "../../../src/core/list-manager/types";
|
|
19
|
-
|
|
20
|
-
// Mock DOM environment
|
|
21
|
-
const mockContainer = {
|
|
22
|
-
getBoundingClientRect: () => ({ width: 400, height: 600, top: 0, left: 0 }),
|
|
23
|
-
scrollTop: 0,
|
|
24
|
-
scrollLeft: 0,
|
|
25
|
-
clientWidth: 400,
|
|
26
|
-
clientHeight: 600,
|
|
27
|
-
addEventListener: mock(),
|
|
28
|
-
removeEventListener: mock(),
|
|
29
|
-
appendChild: mock(),
|
|
30
|
-
removeChild: mock(),
|
|
31
|
-
style: {},
|
|
32
|
-
} as unknown as HTMLElement;
|
|
33
|
-
|
|
34
|
-
// Mock features
|
|
35
|
-
const mockViewportFeature = {
|
|
36
|
-
initialize: mock(),
|
|
37
|
-
destroy: mock(),
|
|
38
|
-
scrollToIndex: mock(),
|
|
39
|
-
calculateVisibleRange: mock(() => ({ start: 0, end: 10 })),
|
|
40
|
-
getViewportInfo: mock(() => ({
|
|
41
|
-
containerSize: 600,
|
|
42
|
-
totalVirtualSize: 50000,
|
|
43
|
-
visibleRange: { start: 0, end: 10 },
|
|
44
|
-
virtualScrollPosition: 0,
|
|
45
|
-
})),
|
|
46
|
-
updateContainerPosition: mock(),
|
|
47
|
-
updateScrollbar: mock(),
|
|
48
|
-
setTotalItems: mock(),
|
|
49
|
-
virtualScrollPosition: 0,
|
|
50
|
-
orientation: "vertical",
|
|
51
|
-
estimatedItemSize: 50,
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const mockCollectionFeature = {
|
|
55
|
-
initialize: mock(),
|
|
56
|
-
destroy: mock(),
|
|
57
|
-
setTemplate: mock(),
|
|
58
|
-
setItems: mock(),
|
|
59
|
-
getItemsInRange: mock(() => [] as any[]),
|
|
60
|
-
handleVisibleRangeChange: mock(),
|
|
61
|
-
handleScrollPositionChange: mock(),
|
|
62
|
-
adaptPaginationStrategy: mock(),
|
|
63
|
-
paginationStrategy: "page",
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Mock feature factories
|
|
67
|
-
mock.module("../../../src/core/list-manager/features/viewport", () => ({
|
|
68
|
-
createViewportFeature: mock(() => mockViewportFeature),
|
|
69
|
-
}));
|
|
70
|
-
|
|
71
|
-
mock.module("../../../src/core/list-manager/features/collection", () => ({
|
|
72
|
-
createCollectionFeature: mock(() => mockCollectionFeature),
|
|
73
|
-
}));
|
|
74
|
-
|
|
75
|
-
describe("ListManager", () => {
|
|
76
|
-
let config: ListManagerConfig;
|
|
77
|
-
let listManager: ListManagerImpl;
|
|
78
|
-
|
|
79
|
-
beforeEach(() => {
|
|
80
|
-
// Reset all mocks
|
|
81
|
-
mock.restore();
|
|
82
|
-
|
|
83
|
-
// Create test configuration with all required properties
|
|
84
|
-
config = {
|
|
85
|
-
container: mockContainer,
|
|
86
|
-
items: Array.from({ length: 100 }, (_, i) => ({
|
|
87
|
-
id: i,
|
|
88
|
-
name: `Item ${i}`,
|
|
89
|
-
})),
|
|
90
|
-
template: {
|
|
91
|
-
template: (item: any, index: number) => {
|
|
92
|
-
const div = document.createElement("div");
|
|
93
|
-
div.textContent = `${index}: ${item.name}`;
|
|
94
|
-
return div;
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
virtual: {
|
|
98
|
-
enabled: true,
|
|
99
|
-
itemSize: "auto",
|
|
100
|
-
estimatedItemSize: 50,
|
|
101
|
-
overscan: 5,
|
|
102
|
-
},
|
|
103
|
-
orientation: {
|
|
104
|
-
orientation: "vertical",
|
|
105
|
-
reverse: false,
|
|
106
|
-
crossAxisAlignment: "stretch",
|
|
107
|
-
},
|
|
108
|
-
initialLoad: {
|
|
109
|
-
strategy: "placeholders",
|
|
110
|
-
viewportMultiplier: 1.5,
|
|
111
|
-
minItems: 10,
|
|
112
|
-
maxItems: 100,
|
|
113
|
-
},
|
|
114
|
-
errorHandling: {
|
|
115
|
-
timeout: 5000,
|
|
116
|
-
showErrorItems: true,
|
|
117
|
-
retryAttempts: 3,
|
|
118
|
-
preserveScrollOnError: true,
|
|
119
|
-
},
|
|
120
|
-
positioning: {
|
|
121
|
-
precisePositioning: true,
|
|
122
|
-
allowPartialItems: true,
|
|
123
|
-
snapToItems: false,
|
|
124
|
-
},
|
|
125
|
-
boundaries: {
|
|
126
|
-
preventOverscroll: true,
|
|
127
|
-
maintainEdgeRanges: true,
|
|
128
|
-
boundaryResistance: 0.15,
|
|
129
|
-
},
|
|
130
|
-
recycling: {
|
|
131
|
-
enabled: false,
|
|
132
|
-
maxPoolSize: 50,
|
|
133
|
-
minPoolSize: 10,
|
|
134
|
-
},
|
|
135
|
-
performance: {
|
|
136
|
-
frameScheduling: true,
|
|
137
|
-
memoryCleanup: true,
|
|
138
|
-
},
|
|
139
|
-
intersection: {
|
|
140
|
-
pagination: {
|
|
141
|
-
enabled: false,
|
|
142
|
-
rootMargin: "100px",
|
|
143
|
-
threshold: 0.1,
|
|
144
|
-
},
|
|
145
|
-
loading: {
|
|
146
|
-
enabled: false,
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
debug: true,
|
|
150
|
-
prefix: "test-list",
|
|
151
|
-
componentName: "TestList",
|
|
152
|
-
};
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
afterEach(() => {
|
|
156
|
-
if (listManager) {
|
|
157
|
-
listManager.destroy();
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe("Initialization", () => {
|
|
162
|
-
it("should create a ListManager with valid config", () => {
|
|
163
|
-
listManager = new ListManagerImpl(config);
|
|
164
|
-
expect(listManager).toBeDefined();
|
|
165
|
-
expect(listManager.getTotalItems()).toBe(100);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("should throw error with invalid container", () => {
|
|
169
|
-
const invalidConfig = { ...config, container: null as any };
|
|
170
|
-
expect(() => new ListManagerImpl(invalidConfig)).toThrow();
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("should initialize features when initialize() is called", () => {
|
|
174
|
-
listManager = new ListManagerImpl(config);
|
|
175
|
-
listManager.initialize();
|
|
176
|
-
|
|
177
|
-
expect(mockViewportFeature.initialize).toHaveBeenCalled();
|
|
178
|
-
expect(mockCollectionFeature.initialize).toHaveBeenCalled();
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("should not initialize twice", () => {
|
|
182
|
-
listManager = new ListManagerImpl(config);
|
|
183
|
-
listManager.initialize();
|
|
184
|
-
listManager.initialize();
|
|
185
|
-
|
|
186
|
-
expect(mockViewportFeature.initialize).toHaveBeenCalledTimes(1);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("should set template if provided", () => {
|
|
190
|
-
listManager = new ListManagerImpl(config);
|
|
191
|
-
listManager.initialize();
|
|
192
|
-
|
|
193
|
-
expect(mockCollectionFeature.setTemplate).toHaveBeenCalledWith(
|
|
194
|
-
config.template!.template
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("should set items if provided", () => {
|
|
199
|
-
listManager = new ListManagerImpl(config);
|
|
200
|
-
listManager.initialize();
|
|
201
|
-
|
|
202
|
-
expect(mockCollectionFeature.setItems).toHaveBeenCalledWith(config.items);
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
describe("Virtual Scrolling API", () => {
|
|
207
|
-
beforeEach(() => {
|
|
208
|
-
listManager = new ListManagerImpl(config);
|
|
209
|
-
listManager.initialize();
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("should scroll to index", () => {
|
|
213
|
-
listManager.scrollToIndex(50, "center");
|
|
214
|
-
expect(mockViewportFeature.scrollToIndex).toHaveBeenCalledWith(
|
|
215
|
-
50,
|
|
216
|
-
"center"
|
|
217
|
-
);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it("should not scroll to invalid index", () => {
|
|
221
|
-
const consoleSpy = spyOn(console, "warn").mockImplementation(() => {});
|
|
222
|
-
|
|
223
|
-
listManager.scrollToIndex(-1);
|
|
224
|
-
listManager.scrollToIndex(1000);
|
|
225
|
-
|
|
226
|
-
expect(mockViewportFeature.scrollToIndex).not.toHaveBeenCalled();
|
|
227
|
-
expect(consoleSpy).toHaveBeenCalledTimes(2);
|
|
228
|
-
|
|
229
|
-
consoleSpy.mockRestore();
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it("should scroll to page", () => {
|
|
233
|
-
listManager.scrollToPage(5, "start");
|
|
234
|
-
|
|
235
|
-
// Page 5 should target index 80 (assuming default page size of 20)
|
|
236
|
-
expect(mockViewportFeature.scrollToIndex).toHaveBeenCalledWith(
|
|
237
|
-
80,
|
|
238
|
-
"start"
|
|
239
|
-
);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("should not scroll to page out of range", () => {
|
|
243
|
-
const consoleSpy = spyOn(console, "warn").mockImplementation(() => {});
|
|
244
|
-
|
|
245
|
-
listManager.scrollToPage(100); // Way beyond item count
|
|
246
|
-
|
|
247
|
-
expect(mockViewportFeature.scrollToIndex).not.toHaveBeenCalled();
|
|
248
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
249
|
-
|
|
250
|
-
consoleSpy.mockRestore();
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it("should get scroll position", () => {
|
|
254
|
-
mockViewportFeature.virtualScrollPosition = 1000;
|
|
255
|
-
expect(listManager.getScrollPosition()).toBe(1000);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
describe("Viewport Management", () => {
|
|
260
|
-
beforeEach(() => {
|
|
261
|
-
listManager = new ListManagerImpl(config);
|
|
262
|
-
listManager.initialize();
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it("should get visible range", () => {
|
|
266
|
-
const range = listManager.getVisibleRange();
|
|
267
|
-
expect(range).toEqual({ start: 0, end: 10 });
|
|
268
|
-
expect(mockViewportFeature.calculateVisibleRange).toHaveBeenCalled();
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("should get viewport info", () => {
|
|
272
|
-
const info = listManager.getViewportInfo();
|
|
273
|
-
expect(info).toEqual({
|
|
274
|
-
containerSize: 600,
|
|
275
|
-
totalVirtualSize: 50000,
|
|
276
|
-
visibleRange: { start: 0, end: 10 },
|
|
277
|
-
virtualScrollPosition: 0,
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it("should update viewport", () => {
|
|
282
|
-
listManager.updateViewport();
|
|
283
|
-
|
|
284
|
-
expect(mockViewportFeature.updateContainerPosition).toHaveBeenCalled();
|
|
285
|
-
expect(mockViewportFeature.updateScrollbar).toHaveBeenCalled();
|
|
286
|
-
expect(mockCollectionFeature.handleVisibleRangeChange).toHaveBeenCalled();
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it("should return default values when not initialized", () => {
|
|
290
|
-
const uninitializedManager = new ListManagerImpl(config);
|
|
291
|
-
|
|
292
|
-
expect(uninitializedManager.getVisibleRange()).toEqual({
|
|
293
|
-
start: 0,
|
|
294
|
-
end: 0,
|
|
295
|
-
});
|
|
296
|
-
expect(uninitializedManager.getScrollPosition()).toBe(0);
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
describe("Collection Integration", () => {
|
|
301
|
-
beforeEach(() => {
|
|
302
|
-
listManager = new ListManagerImpl(config);
|
|
303
|
-
listManager.initialize();
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it("should set items", () => {
|
|
307
|
-
const newItems = [{ id: 1, name: "New Item" }];
|
|
308
|
-
listManager.setItems(newItems);
|
|
309
|
-
|
|
310
|
-
expect(mockViewportFeature.setTotalItems).toHaveBeenCalledWith(1);
|
|
311
|
-
expect(mockCollectionFeature.setItems).toHaveBeenCalledWith(newItems);
|
|
312
|
-
expect(listManager.getTotalItems()).toBe(1);
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it("should set total items", () => {
|
|
316
|
-
listManager.setTotalItems(500);
|
|
317
|
-
|
|
318
|
-
expect(mockViewportFeature.setTotalItems).toHaveBeenCalledWith(500);
|
|
319
|
-
expect(listManager.getTotalItems()).toBe(500);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
it("should get items in visible range", () => {
|
|
323
|
-
const mockItems: any[] = [
|
|
324
|
-
{ item: { id: 1, name: "Item 1" }, index: 0 },
|
|
325
|
-
{ item: { id: 2, name: "Item 2" }, index: 1 },
|
|
326
|
-
];
|
|
327
|
-
mockCollectionFeature.getItemsInRange.mockReturnValue(mockItems);
|
|
328
|
-
|
|
329
|
-
const items = listManager.getItems();
|
|
330
|
-
expect(items).toEqual([
|
|
331
|
-
{ id: 1, name: "Item 1" },
|
|
332
|
-
{ id: 2, name: "Item 2" },
|
|
333
|
-
]);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it("should set pagination strategy", () => {
|
|
337
|
-
listManager.setPaginationStrategy("cursor");
|
|
338
|
-
|
|
339
|
-
expect(
|
|
340
|
-
mockCollectionFeature.adaptPaginationStrategy
|
|
341
|
-
).toHaveBeenCalledWith("cursor");
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it("should get pagination strategy", () => {
|
|
345
|
-
mockCollectionFeature.paginationStrategy = "offset";
|
|
346
|
-
expect(listManager.getPaginationStrategy()).toBe("offset");
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
describe("Configuration Management", () => {
|
|
351
|
-
beforeEach(() => {
|
|
352
|
-
listManager = new ListManagerImpl(config);
|
|
353
|
-
listManager.initialize();
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it("should update configuration", () => {
|
|
357
|
-
const configUpdate: ListManagerConfigUpdate = {
|
|
358
|
-
virtual: {
|
|
359
|
-
enabled: true,
|
|
360
|
-
itemSize: "auto",
|
|
361
|
-
estimatedItemSize: 100,
|
|
362
|
-
overscan: 5,
|
|
363
|
-
},
|
|
364
|
-
debug: false,
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
listManager.updateConfig(configUpdate);
|
|
368
|
-
|
|
369
|
-
const updatedConfig = listManager.getConfig();
|
|
370
|
-
expect(updatedConfig.virtual.estimatedItemSize).toBe(100);
|
|
371
|
-
expect(updatedConfig.debug).toBe(false);
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
it("should return copy of config", () => {
|
|
375
|
-
const returnedConfig = listManager.getConfig();
|
|
376
|
-
returnedConfig.debug = false;
|
|
377
|
-
|
|
378
|
-
expect(listManager.getConfig().debug).toBe(true); // Original should be unchanged
|
|
379
|
-
});
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
describe("Event System", () => {
|
|
383
|
-
beforeEach(() => {
|
|
384
|
-
listManager = new ListManagerImpl(config);
|
|
385
|
-
listManager.initialize();
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it("should subscribe to events", () => {
|
|
389
|
-
const observer = mock();
|
|
390
|
-
const unsubscribe = listManager.subscribe(observer);
|
|
391
|
-
|
|
392
|
-
listManager.emit(ListManagerEvents.INITIALIZED, { config });
|
|
393
|
-
|
|
394
|
-
expect(observer).toHaveBeenCalledWith(ListManagerEvents.INITIALIZED, {
|
|
395
|
-
config,
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
unsubscribe();
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it("should unsubscribe from events", () => {
|
|
402
|
-
const observer = mock();
|
|
403
|
-
const unsubscribe = listManager.subscribe(observer);
|
|
404
|
-
|
|
405
|
-
unsubscribe();
|
|
406
|
-
listManager.emit(ListManagerEvents.INITIALIZED, { config });
|
|
407
|
-
|
|
408
|
-
expect(observer).not.toHaveBeenCalled();
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
it("should handle observer errors gracefully", () => {
|
|
412
|
-
const badObserver = mock(() => {
|
|
413
|
-
throw new Error("Observer error");
|
|
414
|
-
});
|
|
415
|
-
const consoleSpy = spyOn(console, "error").mockImplementation(() => {});
|
|
416
|
-
|
|
417
|
-
listManager.subscribe(badObserver);
|
|
418
|
-
listManager.emit(ListManagerEvents.INITIALIZED, { config });
|
|
419
|
-
|
|
420
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
421
|
-
"[List Manager] Observer error:",
|
|
422
|
-
expect.any(Error)
|
|
423
|
-
);
|
|
424
|
-
consoleSpy.mockRestore();
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
it("should emit events to multiple observers", () => {
|
|
428
|
-
const observer1 = mock();
|
|
429
|
-
const observer2 = mock();
|
|
430
|
-
|
|
431
|
-
listManager.subscribe(observer1);
|
|
432
|
-
listManager.subscribe(observer2);
|
|
433
|
-
|
|
434
|
-
listManager.emit(ListManagerEvents.INITIALIZED, { config });
|
|
435
|
-
|
|
436
|
-
expect(observer1).toHaveBeenCalled();
|
|
437
|
-
expect(observer2).toHaveBeenCalled();
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
describe("Feature Coordination", () => {
|
|
442
|
-
beforeEach(() => {
|
|
443
|
-
listManager = new ListManagerImpl(config);
|
|
444
|
-
listManager.initialize();
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
it("should coordinate scroll events between features", () => {
|
|
448
|
-
// Simulate scroll position change
|
|
449
|
-
listManager.emit(ListManagerEvents.SCROLL_POSITION_CHANGED, {
|
|
450
|
-
position: 1000,
|
|
451
|
-
direction: "forward",
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
expect(
|
|
455
|
-
mockCollectionFeature.handleScrollPositionChange
|
|
456
|
-
).toHaveBeenCalledWith(1000, "forward");
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
it("should coordinate range change events", () => {
|
|
460
|
-
const newRange = { start: 10, end: 20 };
|
|
461
|
-
|
|
462
|
-
listManager.emit(ListManagerEvents.VIRTUAL_RANGE_CHANGED, newRange);
|
|
463
|
-
|
|
464
|
-
expect(
|
|
465
|
-
mockCollectionFeature.handleVisibleRangeChange
|
|
466
|
-
).toHaveBeenCalledWith(newRange);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
it("should coordinate viewport change events", () => {
|
|
470
|
-
const viewportData = {
|
|
471
|
-
containerSize: 600,
|
|
472
|
-
totalVirtualSize: 50000,
|
|
473
|
-
visibleRange: { start: 5, end: 15 },
|
|
474
|
-
virtualScrollPosition: 500,
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
listManager.emit(ListManagerEvents.VIEWPORT_CHANGED, viewportData);
|
|
478
|
-
|
|
479
|
-
expect(
|
|
480
|
-
mockCollectionFeature.handleVisibleRangeChange
|
|
481
|
-
).toHaveBeenCalledWith(viewportData.visibleRange);
|
|
482
|
-
});
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
describe("Lifecycle Management", () => {
|
|
486
|
-
beforeEach(() => {
|
|
487
|
-
listManager = new ListManagerImpl(config);
|
|
488
|
-
listManager.initialize();
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
it("should destroy properly", () => {
|
|
492
|
-
listManager.destroy();
|
|
493
|
-
|
|
494
|
-
expect(mockViewportFeature.destroy).toHaveBeenCalled();
|
|
495
|
-
expect(mockCollectionFeature.destroy).toHaveBeenCalled();
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it("should not destroy twice", () => {
|
|
499
|
-
listManager.destroy();
|
|
500
|
-
listManager.destroy();
|
|
501
|
-
|
|
502
|
-
expect(mockViewportFeature.destroy).toHaveBeenCalledTimes(1);
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
it("should emit destroy event", () => {
|
|
506
|
-
const observer = mock();
|
|
507
|
-
listManager.subscribe(observer);
|
|
508
|
-
|
|
509
|
-
listManager.destroy();
|
|
510
|
-
|
|
511
|
-
expect(observer).toHaveBeenCalledWith(ListManagerEvents.DESTROYED, {
|
|
512
|
-
reason: "manual-destroy",
|
|
513
|
-
});
|
|
514
|
-
});
|
|
515
|
-
});
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
describe("createListManager factory", () => {
|
|
519
|
-
it("should create and auto-initialize ListManager", () => {
|
|
520
|
-
const consoleSpy = spyOn(console, "log").mockImplementation(() => {});
|
|
521
|
-
|
|
522
|
-
const factoryConfig: ListManagerConfig = {
|
|
523
|
-
container: mockContainer,
|
|
524
|
-
virtual: {
|
|
525
|
-
enabled: true,
|
|
526
|
-
itemSize: "auto",
|
|
527
|
-
estimatedItemSize: 50,
|
|
528
|
-
overscan: 5,
|
|
529
|
-
},
|
|
530
|
-
orientation: {
|
|
531
|
-
orientation: "vertical",
|
|
532
|
-
reverse: false,
|
|
533
|
-
crossAxisAlignment: "stretch",
|
|
534
|
-
},
|
|
535
|
-
initialLoad: {
|
|
536
|
-
strategy: "placeholders",
|
|
537
|
-
viewportMultiplier: 1.5,
|
|
538
|
-
minItems: 10,
|
|
539
|
-
maxItems: 100,
|
|
540
|
-
},
|
|
541
|
-
errorHandling: {
|
|
542
|
-
timeout: 5000,
|
|
543
|
-
showErrorItems: true,
|
|
544
|
-
retryAttempts: 3,
|
|
545
|
-
preserveScrollOnError: true,
|
|
546
|
-
},
|
|
547
|
-
positioning: {
|
|
548
|
-
precisePositioning: true,
|
|
549
|
-
allowPartialItems: true,
|
|
550
|
-
snapToItems: false,
|
|
551
|
-
},
|
|
552
|
-
boundaries: {
|
|
553
|
-
preventOverscroll: true,
|
|
554
|
-
maintainEdgeRanges: true,
|
|
555
|
-
boundaryResistance: 0.15,
|
|
556
|
-
},
|
|
557
|
-
recycling: {
|
|
558
|
-
enabled: false,
|
|
559
|
-
maxPoolSize: 50,
|
|
560
|
-
minPoolSize: 10,
|
|
561
|
-
},
|
|
562
|
-
performance: {
|
|
563
|
-
frameScheduling: true,
|
|
564
|
-
memoryCleanup: true,
|
|
565
|
-
},
|
|
566
|
-
intersection: {
|
|
567
|
-
pagination: {
|
|
568
|
-
enabled: false,
|
|
569
|
-
rootMargin: "100px",
|
|
570
|
-
threshold: 0.1,
|
|
571
|
-
},
|
|
572
|
-
loading: {
|
|
573
|
-
enabled: false,
|
|
574
|
-
},
|
|
575
|
-
},
|
|
576
|
-
debug: true,
|
|
577
|
-
prefix: "test-list",
|
|
578
|
-
componentName: "TestList",
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
const listManager = createListManager(factoryConfig);
|
|
582
|
-
|
|
583
|
-
expect(listManager).toBeDefined();
|
|
584
|
-
expect(listManager.getTotalItems()).toBe(0);
|
|
585
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
586
|
-
"[List Manager] Created with config:",
|
|
587
|
-
expect.any(Object)
|
|
588
|
-
);
|
|
589
|
-
|
|
590
|
-
consoleSpy.mockRestore();
|
|
591
|
-
listManager.destroy();
|
|
592
|
-
});
|
|
593
|
-
});
|