Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/test/common/iterativePaging.test.ts
4778 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { CancellationToken, CancellationTokenSource } from '../../common/cancellation.js';
8
import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js';
9
import { IterativePagedModel, IIterativePager, IIterativePage } from '../../common/paging.js';
10
11
function createTestPager(pageSize: number, maxPages: number): IIterativePager<number> {
12
let currentPage = 0;
13
14
const createPage = (page: number): IIterativePage<number> => {
15
const start = page * pageSize;
16
const items: number[] = [];
17
for (let i = 0; i < pageSize; i++) {
18
items.push(start + i);
19
}
20
const hasMore = page + 1 < maxPages;
21
return { items, hasMore };
22
};
23
24
return {
25
firstPage: createPage(currentPage++),
26
getNextPage: async (cancellationToken: CancellationToken): Promise<IIterativePage<number>> => {
27
if (currentPage >= maxPages) {
28
return { items: [], hasMore: false };
29
}
30
return createPage(currentPage++);
31
}
32
};
33
}
34
35
suite('IterativePagedModel', () => {
36
37
const store = ensureNoDisposablesAreLeakedInTestSuite();
38
39
test('initial state', () => {
40
const pager = createTestPager(10, 3);
41
const model = store.add(new IterativePagedModel(pager));
42
43
// Initially first page is loaded, so length should be 10 + 1 sentinel
44
assert.strictEqual(model.length, 11);
45
assert.strictEqual(model.isResolved(0), true);
46
assert.strictEqual(model.isResolved(9), true);
47
assert.strictEqual(model.isResolved(10), false); // sentinel
48
});
49
50
test('load first page via sentinel access', async () => {
51
const pager = createTestPager(10, 3);
52
const model = store.add(new IterativePagedModel(pager));
53
54
// Access an item in the first page (already loaded)
55
const item = await model.resolve(0, CancellationToken.None);
56
57
assert.strictEqual(item, 0);
58
assert.strictEqual(model.length, 11); // 10 items + 1 sentinel
59
assert.strictEqual(model.isResolved(0), true);
60
assert.strictEqual(model.isResolved(9), true);
61
assert.strictEqual(model.isResolved(10), false); // sentinel
62
});
63
64
test('load multiple pages', async () => {
65
const pager = createTestPager(10, 3);
66
const model = store.add(new IterativePagedModel(pager));
67
68
// First page already loaded
69
assert.strictEqual(model.length, 11);
70
71
// Load second page by accessing its sentinel
72
await model.resolve(10, CancellationToken.None);
73
assert.strictEqual(model.length, 21); // 20 items + 1 sentinel
74
assert.strictEqual(model.get(10), 10); // First item of second page
75
76
// Load third (final) page
77
await model.resolve(20, CancellationToken.None);
78
assert.strictEqual(model.length, 30); // 30 items, no sentinel (no more pages)
79
});
80
81
test('onDidIncrementLength event fires correctly', async () => {
82
const pager = createTestPager(10, 3);
83
const model = store.add(new IterativePagedModel(pager));
84
const lengths: number[] = [];
85
86
store.add(model.onDidIncrementLength((length: number) => lengths.push(length)));
87
88
// Load second page
89
await model.resolve(10, CancellationToken.None);
90
91
assert.strictEqual(lengths.length, 1);
92
assert.strictEqual(lengths[0], 21); // 20 items + 1 sentinel
93
94
// Load third page
95
await model.resolve(20, CancellationToken.None);
96
97
assert.strictEqual(lengths.length, 2);
98
assert.strictEqual(lengths[1], 30); // 30 items, no sentinel
99
});
100
101
test('accessing regular items does not trigger loading', async () => {
102
const pager = createTestPager(10, 3);
103
const model = store.add(new IterativePagedModel(pager));
104
105
const initialLength = model.length;
106
107
// Access items within the loaded range
108
assert.strictEqual(model.get(5), 5);
109
assert.strictEqual(model.isResolved(5), true);
110
111
// Length should not change
112
assert.strictEqual(model.length, initialLength);
113
});
114
115
test('reaching end of data removes sentinel', async () => {
116
const pager = createTestPager(10, 3);
117
const model = store.add(new IterativePagedModel(pager));
118
119
// Load all pages
120
await model.resolve(10, CancellationToken.None); // Page 2
121
await model.resolve(20, CancellationToken.None); // Page 3 (final)
122
123
// After loading all data, there should be no more pages
124
assert.strictEqual(model.length, 30); // Exactly 30 items, no sentinel
125
126
// Accessing resolved items should work
127
assert.strictEqual(model.isResolved(29), true);
128
assert.strictEqual(model.isResolved(30), false);
129
});
130
131
test('concurrent access to sentinel only loads once', async () => {
132
const pager = createTestPager(10, 3);
133
const model = store.add(new IterativePagedModel(pager));
134
135
// Access sentinel concurrently
136
const [item1, item2, item3] = await Promise.all([
137
model.resolve(10, CancellationToken.None),
138
model.resolve(10, CancellationToken.None),
139
model.resolve(10, CancellationToken.None)
140
]);
141
142
// All should get the same item
143
assert.strictEqual(item1, 10);
144
assert.strictEqual(item2, 10);
145
assert.strictEqual(item3, 10);
146
assert.strictEqual(model.length, 21); // 20 items + 1 sentinel
147
});
148
149
test('empty pager with no items', () => {
150
const emptyPager: IIterativePager<number> = {
151
firstPage: { items: [], hasMore: false },
152
getNextPage: async () => ({ items: [], hasMore: false })
153
};
154
const model = store.add(new IterativePagedModel(emptyPager));
155
156
assert.strictEqual(model.length, 0);
157
assert.strictEqual(model.isResolved(0), false);
158
});
159
160
test('single page pager with no more pages', () => {
161
const singlePagePager: IIterativePager<number> = {
162
firstPage: { items: [1, 2, 3], hasMore: false },
163
getNextPage: async () => ({ items: [], hasMore: false })
164
};
165
const model = store.add(new IterativePagedModel(singlePagePager));
166
167
assert.strictEqual(model.length, 3); // No sentinel
168
assert.strictEqual(model.isResolved(0), true);
169
assert.strictEqual(model.isResolved(2), true);
170
assert.strictEqual(model.isResolved(3), false);
171
assert.strictEqual(model.get(0), 1);
172
assert.strictEqual(model.get(2), 3);
173
});
174
175
test('accessing item beyond loaded range throws', () => {
176
const pager = createTestPager(10, 3);
177
const model = store.add(new IterativePagedModel(pager));
178
179
// Try to access item beyond current length
180
assert.throws(() => model.get(15), /Item not resolved yet/);
181
});
182
183
test('resolving item beyond all pages throws', async () => {
184
const pager = createTestPager(10, 3);
185
const model = store.add(new IterativePagedModel(pager));
186
187
// Load all pages
188
await model.resolve(10, CancellationToken.None);
189
await model.resolve(20, CancellationToken.None);
190
191
// Try to resolve beyond the last item
192
await assert.rejects(
193
async () => model.resolve(30, CancellationToken.None),
194
/Index out of bounds/
195
);
196
});
197
198
test('cancelled token during initial resolve', async () => {
199
const pager = createTestPager(10, 3);
200
const model = store.add(new IterativePagedModel(pager));
201
202
const cts = new CancellationTokenSource();
203
cts.cancel();
204
205
await assert.rejects(
206
async () => model.resolve(0, cts.token),
207
/Canceled/
208
);
209
});
210
211
test('event fires for each page load', async () => {
212
const pager = createTestPager(5, 4);
213
const model = store.add(new IterativePagedModel(pager));
214
const lengths: number[] = [];
215
216
store.add(model.onDidIncrementLength((length: number) => lengths.push(length)));
217
218
// Initially has first page (5 items + 1 sentinel = 6)
219
assert.strictEqual(model.length, 6);
220
221
// Load page 2
222
await model.resolve(5, CancellationToken.None);
223
assert.deepStrictEqual(lengths, [11]); // 10 items + 1 sentinel
224
225
// Load page 3
226
await model.resolve(10, CancellationToken.None);
227
assert.deepStrictEqual(lengths, [11, 16]); // 15 items + 1 sentinel
228
229
// Load page 4 (final)
230
await model.resolve(15, CancellationToken.None);
231
assert.deepStrictEqual(lengths, [11, 16, 20]); // 20 items, no sentinel
232
});
233
234
test('sequential page loads work correctly', async () => {
235
const pager = createTestPager(5, 3);
236
const model = store.add(new IterativePagedModel(pager));
237
238
// Load pages sequentially
239
for (let page = 1; page < 3; page++) {
240
const sentinelIndex = page * 5;
241
await model.resolve(sentinelIndex, CancellationToken.None);
242
}
243
244
// Verify all items are accessible
245
assert.strictEqual(model.length, 15); // 3 pages * 5 items, no sentinel
246
for (let i = 0; i < 15; i++) {
247
assert.strictEqual(model.get(i), i);
248
assert.strictEqual(model.isResolved(i), true);
249
}
250
});
251
252
test('accessing items after loading all pages', async () => {
253
const pager = createTestPager(10, 2);
254
const model = store.add(new IterativePagedModel(pager));
255
256
// Load second page
257
await model.resolve(10, CancellationToken.None);
258
259
// No sentinel after loading all pages
260
assert.strictEqual(model.length, 20);
261
assert.strictEqual(model.isResolved(19), true);
262
assert.strictEqual(model.isResolved(20), false);
263
264
// All items should be accessible
265
for (let i = 0; i < 20; i++) {
266
assert.strictEqual(model.get(i), i);
267
}
268
});
269
270
test('pager with varying page sizes', async () => {
271
let pageNum = 0;
272
const varyingPager: IIterativePager<string> = {
273
firstPage: { items: ['a', 'b', 'c'], hasMore: true },
274
getNextPage: async (): Promise<IIterativePage<string>> => {
275
pageNum++;
276
if (pageNum === 1) {
277
return { items: ['d', 'e'], hasMore: true };
278
} else if (pageNum === 2) {
279
return { items: ['f', 'g', 'h', 'i'], hasMore: false };
280
}
281
return { items: [], hasMore: false };
282
}
283
};
284
285
const model = store.add(new IterativePagedModel(varyingPager));
286
287
assert.strictEqual(model.length, 4); // 3 items + 1 sentinel
288
289
// Load second page (2 items)
290
await model.resolve(3, CancellationToken.None);
291
assert.strictEqual(model.length, 6); // 5 items + 1 sentinel
292
assert.strictEqual(model.get(3), 'd');
293
294
// Load third page (4 items)
295
await model.resolve(5, CancellationToken.None);
296
assert.strictEqual(model.length, 9); // 9 items, no sentinel
297
assert.strictEqual(model.get(5), 'f');
298
assert.strictEqual(model.get(8), 'i');
299
});
300
});
301
302