mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-09 01:32:34 -05:00
1663 lines
50 KiB
TypeScript
1663 lines
50 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as assert from 'assert';
|
|
import * as lifecycle from 'vs/base/common/lifecycle';
|
|
import * as _ from 'vs/base/parts/tree/browser/tree';
|
|
import * as model from 'vs/base/parts/tree/browser/treeModel';
|
|
import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults';
|
|
import { Event, Emitter } from 'vs/base/common/event';
|
|
import { timeout } from 'vs/base/common/async';
|
|
|
|
export class FakeRenderer {
|
|
|
|
public getHeight(tree: _.ITree, element: any): number {
|
|
return 20;
|
|
}
|
|
|
|
public getTemplateId(tree: _.ITree, element: any): string {
|
|
return 'fake';
|
|
}
|
|
|
|
public renderTemplate(tree: _.ITree, templateId: string, container: any): any {
|
|
return null;
|
|
}
|
|
|
|
public renderElement(tree: _.ITree, element: any, templateId: string, templateData: any): void {
|
|
// noop
|
|
}
|
|
|
|
public disposeTemplate(tree: _.ITree, templateId: string, templateData: any): void {
|
|
// noop
|
|
}
|
|
}
|
|
|
|
class TreeContext implements _.ITreeContext {
|
|
|
|
public tree: _.ITree = null!;
|
|
public options: _.ITreeOptions = { autoExpandSingleChildren: true };
|
|
public dataSource: _.IDataSource;
|
|
public renderer: _.IRenderer;
|
|
public controller?: _.IController;
|
|
public dnd?: _.IDragAndDrop;
|
|
public filter: _.IFilter;
|
|
public sorter: _.ISorter;
|
|
|
|
constructor(public configuration: _.ITreeConfiguration) {
|
|
this.dataSource = configuration.dataSource;
|
|
this.renderer = configuration.renderer || new FakeRenderer();
|
|
this.controller = configuration.controller;
|
|
this.dnd = configuration.dnd;
|
|
this.filter = configuration.filter || new TreeDefaults.DefaultFilter();
|
|
this.sorter = configuration.sorter || new TreeDefaults.DefaultSorter();
|
|
}
|
|
}
|
|
|
|
class TreeModel extends model.TreeModel {
|
|
|
|
constructor(configuration: _.ITreeConfiguration) {
|
|
super(new TreeContext(configuration));
|
|
}
|
|
}
|
|
|
|
class EventCounter {
|
|
|
|
private listeners: lifecycle.IDisposable[];
|
|
private _count: number;
|
|
|
|
constructor() {
|
|
this.listeners = [];
|
|
this._count = 0;
|
|
}
|
|
|
|
public listen<T>(event: Event<T>, fn: ((e: T) => void) | null = null): () => void {
|
|
let r = event(data => {
|
|
this._count++;
|
|
if (fn) {
|
|
fn(data);
|
|
}
|
|
});
|
|
|
|
this.listeners.push(r);
|
|
|
|
return () => {
|
|
let idx = this.listeners.indexOf(r);
|
|
if (idx > -1) {
|
|
this.listeners.splice(idx, 1);
|
|
r.dispose();
|
|
}
|
|
};
|
|
}
|
|
|
|
public up(): void {
|
|
this._count++;
|
|
}
|
|
|
|
public get count(): number {
|
|
return this._count;
|
|
}
|
|
|
|
public dispose(): void {
|
|
this.listeners = lifecycle.dispose(this.listeners);
|
|
this._count = -1;
|
|
}
|
|
}
|
|
|
|
const SAMPLE: any = {
|
|
ONE: { id: 'one' },
|
|
|
|
AB: {
|
|
id: 'ROOT', children: [
|
|
{
|
|
id: 'a', children: [
|
|
{ id: 'aa' },
|
|
{ id: 'ab' }
|
|
]
|
|
},
|
|
{ id: 'b' },
|
|
{
|
|
id: 'c', children: [
|
|
{ id: 'ca' },
|
|
{ id: 'cb' }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
|
|
DEEP: {
|
|
id: 'ROOT', children: [
|
|
{
|
|
id: 'a', children: [
|
|
{
|
|
id: 'x', children: [
|
|
{ id: 'xa' },
|
|
{ id: 'xb' },
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{ id: 'b' }
|
|
]
|
|
},
|
|
|
|
DEEP2: {
|
|
id: 'ROOT', children: [
|
|
{
|
|
id: 'a', children: [
|
|
{
|
|
id: 'x', children: [
|
|
{ id: 'xa' },
|
|
{ id: 'xb' },
|
|
]
|
|
},
|
|
{ id: 'y' }
|
|
]
|
|
},
|
|
{ id: 'b' }
|
|
]
|
|
}
|
|
};
|
|
|
|
class TestDataSource implements _.IDataSource {
|
|
public getId(tree: _.ITree, element: any): string {
|
|
return element.id;
|
|
}
|
|
|
|
public hasChildren(tree: _.ITree, element: any): boolean {
|
|
return !!element.children;
|
|
}
|
|
|
|
public getChildren(tree: _.ITree, element: any): Promise<any> {
|
|
return Promise.resolve(element.children);
|
|
}
|
|
|
|
public getParent(tree: _.ITree, element: any): Promise<any> {
|
|
throw new Error('Not implemented');
|
|
}
|
|
}
|
|
|
|
suite('TreeModel', () => {
|
|
let model: model.TreeModel;
|
|
let counter: EventCounter;
|
|
|
|
setup(() => {
|
|
counter = new EventCounter();
|
|
model = new TreeModel({
|
|
dataSource: new TestDataSource()
|
|
});
|
|
});
|
|
|
|
teardown(() => {
|
|
counter.dispose();
|
|
model.dispose();
|
|
});
|
|
|
|
test('setInput, getInput', () => {
|
|
model.setInput(SAMPLE.ONE);
|
|
assert.strictEqual(model.getInput(), SAMPLE.ONE);
|
|
});
|
|
|
|
test('refresh() refreshes all', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
counter.listen(model.onRefresh); // 1
|
|
counter.listen(model.onDidRefresh); // 1
|
|
counter.listen(model.onDidRefreshItem); // 4
|
|
counter.listen(model.onRefreshItemChildren); // 1
|
|
counter.listen(model.onDidRefreshItemChildren); // 1
|
|
return model.refresh(null);
|
|
}).then(() => {
|
|
assert.strictEqual(counter.count, 8);
|
|
});
|
|
});
|
|
|
|
test('refresh(root) refreshes all', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
counter.listen(model.onRefresh); // 1
|
|
counter.listen(model.onDidRefresh); // 1
|
|
counter.listen(model.onDidRefreshItem); // 4
|
|
counter.listen(model.onRefreshItemChildren); // 1
|
|
counter.listen(model.onDidRefreshItemChildren); // 1
|
|
return model.refresh(SAMPLE.AB);
|
|
}).then(() => {
|
|
assert.strictEqual(counter.count, 8);
|
|
});
|
|
});
|
|
|
|
test('refresh(root, false) refreshes the root', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
counter.listen(model.onRefresh); // 1
|
|
counter.listen(model.onDidRefresh); // 1
|
|
counter.listen(model.onDidRefreshItem); // 1
|
|
counter.listen(model.onRefreshItemChildren); // 1
|
|
counter.listen(model.onDidRefreshItemChildren); // 1
|
|
return model.refresh(SAMPLE.AB, false);
|
|
}).then(() => {
|
|
assert.strictEqual(counter.count, 5);
|
|
});
|
|
});
|
|
|
|
test('refresh(collapsed element) does not refresh descendants', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
counter.listen(model.onRefresh); // 1
|
|
counter.listen(model.onDidRefresh); // 1
|
|
counter.listen(model.onDidRefreshItem); // 1
|
|
counter.listen(model.onRefreshItemChildren); // 0
|
|
counter.listen(model.onDidRefreshItemChildren); // 0
|
|
return model.refresh(SAMPLE.AB.children[0]);
|
|
}).then(() => {
|
|
assert.strictEqual(counter.count, 3);
|
|
});
|
|
});
|
|
|
|
test('refresh(expanded element) refreshes the element and descendants', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expand(SAMPLE.AB.children[0]).then(() => {
|
|
counter.listen(model.onRefresh); // 1
|
|
counter.listen(model.onDidRefresh); // 1
|
|
counter.listen(model.onDidRefreshItem); // 3
|
|
counter.listen(model.onRefreshItemChildren); // 1
|
|
counter.listen(model.onDidRefreshItemChildren); // 1
|
|
return model.refresh(SAMPLE.AB.children[0]);
|
|
});
|
|
}).then(() => {
|
|
assert.strictEqual(counter.count, 7);
|
|
});
|
|
});
|
|
|
|
test('refresh(element, false) refreshes the element', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expand(SAMPLE.AB.children[0]).then(() => {
|
|
counter.listen(model.onRefresh); // 1
|
|
counter.listen(model.onDidRefresh); // 1
|
|
counter.listen(model.onDidRefreshItem, item => { // 1
|
|
assert.strictEqual(item.id, 'a');
|
|
counter.up();
|
|
});
|
|
counter.listen(model.onRefreshItemChildren); // 1
|
|
counter.listen(model.onDidRefreshItemChildren); // 1
|
|
return model.refresh(SAMPLE.AB.children[0], false);
|
|
});
|
|
}).then(() => {
|
|
assert.strictEqual(counter.count, 6);
|
|
});
|
|
});
|
|
|
|
test('depths', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expandAll(['a', 'c']).then(() => {
|
|
counter.listen(model.onDidRefreshItem, item => {
|
|
switch (item.id) {
|
|
case 'ROOT': assert.strictEqual(item.getDepth(), 0); break;
|
|
case 'a': assert.strictEqual(item.getDepth(), 1); break;
|
|
case 'aa': assert.strictEqual(item.getDepth(), 2); break;
|
|
case 'ab': assert.strictEqual(item.getDepth(), 2); break;
|
|
case 'b': assert.strictEqual(item.getDepth(), 1); break;
|
|
case 'c': assert.strictEqual(item.getDepth(), 1); break;
|
|
case 'ca': assert.strictEqual(item.getDepth(), 2); break;
|
|
case 'cb': assert.strictEqual(item.getDepth(), 2); break;
|
|
default: return;
|
|
}
|
|
counter.up();
|
|
});
|
|
|
|
return model.refresh();
|
|
});
|
|
}).then(() => {
|
|
assert.strictEqual(counter.count, 16);
|
|
});
|
|
});
|
|
|
|
test('intersections', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expandAll(['a', 'c']).then(() => {
|
|
// going internals
|
|
const r = (<any>model).registry;
|
|
|
|
assert(r.getItem('a').intersects(r.getItem('a')));
|
|
assert(r.getItem('a').intersects(r.getItem('aa')));
|
|
assert(r.getItem('a').intersects(r.getItem('ab')));
|
|
assert(r.getItem('aa').intersects(r.getItem('a')));
|
|
assert(r.getItem('ab').intersects(r.getItem('a')));
|
|
assert(!r.getItem('aa').intersects(r.getItem('ab')));
|
|
assert(!r.getItem('a').intersects(r.getItem('b')));
|
|
assert(!r.getItem('a').intersects(r.getItem('c')));
|
|
assert(!r.getItem('a').intersects(r.getItem('ca')));
|
|
assert(!r.getItem('aa').intersects(r.getItem('ca')));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('TreeModel - TreeNavigator', () => {
|
|
let model: model.TreeModel;
|
|
let counter: EventCounter;
|
|
|
|
setup(() => {
|
|
counter = new EventCounter();
|
|
model = new TreeModel({
|
|
dataSource: new TestDataSource()
|
|
});
|
|
});
|
|
|
|
teardown(() => {
|
|
counter.dispose();
|
|
model.dispose();
|
|
});
|
|
|
|
test('next()', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
const nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'a');
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
});
|
|
});
|
|
|
|
test('previous()', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
const nav = model.getNavigator();
|
|
|
|
nav.next();
|
|
nav.next();
|
|
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.previous()!.id, 'b');
|
|
assert.strictEqual(nav.previous()!.id, 'a');
|
|
assert.strictEqual(nav.previous() && false, null);
|
|
});
|
|
});
|
|
|
|
test('parent()', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => {
|
|
const nav = model.getNavigator();
|
|
|
|
assert.strictEqual(nav.next()!.id, 'a');
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.parent()!.id, 'a');
|
|
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.parent()!.id, 'a');
|
|
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.next()!.id, 'ca');
|
|
assert.strictEqual(nav.parent()!.id, 'c');
|
|
|
|
assert.strictEqual(nav.parent() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('next() - scoped', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
const nav = model.getNavigator(SAMPLE.AB.children[0]);
|
|
return model.expand({ id: 'a' }).then(() => {
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('previous() - scoped', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
const nav = model.getNavigator(SAMPLE.AB.children[0]);
|
|
return model.expand({ id: 'a' }).then(() => {
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.previous()!.id, 'aa');
|
|
assert.strictEqual(nav.previous() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('parent() - scoped', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => {
|
|
const nav = model.getNavigator(SAMPLE.AB.children[0]);
|
|
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.parent() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('next() - non sub tree only', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
const nav = model.getNavigator(SAMPLE.AB.children[0], false);
|
|
return model.expand({ id: 'a' }).then(() => {
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('previous() - non sub tree only', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
const nav = model.getNavigator(SAMPLE.AB.children[0], false);
|
|
return model.expand({ id: 'a' }).then(() => {
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.previous()!.id, 'b');
|
|
assert.strictEqual(nav.previous()!.id, 'ab');
|
|
assert.strictEqual(nav.previous()!.id, 'aa');
|
|
assert.strictEqual(nav.previous()!.id, 'a');
|
|
assert.strictEqual(nav.previous() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('parent() - non sub tree only', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => {
|
|
const nav = model.getNavigator(SAMPLE.AB.children[0], false);
|
|
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.parent()!.id, 'a');
|
|
assert.strictEqual(nav.parent() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('deep next() - scoped', () => {
|
|
return model.setInput(SAMPLE.DEEP).then(() => {
|
|
return model.expand(SAMPLE.DEEP.children[0]).then(() => {
|
|
return model.expand(SAMPLE.DEEP.children[0].children[0]).then(() => {
|
|
const nav = model.getNavigator(SAMPLE.DEEP.children[0].children[0]);
|
|
assert.strictEqual(nav.next()!.id, 'xa');
|
|
assert.strictEqual(nav.next()!.id, 'xb');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('deep previous() - scoped', () => {
|
|
return model.setInput(SAMPLE.DEEP).then(() => {
|
|
return model.expand(SAMPLE.DEEP.children[0]).then(() => {
|
|
return model.expand(SAMPLE.DEEP.children[0].children[0]).then(() => {
|
|
const nav = model.getNavigator(SAMPLE.DEEP.children[0].children[0]);
|
|
assert.strictEqual(nav.next()!.id, 'xa');
|
|
assert.strictEqual(nav.next()!.id, 'xb');
|
|
assert.strictEqual(nav.previous()!.id, 'xa');
|
|
assert.strictEqual(nav.previous() && false, null);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('last()', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => {
|
|
const nav = model.getNavigator();
|
|
assert.strictEqual(nav.last()!.id, 'cb');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('TreeModel - Expansion', () => {
|
|
let model: model.TreeModel;
|
|
let counter: EventCounter;
|
|
|
|
setup(() => {
|
|
counter = new EventCounter();
|
|
model = new TreeModel({
|
|
dataSource: new TestDataSource()
|
|
});
|
|
});
|
|
|
|
teardown(() => {
|
|
counter.dispose();
|
|
model.dispose();
|
|
});
|
|
|
|
test('collapse, expand', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
counter.listen(model.onExpandItem, (e) => {
|
|
assert.strictEqual(e.item.id, 'a');
|
|
const nav = model.getNavigator(e.item);
|
|
assert.strictEqual(nav.next() && false, null);
|
|
});
|
|
|
|
counter.listen(model.onDidExpandItem, (e) => {
|
|
assert.strictEqual(e.item.id, 'a');
|
|
const nav = model.getNavigator(e.item);
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
});
|
|
|
|
assert(!model.isExpanded(SAMPLE.AB.children[0]));
|
|
|
|
let nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'a');
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
assert.strictEqual(model.getExpandedElements().length, 0);
|
|
|
|
return model.expand(SAMPLE.AB.children[0]).then(() => {
|
|
assert(model.isExpanded(SAMPLE.AB.children[0]));
|
|
|
|
nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'a');
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
const expandedElements = model.getExpandedElements();
|
|
assert.strictEqual(expandedElements.length, 1);
|
|
assert.strictEqual(expandedElements[0].id, 'a');
|
|
|
|
assert.strictEqual(counter.count, 2);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('toggleExpansion', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
assert(!model.isExpanded(SAMPLE.AB.children[0]));
|
|
|
|
return model.toggleExpansion(SAMPLE.AB.children[0]).then(() => {
|
|
assert(model.isExpanded(SAMPLE.AB.children[0]));
|
|
assert(!model.isExpanded(SAMPLE.AB.children[0].children[0]));
|
|
|
|
return model.toggleExpansion(SAMPLE.AB.children[0].children[0]).then(() => {
|
|
assert(!model.isExpanded(SAMPLE.AB.children[0].children[0]));
|
|
|
|
return model.toggleExpansion(SAMPLE.AB.children[0]).then(() => {
|
|
assert(!model.isExpanded(SAMPLE.AB.children[0]));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('collapseAll', () => {
|
|
return model.setInput(SAMPLE.DEEP2).then(() => {
|
|
return model.expand(SAMPLE.DEEP2.children[0]).then(() => {
|
|
return model.expand(SAMPLE.DEEP2.children[0].children[0]).then(() => {
|
|
|
|
assert(model.isExpanded(SAMPLE.DEEP2.children[0]));
|
|
assert(model.isExpanded(SAMPLE.DEEP2.children[0].children[0]));
|
|
|
|
return model.collapseAll().then(() => {
|
|
assert(!model.isExpanded(SAMPLE.DEEP2.children[0]));
|
|
|
|
return model.expand(SAMPLE.DEEP2.children[0]).then(() => {
|
|
assert(!model.isExpanded(SAMPLE.DEEP2.children[0].children[0]));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('auto expand single child folders', () => {
|
|
return model.setInput(SAMPLE.DEEP).then(() => {
|
|
return model.expand(SAMPLE.DEEP.children[0]).then(() => {
|
|
assert(model.isExpanded(SAMPLE.DEEP.children[0]));
|
|
assert(model.isExpanded(SAMPLE.DEEP.children[0].children[0]));
|
|
});
|
|
});
|
|
});
|
|
|
|
test('expand can trigger refresh', () => {
|
|
// MUnit.expect(16);
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
|
|
assert(!model.isExpanded(SAMPLE.AB.children[0]));
|
|
|
|
let nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'a');
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
const f: () => void = counter.listen(model.onRefreshItemChildren, (e) => {
|
|
assert.strictEqual(e.item.id, 'a');
|
|
f();
|
|
});
|
|
|
|
const g: () => void = counter.listen(model.onDidRefreshItemChildren, (e) => {
|
|
assert.strictEqual(e.item.id, 'a');
|
|
g();
|
|
});
|
|
|
|
return model.expand(SAMPLE.AB.children[0]).then(() => {
|
|
assert(model.isExpanded(SAMPLE.AB.children[0]));
|
|
|
|
nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'a');
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
assert.strictEqual(counter.count, 2);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('top level collapsed', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.collapseAll([{ id: 'a' }, { id: 'b' }, { id: 'c' }]).then(() => {
|
|
const nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'a');
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.previous()!.id, 'b');
|
|
assert.strictEqual(nav.previous()!.id, 'a');
|
|
assert.strictEqual(nav.previous() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('shouldAutoexpand', () => {
|
|
// setup
|
|
const model = new TreeModel({
|
|
dataSource: {
|
|
getId: (_, e) => e,
|
|
hasChildren: (_, e) => true,
|
|
getChildren: (_, e) => {
|
|
if (e === 'root') { return Promise.resolve(['a', 'b', 'c']); }
|
|
if (e === 'b') { return Promise.resolve(['b1']); }
|
|
return Promise.resolve([]);
|
|
},
|
|
getParent: (_, e): Promise<any> => { throw new Error('not implemented'); },
|
|
shouldAutoexpand: (_, e) => e === 'b'
|
|
}
|
|
});
|
|
|
|
return model.setInput('root').then(() => {
|
|
return model.refresh('root', true);
|
|
}).then(() => {
|
|
assert(!model.isExpanded('a'));
|
|
assert(model.isExpanded('b'));
|
|
assert(!model.isExpanded('c'));
|
|
});
|
|
});
|
|
});
|
|
|
|
class TestFilter implements _.IFilter {
|
|
|
|
public fn: (element: any) => boolean;
|
|
|
|
constructor() {
|
|
this.fn = () => true;
|
|
}
|
|
|
|
public isVisible(tree: _.ITree, element: any): boolean {
|
|
return this.fn(element);
|
|
}
|
|
}
|
|
|
|
suite('TreeModel - Filter', () => {
|
|
let model: model.TreeModel;
|
|
let counter: EventCounter;
|
|
let filter: TestFilter;
|
|
|
|
setup(() => {
|
|
counter = new EventCounter();
|
|
filter = new TestFilter();
|
|
model = new TreeModel({
|
|
dataSource: new TestDataSource(),
|
|
filter: filter
|
|
});
|
|
});
|
|
|
|
teardown(() => {
|
|
counter.dispose();
|
|
model.dispose();
|
|
});
|
|
|
|
test('no filter', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
|
|
return model.expandAll([{ id: 'a' }, { id: 'c' }]).then(() => {
|
|
const nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'a');
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.next()!.id, 'ca');
|
|
assert.strictEqual(nav.next()!.id, 'cb');
|
|
|
|
assert.strictEqual(nav.previous()!.id, 'ca');
|
|
assert.strictEqual(nav.previous()!.id, 'c');
|
|
assert.strictEqual(nav.previous()!.id, 'b');
|
|
assert.strictEqual(nav.previous()!.id, 'ab');
|
|
assert.strictEqual(nav.previous()!.id, 'aa');
|
|
assert.strictEqual(nav.previous()!.id, 'a');
|
|
assert.strictEqual(nav.previous() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('filter all', () => {
|
|
filter.fn = () => false;
|
|
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.refresh().then(() => {
|
|
const nav = model.getNavigator();
|
|
assert.strictEqual(nav.next() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('simple filter', () => {
|
|
// hide elements that do not start with 'a'
|
|
filter.fn = (e) => e.id[0] === 'a';
|
|
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expand({ id: 'a' }).then(() => {
|
|
|
|
const nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'a');
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'ab');
|
|
assert.strictEqual(nav.previous()!.id, 'aa');
|
|
assert.strictEqual(nav.previous()!.id, 'a');
|
|
assert.strictEqual(nav.previous() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('simple filter 2', () => {
|
|
// hide 'ab'
|
|
filter.fn = (e) => e.id !== 'ab';
|
|
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expand({ id: 'a' }).then(() => {
|
|
const nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'a');
|
|
assert.strictEqual(nav.next()!.id, 'aa');
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('simple filter, opposite', () => {
|
|
// hide elements that start with 'a'
|
|
filter.fn = (e) => e.id[0] !== 'a';
|
|
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expand({ id: 'c' }).then(() => {
|
|
|
|
const nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.next()!.id, 'ca');
|
|
assert.strictEqual(nav.next()!.id, 'cb');
|
|
assert.strictEqual(nav.previous()!.id, 'ca');
|
|
assert.strictEqual(nav.previous()!.id, 'c');
|
|
assert.strictEqual(nav.previous()!.id, 'b');
|
|
assert.strictEqual(nav.previous() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('simple filter, mischieving', () => {
|
|
// hide the element 'a'
|
|
filter.fn = (e) => e.id !== 'a';
|
|
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
return model.expand({ id: 'c' }).then(() => {
|
|
|
|
const nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'b');
|
|
assert.strictEqual(nav.next()!.id, 'c');
|
|
assert.strictEqual(nav.next()!.id, 'ca');
|
|
assert.strictEqual(nav.next()!.id, 'cb');
|
|
assert.strictEqual(nav.previous()!.id, 'ca');
|
|
assert.strictEqual(nav.previous()!.id, 'c');
|
|
assert.strictEqual(nav.previous()!.id, 'b');
|
|
assert.strictEqual(nav.previous() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('simple filter & previous', () => {
|
|
// hide 'b'
|
|
filter.fn = (e) => e.id !== 'b';
|
|
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
const nav = model.getNavigator({ id: 'c' }, false);
|
|
assert.strictEqual(nav.previous()!.id, 'a');
|
|
assert.strictEqual(nav.previous() && false, null);
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('TreeModel - Traits', () => {
|
|
let model: model.TreeModel;
|
|
let counter: EventCounter;
|
|
|
|
setup(() => {
|
|
counter = new EventCounter();
|
|
model = new TreeModel({
|
|
dataSource: new TestDataSource()
|
|
});
|
|
});
|
|
|
|
teardown(() => {
|
|
counter.dispose();
|
|
model.dispose();
|
|
});
|
|
|
|
test('Selection', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
assert.strictEqual(model.getSelection().length, 0);
|
|
model.select(SAMPLE.AB.children[1]);
|
|
assert(model.isSelected(SAMPLE.AB.children[1]));
|
|
assert.strictEqual(model.getSelection().length, 1);
|
|
model.select(SAMPLE.AB.children[0]);
|
|
assert(model.isSelected(SAMPLE.AB.children[0]));
|
|
assert.strictEqual(model.getSelection().length, 2);
|
|
model.select(SAMPLE.AB.children[2]);
|
|
assert(model.isSelected(SAMPLE.AB.children[2]));
|
|
assert.strictEqual(model.getSelection().length, 3);
|
|
model.deselect(SAMPLE.AB.children[0]);
|
|
assert(!model.isSelected(SAMPLE.AB.children[0]));
|
|
assert.strictEqual(model.getSelection().length, 2);
|
|
model.setSelection([]);
|
|
assert(!model.isSelected(SAMPLE.AB.children[0]));
|
|
assert(!model.isSelected(SAMPLE.AB.children[1]));
|
|
assert(!model.isSelected(SAMPLE.AB.children[2]));
|
|
assert.strictEqual(model.getSelection().length, 0);
|
|
model.selectAll([SAMPLE.AB.children[0], SAMPLE.AB.children[1], SAMPLE.AB.children[2]]);
|
|
assert.strictEqual(model.getSelection().length, 3);
|
|
model.select(SAMPLE.AB.children[0]);
|
|
assert.strictEqual(model.getSelection().length, 3);
|
|
model.deselectAll([SAMPLE.AB.children[0], SAMPLE.AB.children[1], SAMPLE.AB.children[2]]);
|
|
assert.strictEqual(model.getSelection().length, 0);
|
|
model.deselect(SAMPLE.AB.children[0]);
|
|
assert.strictEqual(model.getSelection().length, 0);
|
|
|
|
model.setSelection([SAMPLE.AB.children[0]]);
|
|
assert.strictEqual(model.getSelection().length, 1);
|
|
assert(model.isSelected(SAMPLE.AB.children[0]));
|
|
assert(!model.isSelected(SAMPLE.AB.children[1]));
|
|
assert(!model.isSelected(SAMPLE.AB.children[2]));
|
|
|
|
model.setSelection([SAMPLE.AB.children[0], SAMPLE.AB.children[1], SAMPLE.AB.children[2]]);
|
|
assert.strictEqual(model.getSelection().length, 3);
|
|
assert(model.isSelected(SAMPLE.AB.children[0]));
|
|
assert(model.isSelected(SAMPLE.AB.children[1]));
|
|
assert(model.isSelected(SAMPLE.AB.children[2]));
|
|
|
|
model.setSelection([SAMPLE.AB.children[1], SAMPLE.AB.children[2]]);
|
|
assert.strictEqual(model.getSelection().length, 2);
|
|
assert(!model.isSelected(SAMPLE.AB.children[0]));
|
|
assert(model.isSelected(SAMPLE.AB.children[1]));
|
|
assert(model.isSelected(SAMPLE.AB.children[2]));
|
|
|
|
model.setSelection([]);
|
|
assert.deepStrictEqual(model.getSelection(), []);
|
|
assert.strictEqual(model.getSelection().length, 0);
|
|
assert(!model.isSelected(SAMPLE.AB.children[0]));
|
|
assert(!model.isSelected(SAMPLE.AB.children[1]));
|
|
assert(!model.isSelected(SAMPLE.AB.children[2]));
|
|
|
|
model.selectNext();
|
|
assert.strictEqual(model.getSelection().length, 1);
|
|
assert(model.isSelected(SAMPLE.AB.children[0]));
|
|
|
|
model.selectNext();
|
|
assert.strictEqual(model.getSelection().length, 1);
|
|
assert(model.isSelected(SAMPLE.AB.children[1]));
|
|
|
|
model.selectNext();
|
|
assert.strictEqual(model.getSelection().length, 1);
|
|
assert(model.isSelected(SAMPLE.AB.children[2]));
|
|
|
|
model.selectNext();
|
|
assert.strictEqual(model.getSelection().length, 1);
|
|
assert(model.isSelected(SAMPLE.AB.children[2]));
|
|
|
|
model.selectPrevious();
|
|
assert.strictEqual(model.getSelection().length, 1);
|
|
assert(model.isSelected(SAMPLE.AB.children[1]));
|
|
|
|
model.selectPrevious();
|
|
assert.strictEqual(model.getSelection().length, 1);
|
|
assert(model.isSelected(SAMPLE.AB.children[0]));
|
|
|
|
model.selectPrevious();
|
|
assert.strictEqual(model.getSelection().length, 1);
|
|
assert(model.isSelected(SAMPLE.AB.children[0]));
|
|
|
|
model.selectNext(2);
|
|
assert.strictEqual(model.getSelection().length, 1);
|
|
assert(model.isSelected(SAMPLE.AB.children[2]));
|
|
|
|
model.selectPrevious(4);
|
|
assert.strictEqual(model.getSelection().length, 1);
|
|
assert(model.isSelected(SAMPLE.AB.children[0]));
|
|
|
|
assert.strictEqual(model.isSelected(SAMPLE.AB.children[0]), true);
|
|
assert.strictEqual(model.isSelected(SAMPLE.AB.children[2]), false);
|
|
});
|
|
});
|
|
|
|
test('Focus', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
assert(!model.getFocus());
|
|
model.setFocus(SAMPLE.AB.children[1]);
|
|
assert(model.isFocused(SAMPLE.AB.children[1]));
|
|
assert(model.getFocus());
|
|
model.setFocus(SAMPLE.AB.children[0]);
|
|
assert(model.isFocused(SAMPLE.AB.children[0]));
|
|
assert(model.getFocus());
|
|
model.setFocus(SAMPLE.AB.children[2]);
|
|
assert(model.isFocused(SAMPLE.AB.children[2]));
|
|
assert(model.getFocus());
|
|
model.setFocus();
|
|
assert(!model.isFocused(SAMPLE.AB.children[0]));
|
|
assert(!model.isFocused(SAMPLE.AB.children[1]));
|
|
assert(!model.isFocused(SAMPLE.AB.children[2]));
|
|
assert(!model.getFocus());
|
|
|
|
model.setFocus(SAMPLE.AB.children[0]);
|
|
assert(model.getFocus());
|
|
assert(model.isFocused(SAMPLE.AB.children[0]));
|
|
assert(!model.isFocused(SAMPLE.AB.children[1]));
|
|
assert(!model.isFocused(SAMPLE.AB.children[2]));
|
|
|
|
model.setFocus();
|
|
assert(!model.getFocus());
|
|
assert(!model.isFocused(SAMPLE.AB.children[0]));
|
|
assert(!model.isFocused(SAMPLE.AB.children[1]));
|
|
assert(!model.isFocused(SAMPLE.AB.children[2]));
|
|
|
|
model.focusNext();
|
|
assert(model.getFocus());
|
|
assert(model.isFocused(SAMPLE.AB.children[0]));
|
|
|
|
model.focusNext();
|
|
assert(model.getFocus());
|
|
assert(model.isFocused(SAMPLE.AB.children[1]));
|
|
|
|
model.focusNext();
|
|
assert(model.getFocus());
|
|
assert(model.isFocused(SAMPLE.AB.children[2]));
|
|
|
|
model.focusNext();
|
|
assert(model.getFocus());
|
|
assert(model.isFocused(SAMPLE.AB.children[2]));
|
|
|
|
model.focusPrevious();
|
|
assert(model.getFocus());
|
|
assert(model.isFocused(SAMPLE.AB.children[1]));
|
|
|
|
model.focusPrevious();
|
|
assert(model.getFocus());
|
|
assert(model.isFocused(SAMPLE.AB.children[0]));
|
|
|
|
model.focusPrevious();
|
|
assert(model.getFocus());
|
|
assert(model.isFocused(SAMPLE.AB.children[0]));
|
|
|
|
model.focusNext(2);
|
|
assert(model.getFocus());
|
|
assert(model.isFocused(SAMPLE.AB.children[2]));
|
|
|
|
model.focusPrevious(4);
|
|
assert(model.getFocus());
|
|
assert(model.isFocused(SAMPLE.AB.children[0]));
|
|
|
|
assert.strictEqual(model.isFocused(SAMPLE.AB.children[0]), true);
|
|
assert.strictEqual(model.isFocused(SAMPLE.AB.children[2]), false);
|
|
|
|
model.focusFirst();
|
|
assert(model.isFocused(SAMPLE.AB.children[0]));
|
|
model.focusNth(0);
|
|
assert(model.isFocused(SAMPLE.AB.children[0]));
|
|
model.focusNth(1);
|
|
assert(model.isFocused(SAMPLE.AB.children[1]));
|
|
});
|
|
});
|
|
|
|
test('Highlight', () => {
|
|
return model.setInput(SAMPLE.AB).then(() => {
|
|
assert(!model.getHighlight());
|
|
model.setHighlight(SAMPLE.AB.children[1]);
|
|
assert(model.isHighlighted(SAMPLE.AB.children[1]));
|
|
assert(model.getHighlight());
|
|
model.setHighlight(SAMPLE.AB.children[0]);
|
|
assert(model.isHighlighted(SAMPLE.AB.children[0]));
|
|
assert(model.getHighlight());
|
|
model.setHighlight(SAMPLE.AB.children[2]);
|
|
assert(model.isHighlighted(SAMPLE.AB.children[2]));
|
|
assert(model.getHighlight());
|
|
model.setHighlight();
|
|
assert(!model.isHighlighted(SAMPLE.AB.children[0]));
|
|
assert(!model.isHighlighted(SAMPLE.AB.children[1]));
|
|
assert(!model.isHighlighted(SAMPLE.AB.children[2]));
|
|
assert(!model.getHighlight());
|
|
|
|
model.setHighlight(SAMPLE.AB.children[0]);
|
|
assert(model.getHighlight());
|
|
assert(model.isHighlighted(SAMPLE.AB.children[0]));
|
|
assert(!model.isHighlighted(SAMPLE.AB.children[1]));
|
|
assert(!model.isHighlighted(SAMPLE.AB.children[2]));
|
|
|
|
assert.strictEqual(model.isHighlighted(SAMPLE.AB.children[0]), true);
|
|
assert.strictEqual(model.isHighlighted(SAMPLE.AB.children[2]), false);
|
|
|
|
model.setHighlight();
|
|
assert(!model.getHighlight());
|
|
assert(!model.isHighlighted(SAMPLE.AB.children[0]));
|
|
assert(!model.isHighlighted(SAMPLE.AB.children[1]));
|
|
assert(!model.isHighlighted(SAMPLE.AB.children[2]));
|
|
});
|
|
});
|
|
});
|
|
|
|
class DynamicModel implements _.IDataSource {
|
|
|
|
private data: any;
|
|
public promiseFactory: { (): Promise<any>; } | null;
|
|
|
|
private readonly _onGetChildren = new Emitter<any>();
|
|
readonly onGetChildren: Event<any> = this._onGetChildren.event;
|
|
|
|
private readonly _onDidGetChildren = new Emitter<any>();
|
|
readonly onDidGetChildren: Event<any> = this._onDidGetChildren.event;
|
|
|
|
constructor() {
|
|
this.data = { root: [] };
|
|
this.promiseFactory = null;
|
|
}
|
|
|
|
public addChild(parent: string, child: string): void {
|
|
if (!this.data[parent]) {
|
|
this.data[parent] = [];
|
|
}
|
|
this.data[parent].push(child);
|
|
}
|
|
|
|
public removeChild(parent: string, child: string): void {
|
|
this.data[parent].splice(this.data[parent].indexOf(child), 1);
|
|
if (this.data[parent].length === 0) {
|
|
delete this.data[parent];
|
|
}
|
|
}
|
|
|
|
public move(element: string, oldParent: string, newParent: string): void {
|
|
this.removeChild(oldParent, element);
|
|
this.addChild(newParent, element);
|
|
}
|
|
|
|
public rename(parent: string, oldName: string, newName: string): void {
|
|
this.removeChild(parent, oldName);
|
|
this.addChild(parent, newName);
|
|
}
|
|
|
|
public getId(tree: _.ITree, element: any): string {
|
|
return element;
|
|
}
|
|
|
|
public hasChildren(tree: _.ITree, element: any): boolean {
|
|
return !!this.data[element];
|
|
}
|
|
|
|
public getChildren(tree: _.ITree, element: any): Promise<any> {
|
|
this._onGetChildren.fire(element);
|
|
const result = this.promiseFactory ? this.promiseFactory() : Promise.resolve(null);
|
|
return result.then(() => {
|
|
this._onDidGetChildren.fire(element);
|
|
return Promise.resolve(this.data[element]);
|
|
});
|
|
}
|
|
|
|
public getParent(tree: _.ITree, element: any): Promise<any> {
|
|
throw new Error('Not implemented');
|
|
}
|
|
}
|
|
|
|
suite('TreeModel - Dynamic data model', () => {
|
|
let model: model.TreeModel;
|
|
let dataModel: DynamicModel;
|
|
let counter: EventCounter;
|
|
|
|
setup(() => {
|
|
counter = new EventCounter();
|
|
dataModel = new DynamicModel();
|
|
model = new TreeModel({
|
|
dataSource: dataModel,
|
|
});
|
|
});
|
|
|
|
teardown(() => {
|
|
counter.dispose();
|
|
model.dispose();
|
|
});
|
|
|
|
test('items get property disposed', () => {
|
|
dataModel.addChild('root', 'grandfather');
|
|
dataModel.addChild('grandfather', 'father');
|
|
dataModel.addChild('father', 'son');
|
|
dataModel.addChild('father', 'daughter');
|
|
dataModel.addChild('son', 'baby');
|
|
|
|
return model.setInput('root').then(() => {
|
|
return model.expandAll(['grandfather', 'father', 'son']).then(() => {
|
|
dataModel.removeChild('grandfather', 'father');
|
|
|
|
const items = ['baby', 'son', 'daughter', 'father'];
|
|
let times = 0;
|
|
counter.listen(model.onDidDisposeItem, item => {
|
|
assert.strictEqual(items[times++], item.id);
|
|
});
|
|
|
|
return model.refresh().then(() => {
|
|
assert.strictEqual(times, items.length);
|
|
assert.strictEqual(counter.count, 4);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('addChild, removeChild, collapse', () => {
|
|
dataModel.addChild('root', 'super');
|
|
dataModel.addChild('root', 'hyper');
|
|
dataModel.addChild('root', 'mega');
|
|
|
|
return model.setInput('root').then(() => {
|
|
let nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'super');
|
|
assert.strictEqual(nav.next()!.id, 'hyper');
|
|
assert.strictEqual(nav.next()!.id, 'mega');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
dataModel.removeChild('root', 'hyper');
|
|
return model.refresh().then(() => {
|
|
nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'super');
|
|
assert.strictEqual(nav.next()!.id, 'mega');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
dataModel.addChild('mega', 'micro');
|
|
dataModel.addChild('mega', 'nano');
|
|
dataModel.addChild('mega', 'pico');
|
|
|
|
return model.refresh().then(() => {
|
|
return model.expand('mega').then(() => {
|
|
nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'super');
|
|
assert.strictEqual(nav.next()!.id, 'mega');
|
|
assert.strictEqual(nav.next()!.id, 'micro');
|
|
assert.strictEqual(nav.next()!.id, 'nano');
|
|
assert.strictEqual(nav.next()!.id, 'pico');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
model.collapse('mega');
|
|
nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'super');
|
|
assert.strictEqual(nav.next()!.id, 'mega');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('move', () => {
|
|
dataModel.addChild('root', 'super');
|
|
dataModel.addChild('super', 'apples');
|
|
dataModel.addChild('super', 'bananas');
|
|
dataModel.addChild('super', 'pears');
|
|
dataModel.addChild('root', 'hyper');
|
|
dataModel.addChild('root', 'mega');
|
|
|
|
return model.setInput('root').then(() => {
|
|
|
|
return model.expand('super').then(() => {
|
|
|
|
let nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'super');
|
|
assert.strictEqual(nav.next()!.id, 'apples');
|
|
assert.strictEqual(nav.next()!.id, 'bananas');
|
|
assert.strictEqual(nav.next()!.id, 'pears');
|
|
assert.strictEqual(nav.next()!.id, 'hyper');
|
|
assert.strictEqual(nav.next()!.id, 'mega');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
dataModel.move('bananas', 'super', 'hyper');
|
|
dataModel.move('apples', 'super', 'mega');
|
|
|
|
return model.refresh().then(() => {
|
|
|
|
return model.expandAll(['hyper', 'mega']).then(() => {
|
|
nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'super');
|
|
assert.strictEqual(nav.next()!.id, 'pears');
|
|
assert.strictEqual(nav.next()!.id, 'hyper');
|
|
assert.strictEqual(nav.next()!.id, 'bananas');
|
|
assert.strictEqual(nav.next()!.id, 'mega');
|
|
assert.strictEqual(nav.next()!.id, 'apples');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('refreshing grandfather recursively should not refresh collapsed father\'s children immediately', () => {
|
|
dataModel.addChild('root', 'grandfather');
|
|
dataModel.addChild('grandfather', 'father');
|
|
dataModel.addChild('father', 'son');
|
|
|
|
return model.setInput('root').then(() => {
|
|
return model.expand('grandfather').then(() => {
|
|
return model.collapse('father').then(() => {
|
|
let times = 0;
|
|
let listener = dataModel.onGetChildren((element) => {
|
|
times++;
|
|
assert.strictEqual(element, 'grandfather');
|
|
});
|
|
|
|
return model.refresh('grandfather').then(() => {
|
|
assert.strictEqual(times, 1);
|
|
listener.dispose();
|
|
|
|
listener = dataModel.onGetChildren((element) => {
|
|
times++;
|
|
assert.strictEqual(element, 'father');
|
|
});
|
|
|
|
return model.expand('father').then(() => {
|
|
assert.strictEqual(times, 2);
|
|
listener.dispose();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('simultaneously refreshing two disjoint elements should parallelize the refreshes', () => {
|
|
dataModel.addChild('root', 'father');
|
|
dataModel.addChild('root', 'mother');
|
|
dataModel.addChild('father', 'son');
|
|
dataModel.addChild('mother', 'daughter');
|
|
|
|
return model.setInput('root').then(() => {
|
|
return model.expand('father').then(() => {
|
|
return model.expand('mother').then(() => {
|
|
|
|
let nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'father');
|
|
assert.strictEqual(nav.next()!.id, 'son');
|
|
assert.strictEqual(nav.next()!.id, 'mother');
|
|
assert.strictEqual(nav.next()!.id, 'daughter');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
dataModel.removeChild('father', 'son');
|
|
dataModel.removeChild('mother', 'daughter');
|
|
dataModel.addChild('father', 'brother');
|
|
dataModel.addChild('mother', 'sister');
|
|
|
|
dataModel.promiseFactory = () => { return timeout(0); };
|
|
|
|
let getTimes = 0;
|
|
let gotTimes = 0;
|
|
const getListener = dataModel.onGetChildren((element) => { getTimes++; });
|
|
const gotListener = dataModel.onDidGetChildren((element) => { gotTimes++; });
|
|
|
|
const p1 = model.refresh('father');
|
|
assert.strictEqual(getTimes, 1);
|
|
assert.strictEqual(gotTimes, 0);
|
|
|
|
const p2 = model.refresh('mother');
|
|
assert.strictEqual(getTimes, 2);
|
|
assert.strictEqual(gotTimes, 0);
|
|
|
|
return Promise.all([p1, p2]).then(() => {
|
|
assert.strictEqual(getTimes, 2);
|
|
assert.strictEqual(gotTimes, 2);
|
|
|
|
nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'father');
|
|
assert.strictEqual(nav.next()!.id, 'brother');
|
|
assert.strictEqual(nav.next()!.id, 'mother');
|
|
assert.strictEqual(nav.next()!.id, 'sister');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
getListener.dispose();
|
|
gotListener.dispose();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('simultaneously recursively refreshing two intersecting elements should concatenate the refreshes - ancestor first', () => {
|
|
dataModel.addChild('root', 'grandfather');
|
|
dataModel.addChild('grandfather', 'father');
|
|
dataModel.addChild('father', 'son');
|
|
|
|
return model.setInput('root').then(() => {
|
|
return model.expand('grandfather').then(() => {
|
|
return model.expand('father').then(() => {
|
|
let nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'grandfather');
|
|
assert.strictEqual(nav.next()!.id, 'father');
|
|
assert.strictEqual(nav.next()!.id, 'son');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
let refreshTimes = 0;
|
|
counter.listen(model.onDidRefreshItem, (e) => { refreshTimes++; });
|
|
|
|
let getTimes = 0;
|
|
const getListener = dataModel.onGetChildren((element) => { getTimes++; });
|
|
|
|
let gotTimes = 0;
|
|
const gotListener = dataModel.onDidGetChildren((element) => { gotTimes++; });
|
|
|
|
const p1Completes: Array<(value?: any) => void> = [];
|
|
dataModel.promiseFactory = () => { return new Promise((c) => { p1Completes.push(c); }); };
|
|
|
|
model.refresh('grandfather').then(() => {
|
|
// just a single get
|
|
assert.strictEqual(refreshTimes, 1); // (+1) grandfather
|
|
assert.strictEqual(getTimes, 1);
|
|
assert.strictEqual(gotTimes, 0);
|
|
|
|
// unblock the first get
|
|
p1Completes.shift()!();
|
|
|
|
// once the first get is unblocked, the second get should appear
|
|
assert.strictEqual(refreshTimes, 2); // (+1) first father refresh
|
|
assert.strictEqual(getTimes, 2);
|
|
assert.strictEqual(gotTimes, 1);
|
|
|
|
let p2Complete: () => void;
|
|
dataModel.promiseFactory = () => { return new Promise<void>((c) => { p2Complete = c; }); };
|
|
const p2 = model.refresh('father');
|
|
|
|
// same situation still
|
|
assert.strictEqual(refreshTimes, 3); // (+1) second father refresh
|
|
assert.strictEqual(getTimes, 2);
|
|
assert.strictEqual(gotTimes, 1);
|
|
|
|
// unblock the second get
|
|
p1Completes.shift()!();
|
|
|
|
// the third get should have appeared, it should've been waiting for the second one
|
|
assert.strictEqual(refreshTimes, 4); // (+1) first son request
|
|
assert.strictEqual(getTimes, 3);
|
|
assert.strictEqual(gotTimes, 2);
|
|
|
|
p2Complete!();
|
|
|
|
// all good
|
|
assert.strictEqual(refreshTimes, 5); // (+1) second son request
|
|
assert.strictEqual(getTimes, 3);
|
|
assert.strictEqual(gotTimes, 3);
|
|
|
|
return p2.then(() => {
|
|
nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'grandfather');
|
|
assert.strictEqual(nav.next()!.id, 'father');
|
|
assert.strictEqual(nav.next()!.id, 'son');
|
|
assert.strictEqual(nav.next() && false, null);
|
|
|
|
getListener.dispose();
|
|
gotListener.dispose();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('refreshing an empty element that adds children should still keep it collapsed', () => {
|
|
dataModel.addChild('root', 'grandfather');
|
|
dataModel.addChild('grandfather', 'father');
|
|
|
|
return model.setInput('root').then(() => {
|
|
return model.expand('grandfather').then(() => {
|
|
return model.expand('father').then(() => {
|
|
assert(!model.isExpanded('father'));
|
|
|
|
dataModel.addChild('father', 'son');
|
|
|
|
return model.refresh('father').then(() => {
|
|
assert(!model.isExpanded('father'));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('refreshing a collapsed element that adds children should still keep it collapsed', () => {
|
|
dataModel.addChild('root', 'grandfather');
|
|
dataModel.addChild('grandfather', 'father');
|
|
dataModel.addChild('father', 'son');
|
|
|
|
return model.setInput('root').then(() => {
|
|
return model.expand('grandfather').then(() => {
|
|
return model.expand('father').then(() => {
|
|
return model.collapse('father').then(() => {
|
|
assert(!model.isExpanded('father'));
|
|
|
|
dataModel.addChild('father', 'daughter');
|
|
|
|
return model.refresh('father').then(() => {
|
|
assert(!model.isExpanded('father'));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('recursively refreshing an ancestor of an expanded element, should keep that element expanded', () => {
|
|
dataModel.addChild('root', 'grandfather');
|
|
dataModel.addChild('grandfather', 'father');
|
|
dataModel.addChild('father', 'son');
|
|
|
|
return model.setInput('root').then(() => {
|
|
return model.expand('grandfather').then(() => {
|
|
return model.expand('father').then(() => {
|
|
assert(model.isExpanded('grandfather'));
|
|
assert(model.isExpanded('father'));
|
|
|
|
return model.refresh('grandfather').then(() => {
|
|
assert(model.isExpanded('grandfather'));
|
|
assert(model.isExpanded('father'));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('recursively refreshing an ancestor of a collapsed element, should keep that element collapsed', () => {
|
|
dataModel.addChild('root', 'grandfather');
|
|
dataModel.addChild('grandfather', 'father');
|
|
dataModel.addChild('father', 'son');
|
|
|
|
return model.setInput('root').then(() => {
|
|
return model.expand('grandfather').then(() => {
|
|
return model.expand('father').then(() => {
|
|
return model.collapse('father').then(() => {
|
|
assert(model.isExpanded('grandfather'));
|
|
assert(!model.isExpanded('father'));
|
|
|
|
return model.refresh('grandfather').then(() => {
|
|
assert(model.isExpanded('grandfather'));
|
|
assert(!model.isExpanded('father'));
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
test('Bug 10855:[explorer] quickly deleting things causes NPE in tree - intersectsLock should always be called when trying to unlock', () => {
|
|
dataModel.addChild('root', 'father');
|
|
dataModel.addChild('father', 'son');
|
|
dataModel.addChild('root', 'mother');
|
|
dataModel.addChild('mother', 'daughter');
|
|
|
|
return model.setInput('root').then(() => {
|
|
|
|
// delay expansions and refreshes
|
|
dataModel.promiseFactory = () => { return timeout(0); };
|
|
|
|
const promises: Promise<any>[] = [];
|
|
|
|
promises.push(model.expand('father'));
|
|
dataModel.removeChild('root', 'father');
|
|
promises.push(model.refresh('root'));
|
|
|
|
promises.push(model.expand('mother'));
|
|
dataModel.removeChild('root', 'mother');
|
|
promises.push(model.refresh('root'));
|
|
|
|
return Promise.all(promises).then(() => {
|
|
assert(true, 'all good');
|
|
}, (errs) => {
|
|
assert(false, 'should not fail');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
suite('TreeModel - bugs', () => {
|
|
let counter: EventCounter;
|
|
|
|
setup(() => {
|
|
counter = new EventCounter();
|
|
});
|
|
|
|
teardown(() => {
|
|
counter.dispose();
|
|
});
|
|
|
|
/**
|
|
* This bug occurs when an item is expanded right during its removal
|
|
*/
|
|
test('Bug 10566:[tree] build viewlet is broken after some time', () => {
|
|
// setup
|
|
let model = new TreeModel({
|
|
dataSource: {
|
|
getId: (_, e) => e,
|
|
hasChildren: (_, e) => e === 'root' || e === 'bart',
|
|
getChildren: (_, e) => {
|
|
if (e === 'root') { return getRootChildren(); }
|
|
if (e === 'bart') { return getBartChildren(); }
|
|
return Promise.resolve([]);
|
|
},
|
|
getParent: (_, e): Promise<any> => { throw new Error('not implemented'); },
|
|
}
|
|
});
|
|
|
|
let listeners = <any>[];
|
|
|
|
// helpers
|
|
const getGetRootChildren = (children: string[], millis = 0) => () => timeout(millis).then(() => children);
|
|
let getRootChildren = getGetRootChildren(['homer', 'bart', 'lisa', 'marge', 'maggie'], 0);
|
|
const getGetBartChildren = (millis = 0) => () => timeout(millis).then(() => ['milhouse', 'nelson']);
|
|
const getBartChildren = getGetBartChildren(0);
|
|
|
|
// item expanding should not exist!
|
|
counter.listen(model.onExpandItem, () => { assert(false, 'should never receive item:expanding event'); });
|
|
counter.listen(model.onDidExpandItem, () => { assert(false, 'should never receive item:expanded event'); });
|
|
|
|
return model.setInput('root').then(() => {
|
|
|
|
// remove bart
|
|
getRootChildren = getGetRootChildren(['homer', 'lisa', 'marge', 'maggie'], 10);
|
|
|
|
// refresh root
|
|
const p1 = model.refresh('root', true).then(() => {
|
|
assert(true);
|
|
}, () => {
|
|
assert(false, 'should never reach this');
|
|
});
|
|
|
|
// at the same time, try to expand bart!
|
|
const p2 = model.expand('bart').then(() => {
|
|
assert(false, 'should never reach this');
|
|
}, () => {
|
|
assert(true, 'bart should fail to expand since he was removed meanwhile');
|
|
});
|
|
|
|
// what now?
|
|
return Promise.all([p1, p2]);
|
|
|
|
}).then(() => {
|
|
|
|
// teardown
|
|
while (listeners.length > 0) { listeners.pop()(); }
|
|
listeners = null;
|
|
model.dispose();
|
|
|
|
assert.strictEqual(counter.count, 0);
|
|
});
|
|
});
|
|
|
|
test('collapsed resolved parent should also update all children visibility on refresh', async function () {
|
|
const counter = new EventCounter();
|
|
const dataModel = new DynamicModel();
|
|
|
|
let isSonVisible = true;
|
|
const filter: _.IFilter = {
|
|
isVisible(_, element) {
|
|
return element !== 'son' || isSonVisible;
|
|
}
|
|
};
|
|
|
|
const model = new TreeModel({ dataSource: dataModel, filter });
|
|
|
|
dataModel.addChild('root', 'father');
|
|
dataModel.addChild('father', 'son');
|
|
|
|
await model.setInput('root');
|
|
await model.expand('father');
|
|
|
|
let nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'father');
|
|
assert.strictEqual(nav.next()!.id, 'son');
|
|
assert.strictEqual(nav.next(), null);
|
|
|
|
await model.collapse('father');
|
|
isSonVisible = false;
|
|
|
|
await model.refresh(undefined, true);
|
|
await model.expand('father');
|
|
|
|
nav = model.getNavigator();
|
|
assert.strictEqual(nav.next()!.id, 'father');
|
|
assert.strictEqual(nav.next(), null);
|
|
|
|
counter.dispose();
|
|
model.dispose();
|
|
});
|
|
});
|