mtrl-addons 0.1.1 → 0.2.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/build.js +139 -108
- package/package.json +13 -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 +322 -0
- package/src/components/vlist/features/index.ts +10 -0
- package/src/components/vlist/features/selection.ts +444 -0
- package/src/components/vlist/features/viewport.ts +65 -0
- package/src/components/vlist/index.ts +16 -0
- package/src/components/{list → vlist}/types.ts +104 -26
- 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 +73 -35
- package/src/core/layout/types.ts +5 -2
- package/src/core/viewport/constants.ts +140 -0
- package/src/core/viewport/features/base.ts +73 -0
- package/src/core/viewport/features/collection.ts +882 -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 +27 -30
- package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
- package/src/core/viewport/features/momentum.ts +260 -0
- package/src/core/viewport/features/placeholders.ts +335 -0
- package/src/core/viewport/features/rendering.ts +568 -0
- package/src/core/viewport/features/scrollbar.ts +434 -0
- package/src/core/viewport/features/scrolling.ts +618 -0
- package/src/core/viewport/features/utils.ts +88 -0
- package/src/core/viewport/features/virtual.ts +384 -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 +246 -0
- package/src/index.ts +0 -7
- package/src/styles/components/_vlist.scss +331 -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 -14
- /package/src/components/{list → vlist}/features.ts +0 -0
- /package/src/core/{compose → viewport}/features/performance.ts +0 -0
|
@@ -1,569 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
calculateRangeIndex,
|
|
4
|
-
calculateRangeBounds,
|
|
5
|
-
calculateRequiredRanges,
|
|
6
|
-
calculatePrefetchRanges,
|
|
7
|
-
calculateRangeResult,
|
|
8
|
-
rangeToPaginationParams,
|
|
9
|
-
rangesToBatchParams,
|
|
10
|
-
calculateOptimalRangeSize,
|
|
11
|
-
isRangeInViewport,
|
|
12
|
-
calculateRangePriority,
|
|
13
|
-
sortRangesByPriority,
|
|
14
|
-
mergeAdjacentRanges,
|
|
15
|
-
calculateRangeCleanupCandidates,
|
|
16
|
-
calculateRangeLoadingOrder,
|
|
17
|
-
getRangeDebugInfo,
|
|
18
|
-
} from "../../../../src/core/list-manager/utils/range-calculator";
|
|
19
|
-
import type { ItemRange } from "../../../../src/core/list-manager/types";
|
|
20
|
-
|
|
21
|
-
describe("Range Calculator Utility", () => {
|
|
22
|
-
describe("calculateRangeIndex", () => {
|
|
23
|
-
it("should calculate range index from item index", () => {
|
|
24
|
-
expect(calculateRangeIndex(0, 10)).toBe(0);
|
|
25
|
-
expect(calculateRangeIndex(5, 10)).toBe(0);
|
|
26
|
-
expect(calculateRangeIndex(10, 10)).toBe(1);
|
|
27
|
-
expect(calculateRangeIndex(15, 10)).toBe(1);
|
|
28
|
-
expect(calculateRangeIndex(25, 10)).toBe(2);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("should handle edge cases", () => {
|
|
32
|
-
expect(calculateRangeIndex(0, 1)).toBe(0);
|
|
33
|
-
expect(calculateRangeIndex(100, 50)).toBe(2);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe("calculateRangeBounds", () => {
|
|
38
|
-
it("should calculate range bounds", () => {
|
|
39
|
-
const bounds = calculateRangeBounds(2, 10, 100);
|
|
40
|
-
|
|
41
|
-
expect(bounds.start).toBe(20);
|
|
42
|
-
expect(bounds.end).toBe(29);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("should handle range at end of list", () => {
|
|
46
|
-
const bounds = calculateRangeBounds(9, 10, 95);
|
|
47
|
-
|
|
48
|
-
expect(bounds.start).toBe(90);
|
|
49
|
-
expect(bounds.end).toBe(94); // Clamped to total items
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("should handle single item ranges", () => {
|
|
53
|
-
const bounds = calculateRangeBounds(5, 1, 10);
|
|
54
|
-
|
|
55
|
-
expect(bounds.start).toBe(5);
|
|
56
|
-
expect(bounds.end).toBe(5);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe("calculateRequiredRanges", () => {
|
|
61
|
-
it("should calculate required ranges for visible area", () => {
|
|
62
|
-
const visibleRange: ItemRange = { start: 15, end: 35 };
|
|
63
|
-
const loadedRanges = new Set([0, 1]); // Ranges 0-9, 10-19 loaded
|
|
64
|
-
|
|
65
|
-
const required = calculateRequiredRanges({
|
|
66
|
-
visibleRange,
|
|
67
|
-
rangeSize: 10,
|
|
68
|
-
totalItems: 100,
|
|
69
|
-
loadedRanges,
|
|
70
|
-
prefetchDistance: 1,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
expect(required.length).toBeGreaterThan(0);
|
|
74
|
-
expect(required.some((r) => r.start >= 15 && r.end <= 35)).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("should include prefetch ranges", () => {
|
|
78
|
-
const visibleRange: ItemRange = { start: 20, end: 30 };
|
|
79
|
-
const loadedRanges = new Set<number>();
|
|
80
|
-
|
|
81
|
-
const required = calculateRequiredRanges({
|
|
82
|
-
visibleRange,
|
|
83
|
-
rangeSize: 10,
|
|
84
|
-
totalItems: 100,
|
|
85
|
-
loadedRanges,
|
|
86
|
-
prefetchDistance: 1,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Should include ranges before and after visible range
|
|
90
|
-
const allRanges = required.flatMap((r) =>
|
|
91
|
-
Array.from({ length: r.end - r.start + 1 }, (_, i) => r.start + i)
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
expect(allRanges.some((i) => i < 20)).toBe(true); // Before visible
|
|
95
|
-
expect(allRanges.some((i) => i > 30)).toBe(true); // After visible
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("should exclude already loaded ranges", () => {
|
|
99
|
-
const visibleRange: ItemRange = { start: 20, end: 30 };
|
|
100
|
-
const loadedRanges = new Set([2]); // Range 20-29 already loaded
|
|
101
|
-
|
|
102
|
-
const required = calculateRequiredRanges({
|
|
103
|
-
visibleRange,
|
|
104
|
-
rangeSize: 10,
|
|
105
|
-
totalItems: 100,
|
|
106
|
-
loadedRanges,
|
|
107
|
-
prefetchDistance: 0,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
// Should not include range 2 (already loaded)
|
|
111
|
-
expect(required.every((r) => !(r.start === 20 && r.end === 29))).toBe(
|
|
112
|
-
true
|
|
113
|
-
);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe("calculatePrefetchRanges", () => {
|
|
118
|
-
it("should calculate prefetch ranges in both directions", () => {
|
|
119
|
-
const visibleRange: ItemRange = { start: 40, end: 60 };
|
|
120
|
-
|
|
121
|
-
const prefetch = calculatePrefetchRanges({
|
|
122
|
-
visibleRange,
|
|
123
|
-
direction: "forward",
|
|
124
|
-
distance: 2,
|
|
125
|
-
rangeSize: 20,
|
|
126
|
-
totalItems: 200,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
expect(prefetch.length).toBeGreaterThan(0);
|
|
130
|
-
expect(prefetch.some((r) => r.start > 60)).toBe(true); // Forward ranges
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("should handle backward prefetch", () => {
|
|
134
|
-
const visibleRange: ItemRange = { start: 40, end: 60 };
|
|
135
|
-
|
|
136
|
-
const prefetch = calculatePrefetchRanges({
|
|
137
|
-
visibleRange,
|
|
138
|
-
direction: "backward",
|
|
139
|
-
distance: 1,
|
|
140
|
-
rangeSize: 20,
|
|
141
|
-
totalItems: 200,
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
expect(prefetch.some((r) => r.end < 40)).toBe(true); // Backward ranges
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("should respect total items boundary", () => {
|
|
148
|
-
const visibleRange: ItemRange = { start: 80, end: 99 };
|
|
149
|
-
|
|
150
|
-
const prefetch = calculatePrefetchRanges({
|
|
151
|
-
visibleRange,
|
|
152
|
-
direction: "forward",
|
|
153
|
-
distance: 5,
|
|
154
|
-
rangeSize: 20,
|
|
155
|
-
totalItems: 100,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Should not exceed total items
|
|
159
|
-
expect(prefetch.every((r) => r.end < 100)).toBe(true);
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
describe("calculateRangeResult", () => {
|
|
164
|
-
it("should calculate comprehensive range result", () => {
|
|
165
|
-
const visibleRange: ItemRange = { start: 25, end: 45 };
|
|
166
|
-
const loadedRanges = new Set([1, 2]); // Some ranges loaded
|
|
167
|
-
|
|
168
|
-
const result = calculateRangeResult({
|
|
169
|
-
visibleRange,
|
|
170
|
-
rangeSize: 20,
|
|
171
|
-
totalItems: 200,
|
|
172
|
-
loadedRanges,
|
|
173
|
-
prefetchDistance: 1,
|
|
174
|
-
direction: "forward",
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
expect(result).toHaveProperty("required");
|
|
178
|
-
expect(result).toHaveProperty("prefetch");
|
|
179
|
-
expect(result).toHaveProperty("missing");
|
|
180
|
-
expect(result).toHaveProperty("priority");
|
|
181
|
-
expect(result).toHaveProperty("loadingOrder");
|
|
182
|
-
|
|
183
|
-
expect(result.required.length).toBeGreaterThan(0);
|
|
184
|
-
expect(result.priority.length).toBeGreaterThan(0);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe("rangeToPaginationParams", () => {
|
|
189
|
-
it("should convert range to page-based pagination", () => {
|
|
190
|
-
const range: ItemRange = { start: 20, end: 39 };
|
|
191
|
-
|
|
192
|
-
const params = rangeToPaginationParams(range, "page", 10);
|
|
193
|
-
|
|
194
|
-
expect(params.strategy).toBe("page");
|
|
195
|
-
expect(params.page).toBe(3); // Page 3 for items 20-29
|
|
196
|
-
expect(params.limit).toBe(20); // Range size
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("should convert range to offset-based pagination", () => {
|
|
200
|
-
const range: ItemRange = { start: 50, end: 74 };
|
|
201
|
-
|
|
202
|
-
const params = rangeToPaginationParams(range, "offset", 25);
|
|
203
|
-
|
|
204
|
-
expect(params.strategy).toBe("offset");
|
|
205
|
-
expect(params.offset).toBe(50);
|
|
206
|
-
expect(params.limit).toBe(25);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it("should convert range to cursor-based pagination", () => {
|
|
210
|
-
const range: ItemRange = { start: 100, end: 119 };
|
|
211
|
-
|
|
212
|
-
const params = rangeToPaginationParams(range, "cursor", 20);
|
|
213
|
-
|
|
214
|
-
expect(params.strategy).toBe("cursor");
|
|
215
|
-
expect(params.after).toBe("item-99"); // Cursor after last item before range
|
|
216
|
-
expect(params.limit).toBe(20);
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
describe("rangesToBatchParams", () => {
|
|
221
|
-
it("should convert multiple ranges to batch parameters", () => {
|
|
222
|
-
const ranges: ItemRange[] = [
|
|
223
|
-
{ start: 0, end: 19 },
|
|
224
|
-
{ start: 40, end: 59 },
|
|
225
|
-
{ start: 80, end: 99 },
|
|
226
|
-
];
|
|
227
|
-
|
|
228
|
-
const batch = rangesToBatchParams(ranges, "offset");
|
|
229
|
-
|
|
230
|
-
expect(batch.strategy).toBe("batch");
|
|
231
|
-
expect(batch.requests).toHaveLength(3);
|
|
232
|
-
expect(batch.requests[0].offset).toBe(0);
|
|
233
|
-
expect(batch.requests[1].offset).toBe(40);
|
|
234
|
-
expect(batch.requests[2].offset).toBe(80);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it("should handle empty ranges", () => {
|
|
238
|
-
const batch = rangesToBatchParams([], "page");
|
|
239
|
-
|
|
240
|
-
expect(batch.requests).toHaveLength(0);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
describe("calculateOptimalRangeSize", () => {
|
|
245
|
-
it("should calculate optimal range size based on parameters", () => {
|
|
246
|
-
const size = calculateOptimalRangeSize({
|
|
247
|
-
averageItemSize: 50,
|
|
248
|
-
containerSize: 500,
|
|
249
|
-
networkLatency: 100,
|
|
250
|
-
bandwidth: 1000, // kb/s
|
|
251
|
-
minRangeSize: 10,
|
|
252
|
-
maxRangeSize: 100,
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
expect(size).toBeGreaterThanOrEqual(10);
|
|
256
|
-
expect(size).toBeLessThanOrEqual(100);
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it("should respect minimum range size", () => {
|
|
260
|
-
const size = calculateOptimalRangeSize({
|
|
261
|
-
averageItemSize: 10,
|
|
262
|
-
containerSize: 100,
|
|
263
|
-
networkLatency: 10,
|
|
264
|
-
bandwidth: 10000,
|
|
265
|
-
minRangeSize: 50,
|
|
266
|
-
maxRangeSize: 200,
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
expect(size).toBeGreaterThanOrEqual(50);
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
it("should respect maximum range size", () => {
|
|
273
|
-
const size = calculateOptimalRangeSize({
|
|
274
|
-
averageItemSize: 200,
|
|
275
|
-
containerSize: 2000,
|
|
276
|
-
networkLatency: 500,
|
|
277
|
-
bandwidth: 100,
|
|
278
|
-
minRangeSize: 5,
|
|
279
|
-
maxRangeSize: 30,
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
expect(size).toBeLessThanOrEqual(30);
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe("isRangeInViewport", () => {
|
|
287
|
-
it("should detect range in viewport", () => {
|
|
288
|
-
const range: ItemRange = { start: 20, end: 40 };
|
|
289
|
-
const viewport: ItemRange = { start: 10, end: 50 };
|
|
290
|
-
|
|
291
|
-
expect(isRangeInViewport(range, viewport)).toBe(true);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
it("should detect range partially in viewport", () => {
|
|
295
|
-
const range: ItemRange = { start: 5, end: 25 };
|
|
296
|
-
const viewport: ItemRange = { start: 20, end: 60 };
|
|
297
|
-
|
|
298
|
-
expect(isRangeInViewport(range, viewport)).toBe(true);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it("should detect range outside viewport", () => {
|
|
302
|
-
const range: ItemRange = { start: 100, end: 120 };
|
|
303
|
-
const viewport: ItemRange = { start: 10, end: 50 };
|
|
304
|
-
|
|
305
|
-
expect(isRangeInViewport(range, viewport)).toBe(false);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it("should handle edge cases", () => {
|
|
309
|
-
const range: ItemRange = { start: 50, end: 70 };
|
|
310
|
-
const viewport: ItemRange = { start: 70, end: 90 };
|
|
311
|
-
|
|
312
|
-
expect(isRangeInViewport(range, viewport)).toBe(false); // Touching but not overlapping
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
describe("calculateRangePriority", () => {
|
|
317
|
-
it("should assign high priority to visible ranges", () => {
|
|
318
|
-
const range: ItemRange = { start: 20, end: 40 };
|
|
319
|
-
const viewport: ItemRange = { start: 15, end: 45 };
|
|
320
|
-
|
|
321
|
-
const priority = calculateRangePriority({
|
|
322
|
-
range,
|
|
323
|
-
viewport,
|
|
324
|
-
direction: "forward",
|
|
325
|
-
loadingStrategy: "normal",
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
expect(priority).toBeGreaterThan(0.8); // High priority
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it("should assign medium priority to prefetch ranges", () => {
|
|
332
|
-
const range: ItemRange = { start: 50, end: 70 };
|
|
333
|
-
const viewport: ItemRange = { start: 10, end: 30 };
|
|
334
|
-
|
|
335
|
-
const priority = calculateRangePriority({
|
|
336
|
-
range,
|
|
337
|
-
viewport,
|
|
338
|
-
direction: "forward",
|
|
339
|
-
loadingStrategy: "normal",
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
expect(priority).toBeGreaterThan(0.3);
|
|
343
|
-
expect(priority).toBeLessThan(0.8);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
it("should assign lower priority to ranges in opposite direction", () => {
|
|
347
|
-
const range: ItemRange = { start: 0, end: 20 };
|
|
348
|
-
const viewport: ItemRange = { start: 50, end: 70 };
|
|
349
|
-
|
|
350
|
-
const forwardPriority = calculateRangePriority({
|
|
351
|
-
range,
|
|
352
|
-
viewport,
|
|
353
|
-
direction: "forward",
|
|
354
|
-
loadingStrategy: "normal",
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
const backwardPriority = calculateRangePriority({
|
|
358
|
-
range,
|
|
359
|
-
viewport,
|
|
360
|
-
direction: "backward",
|
|
361
|
-
loadingStrategy: "normal",
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
expect(backwardPriority).toBeGreaterThan(forwardPriority);
|
|
365
|
-
});
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
describe("sortRangesByPriority", () => {
|
|
369
|
-
it("should sort ranges by priority", () => {
|
|
370
|
-
const ranges: ItemRange[] = [
|
|
371
|
-
{ start: 100, end: 120 }, // Far from viewport
|
|
372
|
-
{ start: 20, end: 40 }, // In viewport
|
|
373
|
-
{ start: 50, end: 70 }, // Near viewport
|
|
374
|
-
];
|
|
375
|
-
const viewport: ItemRange = { start: 15, end: 35 };
|
|
376
|
-
|
|
377
|
-
const sorted = sortRangesByPriority({
|
|
378
|
-
ranges,
|
|
379
|
-
viewport,
|
|
380
|
-
direction: "forward",
|
|
381
|
-
loadingStrategy: "normal",
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
// First range should be the one in viewport
|
|
385
|
-
expect(sorted[0].start).toBe(20);
|
|
386
|
-
expect(sorted[0].end).toBe(40);
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
describe("mergeAdjacentRanges", () => {
|
|
391
|
-
it("should merge adjacent ranges", () => {
|
|
392
|
-
const ranges: ItemRange[] = [
|
|
393
|
-
{ start: 0, end: 19 },
|
|
394
|
-
{ start: 20, end: 39 },
|
|
395
|
-
{ start: 40, end: 59 },
|
|
396
|
-
{ start: 80, end: 99 }, // Gap, should not merge
|
|
397
|
-
];
|
|
398
|
-
|
|
399
|
-
const merged = mergeAdjacentRanges(ranges);
|
|
400
|
-
|
|
401
|
-
expect(merged).toHaveLength(2);
|
|
402
|
-
expect(merged[0]).toEqual({ start: 0, end: 59 });
|
|
403
|
-
expect(merged[1]).toEqual({ start: 80, end: 99 });
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
it("should handle overlapping ranges", () => {
|
|
407
|
-
const ranges: ItemRange[] = [
|
|
408
|
-
{ start: 0, end: 25 },
|
|
409
|
-
{ start: 20, end: 45 },
|
|
410
|
-
{ start: 40, end: 60 },
|
|
411
|
-
];
|
|
412
|
-
|
|
413
|
-
const merged = mergeAdjacentRanges(ranges);
|
|
414
|
-
|
|
415
|
-
expect(merged).toHaveLength(1);
|
|
416
|
-
expect(merged[0]).toEqual({ start: 0, end: 60 });
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
it("should handle non-adjacent ranges", () => {
|
|
420
|
-
const ranges: ItemRange[] = [
|
|
421
|
-
{ start: 0, end: 10 },
|
|
422
|
-
{ start: 20, end: 30 },
|
|
423
|
-
{ start: 40, end: 50 },
|
|
424
|
-
];
|
|
425
|
-
|
|
426
|
-
const merged = mergeAdjacentRanges(ranges);
|
|
427
|
-
|
|
428
|
-
expect(merged).toHaveLength(3); // No merging
|
|
429
|
-
expect(merged).toEqual(ranges);
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
describe("calculateRangeCleanupCandidates", () => {
|
|
434
|
-
it("should identify ranges for cleanup", () => {
|
|
435
|
-
const loadedRanges: ItemRange[] = [
|
|
436
|
-
{ start: 0, end: 19 }, // Far from viewport
|
|
437
|
-
{ start: 20, end: 39 }, // In viewport
|
|
438
|
-
{ start: 40, end: 59 }, // Near viewport
|
|
439
|
-
{ start: 100, end: 119 }, // Very far from viewport
|
|
440
|
-
];
|
|
441
|
-
const viewport: ItemRange = { start: 25, end: 45 };
|
|
442
|
-
|
|
443
|
-
const candidates = calculateRangeCleanupCandidates({
|
|
444
|
-
loadedRanges,
|
|
445
|
-
viewport,
|
|
446
|
-
maxDistance: 50,
|
|
447
|
-
keepMinimum: 2,
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
expect(candidates.length).toBeGreaterThan(0);
|
|
451
|
-
expect(candidates.some((r) => r.start === 100)).toBe(true); // Far range should be candidate
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
it("should respect minimum keep count", () => {
|
|
455
|
-
const loadedRanges: ItemRange[] = [
|
|
456
|
-
{ start: 0, end: 19 },
|
|
457
|
-
{ start: 200, end: 219 },
|
|
458
|
-
];
|
|
459
|
-
const viewport: ItemRange = { start: 100, end: 120 };
|
|
460
|
-
|
|
461
|
-
const candidates = calculateRangeCleanupCandidates({
|
|
462
|
-
loadedRanges,
|
|
463
|
-
viewport,
|
|
464
|
-
maxDistance: 10,
|
|
465
|
-
keepMinimum: 2,
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
expect(candidates.length).toBe(0); // Should keep all due to minimum
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
describe("calculateRangeLoadingOrder", () => {
|
|
473
|
-
it("should determine optimal loading order", () => {
|
|
474
|
-
const ranges: ItemRange[] = [
|
|
475
|
-
{ start: 0, end: 19 }, // Far
|
|
476
|
-
{ start: 20, end: 39 }, // Visible
|
|
477
|
-
{ start: 40, end: 59 }, // Adjacent
|
|
478
|
-
{ start: 100, end: 119 }, // Very far
|
|
479
|
-
];
|
|
480
|
-
const viewport: ItemRange = { start: 25, end: 35 };
|
|
481
|
-
|
|
482
|
-
const order = calculateRangeLoadingOrder({
|
|
483
|
-
ranges,
|
|
484
|
-
viewport,
|
|
485
|
-
direction: "forward",
|
|
486
|
-
maxConcurrent: 2,
|
|
487
|
-
loadingStrategy: "normal",
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
expect(order.length).toBeGreaterThan(0);
|
|
491
|
-
expect(order[0].priority).toBeGreaterThan(
|
|
492
|
-
order[order.length - 1].priority
|
|
493
|
-
);
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
it("should group concurrent loads", () => {
|
|
497
|
-
const ranges: ItemRange[] = [
|
|
498
|
-
{ start: 0, end: 19 },
|
|
499
|
-
{ start: 20, end: 39 },
|
|
500
|
-
{ start: 40, end: 59 },
|
|
501
|
-
{ start: 60, end: 79 },
|
|
502
|
-
];
|
|
503
|
-
const viewport: ItemRange = { start: 25, end: 35 };
|
|
504
|
-
|
|
505
|
-
const order = calculateRangeLoadingOrder({
|
|
506
|
-
ranges,
|
|
507
|
-
viewport,
|
|
508
|
-
direction: "forward",
|
|
509
|
-
maxConcurrent: 2,
|
|
510
|
-
loadingStrategy: "aggressive",
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
// Should group some ranges for concurrent loading
|
|
514
|
-
expect(order.some((item) => item.concurrentGroup !== undefined)).toBe(
|
|
515
|
-
true
|
|
516
|
-
);
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
describe("getRangeDebugInfo", () => {
|
|
521
|
-
it("should provide comprehensive debug information", () => {
|
|
522
|
-
const ranges: ItemRange[] = [
|
|
523
|
-
{ start: 0, end: 19 },
|
|
524
|
-
{ start: 20, end: 39 },
|
|
525
|
-
{ start: 60, end: 79 },
|
|
526
|
-
];
|
|
527
|
-
const viewport: ItemRange = { start: 25, end: 45 };
|
|
528
|
-
const loadedRanges = new Set([1]);
|
|
529
|
-
|
|
530
|
-
const debug = getRangeDebugInfo({
|
|
531
|
-
ranges,
|
|
532
|
-
viewport,
|
|
533
|
-
loadedRanges,
|
|
534
|
-
rangeSize: 20,
|
|
535
|
-
totalItems: 200,
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
expect(debug).toHaveProperty("totalRanges");
|
|
539
|
-
expect(debug).toHaveProperty("loadedCount");
|
|
540
|
-
expect(debug).toHaveProperty("visibleRanges");
|
|
541
|
-
expect(debug).toHaveProperty("coverage");
|
|
542
|
-
expect(debug).toHaveProperty("efficiency");
|
|
543
|
-
expect(debug).toHaveProperty("gaps");
|
|
544
|
-
expect(debug).toHaveProperty("suggestions");
|
|
545
|
-
|
|
546
|
-
expect(debug.totalRanges).toBe(3);
|
|
547
|
-
expect(debug.loadedCount).toBe(1);
|
|
548
|
-
expect(debug.visibleRanges).toBeGreaterThan(0);
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
it("should calculate coverage percentage", () => {
|
|
552
|
-
const ranges: ItemRange[] = [
|
|
553
|
-
{ start: 0, end: 49 }, // 50 items
|
|
554
|
-
];
|
|
555
|
-
const viewport: ItemRange = { start: 0, end: 99 }; // 100 items needed
|
|
556
|
-
const loadedRanges = new Set([0]);
|
|
557
|
-
|
|
558
|
-
const debug = getRangeDebugInfo({
|
|
559
|
-
ranges,
|
|
560
|
-
viewport,
|
|
561
|
-
loadedRanges,
|
|
562
|
-
rangeSize: 50,
|
|
563
|
-
totalItems: 200,
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
expect(debug.coverage).toBeCloseTo(0.5, 1); // 50% coverage
|
|
567
|
-
});
|
|
568
|
-
});
|
|
569
|
-
});
|