Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts
3296 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 * as sinon from 'sinon';
8
import { Emitter } from '../../../../base/common/event.js';
9
import { ExtHostTreeViews } from '../../common/extHostTreeViews.js';
10
import { ExtHostCommands } from '../../common/extHostCommands.js';
11
import { MainThreadTreeViewsShape, MainContext, MainThreadCommandsShape } from '../../common/extHost.protocol.js';
12
import { TreeDataProvider, TreeItem } from 'vscode';
13
import { TestRPCProtocol } from '../common/testRPCProtocol.js';
14
import { mock } from '../../../../base/test/common/mock.js';
15
import { TreeItemCollapsibleState, ITreeItem, IRevealOptions } from '../../../common/views.js';
16
import { NullLogService } from '../../../../platform/log/common/log.js';
17
import type { IDisposable } from '../../../../base/common/lifecycle.js';
18
import { nullExtensionDescription as extensionsDescription } from '../../../services/extensions/common/extensions.js';
19
import { runWithFakedTimers } from '../../../../base/test/common/timeTravelScheduler.js';
20
import { IExtHostTelemetry } from '../../common/extHostTelemetry.js';
21
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
22
23
function unBatchChildren(result: (number | ITreeItem)[][] | undefined): ITreeItem[] | undefined {
24
if (!result || result.length === 0) {
25
return undefined;
26
}
27
if (result.length > 1) {
28
throw new Error('Unexpected result length, all tests are unbatched.');
29
}
30
return result[0].slice(1) as ITreeItem[];
31
}
32
33
suite('ExtHostTreeView', function () {
34
const store = ensureNoDisposablesAreLeakedInTestSuite();
35
36
class RecordingShape extends mock<MainThreadTreeViewsShape>() {
37
38
onRefresh = new Emitter<{ [treeItemHandle: string]: ITreeItem }>();
39
40
override async $registerTreeViewDataProvider(treeViewId: string): Promise<void> {
41
}
42
43
override $refresh(viewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): Promise<void> {
44
return Promise.resolve(null).then(() => {
45
this.onRefresh.fire(itemsToRefresh);
46
});
47
}
48
49
override $reveal(treeViewId: string, itemInfo: { item: ITreeItem; parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise<void> {
50
return Promise.resolve();
51
}
52
53
override $disposeTree(treeViewId: string): Promise<void> {
54
return Promise.resolve();
55
}
56
57
}
58
59
let testObject: ExtHostTreeViews;
60
let target: RecordingShape;
61
let onDidChangeTreeNode: Emitter<{ key: string } | undefined>;
62
let onDidChangeTreeNodeWithId: Emitter<{ key: string }>;
63
let tree: { [key: string]: any };
64
let labels: { [key: string]: string };
65
let nodes: { [key: string]: { key: string } };
66
67
setup(() => {
68
tree = {
69
'a': {
70
'aa': {},
71
'ab': {}
72
},
73
'b': {
74
'ba': {},
75
'bb': {}
76
}
77
};
78
79
labels = {};
80
nodes = {};
81
82
const rpcProtocol = new TestRPCProtocol();
83
84
rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock<MainThreadCommandsShape>() {
85
override $registerCommand() { }
86
});
87
target = new RecordingShape();
88
testObject = store.add(new ExtHostTreeViews(target, new ExtHostCommands(
89
rpcProtocol,
90
new NullLogService(),
91
new class extends mock<IExtHostTelemetry>() {
92
override onExtensionError(): boolean {
93
return true;
94
}
95
}
96
), new NullLogService()));
97
onDidChangeTreeNode = new Emitter<{ key: string } | undefined>();
98
onDidChangeTreeNodeWithId = new Emitter<{ key: string }>();
99
testObject.createTreeView('testNodeTreeProvider', { treeDataProvider: aNodeTreeDataProvider() }, extensionsDescription);
100
testObject.createTreeView('testNodeWithIdTreeProvider', { treeDataProvider: aNodeWithIdTreeDataProvider() }, extensionsDescription);
101
testObject.createTreeView('testNodeWithHighlightsTreeProvider', { treeDataProvider: aNodeWithHighlightedLabelTreeDataProvider() }, extensionsDescription);
102
103
return loadCompleteTree('testNodeTreeProvider');
104
});
105
106
test('construct node tree', () => {
107
return testObject.$getChildren('testNodeTreeProvider')
108
.then(elements => {
109
const actuals = unBatchChildren(elements)?.map(e => e.handle);
110
assert.deepStrictEqual(actuals, ['0/0:a', '0/0:b']);
111
return Promise.all([
112
testObject.$getChildren('testNodeTreeProvider', ['0/0:a'])
113
.then(children => {
114
const actuals = unBatchChildren(children)?.map(e => e.handle);
115
assert.deepStrictEqual(actuals, ['0/0:a/0:aa', '0/0:a/0:ab']);
116
return Promise.all([
117
testObject.$getChildren('testNodeTreeProvider', ['0/0:a/0:aa']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)),
118
testObject.$getChildren('testNodeTreeProvider', ['0/0:a/0:ab']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0))
119
]);
120
}),
121
testObject.$getChildren('testNodeTreeProvider', ['0/0:b'])
122
.then(children => {
123
const actuals = unBatchChildren(children)?.map(e => e.handle);
124
assert.deepStrictEqual(actuals, ['0/0:b/0:ba', '0/0:b/0:bb']);
125
return Promise.all([
126
testObject.$getChildren('testNodeTreeProvider', ['0/0:b/0:ba']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)),
127
testObject.$getChildren('testNodeTreeProvider', ['0/0:b/0:bb']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0))
128
]);
129
})
130
]);
131
});
132
});
133
134
test('construct id tree', () => {
135
return testObject.$getChildren('testNodeWithIdTreeProvider')
136
.then(elements => {
137
const actuals = unBatchChildren(elements)?.map(e => e.handle);
138
assert.deepStrictEqual(actuals, ['1/a', '1/b']);
139
return Promise.all([
140
testObject.$getChildren('testNodeWithIdTreeProvider', ['1/a'])
141
.then(children => {
142
const actuals = unBatchChildren(children)?.map(e => e.handle);
143
assert.deepStrictEqual(actuals, ['1/aa', '1/ab']);
144
return Promise.all([
145
testObject.$getChildren('testNodeWithIdTreeProvider', ['1/aa']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)),
146
testObject.$getChildren('testNodeWithIdTreeProvider', ['1/ab']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0))
147
]);
148
}),
149
testObject.$getChildren('testNodeWithIdTreeProvider', ['1/b'])
150
.then(children => {
151
const actuals = unBatchChildren(children)?.map(e => e.handle);
152
assert.deepStrictEqual(actuals, ['1/ba', '1/bb']);
153
return Promise.all([
154
testObject.$getChildren('testNodeWithIdTreeProvider', ['1/ba']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)),
155
testObject.$getChildren('testNodeWithIdTreeProvider', ['1/bb']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0))
156
]);
157
})
158
]);
159
});
160
});
161
162
test('construct highlights tree', () => {
163
return testObject.$getChildren('testNodeWithHighlightsTreeProvider')
164
.then(elements => {
165
assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(elements)), [{
166
handle: '1/a',
167
label: { label: 'a', highlights: [[0, 2], [3, 5]] },
168
collapsibleState: TreeItemCollapsibleState.Collapsed
169
}, {
170
handle: '1/b',
171
label: { label: 'b', highlights: [[0, 2], [3, 5]] },
172
collapsibleState: TreeItemCollapsibleState.Collapsed
173
}]);
174
return Promise.all([
175
testObject.$getChildren('testNodeWithHighlightsTreeProvider', ['1/a'])
176
.then(children => {
177
assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(children)), [{
178
handle: '1/aa',
179
parentHandle: '1/a',
180
label: { label: 'aa', highlights: [[0, 2], [3, 5]] },
181
collapsibleState: TreeItemCollapsibleState.None
182
}, {
183
handle: '1/ab',
184
parentHandle: '1/a',
185
label: { label: 'ab', highlights: [[0, 2], [3, 5]] },
186
collapsibleState: TreeItemCollapsibleState.None
187
}]);
188
}),
189
testObject.$getChildren('testNodeWithHighlightsTreeProvider', ['1/b'])
190
.then(children => {
191
assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(children)), [{
192
handle: '1/ba',
193
parentHandle: '1/b',
194
label: { label: 'ba', highlights: [[0, 2], [3, 5]] },
195
collapsibleState: TreeItemCollapsibleState.None
196
}, {
197
handle: '1/bb',
198
parentHandle: '1/b',
199
label: { label: 'bb', highlights: [[0, 2], [3, 5]] },
200
collapsibleState: TreeItemCollapsibleState.None
201
}]);
202
})
203
]);
204
});
205
});
206
207
test('error is thrown if id is not unique', (done) => {
208
tree['a'] = {
209
'aa': {},
210
};
211
tree['b'] = {
212
'aa': {},
213
'ba': {}
214
};
215
let caughtExpectedError = false;
216
store.add(target.onRefresh.event(() => {
217
testObject.$getChildren('testNodeWithIdTreeProvider')
218
.then(elements => {
219
const actuals = unBatchChildren(elements)?.map(e => e.handle);
220
assert.deepStrictEqual(actuals, ['1/a', '1/b']);
221
return testObject.$getChildren('testNodeWithIdTreeProvider', ['1/a'])
222
.then(() => testObject.$getChildren('testNodeWithIdTreeProvider', ['1/b']))
223
.then(() => assert.fail('Should fail with duplicate id'))
224
.catch(() => caughtExpectedError = true)
225
.finally(() => caughtExpectedError ? done() : assert.fail('Expected duplicate id error not thrown.'));
226
});
227
}));
228
onDidChangeTreeNode.fire(undefined);
229
});
230
231
test('refresh root', function (done) {
232
store.add(target.onRefresh.event(actuals => {
233
assert.strictEqual(undefined, actuals);
234
done();
235
}));
236
onDidChangeTreeNode.fire(undefined);
237
});
238
239
test('refresh a parent node', () => {
240
return new Promise((c, e) => {
241
store.add(target.onRefresh.event(actuals => {
242
assert.deepStrictEqual(['0/0:b'], Object.keys(actuals));
243
assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b']), {
244
handle: '0/0:b',
245
label: { label: 'b' },
246
collapsibleState: TreeItemCollapsibleState.Collapsed
247
});
248
c(undefined);
249
}));
250
onDidChangeTreeNode.fire(getNode('b'));
251
});
252
});
253
254
test('refresh a leaf node', function (done) {
255
store.add(target.onRefresh.event(actuals => {
256
assert.deepStrictEqual(['0/0:b/0:bb'], Object.keys(actuals));
257
assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b/0:bb']), {
258
handle: '0/0:b/0:bb',
259
parentHandle: '0/0:b',
260
label: { label: 'bb' },
261
collapsibleState: TreeItemCollapsibleState.None
262
});
263
done();
264
}));
265
onDidChangeTreeNode.fire(getNode('bb'));
266
});
267
268
async function runWithEventMerging(action: (resolve: () => void) => void) {
269
await runWithFakedTimers({}, async () => {
270
await new Promise<void>((resolve) => {
271
let subscription: IDisposable | undefined = undefined;
272
subscription = target.onRefresh.event(() => {
273
subscription!.dispose();
274
resolve();
275
});
276
onDidChangeTreeNode.fire(getNode('b'));
277
});
278
await new Promise<void>(action);
279
});
280
}
281
282
test('refresh parent and child node trigger refresh only on parent - scenario 1', async () => {
283
return runWithEventMerging((resolve) => {
284
store.add(target.onRefresh.event(actuals => {
285
assert.deepStrictEqual(['0/0:b', '0/0:a/0:aa'], Object.keys(actuals));
286
assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b']), {
287
handle: '0/0:b',
288
label: { label: 'b' },
289
collapsibleState: TreeItemCollapsibleState.Collapsed
290
});
291
assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), {
292
handle: '0/0:a/0:aa',
293
parentHandle: '0/0:a',
294
label: { label: 'aa' },
295
collapsibleState: TreeItemCollapsibleState.None
296
});
297
resolve();
298
}));
299
onDidChangeTreeNode.fire(getNode('b'));
300
onDidChangeTreeNode.fire(getNode('aa'));
301
onDidChangeTreeNode.fire(getNode('bb'));
302
});
303
});
304
305
test('refresh parent and child node trigger refresh only on parent - scenario 2', async () => {
306
return runWithEventMerging((resolve) => {
307
store.add(target.onRefresh.event(actuals => {
308
assert.deepStrictEqual(['0/0:a/0:aa', '0/0:b'], Object.keys(actuals));
309
assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b']), {
310
handle: '0/0:b',
311
label: { label: 'b' },
312
collapsibleState: TreeItemCollapsibleState.Collapsed
313
});
314
assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), {
315
handle: '0/0:a/0:aa',
316
parentHandle: '0/0:a',
317
label: { label: 'aa' },
318
collapsibleState: TreeItemCollapsibleState.None
319
});
320
resolve();
321
}));
322
onDidChangeTreeNode.fire(getNode('bb'));
323
onDidChangeTreeNode.fire(getNode('aa'));
324
onDidChangeTreeNode.fire(getNode('b'));
325
});
326
});
327
328
test('refresh an element for label change', function (done) {
329
labels['a'] = 'aa';
330
store.add(target.onRefresh.event(actuals => {
331
assert.deepStrictEqual(['0/0:a'], Object.keys(actuals));
332
assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:a']), {
333
handle: '0/0:aa',
334
label: { label: 'aa' },
335
collapsibleState: TreeItemCollapsibleState.Collapsed
336
});
337
done();
338
}));
339
onDidChangeTreeNode.fire(getNode('a'));
340
});
341
342
test('refresh calls are throttled on roots', () => {
343
return runWithEventMerging((resolve) => {
344
store.add(target.onRefresh.event(actuals => {
345
assert.strictEqual(undefined, actuals);
346
resolve();
347
}));
348
onDidChangeTreeNode.fire(undefined);
349
onDidChangeTreeNode.fire(undefined);
350
onDidChangeTreeNode.fire(undefined);
351
onDidChangeTreeNode.fire(undefined);
352
});
353
});
354
355
test('refresh calls are throttled on elements', () => {
356
return runWithEventMerging((resolve) => {
357
store.add(target.onRefresh.event(actuals => {
358
assert.deepStrictEqual(['0/0:a', '0/0:b'], Object.keys(actuals));
359
resolve();
360
}));
361
362
onDidChangeTreeNode.fire(getNode('a'));
363
onDidChangeTreeNode.fire(getNode('b'));
364
onDidChangeTreeNode.fire(getNode('b'));
365
onDidChangeTreeNode.fire(getNode('a'));
366
});
367
});
368
369
test('refresh calls are throttled on unknown elements', () => {
370
return runWithEventMerging((resolve) => {
371
store.add(target.onRefresh.event(actuals => {
372
assert.deepStrictEqual(['0/0:a', '0/0:b'], Object.keys(actuals));
373
resolve();
374
}));
375
376
onDidChangeTreeNode.fire(getNode('a'));
377
onDidChangeTreeNode.fire(getNode('b'));
378
onDidChangeTreeNode.fire(getNode('g'));
379
onDidChangeTreeNode.fire(getNode('a'));
380
});
381
});
382
383
test('refresh calls are throttled on unknown elements and root', () => {
384
return runWithEventMerging((resolve) => {
385
store.add(target.onRefresh.event(actuals => {
386
assert.strictEqual(undefined, actuals);
387
resolve();
388
}));
389
390
onDidChangeTreeNode.fire(getNode('a'));
391
onDidChangeTreeNode.fire(getNode('b'));
392
onDidChangeTreeNode.fire(getNode('g'));
393
onDidChangeTreeNode.fire(undefined);
394
});
395
});
396
397
test('refresh calls are throttled on elements and root', () => {
398
return runWithEventMerging((resolve) => {
399
store.add(target.onRefresh.event(actuals => {
400
assert.strictEqual(undefined, actuals);
401
resolve();
402
}));
403
404
onDidChangeTreeNode.fire(getNode('a'));
405
onDidChangeTreeNode.fire(getNode('b'));
406
onDidChangeTreeNode.fire(undefined);
407
onDidChangeTreeNode.fire(getNode('a'));
408
});
409
});
410
411
test('generate unique handles from labels by escaping them', (done) => {
412
tree = {
413
'a/0:b': {}
414
};
415
416
store.add(target.onRefresh.event(() => {
417
testObject.$getChildren('testNodeTreeProvider')
418
.then(elements => {
419
assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:a//0:b']);
420
done();
421
});
422
}));
423
onDidChangeTreeNode.fire(undefined);
424
});
425
426
test('tree with duplicate labels', (done) => {
427
428
const dupItems = {
429
'adup1': 'c',
430
'adup2': 'g',
431
'bdup1': 'e',
432
'hdup1': 'i',
433
'hdup2': 'l',
434
'jdup1': 'k'
435
};
436
437
labels['c'] = 'a';
438
labels['e'] = 'b';
439
labels['g'] = 'a';
440
labels['i'] = 'h';
441
labels['l'] = 'h';
442
labels['k'] = 'j';
443
444
tree[dupItems['adup1']] = {};
445
tree['d'] = {};
446
447
const bdup1Tree: { [key: string]: any } = {};
448
bdup1Tree['h'] = {};
449
bdup1Tree[dupItems['hdup1']] = {};
450
bdup1Tree['j'] = {};
451
bdup1Tree[dupItems['jdup1']] = {};
452
bdup1Tree[dupItems['hdup2']] = {};
453
454
tree[dupItems['bdup1']] = bdup1Tree;
455
tree['f'] = {};
456
tree[dupItems['adup2']] = {};
457
458
store.add(target.onRefresh.event(() => {
459
testObject.$getChildren('testNodeTreeProvider')
460
.then(elements => {
461
const actuals = unBatchChildren(elements)?.map(e => e.handle);
462
assert.deepStrictEqual(actuals, ['0/0:a', '0/0:b', '0/1:a', '0/0:d', '0/1:b', '0/0:f', '0/2:a']);
463
return testObject.$getChildren('testNodeTreeProvider', ['0/1:b'])
464
.then(elements => {
465
const actuals = unBatchChildren(elements)?.map(e => e.handle);
466
assert.deepStrictEqual(actuals, ['0/1:b/0:h', '0/1:b/1:h', '0/1:b/0:j', '0/1:b/1:j', '0/1:b/2:h']);
467
done();
468
});
469
});
470
}));
471
472
onDidChangeTreeNode.fire(undefined);
473
});
474
475
test('getChildren is not returned from cache if refreshed', (done) => {
476
tree = {
477
'c': {}
478
};
479
480
store.add(target.onRefresh.event(() => {
481
testObject.$getChildren('testNodeTreeProvider')
482
.then(elements => {
483
assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:c']);
484
done();
485
});
486
}));
487
488
onDidChangeTreeNode.fire(undefined);
489
});
490
491
test('getChildren is returned from cache if not refreshed', () => {
492
tree = {
493
'c': {}
494
};
495
496
return testObject.$getChildren('testNodeTreeProvider')
497
.then(elements => {
498
assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:a', '0/0:b']);
499
});
500
});
501
502
test('reveal will throw an error if getParent is not implemented', () => {
503
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aNodeTreeDataProvider() }, extensionsDescription);
504
return treeView.reveal({ key: 'a' })
505
.then(() => assert.fail('Reveal should throw an error as getParent is not implemented'), () => null);
506
});
507
508
test('reveal will return empty array for root element', () => {
509
const revealTarget = sinon.spy(target, '$reveal');
510
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);
511
const expected = {
512
item:
513
{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed },
514
parentChain: []
515
};
516
return treeView.reveal({ key: 'a' })
517
.then(() => {
518
assert.ok(revealTarget.calledOnce);
519
assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);
520
assert.deepStrictEqual(expected, removeUnsetKeys(revealTarget.args[0][1]));
521
assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]);
522
});
523
});
524
525
test('reveal will return parents array for an element when hierarchy is not loaded', () => {
526
const revealTarget = sinon.spy(target, '$reveal');
527
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);
528
const expected = {
529
item: { handle: '0/0:a/0:aa', label: { label: 'aa' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' },
530
parentChain: [{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }]
531
};
532
return treeView.reveal({ key: 'aa' })
533
.then(() => {
534
assert.ok(revealTarget.calledOnce);
535
assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);
536
assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item));
537
assert.deepStrictEqual(expected.parentChain, (<Array<any>>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg)));
538
assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]);
539
});
540
});
541
542
test('reveal will return parents array for an element when hierarchy is loaded', () => {
543
const revealTarget = sinon.spy(target, '$reveal');
544
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);
545
const expected = {
546
item: { handle: '0/0:a/0:aa', label: { label: 'aa' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' },
547
parentChain: [{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }]
548
};
549
return testObject.$getChildren('treeDataProvider')
550
.then(() => testObject.$getChildren('treeDataProvider', ['0/0:a']))
551
.then(() => treeView.reveal({ key: 'aa' })
552
.then(() => {
553
assert.ok(revealTarget.calledOnce);
554
assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);
555
assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item));
556
assert.deepStrictEqual(expected.parentChain, (<Array<any>>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg)));
557
assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]);
558
}));
559
});
560
561
test('reveal will return parents array for deeper element with no selection', () => {
562
tree = {
563
'b': {
564
'ba': {
565
'bac': {}
566
}
567
}
568
};
569
const revealTarget = sinon.spy(target, '$reveal');
570
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);
571
const expected = {
572
item: { handle: '0/0:b/0:ba/0:bac', label: { label: 'bac' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b/0:ba' },
573
parentChain: [
574
{ handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed },
575
{ handle: '0/0:b/0:ba', label: { label: 'ba' }, collapsibleState: TreeItemCollapsibleState.Collapsed, parentHandle: '0/0:b' }
576
]
577
};
578
return treeView.reveal({ key: 'bac' }, { select: false, focus: false, expand: false })
579
.then(() => {
580
assert.ok(revealTarget.calledOnce);
581
assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);
582
assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item));
583
assert.deepStrictEqual(expected.parentChain, (<Array<any>>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg)));
584
assert.deepStrictEqual({ select: false, focus: false, expand: false }, revealTarget.args[0][2]);
585
});
586
});
587
588
test('reveal after first udpate', () => {
589
const revealTarget = sinon.spy(target, '$reveal');
590
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);
591
const expected = {
592
item: { handle: '0/0:a/0:ac', label: { label: 'ac' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' },
593
parentChain: [{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }]
594
};
595
return loadCompleteTree('treeDataProvider')
596
.then(() => {
597
tree = {
598
'a': {
599
'aa': {},
600
'ac': {}
601
},
602
'b': {
603
'ba': {},
604
'bb': {}
605
}
606
};
607
onDidChangeTreeNode.fire(getNode('a'));
608
609
return treeView.reveal({ key: 'ac' })
610
.then(() => {
611
assert.ok(revealTarget.calledOnce);
612
assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);
613
assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item));
614
assert.deepStrictEqual(expected.parentChain, (<Array<any>>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg)));
615
assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]);
616
});
617
});
618
});
619
620
test('reveal after second udpate', () => {
621
const revealTarget = sinon.spy(target, '$reveal');
622
const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);
623
return loadCompleteTree('treeDataProvider')
624
.then(() => {
625
return runWithEventMerging((resolve) => {
626
tree = {
627
'a': {
628
'aa': {},
629
'ac': {}
630
},
631
'b': {
632
'ba': {},
633
'bb': {}
634
}
635
};
636
onDidChangeTreeNode.fire(getNode('a'));
637
tree = {
638
'a': {
639
'aa': {},
640
'ac': {}
641
},
642
'b': {
643
'ba': {},
644
'bc': {}
645
}
646
};
647
onDidChangeTreeNode.fire(getNode('b'));
648
resolve();
649
}).then(() => {
650
return treeView.reveal({ key: 'bc' })
651
.then(() => {
652
assert.ok(revealTarget.calledOnce);
653
assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);
654
assert.deepStrictEqual({ handle: '0/0:b/0:bc', label: { label: 'bc' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b' }, removeUnsetKeys(revealTarget.args[0][1]!.item));
655
assert.deepStrictEqual([{ handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (<Array<any>>revealTarget.args[0][1]!.parentChain).map(arg => removeUnsetKeys(arg)));
656
assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]);
657
});
658
});
659
});
660
});
661
662
function loadCompleteTree(treeId: string, element?: string): Promise<null> {
663
return testObject.$getChildren(treeId, element ? [element] : undefined)
664
.then(elements => {
665
if (!elements || elements?.length === 0) {
666
return null;
667
}
668
return elements[0].slice(1).map(e => loadCompleteTree(treeId, (e as ITreeItem).handle));
669
})
670
.then(() => null);
671
}
672
673
function removeUnsetKeys(obj: any): any {
674
if (Array.isArray(obj)) {
675
return obj.map(o => removeUnsetKeys(o));
676
}
677
678
if (typeof obj === 'object') {
679
const result: { [key: string]: any } = {};
680
for (const key of Object.keys(obj)) {
681
if (obj[key] !== undefined) {
682
result[key] = removeUnsetKeys(obj[key]);
683
}
684
}
685
return result;
686
}
687
return obj;
688
}
689
690
function aNodeTreeDataProvider(): TreeDataProvider<{ key: string }> {
691
return {
692
getChildren: (element: { key: string }): { key: string }[] => {
693
return getChildren(element ? element.key : undefined).map(key => getNode(key));
694
},
695
getTreeItem: (element: { key: string }): TreeItem => {
696
return getTreeItem(element.key);
697
},
698
onDidChangeTreeData: onDidChangeTreeNode.event
699
};
700
}
701
702
function aCompleteNodeTreeDataProvider(): TreeDataProvider<{ key: string }> {
703
return {
704
getChildren: (element: { key: string }): { key: string }[] => {
705
return getChildren(element ? element.key : undefined).map(key => getNode(key));
706
},
707
getTreeItem: (element: { key: string }): TreeItem => {
708
return getTreeItem(element.key);
709
},
710
getParent: ({ key }: { key: string }): { key: string } | undefined => {
711
const parentKey = key.substring(0, key.length - 1);
712
return parentKey ? new Key(parentKey) : undefined;
713
},
714
onDidChangeTreeData: onDidChangeTreeNode.event
715
};
716
}
717
718
function aNodeWithIdTreeDataProvider(): TreeDataProvider<{ key: string }> {
719
return {
720
getChildren: (element: { key: string }): { key: string }[] => {
721
return getChildren(element ? element.key : undefined).map(key => getNode(key));
722
},
723
getTreeItem: (element: { key: string }): TreeItem => {
724
const treeItem = getTreeItem(element.key);
725
treeItem.id = element.key;
726
return treeItem;
727
},
728
onDidChangeTreeData: onDidChangeTreeNodeWithId.event
729
};
730
}
731
732
function aNodeWithHighlightedLabelTreeDataProvider(): TreeDataProvider<{ key: string }> {
733
return {
734
getChildren: (element: { key: string }): { key: string }[] => {
735
return getChildren(element ? element.key : undefined).map(key => getNode(key));
736
},
737
getTreeItem: (element: { key: string }): TreeItem => {
738
const treeItem = getTreeItem(element.key, [[0, 2], [3, 5]]);
739
treeItem.id = element.key;
740
return treeItem;
741
},
742
onDidChangeTreeData: onDidChangeTreeNodeWithId.event
743
};
744
}
745
746
function getTreeElement(element: string): any {
747
let parent = tree;
748
for (let i = 0; i < element.length; i++) {
749
parent = parent[element.substring(0, i + 1)];
750
if (!parent) {
751
return null;
752
}
753
}
754
return parent;
755
}
756
757
function getChildren(key: string | undefined): string[] {
758
if (!key) {
759
return Object.keys(tree);
760
}
761
const treeElement = getTreeElement(key);
762
if (treeElement) {
763
return Object.keys(treeElement);
764
}
765
return [];
766
}
767
768
function getTreeItem(key: string, highlights?: [number, number][]): TreeItem {
769
const treeElement = getTreeElement(key);
770
return {
771
label: <any>{ label: labels[key] || key, highlights },
772
collapsibleState: treeElement && Object.keys(treeElement).length ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None
773
};
774
}
775
776
function getNode(key: string): { key: string } {
777
if (!nodes[key]) {
778
nodes[key] = new Key(key);
779
}
780
return nodes[key];
781
}
782
783
class Key {
784
constructor(readonly key: string) { }
785
}
786
787
});
788
789