Path: blob/main/src/vs/workbench/api/test/browser/extHostTreeViews.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import * as sinon from 'sinon';7import { Emitter } from '../../../../base/common/event.js';8import { ExtHostTreeViews } from '../../common/extHostTreeViews.js';9import { ExtHostCommands } from '../../common/extHostCommands.js';10import { MainThreadTreeViewsShape, MainContext, MainThreadCommandsShape } from '../../common/extHost.protocol.js';11import { TreeDataProvider, TreeItem } from 'vscode';12import { TestRPCProtocol } from '../common/testRPCProtocol.js';13import { mock } from '../../../../base/test/common/mock.js';14import { TreeItemCollapsibleState, ITreeItem, IRevealOptions } from '../../../common/views.js';15import { NullLogService } from '../../../../platform/log/common/log.js';16import type { IDisposable } from '../../../../base/common/lifecycle.js';17import { nullExtensionDescription as extensionsDescription } from '../../../services/extensions/common/extensions.js';18import { runWithFakedTimers } from '../../../../base/test/common/timeTravelScheduler.js';19import { IExtHostTelemetry } from '../../common/extHostTelemetry.js';20import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';2122function unBatchChildren(result: (number | ITreeItem)[][] | undefined): ITreeItem[] | undefined {23if (!result || result.length === 0) {24return undefined;25}26if (result.length > 1) {27throw new Error('Unexpected result length, all tests are unbatched.');28}29return result[0].slice(1) as ITreeItem[];30}3132suite('ExtHostTreeView', function () {33const store = ensureNoDisposablesAreLeakedInTestSuite();3435class RecordingShape extends mock<MainThreadTreeViewsShape>() {3637onRefresh = new Emitter<{ [treeItemHandle: string]: ITreeItem }>();3839override async $registerTreeViewDataProvider(treeViewId: string): Promise<void> {40}4142override $refresh(viewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): Promise<void> {43return Promise.resolve(null).then(() => {44this.onRefresh.fire(itemsToRefresh);45});46}4748override $reveal(treeViewId: string, itemInfo: { item: ITreeItem; parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise<void> {49return Promise.resolve();50}5152override $disposeTree(treeViewId: string): Promise<void> {53return Promise.resolve();54}5556}5758let testObject: ExtHostTreeViews;59let target: RecordingShape;60let onDidChangeTreeNode: Emitter<{ key: string } | undefined>;61let onDidChangeTreeNodeWithId: Emitter<{ key: string }>;62let tree: { [key: string]: any };63let labels: { [key: string]: string };64let nodes: { [key: string]: { key: string } };6566setup(() => {67tree = {68'a': {69'aa': {},70'ab': {}71},72'b': {73'ba': {},74'bb': {}75}76};7778labels = {};79nodes = {};8081const rpcProtocol = new TestRPCProtocol();8283rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock<MainThreadCommandsShape>() {84override $registerCommand() { }85});86target = new RecordingShape();87testObject = store.add(new ExtHostTreeViews(target, new ExtHostCommands(88rpcProtocol,89new NullLogService(),90new class extends mock<IExtHostTelemetry>() {91override onExtensionError(): boolean {92return true;93}94}95), new NullLogService()));96onDidChangeTreeNode = new Emitter<{ key: string } | undefined>();97onDidChangeTreeNodeWithId = new Emitter<{ key: string }>();98testObject.createTreeView('testNodeTreeProvider', { treeDataProvider: aNodeTreeDataProvider() }, extensionsDescription);99testObject.createTreeView('testNodeWithIdTreeProvider', { treeDataProvider: aNodeWithIdTreeDataProvider() }, extensionsDescription);100testObject.createTreeView('testNodeWithHighlightsTreeProvider', { treeDataProvider: aNodeWithHighlightedLabelTreeDataProvider() }, extensionsDescription);101102return loadCompleteTree('testNodeTreeProvider');103});104105test('construct node tree', () => {106return testObject.$getChildren('testNodeTreeProvider')107.then(elements => {108const actuals = unBatchChildren(elements)?.map(e => e.handle);109assert.deepStrictEqual(actuals, ['0/0:a', '0/0:b']);110return Promise.all([111testObject.$getChildren('testNodeTreeProvider', ['0/0:a'])112.then(children => {113const actuals = unBatchChildren(children)?.map(e => e.handle);114assert.deepStrictEqual(actuals, ['0/0:a/0:aa', '0/0:a/0:ab']);115return Promise.all([116testObject.$getChildren('testNodeTreeProvider', ['0/0:a/0:aa']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)),117testObject.$getChildren('testNodeTreeProvider', ['0/0:a/0:ab']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0))118]);119}),120testObject.$getChildren('testNodeTreeProvider', ['0/0:b'])121.then(children => {122const actuals = unBatchChildren(children)?.map(e => e.handle);123assert.deepStrictEqual(actuals, ['0/0:b/0:ba', '0/0:b/0:bb']);124return Promise.all([125testObject.$getChildren('testNodeTreeProvider', ['0/0:b/0:ba']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)),126testObject.$getChildren('testNodeTreeProvider', ['0/0:b/0:bb']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0))127]);128})129]);130});131});132133test('construct id tree', () => {134return testObject.$getChildren('testNodeWithIdTreeProvider')135.then(elements => {136const actuals = unBatchChildren(elements)?.map(e => e.handle);137assert.deepStrictEqual(actuals, ['1/a', '1/b']);138return Promise.all([139testObject.$getChildren('testNodeWithIdTreeProvider', ['1/a'])140.then(children => {141const actuals = unBatchChildren(children)?.map(e => e.handle);142assert.deepStrictEqual(actuals, ['1/aa', '1/ab']);143return Promise.all([144testObject.$getChildren('testNodeWithIdTreeProvider', ['1/aa']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)),145testObject.$getChildren('testNodeWithIdTreeProvider', ['1/ab']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0))146]);147}),148testObject.$getChildren('testNodeWithIdTreeProvider', ['1/b'])149.then(children => {150const actuals = unBatchChildren(children)?.map(e => e.handle);151assert.deepStrictEqual(actuals, ['1/ba', '1/bb']);152return Promise.all([153testObject.$getChildren('testNodeWithIdTreeProvider', ['1/ba']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0)),154testObject.$getChildren('testNodeWithIdTreeProvider', ['1/bb']).then(children => assert.strictEqual(unBatchChildren(children)?.length, 0))155]);156})157]);158});159});160161test('construct highlights tree', () => {162return testObject.$getChildren('testNodeWithHighlightsTreeProvider')163.then(elements => {164assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(elements)), [{165handle: '1/a',166label: { label: 'a', highlights: [[0, 2], [3, 5]] },167collapsibleState: TreeItemCollapsibleState.Collapsed168}, {169handle: '1/b',170label: { label: 'b', highlights: [[0, 2], [3, 5]] },171collapsibleState: TreeItemCollapsibleState.Collapsed172}]);173return Promise.all([174testObject.$getChildren('testNodeWithHighlightsTreeProvider', ['1/a'])175.then(children => {176assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(children)), [{177handle: '1/aa',178parentHandle: '1/a',179label: { label: 'aa', highlights: [[0, 2], [3, 5]] },180collapsibleState: TreeItemCollapsibleState.None181}, {182handle: '1/ab',183parentHandle: '1/a',184label: { label: 'ab', highlights: [[0, 2], [3, 5]] },185collapsibleState: TreeItemCollapsibleState.None186}]);187}),188testObject.$getChildren('testNodeWithHighlightsTreeProvider', ['1/b'])189.then(children => {190assert.deepStrictEqual(removeUnsetKeys(unBatchChildren(children)), [{191handle: '1/ba',192parentHandle: '1/b',193label: { label: 'ba', highlights: [[0, 2], [3, 5]] },194collapsibleState: TreeItemCollapsibleState.None195}, {196handle: '1/bb',197parentHandle: '1/b',198label: { label: 'bb', highlights: [[0, 2], [3, 5]] },199collapsibleState: TreeItemCollapsibleState.None200}]);201})202]);203});204});205206test('error is thrown if id is not unique', (done) => {207tree['a'] = {208'aa': {},209};210tree['b'] = {211'aa': {},212'ba': {}213};214let caughtExpectedError = false;215store.add(target.onRefresh.event(() => {216testObject.$getChildren('testNodeWithIdTreeProvider')217.then(elements => {218const actuals = unBatchChildren(elements)?.map(e => e.handle);219assert.deepStrictEqual(actuals, ['1/a', '1/b']);220return testObject.$getChildren('testNodeWithIdTreeProvider', ['1/a'])221.then(() => testObject.$getChildren('testNodeWithIdTreeProvider', ['1/b']))222.then(() => assert.fail('Should fail with duplicate id'))223.catch(() => caughtExpectedError = true)224.finally(() => caughtExpectedError ? done() : assert.fail('Expected duplicate id error not thrown.'));225});226}));227onDidChangeTreeNode.fire(undefined);228});229230test('refresh root', function (done) {231store.add(target.onRefresh.event(actuals => {232assert.strictEqual(undefined, actuals);233done();234}));235onDidChangeTreeNode.fire(undefined);236});237238test('refresh a parent node', () => {239return new Promise((c, e) => {240store.add(target.onRefresh.event(actuals => {241assert.deepStrictEqual(['0/0:b'], Object.keys(actuals));242assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b']), {243handle: '0/0:b',244label: { label: 'b' },245collapsibleState: TreeItemCollapsibleState.Collapsed246});247c(undefined);248}));249onDidChangeTreeNode.fire(getNode('b'));250});251});252253test('refresh a leaf node', function (done) {254store.add(target.onRefresh.event(actuals => {255assert.deepStrictEqual(['0/0:b/0:bb'], Object.keys(actuals));256assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b/0:bb']), {257handle: '0/0:b/0:bb',258parentHandle: '0/0:b',259label: { label: 'bb' },260collapsibleState: TreeItemCollapsibleState.None261});262done();263}));264onDidChangeTreeNode.fire(getNode('bb'));265});266267async function runWithEventMerging(action: (resolve: () => void) => void) {268await runWithFakedTimers({}, async () => {269await new Promise<void>((resolve) => {270let subscription: IDisposable | undefined = undefined;271subscription = target.onRefresh.event(() => {272subscription!.dispose();273resolve();274});275onDidChangeTreeNode.fire(getNode('b'));276});277await new Promise<void>(action);278});279}280281test('refresh parent and child node trigger refresh only on parent - scenario 1', async () => {282return runWithEventMerging((resolve) => {283store.add(target.onRefresh.event(actuals => {284assert.deepStrictEqual(['0/0:b', '0/0:a/0:aa'], Object.keys(actuals));285assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b']), {286handle: '0/0:b',287label: { label: 'b' },288collapsibleState: TreeItemCollapsibleState.Collapsed289});290assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), {291handle: '0/0:a/0:aa',292parentHandle: '0/0:a',293label: { label: 'aa' },294collapsibleState: TreeItemCollapsibleState.None295});296resolve();297}));298onDidChangeTreeNode.fire(getNode('b'));299onDidChangeTreeNode.fire(getNode('aa'));300onDidChangeTreeNode.fire(getNode('bb'));301});302});303304test('refresh parent and child node trigger refresh only on parent - scenario 2', async () => {305return runWithEventMerging((resolve) => {306store.add(target.onRefresh.event(actuals => {307assert.deepStrictEqual(['0/0:a/0:aa', '0/0:b'], Object.keys(actuals));308assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:b']), {309handle: '0/0:b',310label: { label: 'b' },311collapsibleState: TreeItemCollapsibleState.Collapsed312});313assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), {314handle: '0/0:a/0:aa',315parentHandle: '0/0:a',316label: { label: 'aa' },317collapsibleState: TreeItemCollapsibleState.None318});319resolve();320}));321onDidChangeTreeNode.fire(getNode('bb'));322onDidChangeTreeNode.fire(getNode('aa'));323onDidChangeTreeNode.fire(getNode('b'));324});325});326327test('refresh an element for label change', function (done) {328labels['a'] = 'aa';329store.add(target.onRefresh.event(actuals => {330assert.deepStrictEqual(['0/0:a'], Object.keys(actuals));331assert.deepStrictEqual(removeUnsetKeys(actuals['0/0:a']), {332handle: '0/0:aa',333label: { label: 'aa' },334collapsibleState: TreeItemCollapsibleState.Collapsed335});336done();337}));338onDidChangeTreeNode.fire(getNode('a'));339});340341test('refresh calls are throttled on roots', () => {342return runWithEventMerging((resolve) => {343store.add(target.onRefresh.event(actuals => {344assert.strictEqual(undefined, actuals);345resolve();346}));347onDidChangeTreeNode.fire(undefined);348onDidChangeTreeNode.fire(undefined);349onDidChangeTreeNode.fire(undefined);350onDidChangeTreeNode.fire(undefined);351});352});353354test('refresh calls are throttled on elements', () => {355return runWithEventMerging((resolve) => {356store.add(target.onRefresh.event(actuals => {357assert.deepStrictEqual(['0/0:a', '0/0:b'], Object.keys(actuals));358resolve();359}));360361onDidChangeTreeNode.fire(getNode('a'));362onDidChangeTreeNode.fire(getNode('b'));363onDidChangeTreeNode.fire(getNode('b'));364onDidChangeTreeNode.fire(getNode('a'));365});366});367368test('refresh calls are throttled on unknown elements', () => {369return runWithEventMerging((resolve) => {370store.add(target.onRefresh.event(actuals => {371assert.deepStrictEqual(['0/0:a', '0/0:b'], Object.keys(actuals));372resolve();373}));374375onDidChangeTreeNode.fire(getNode('a'));376onDidChangeTreeNode.fire(getNode('b'));377onDidChangeTreeNode.fire(getNode('g'));378onDidChangeTreeNode.fire(getNode('a'));379});380});381382test('refresh calls are throttled on unknown elements and root', () => {383return runWithEventMerging((resolve) => {384store.add(target.onRefresh.event(actuals => {385assert.strictEqual(undefined, actuals);386resolve();387}));388389onDidChangeTreeNode.fire(getNode('a'));390onDidChangeTreeNode.fire(getNode('b'));391onDidChangeTreeNode.fire(getNode('g'));392onDidChangeTreeNode.fire(undefined);393});394});395396test('refresh calls are throttled on elements and root', () => {397return runWithEventMerging((resolve) => {398store.add(target.onRefresh.event(actuals => {399assert.strictEqual(undefined, actuals);400resolve();401}));402403onDidChangeTreeNode.fire(getNode('a'));404onDidChangeTreeNode.fire(getNode('b'));405onDidChangeTreeNode.fire(undefined);406onDidChangeTreeNode.fire(getNode('a'));407});408});409410test('generate unique handles from labels by escaping them', (done) => {411tree = {412'a/0:b': {}413};414415store.add(target.onRefresh.event(() => {416testObject.$getChildren('testNodeTreeProvider')417.then(elements => {418assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:a//0:b']);419done();420});421}));422onDidChangeTreeNode.fire(undefined);423});424425test('tree with duplicate labels', (done) => {426427const dupItems = {428'adup1': 'c',429'adup2': 'g',430'bdup1': 'e',431'hdup1': 'i',432'hdup2': 'l',433'jdup1': 'k'434};435436labels['c'] = 'a';437labels['e'] = 'b';438labels['g'] = 'a';439labels['i'] = 'h';440labels['l'] = 'h';441labels['k'] = 'j';442443tree[dupItems['adup1']] = {};444tree['d'] = {};445446const bdup1Tree: { [key: string]: any } = {};447bdup1Tree['h'] = {};448bdup1Tree[dupItems['hdup1']] = {};449bdup1Tree['j'] = {};450bdup1Tree[dupItems['jdup1']] = {};451bdup1Tree[dupItems['hdup2']] = {};452453tree[dupItems['bdup1']] = bdup1Tree;454tree['f'] = {};455tree[dupItems['adup2']] = {};456457store.add(target.onRefresh.event(() => {458testObject.$getChildren('testNodeTreeProvider')459.then(elements => {460const actuals = unBatchChildren(elements)?.map(e => e.handle);461assert.deepStrictEqual(actuals, ['0/0:a', '0/0:b', '0/1:a', '0/0:d', '0/1:b', '0/0:f', '0/2:a']);462return testObject.$getChildren('testNodeTreeProvider', ['0/1:b'])463.then(elements => {464const actuals = unBatchChildren(elements)?.map(e => e.handle);465assert.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']);466done();467});468});469}));470471onDidChangeTreeNode.fire(undefined);472});473474test('getChildren is not returned from cache if refreshed', (done) => {475tree = {476'c': {}477};478479store.add(target.onRefresh.event(() => {480testObject.$getChildren('testNodeTreeProvider')481.then(elements => {482assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:c']);483done();484});485}));486487onDidChangeTreeNode.fire(undefined);488});489490test('getChildren is returned from cache if not refreshed', () => {491tree = {492'c': {}493};494495return testObject.$getChildren('testNodeTreeProvider')496.then(elements => {497assert.deepStrictEqual(unBatchChildren(elements)?.map(e => e.handle), ['0/0:a', '0/0:b']);498});499});500501test('reveal will throw an error if getParent is not implemented', () => {502const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aNodeTreeDataProvider() }, extensionsDescription);503return treeView.reveal({ key: 'a' })504.then(() => assert.fail('Reveal should throw an error as getParent is not implemented'), () => null);505});506507test('reveal will return empty array for root element', () => {508const revealTarget = sinon.spy(target, '$reveal');509const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);510const expected = {511item:512{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed },513parentChain: []514};515return treeView.reveal({ key: 'a' })516.then(() => {517assert.ok(revealTarget.calledOnce);518assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);519assert.deepStrictEqual(expected, removeUnsetKeys(revealTarget.args[0][1]));520assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]);521});522});523524test('reveal will return parents array for an element when hierarchy is not loaded', () => {525const revealTarget = sinon.spy(target, '$reveal');526const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);527const expected = {528item: { handle: '0/0:a/0:aa', label: { label: 'aa' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' },529parentChain: [{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }]530};531return treeView.reveal({ key: 'aa' })532.then(() => {533assert.ok(revealTarget.calledOnce);534assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);535assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item));536assert.deepStrictEqual(expected.parentChain, (<Array<any>>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg)));537assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]);538});539});540541test('reveal will return parents array for an element when hierarchy is loaded', () => {542const revealTarget = sinon.spy(target, '$reveal');543const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);544const expected = {545item: { handle: '0/0:a/0:aa', label: { label: 'aa' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' },546parentChain: [{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }]547};548return testObject.$getChildren('treeDataProvider')549.then(() => testObject.$getChildren('treeDataProvider', ['0/0:a']))550.then(() => treeView.reveal({ key: 'aa' })551.then(() => {552assert.ok(revealTarget.calledOnce);553assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);554assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item));555assert.deepStrictEqual(expected.parentChain, (<Array<any>>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg)));556assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]);557}));558});559560test('reveal will return parents array for deeper element with no selection', () => {561tree = {562'b': {563'ba': {564'bac': {}565}566}567};568const revealTarget = sinon.spy(target, '$reveal');569const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);570const expected = {571item: { handle: '0/0:b/0:ba/0:bac', label: { label: 'bac' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b/0:ba' },572parentChain: [573{ handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed },574{ handle: '0/0:b/0:ba', label: { label: 'ba' }, collapsibleState: TreeItemCollapsibleState.Collapsed, parentHandle: '0/0:b' }575]576};577return treeView.reveal({ key: 'bac' }, { select: false, focus: false, expand: false })578.then(() => {579assert.ok(revealTarget.calledOnce);580assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);581assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item));582assert.deepStrictEqual(expected.parentChain, (<Array<any>>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg)));583assert.deepStrictEqual({ select: false, focus: false, expand: false }, revealTarget.args[0][2]);584});585});586587test('reveal after first udpate', () => {588const revealTarget = sinon.spy(target, '$reveal');589const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);590const expected = {591item: { handle: '0/0:a/0:ac', label: { label: 'ac' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' },592parentChain: [{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }]593};594return loadCompleteTree('treeDataProvider')595.then(() => {596tree = {597'a': {598'aa': {},599'ac': {}600},601'b': {602'ba': {},603'bb': {}604}605};606onDidChangeTreeNode.fire(getNode('a'));607608return treeView.reveal({ key: 'ac' })609.then(() => {610assert.ok(revealTarget.calledOnce);611assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);612assert.deepStrictEqual(expected.item, removeUnsetKeys(revealTarget.args[0][1]!.item));613assert.deepStrictEqual(expected.parentChain, (<Array<any>>(revealTarget.args[0][1]!.parentChain)).map(arg => removeUnsetKeys(arg)));614assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]);615});616});617});618619test('reveal after second udpate', () => {620const revealTarget = sinon.spy(target, '$reveal');621const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, extensionsDescription);622return loadCompleteTree('treeDataProvider')623.then(() => {624return runWithEventMerging((resolve) => {625tree = {626'a': {627'aa': {},628'ac': {}629},630'b': {631'ba': {},632'bb': {}633}634};635onDidChangeTreeNode.fire(getNode('a'));636tree = {637'a': {638'aa': {},639'ac': {}640},641'b': {642'ba': {},643'bc': {}644}645};646onDidChangeTreeNode.fire(getNode('b'));647resolve();648}).then(() => {649return treeView.reveal({ key: 'bc' })650.then(() => {651assert.ok(revealTarget.calledOnce);652assert.deepStrictEqual('treeDataProvider', revealTarget.args[0][0]);653assert.deepStrictEqual({ handle: '0/0:b/0:bc', label: { label: 'bc' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b' }, removeUnsetKeys(revealTarget.args[0][1]!.item));654assert.deepStrictEqual([{ handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (<Array<any>>revealTarget.args[0][1]!.parentChain).map(arg => removeUnsetKeys(arg)));655assert.deepStrictEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][2]);656});657});658});659});660661function loadCompleteTree(treeId: string, element?: string): Promise<null> {662return testObject.$getChildren(treeId, element ? [element] : undefined)663.then(elements => {664if (!elements || elements?.length === 0) {665return null;666}667return elements[0].slice(1).map(e => loadCompleteTree(treeId, (e as ITreeItem).handle));668})669.then(() => null);670}671672function removeUnsetKeys(obj: any): any {673if (Array.isArray(obj)) {674return obj.map(o => removeUnsetKeys(o));675}676677if (typeof obj === 'object') {678const result: { [key: string]: any } = {};679for (const key of Object.keys(obj)) {680if (obj[key] !== undefined) {681result[key] = removeUnsetKeys(obj[key]);682}683}684return result;685}686return obj;687}688689function aNodeTreeDataProvider(): TreeDataProvider<{ key: string }> {690return {691getChildren: (element: { key: string }): { key: string }[] => {692return getChildren(element ? element.key : undefined).map(key => getNode(key));693},694getTreeItem: (element: { key: string }): TreeItem => {695return getTreeItem(element.key);696},697onDidChangeTreeData: onDidChangeTreeNode.event698};699}700701function aCompleteNodeTreeDataProvider(): TreeDataProvider<{ key: string }> {702return {703getChildren: (element: { key: string }): { key: string }[] => {704return getChildren(element ? element.key : undefined).map(key => getNode(key));705},706getTreeItem: (element: { key: string }): TreeItem => {707return getTreeItem(element.key);708},709getParent: ({ key }: { key: string }): { key: string } | undefined => {710const parentKey = key.substring(0, key.length - 1);711return parentKey ? new Key(parentKey) : undefined;712},713onDidChangeTreeData: onDidChangeTreeNode.event714};715}716717function aNodeWithIdTreeDataProvider(): TreeDataProvider<{ key: string }> {718return {719getChildren: (element: { key: string }): { key: string }[] => {720return getChildren(element ? element.key : undefined).map(key => getNode(key));721},722getTreeItem: (element: { key: string }): TreeItem => {723const treeItem = getTreeItem(element.key);724treeItem.id = element.key;725return treeItem;726},727onDidChangeTreeData: onDidChangeTreeNodeWithId.event728};729}730731function aNodeWithHighlightedLabelTreeDataProvider(): TreeDataProvider<{ key: string }> {732return {733getChildren: (element: { key: string }): { key: string }[] => {734return getChildren(element ? element.key : undefined).map(key => getNode(key));735},736getTreeItem: (element: { key: string }): TreeItem => {737const treeItem = getTreeItem(element.key, [[0, 2], [3, 5]]);738treeItem.id = element.key;739return treeItem;740},741onDidChangeTreeData: onDidChangeTreeNodeWithId.event742};743}744745function getTreeElement(element: string): any {746let parent = tree;747for (let i = 0; i < element.length; i++) {748parent = parent[element.substring(0, i + 1)];749if (!parent) {750return null;751}752}753return parent;754}755756function getChildren(key: string | undefined): string[] {757if (!key) {758return Object.keys(tree);759}760const treeElement = getTreeElement(key);761if (treeElement) {762return Object.keys(treeElement);763}764return [];765}766767function getTreeItem(key: string, highlights?: [number, number][]): TreeItem {768const treeElement = getTreeElement(key);769return {770label: <any>{ label: labels[key] || key, highlights },771collapsibleState: treeElement && Object.keys(treeElement).length ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None772};773}774775function getNode(key: string): { key: string } {776if (!nodes[key]) {777nodes[key] = new Key(key);778}779return nodes[key];780}781782class Key {783constructor(readonly key: string) { }784}785786});787788789