Squashed 'lib/vscode/' content from commit e5a624b788

git-subtree-dir: lib/vscode
git-subtree-split: e5a624b788d92b8d34d1392e4c4d9789406efe8f
This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
commit be3e823608
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,438 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ITreeNode, ITreeRenderer, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { timeout } from 'vs/base/common/async';
interface Element {
id: string;
suffix?: string;
children?: Element[];
}
function find(element: Element, id: string): Element | undefined {
if (element.id === id) {
return element;
}
if (!element.children) {
return undefined;
}
for (const child of element.children) {
const result = find(child, id);
if (result) {
return result;
}
}
return undefined;
}
class Renderer implements ITreeRenderer<Element, void, HTMLElement> {
readonly templateId = 'default';
renderTemplate(container: HTMLElement): HTMLElement {
return container;
}
renderElement(element: ITreeNode<Element, void>, index: number, templateData: HTMLElement): void {
templateData.textContent = element.element.id + (element.element.suffix || '');
}
disposeTemplate(templateData: HTMLElement): void {
// noop
}
}
class IdentityProvider implements IIdentityProvider<Element> {
getId(element: Element) {
return element.id;
}
}
class VirtualDelegate implements IListVirtualDelegate<Element> {
getHeight() { return 20; }
getTemplateId(element: Element): string { return 'default'; }
}
class DataSource implements IAsyncDataSource<Element, Element> {
hasChildren(element: Element): boolean {
return !!element.children && element.children.length > 0;
}
getChildren(element: Element): Promise<Element[]> {
return Promise.resolve(element.children || []);
}
}
class Model {
constructor(readonly root: Element) { }
get(id: string): Element {
const result = find(this.root, id);
if (!result) {
throw new Error('element not found');
}
return result;
}
}
suite('AsyncDataTree', function () {
test('Collapse state should be preserved across refresh calls', async () => {
const container = document.createElement('div');
const model = new Model({
id: 'root',
children: [{
id: 'a'
}]
});
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() });
tree.layout(200);
assert.equal(container.querySelectorAll('.monaco-list-row').length, 0);
await tree.setInput(model.root);
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
assert(!twistie.classList.contains('collapsible'));
assert(!twistie.classList.contains('collapsed'));
model.get('a').children = [
{ id: 'aa' },
{ id: 'ab' },
{ id: 'ac' }
];
await tree.updateChildren(model.root);
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
await tree.expand(model.get('a'));
assert.equal(container.querySelectorAll('.monaco-list-row').length, 4);
model.get('a').children = [];
await tree.updateChildren(model.root);
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
});
test('issue #68648', async () => {
const container = document.createElement('div');
const getChildrenCalls: string[] = [];
const dataSource = new class implements IAsyncDataSource<Element, Element> {
hasChildren(element: Element): boolean {
return !!element.children && element.children.length > 0;
}
getChildren(element: Element): Promise<Element[]> {
getChildrenCalls.push(element.id);
return Promise.resolve(element.children || []);
}
};
const model = new Model({
id: 'root',
children: [{
id: 'a'
}]
});
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() });
tree.layout(200);
await tree.setInput(model.root);
assert.deepStrictEqual(getChildrenCalls, ['root']);
let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
assert(!twistie.classList.contains('collapsible'));
assert(!twistie.classList.contains('collapsed'));
assert(tree.getNode().children[0].collapsed);
model.get('a').children = [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }];
await tree.updateChildren(model.root);
assert.deepStrictEqual(getChildrenCalls, ['root', 'root']);
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
assert(twistie.classList.contains('collapsible'));
assert(twistie.classList.contains('collapsed'));
assert(tree.getNode().children[0].collapsed);
model.get('a').children = [];
await tree.updateChildren(model.root);
assert.deepStrictEqual(getChildrenCalls, ['root', 'root', 'root']);
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
assert(!twistie.classList.contains('collapsible'));
assert(!twistie.classList.contains('collapsed'));
assert(tree.getNode().children[0].collapsed);
model.get('a').children = [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }];
await tree.updateChildren(model.root);
assert.deepStrictEqual(getChildrenCalls, ['root', 'root', 'root', 'root']);
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
assert(twistie.classList.contains('collapsible'));
assert(twistie.classList.contains('collapsed'));
assert(tree.getNode().children[0].collapsed);
});
test('issue #67722 - once resolved, refreshed collapsed nodes should only get children when expanded', async () => {
const container = document.createElement('div');
const getChildrenCalls: string[] = [];
const dataSource = new class implements IAsyncDataSource<Element, Element> {
hasChildren(element: Element): boolean {
return !!element.children && element.children.length > 0;
}
getChildren(element: Element): Promise<Element[]> {
getChildrenCalls.push(element.id);
return Promise.resolve(element.children || []);
}
};
const model = new Model({
id: 'root',
children: [{
id: 'a', children: [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }]
}]
});
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() });
tree.layout(200);
await tree.setInput(model.root);
assert(tree.getNode(model.get('a')).collapsed);
assert.deepStrictEqual(getChildrenCalls, ['root']);
await tree.expand(model.get('a'));
assert(!tree.getNode(model.get('a')).collapsed);
assert.deepStrictEqual(getChildrenCalls, ['root', 'a']);
tree.collapse(model.get('a'));
assert(tree.getNode(model.get('a')).collapsed);
assert.deepStrictEqual(getChildrenCalls, ['root', 'a']);
await tree.updateChildren();
assert(tree.getNode(model.get('a')).collapsed);
assert.deepStrictEqual(getChildrenCalls, ['root', 'a', 'root'], 'a should not be refreshed, since it\' collapsed');
});
test('resolved collapsed nodes which lose children should lose twistie as well', async () => {
const container = document.createElement('div');
const model = new Model({
id: 'root',
children: [{
id: 'a', children: [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }]
}]
});
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() });
tree.layout(200);
await tree.setInput(model.root);
await tree.expand(model.get('a'));
let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
assert(twistie.classList.contains('collapsible'));
assert(!twistie.classList.contains('collapsed'));
assert(!tree.getNode(model.get('a')).collapsed);
tree.collapse(model.get('a'));
model.get('a').children = [];
await tree.updateChildren(model.root);
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
assert(!twistie.classList.contains('collapsible'));
assert(!twistie.classList.contains('collapsed'));
assert(tree.getNode(model.get('a')).collapsed);
});
test('support default collapse state per element', async () => {
const container = document.createElement('div');
const getChildrenCalls: string[] = [];
const dataSource = new class implements IAsyncDataSource<Element, Element> {
hasChildren(element: Element): boolean {
return !!element.children && element.children.length > 0;
}
getChildren(element: Element): Promise<Element[]> {
getChildrenCalls.push(element.id);
return Promise.resolve(element.children || []);
}
};
const model = new Model({
id: 'root',
children: [{
id: 'a', children: [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }]
}]
});
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, {
collapseByDefault: el => el.id !== 'a'
});
tree.layout(200);
await tree.setInput(model.root);
assert(!tree.getNode(model.get('a')).collapsed);
assert.deepStrictEqual(getChildrenCalls, ['root', 'a']);
});
test('issue #80098 - concurrent refresh and expand', async () => {
const container = document.createElement('div');
const calls: Function[] = [];
const dataSource = new class implements IAsyncDataSource<Element, Element> {
hasChildren(element: Element): boolean {
return !!element.children && element.children.length > 0;
}
getChildren(element: Element): Promise<Element[]> {
return new Promise(c => calls.push(() => c(element.children || [])));
}
};
const model = new Model({
id: 'root',
children: [{
id: 'a', children: [{
id: 'aa'
}]
}]
});
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() });
tree.layout(200);
const pSetInput = tree.setInput(model.root);
calls.pop()!(); // resolve getChildren(root)
await pSetInput;
const pUpdateChildrenA = tree.updateChildren(model.get('a'));
const pExpandA = tree.expand(model.get('a'));
assert.equal(calls.length, 1, 'expand(a) still hasn\'t called getChildren(a)');
calls.pop()!();
assert.equal(calls.length, 0, 'no pending getChildren calls');
await pUpdateChildrenA;
assert.equal(calls.length, 0, 'expand(a) should not have forced a second refresh');
const result = await pExpandA;
assert.equal(result, true, 'expand(a) should be done');
});
test('issue #80098 - first expand should call getChildren', async () => {
const container = document.createElement('div');
const calls: Function[] = [];
const dataSource = new class implements IAsyncDataSource<Element, Element> {
hasChildren(element: Element): boolean {
return !!element.children && element.children.length > 0;
}
getChildren(element: Element): Promise<Element[]> {
return new Promise(c => calls.push(() => c(element.children || [])));
}
};
const model = new Model({
id: 'root',
children: [{
id: 'a', children: [{
id: 'aa'
}]
}]
});
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], dataSource, { identityProvider: new IdentityProvider() });
tree.layout(200);
const pSetInput = tree.setInput(model.root);
calls.pop()!(); // resolve getChildren(root)
await pSetInput;
const pExpandA = tree.expand(model.get('a'));
assert.equal(calls.length, 1, 'expand(a) should\'ve called getChildren(a)');
let race = await Promise.race([pExpandA.then(() => 'expand'), timeout(1).then(() => 'timeout')]);
assert.equal(race, 'timeout', 'expand(a) should not be yet done');
calls.pop()!();
assert.equal(calls.length, 0, 'no pending getChildren calls');
race = await Promise.race([pExpandA.then(() => 'expand'), timeout(1).then(() => 'timeout')]);
assert.equal(race, 'expand', 'expand(a) should now be done');
});
test('issue #78388 - tree should react to hasChildren toggles', async () => {
const container = document.createElement('div');
const model = new Model({
id: 'root',
children: [{
id: 'a'
}]
});
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() });
tree.layout(200);
await tree.setInput(model.root);
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
assert(!twistie.classList.contains('collapsible'));
assert(!twistie.classList.contains('collapsed'));
model.get('a').children = [{ id: 'aa' }];
await tree.updateChildren(model.get('a'), false);
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
assert(twistie.classList.contains('collapsible'));
assert(twistie.classList.contains('collapsed'));
model.get('a').children = [];
await tree.updateChildren(model.get('a'), false);
assert.equal(container.querySelectorAll('.monaco-list-row').length, 1);
twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement;
assert(!twistie.classList.contains('collapsible'));
assert(!twistie.classList.contains('collapsed'));
});
test('issues #84569, #82629 - rerender', async () => {
const container = document.createElement('div');
const model = new Model({
id: 'root',
children: [{
id: 'a',
children: [{
id: 'b',
suffix: '1'
}]
}]
});
const tree = new AsyncDataTree<Element, Element>('test', container, new VirtualDelegate(), [new Renderer()], new DataSource(), { identityProvider: new IdentityProvider() });
tree.layout(200);
await tree.setInput(model.root);
await tree.expand(model.get('a'));
assert.deepEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b1']);
const a = model.get('a');
const b = model.get('b');
a.children?.splice(0, 1, { id: 'b', suffix: '2' });
await Promise.all([
tree.updateChildren(a, true, true),
tree.updateChildren(b, true, true)
]);
assert.deepEqual(Array.from(container.querySelectorAll('.monaco-list-row')).map(e => e.textContent), ['a', 'b2']);
});
});

View File

@@ -0,0 +1,431 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { compress, ICompressedTreeElement, ICompressedTreeNode, decompress, CompressedObjectTreeModel } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { Iterable } from 'vs/base/common/iterator';
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
interface IResolvedCompressedTreeElement<T> extends ICompressedTreeElement<T> {
readonly element: T;
readonly children?: ICompressedTreeElement<T>[];
}
function resolve<T>(treeElement: ICompressedTreeElement<T>): IResolvedCompressedTreeElement<T> {
const result: any = { element: treeElement.element };
const children = [...Iterable.map(Iterable.from(treeElement.children), resolve)];
if (treeElement.incompressible) {
result.incompressible = true;
}
if (children.length > 0) {
result.children = children;
}
return result;
}
suite('CompressedObjectTree', function () {
suite('compress & decompress', function () {
test('small', function () {
const decompressed: ICompressedTreeElement<number> = { element: 1 };
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> =
{ element: { elements: [1], incompressible: false } };
assert.deepEqual(resolve(compress(decompressed)), compressed);
assert.deepEqual(resolve(decompress(compressed)), decompressed);
});
test('no compression', function () {
const decompressed: ICompressedTreeElement<number> = {
element: 1, children: [
{ element: 11 },
{ element: 12 },
{ element: 13 }
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
element: { elements: [1], incompressible: false },
children: [
{ element: { elements: [11], incompressible: false } },
{ element: { elements: [12], incompressible: false } },
{ element: { elements: [13], incompressible: false } }
]
};
assert.deepEqual(resolve(compress(decompressed)), compressed);
assert.deepEqual(resolve(decompress(compressed)), decompressed);
});
test('single hierarchy', function () {
const decompressed: ICompressedTreeElement<number> = {
element: 1, children: [
{
element: 11, children: [
{
element: 111, children: [
{ element: 1111 }
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
element: { elements: [1, 11, 111, 1111], incompressible: false }
};
assert.deepEqual(resolve(compress(decompressed)), compressed);
assert.deepEqual(resolve(decompress(compressed)), decompressed);
});
test('deep compression', function () {
const decompressed: ICompressedTreeElement<number> = {
element: 1, children: [
{
element: 11, children: [
{
element: 111, children: [
{ element: 1111 },
{ element: 1112 },
{ element: 1113 },
{ element: 1114 },
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
element: { elements: [1, 11, 111], incompressible: false },
children: [
{ element: { elements: [1111], incompressible: false } },
{ element: { elements: [1112], incompressible: false } },
{ element: { elements: [1113], incompressible: false } },
{ element: { elements: [1114], incompressible: false } },
]
};
assert.deepEqual(resolve(compress(decompressed)), compressed);
assert.deepEqual(resolve(decompress(compressed)), decompressed);
});
test('double deep compression', function () {
const decompressed: ICompressedTreeElement<number> = {
element: 1, children: [
{
element: 11, children: [
{
element: 111, children: [
{ element: 1112 },
{ element: 1113 },
]
}
]
},
{
element: 12, children: [
{
element: 121, children: [
{ element: 1212 },
{ element: 1213 },
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
element: { elements: [1], incompressible: false },
children: [
{
element: { elements: [11, 111], incompressible: false },
children: [
{ element: { elements: [1112], incompressible: false } },
{ element: { elements: [1113], incompressible: false } },
]
},
{
element: { elements: [12, 121], incompressible: false },
children: [
{ element: { elements: [1212], incompressible: false } },
{ element: { elements: [1213], incompressible: false } },
]
}
]
};
assert.deepEqual(resolve(compress(decompressed)), compressed);
assert.deepEqual(resolve(decompress(compressed)), decompressed);
});
test('incompressible leaf', function () {
const decompressed: ICompressedTreeElement<number> = {
element: 1, children: [
{
element: 11, children: [
{
element: 111, children: [
{ element: 1111, incompressible: true }
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
element: { elements: [1, 11, 111], incompressible: false },
children: [
{ element: { elements: [1111], incompressible: true } }
]
};
assert.deepEqual(resolve(compress(decompressed)), compressed);
assert.deepEqual(resolve(decompress(compressed)), decompressed);
});
test('incompressible branch', function () {
const decompressed: ICompressedTreeElement<number> = {
element: 1, children: [
{
element: 11, children: [
{
element: 111, incompressible: true, children: [
{ element: 1111 }
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
element: { elements: [1, 11], incompressible: false },
children: [
{ element: { elements: [111, 1111], incompressible: true } }
]
};
assert.deepEqual(resolve(compress(decompressed)), compressed);
assert.deepEqual(resolve(decompress(compressed)), decompressed);
});
test('incompressible chain', function () {
const decompressed: ICompressedTreeElement<number> = {
element: 1, children: [
{
element: 11, children: [
{
element: 111, incompressible: true, children: [
{ element: 1111, incompressible: true }
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
element: { elements: [1, 11], incompressible: false },
children: [
{
element: { elements: [111], incompressible: true },
children: [
{ element: { elements: [1111], incompressible: true } }
]
}
]
};
assert.deepEqual(resolve(compress(decompressed)), compressed);
assert.deepEqual(resolve(decompress(compressed)), decompressed);
});
test('incompressible tree', function () {
const decompressed: ICompressedTreeElement<number> = {
element: 1, children: [
{
element: 11, incompressible: true, children: [
{
element: 111, incompressible: true, children: [
{ element: 1111, incompressible: true }
]
}
]
}
]
};
const compressed: IResolvedCompressedTreeElement<ICompressedTreeNode<number>> = {
element: { elements: [1], incompressible: false },
children: [
{
element: { elements: [11], incompressible: true },
children: [
{
element: { elements: [111], incompressible: true },
children: [
{ element: { elements: [1111], incompressible: true } }
]
}
]
}
]
};
assert.deepEqual(resolve(compress(decompressed)), compressed);
assert.deepEqual(resolve(decompress(compressed)), decompressed);
});
});
function toList<T>(arr: T[]): IList<T> {
return {
splice(start: number, deleteCount: number, elements: T[]): void {
arr.splice(start, deleteCount, ...elements);
},
updateElementHeight() { }
};
}
function toArray<T>(list: ITreeNode<ICompressedTreeNode<T>>[]): T[][] {
return list.map(i => i.element.elements);
}
suite('CompressedObjectTreeModel', function () {
test('ctor', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedObjectTreeModel<number>('test', toList(list));
assert(model);
assert.equal(list.length, 0);
assert.equal(model.size, 0);
});
test('flat', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedObjectTreeModel<number>('test', toList(list));
model.setChildren(null, [
{ element: 0 },
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(toArray(list), [[0], [1], [2]]);
assert.equal(model.size, 3);
model.setChildren(null, [
{ element: 3 },
{ element: 4 },
{ element: 5 },
]);
assert.deepEqual(toArray(list), [[3], [4], [5]]);
assert.equal(model.size, 3);
model.setChildren(null);
assert.deepEqual(toArray(list), []);
assert.equal(model.size, 0);
});
test('nested', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedObjectTreeModel<number>('test', toList(list));
model.setChildren(null, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(toArray(list), [[0], [10], [11], [12], [1], [2]]);
assert.equal(model.size, 6);
model.setChildren(12, [
{ element: 120 },
{ element: 121 }
]);
assert.deepEqual(toArray(list), [[0], [10], [11], [12], [120], [121], [1], [2]]);
assert.equal(model.size, 8);
model.setChildren(0);
assert.deepEqual(toArray(list), [[0], [1], [2]]);
assert.equal(model.size, 3);
model.setChildren(null);
assert.deepEqual(toArray(list), []);
assert.equal(model.size, 0);
});
test('compressed', () => {
const list: ITreeNode<ICompressedTreeNode<number>>[] = [];
const model = new CompressedObjectTreeModel<number>('test', toList(list));
model.setChildren(null, [
{
element: 1, children: [{
element: 11, children: [{
element: 111, children: [
{ element: 1111 },
{ element: 1112 },
{ element: 1113 },
]
}]
}]
}
]);
assert.deepEqual(toArray(list), [[1, 11, 111], [1111], [1112], [1113]]);
assert.equal(model.size, 6);
model.setChildren(11, [
{ element: 111 },
{ element: 112 },
{ element: 113 },
]);
assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113]]);
assert.equal(model.size, 5);
model.setChildren(113, [
{ element: 1131 }
]);
assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131]]);
assert.equal(model.size, 6);
model.setChildren(1131, [
{ element: 1132 }
]);
assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131, 1132]]);
assert.equal(model.size, 7);
model.setChildren(1131, [
{ element: 1132 },
{ element: 1133 },
]);
assert.deepEqual(toArray(list), [[1, 11], [111], [112], [113, 1131], [1132], [1133]]);
assert.equal(model.size, 8);
});
});
});

View File

@@ -0,0 +1,148 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ITreeNode, ITreeRenderer, IDataSource } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { DataTree } from 'vs/base/browser/ui/tree/dataTree';
interface E {
value: number;
children?: E[];
}
suite('DataTree', function () {
let tree: DataTree<E, E>;
const root: E = {
value: -1,
children: [
{ value: 0, children: [{ value: 10 }, { value: 11 }, { value: 12 }] },
{ value: 1 },
{ value: 2 },
]
};
const empty: E = {
value: -1,
children: []
};
setup(() => {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const delegate = new class implements IListVirtualDelegate<E> {
getHeight() { return 20; }
getTemplateId(): string { return 'default'; }
};
const renderer = new class implements ITreeRenderer<E, void, HTMLElement> {
readonly templateId = 'default';
renderTemplate(container: HTMLElement): HTMLElement {
return container;
}
renderElement(element: ITreeNode<E, void>, index: number, templateData: HTMLElement): void {
templateData.textContent = `${element.element.value}`;
}
disposeTemplate(): void { }
};
const dataSource = new class implements IDataSource<E, E> {
getChildren(element: E): E[] {
return element.children || [];
}
};
const identityProvider = new class implements IIdentityProvider<E> {
getId(element: E): { toString(): string; } {
return `${element.value}`;
}
};
tree = new DataTree<E, E>('test', container, delegate, [renderer], dataSource, {
identityProvider
});
tree.layout(200);
});
teardown(() => {
tree.dispose();
});
test('view state is lost implicitly', () => {
tree.setInput(root);
let navigator = tree.navigate();
assert.equal(navigator.next()!.value, 0);
assert.equal(navigator.next()!.value, 10);
assert.equal(navigator.next()!.value, 11);
assert.equal(navigator.next()!.value, 12);
assert.equal(navigator.next()!.value, 1);
assert.equal(navigator.next()!.value, 2);
assert.equal(navigator.next()!, null);
tree.collapse(root.children![0]);
navigator = tree.navigate();
assert.equal(navigator.next()!.value, 0);
assert.equal(navigator.next()!.value, 1);
assert.equal(navigator.next()!.value, 2);
assert.equal(navigator.next()!, null);
tree.setSelection([root.children![1]]);
tree.setFocus([root.children![2]]);
tree.setInput(empty);
tree.setInput(root);
navigator = tree.navigate();
assert.equal(navigator.next()!.value, 0);
assert.equal(navigator.next()!.value, 10);
assert.equal(navigator.next()!.value, 11);
assert.equal(navigator.next()!.value, 12);
assert.equal(navigator.next()!.value, 1);
assert.equal(navigator.next()!.value, 2);
assert.equal(navigator.next()!, null);
assert.deepEqual(tree.getSelection(), []);
assert.deepEqual(tree.getFocus(), []);
});
test('view state can be preserved', () => {
tree.setInput(root);
let navigator = tree.navigate();
assert.equal(navigator.next()!.value, 0);
assert.equal(navigator.next()!.value, 10);
assert.equal(navigator.next()!.value, 11);
assert.equal(navigator.next()!.value, 12);
assert.equal(navigator.next()!.value, 1);
assert.equal(navigator.next()!.value, 2);
assert.equal(navigator.next()!, null);
tree.collapse(root.children![0]);
navigator = tree.navigate();
assert.equal(navigator.next()!.value, 0);
assert.equal(navigator.next()!.value, 1);
assert.equal(navigator.next()!.value, 2);
assert.equal(navigator.next()!, null);
tree.setSelection([root.children![1]]);
tree.setFocus([root.children![2]]);
const viewState = tree.getViewState();
tree.setInput(empty);
tree.setInput(root, viewState);
navigator = tree.navigate();
assert.equal(navigator.next()!.value, 0);
assert.equal(navigator.next()!.value, 1);
assert.equal(navigator.next()!.value, 2);
assert.equal(navigator.next()!, null);
assert.deepEqual(tree.getSelection(), [root.children![1]]);
assert.deepEqual(tree.getFocus(), [root.children![2]]);
});
});

View File

@@ -0,0 +1,759 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
import { IndexTreeModel, IIndexTreeNode, IList } from 'vs/base/browser/ui/tree/indexTreeModel';
function toList<T>(arr: T[]): IList<T> {
return {
splice(start: number, deleteCount: number, elements: T[]): void {
arr.splice(start, deleteCount, ...elements);
},
updateElementHeight() { }
};
}
function toArray<T>(list: ITreeNode<T>[]): T[] {
return list.map(i => i.element);
}
suite('IndexTreeModel', function () {
test('ctor', () => {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
assert(model);
assert.equal(list.length, 0);
});
test('insert', () => {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{ element: 0 },
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(list.length, 3);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 1);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
assert.deepEqual(list[2].element, 2);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 1);
});
test('deep insert', function () {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(list.length, 6);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 2);
assert.deepEqual(list[2].element, 11);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 2);
assert.deepEqual(list[3].element, 12);
assert.deepEqual(list[3].collapsed, false);
assert.deepEqual(list[3].depth, 2);
assert.deepEqual(list[4].element, 1);
assert.deepEqual(list[4].collapsed, false);
assert.deepEqual(list[4].depth, 1);
assert.deepEqual(list[5].element, 2);
assert.deepEqual(list[5].collapsed, false);
assert.deepEqual(list[5].depth, 1);
});
test('deep insert collapsed', function () {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{
element: 0, collapsed: true, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(list.length, 3);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, true);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 1);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
assert.deepEqual(list[2].element, 2);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 1);
});
test('delete', () => {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{ element: 0 },
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(list.length, 3);
model.splice([1], 1);
assert.deepEqual(list.length, 2);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 2);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
model.splice([0], 2);
assert.deepEqual(list.length, 0);
});
test('nested delete', function () {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(list.length, 6);
model.splice([1], 2);
assert.deepEqual(list.length, 4);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 2);
assert.deepEqual(list[2].element, 11);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 2);
assert.deepEqual(list[3].element, 12);
assert.deepEqual(list[3].collapsed, false);
assert.deepEqual(list[3].depth, 2);
});
test('deep delete', function () {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(list.length, 6);
model.splice([0], 1);
assert.deepEqual(list.length, 2);
assert.deepEqual(list[0].element, 1);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 2);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
});
test('hidden delete', function () {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{
element: 0, collapsed: true, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(list.length, 3);
model.splice([0, 1], 1);
assert.deepEqual(list.length, 3);
model.splice([0, 0], 2);
assert.deepEqual(list.length, 3);
});
test('collapse', () => {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(list.length, 6);
model.setCollapsed([0], true);
assert.deepEqual(list.length, 3);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, true);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 1);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 1);
assert.deepEqual(list[2].element, 2);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 1);
});
test('expand', () => {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{
element: 0, collapsed: true, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(list.length, 3);
model.setCollapsed([0], false);
assert.deepEqual(list.length, 6);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[0].depth, 1);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(list[1].depth, 2);
assert.deepEqual(list[2].element, 11);
assert.deepEqual(list[2].collapsed, false);
assert.deepEqual(list[2].depth, 2);
assert.deepEqual(list[3].element, 12);
assert.deepEqual(list[3].collapsed, false);
assert.deepEqual(list[3].depth, 2);
assert.deepEqual(list[4].element, 1);
assert.deepEqual(list[4].collapsed, false);
assert.deepEqual(list[4].depth, 1);
assert.deepEqual(list[5].element, 2);
assert.deepEqual(list[5].collapsed, false);
assert.deepEqual(list[5].depth, 1);
});
test('collapse should recursively adjust visible count', function () {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{
element: 1, children: [
{
element: 11, children: [
{ element: 111 }
]
}
]
},
{
element: 2, children: [
{ element: 21 }
]
}
]);
assert.deepEqual(list.length, 5);
assert.deepEqual(toArray(list), [1, 11, 111, 2, 21]);
model.setCollapsed([0, 0], true);
assert.deepEqual(list.length, 4);
assert.deepEqual(toArray(list), [1, 11, 2, 21]);
model.setCollapsed([1], true);
assert.deepEqual(list.length, 3);
assert.deepEqual(toArray(list), [1, 11, 2]);
});
test('setCollapsible', () => {
const list: ITreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{
element: 0, children: [
{ element: 10 }
]
}
]);
assert.deepEqual(list.length, 2);
model.setCollapsible([0], false);
assert.deepEqual(list.length, 2);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsible, false);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsible, false);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(model.setCollapsed([0], true), false);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsible, false);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsible, false);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(model.setCollapsed([0], false), false);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsible, false);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsible, false);
assert.deepEqual(list[1].collapsed, false);
model.setCollapsible([0], true);
assert.deepEqual(list.length, 2);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsible, true);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsible, false);
assert.deepEqual(list[1].collapsed, false);
assert.deepEqual(model.setCollapsed([0], true), true);
assert.deepEqual(list.length, 1);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsible, true);
assert.deepEqual(list[0].collapsed, true);
assert.deepEqual(model.setCollapsed([0], false), true);
assert.deepEqual(list[0].element, 0);
assert.deepEqual(list[0].collapsible, true);
assert.deepEqual(list[0].collapsed, false);
assert.deepEqual(list[1].element, 10);
assert.deepEqual(list[1].collapsible, false);
assert.deepEqual(list[1].collapsed, false);
});
test('simple filter', function () {
const list: ITreeNode<number>[] = [];
const filter = new class implements ITreeFilter<number> {
filter(element: number): TreeVisibility {
return element % 2 === 0 ? TreeVisibility.Visible : TreeVisibility.Hidden;
}
};
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
model.splice([0], 0, [
{
element: 0, children: [
{ element: 1 },
{ element: 2 },
{ element: 3 },
{ element: 4 },
{ element: 5 },
{ element: 6 },
{ element: 7 }
]
}
]);
assert.deepEqual(list.length, 4);
assert.deepEqual(toArray(list), [0, 2, 4, 6]);
model.setCollapsed([0], true);
assert.deepEqual(toArray(list), [0]);
model.setCollapsed([0], false);
assert.deepEqual(toArray(list), [0, 2, 4, 6]);
});
test('recursive filter on initial model', function () {
const list: ITreeNode<number>[] = [];
const filter = new class implements ITreeFilter<number> {
filter(element: number): TreeVisibility {
return element === 0 ? TreeVisibility.Recurse : TreeVisibility.Hidden;
}
};
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
model.splice([0], 0, [
{
element: 0, children: [
{ element: 1 },
{ element: 2 }
]
}
]);
assert.deepEqual(toArray(list), []);
});
test('refilter', function () {
const list: ITreeNode<number>[] = [];
let shouldFilter = false;
const filter = new class implements ITreeFilter<number> {
filter(element: number): TreeVisibility {
return (!shouldFilter || element % 2 === 0) ? TreeVisibility.Visible : TreeVisibility.Hidden;
}
};
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
model.splice([0], 0, [
{
element: 0, children: [
{ element: 1 },
{ element: 2 },
{ element: 3 },
{ element: 4 },
{ element: 5 },
{ element: 6 },
{ element: 7 }
]
},
]);
assert.deepEqual(toArray(list), [0, 1, 2, 3, 4, 5, 6, 7]);
model.refilter();
assert.deepEqual(toArray(list), [0, 1, 2, 3, 4, 5, 6, 7]);
shouldFilter = true;
model.refilter();
assert.deepEqual(toArray(list), [0, 2, 4, 6]);
shouldFilter = false;
model.refilter();
assert.deepEqual(toArray(list), [0, 1, 2, 3, 4, 5, 6, 7]);
});
test('recursive filter', function () {
const list: ITreeNode<string>[] = [];
let query = new RegExp('');
const filter = new class implements ITreeFilter<string> {
filter(element: string): TreeVisibility {
return query.test(element) ? TreeVisibility.Visible : TreeVisibility.Recurse;
}
};
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
model.splice([0], 0, [
{
element: 'vscode', children: [
{ element: '.build' },
{ element: 'git' },
{
element: 'github', children: [
{ element: 'calendar.yml' },
{ element: 'endgame' },
{ element: 'build.js' },
]
},
{
element: 'build', children: [
{ element: 'lib' },
{ element: 'gulpfile.js' }
]
}
]
},
]);
assert.deepEqual(list.length, 10);
query = /build/;
model.refilter();
assert.deepEqual(toArray(list), ['vscode', '.build', 'github', 'build.js', 'build']);
model.setCollapsed([0], true);
assert.deepEqual(toArray(list), ['vscode']);
model.setCollapsed([0], false);
assert.deepEqual(toArray(list), ['vscode', '.build', 'github', 'build.js', 'build']);
});
test('recursive filter with collapse', function () {
const list: ITreeNode<string>[] = [];
let query = new RegExp('');
const filter = new class implements ITreeFilter<string> {
filter(element: string): TreeVisibility {
return query.test(element) ? TreeVisibility.Visible : TreeVisibility.Recurse;
}
};
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
model.splice([0], 0, [
{
element: 'vscode', children: [
{ element: '.build' },
{ element: 'git' },
{
element: 'github', children: [
{ element: 'calendar.yml' },
{ element: 'endgame' },
{ element: 'build.js' },
]
},
{
element: 'build', children: [
{ element: 'lib' },
{ element: 'gulpfile.js' }
]
}
]
},
]);
assert.deepEqual(list.length, 10);
query = /gulp/;
model.refilter();
assert.deepEqual(toArray(list), ['vscode', 'build', 'gulpfile.js']);
model.setCollapsed([0, 3], true);
assert.deepEqual(toArray(list), ['vscode', 'build']);
model.setCollapsed([0], true);
assert.deepEqual(toArray(list), ['vscode']);
});
test('recursive filter while collapsed', function () {
const list: ITreeNode<string>[] = [];
let query = new RegExp('');
const filter = new class implements ITreeFilter<string> {
filter(element: string): TreeVisibility {
return query.test(element) ? TreeVisibility.Visible : TreeVisibility.Recurse;
}
};
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
model.splice([0], 0, [
{
element: 'vscode', collapsed: true, children: [
{ element: '.build' },
{ element: 'git' },
{
element: 'github', children: [
{ element: 'calendar.yml' },
{ element: 'endgame' },
{ element: 'build.js' },
]
},
{
element: 'build', children: [
{ element: 'lib' },
{ element: 'gulpfile.js' }
]
}
]
},
]);
assert.deepEqual(toArray(list), ['vscode']);
query = /gulp/;
model.refilter();
assert.deepEqual(toArray(list), ['vscode']);
model.setCollapsed([0], false);
assert.deepEqual(toArray(list), ['vscode', 'build', 'gulpfile.js']);
model.setCollapsed([0], true);
assert.deepEqual(toArray(list), ['vscode']);
query = new RegExp('');
model.refilter();
assert.deepEqual(toArray(list), ['vscode']);
model.setCollapsed([0], false);
assert.deepEqual(list.length, 10);
});
suite('getNodeLocation', function () {
test('simple', function () {
const list: IIndexTreeNode<number>[] = [];
const model = new IndexTreeModel<number>('test', toList(list), -1);
model.splice([0], 0, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(model.getNodeLocation(list[0]), [0]);
assert.deepEqual(model.getNodeLocation(list[1]), [0, 0]);
assert.deepEqual(model.getNodeLocation(list[2]), [0, 1]);
assert.deepEqual(model.getNodeLocation(list[3]), [0, 2]);
assert.deepEqual(model.getNodeLocation(list[4]), [1]);
assert.deepEqual(model.getNodeLocation(list[5]), [2]);
});
test('with filter', function () {
const list: IIndexTreeNode<number>[] = [];
const filter = new class implements ITreeFilter<number> {
filter(element: number): TreeVisibility {
return element % 2 === 0 ? TreeVisibility.Visible : TreeVisibility.Hidden;
}
};
const model = new IndexTreeModel<number>('test', toList(list), -1, { filter });
model.splice([0], 0, [
{
element: 0, children: [
{ element: 1 },
{ element: 2 },
{ element: 3 },
{ element: 4 },
{ element: 5 },
{ element: 6 },
{ element: 7 }
]
}
]);
assert.deepEqual(model.getNodeLocation(list[0]), [0]);
assert.deepEqual(model.getNodeLocation(list[1]), [0, 1]);
assert.deepEqual(model.getNodeLocation(list[2]), [0, 3]);
assert.deepEqual(model.getNodeLocation(list[3]), [0, 5]);
});
});
test('refilter with filtered out nodes', function () {
const list: ITreeNode<string>[] = [];
let query = new RegExp('');
const filter = new class implements ITreeFilter<string> {
filter(element: string): boolean {
return query.test(element);
}
};
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
model.splice([0], 0, [
{ element: 'silver' },
{ element: 'gold' },
{ element: 'platinum' }
]);
assert.deepEqual(toArray(list), ['silver', 'gold', 'platinum']);
query = /platinum/;
model.refilter();
assert.deepEqual(toArray(list), ['platinum']);
model.splice([0], Number.POSITIVE_INFINITY, [
{ element: 'silver' },
{ element: 'gold' },
{ element: 'platinum' }
]);
assert.deepEqual(toArray(list), ['platinum']);
model.refilter();
assert.deepEqual(toArray(list), ['platinum']);
});
test('explicit hidden nodes should have renderNodeCount == 0, issue #83211', function () {
const list: ITreeNode<string>[] = [];
let query = new RegExp('');
const filter = new class implements ITreeFilter<string> {
filter(element: string): boolean {
return query.test(element);
}
};
const model = new IndexTreeModel<string>('test', toList(list), 'root', { filter });
model.splice([0], 0, [
{ element: 'a', children: [{ element: 'aa' }] },
{ element: 'b', children: [{ element: 'bb' }] }
]);
assert.deepEqual(toArray(list), ['a', 'aa', 'b', 'bb']);
assert.deepEqual(model.getListIndex([0]), 0);
assert.deepEqual(model.getListIndex([0, 0]), 1);
assert.deepEqual(model.getListIndex([1]), 2);
assert.deepEqual(model.getListIndex([1, 0]), 3);
query = /b/;
model.refilter();
assert.deepEqual(toArray(list), ['b', 'bb']);
assert.deepEqual(model.getListIndex([0]), -1);
assert.deepEqual(model.getListIndex([0, 0]), -1);
assert.deepEqual(model.getListIndex([1]), 0);
assert.deepEqual(model.getListIndex([1, 0]), 1);
});
});

View File

@@ -0,0 +1,375 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ObjectTree, CompressibleObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
suite('ObjectTree', function () {
suite('TreeNavigator', function () {
let tree: ObjectTree<number>;
let filter = (_: number) => true;
setup(() => {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const delegate = new class implements IListVirtualDelegate<number> {
getHeight() { return 20; }
getTemplateId(): string { return 'default'; }
};
const renderer = new class implements ITreeRenderer<number, void, HTMLElement> {
readonly templateId = 'default';
renderTemplate(container: HTMLElement): HTMLElement {
return container;
}
renderElement(element: ITreeNode<number, void>, index: number, templateData: HTMLElement): void {
templateData.textContent = `${element.element}`;
}
disposeTemplate(): void { }
};
tree = new ObjectTree<number>('test', container, delegate, [renderer], { filter: { filter: (el) => filter(el) } });
tree.layout(200);
});
teardown(() => {
tree.dispose();
filter = (_: number) => true;
});
test('should be able to navigate', () => {
tree.setChildren(null, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
const navigator = tree.navigate();
assert.equal(navigator.current(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.current(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.current(), 10);
assert.equal(navigator.next(), 11);
assert.equal(navigator.current(), 11);
assert.equal(navigator.next(), 12);
assert.equal(navigator.current(), 12);
assert.equal(navigator.next(), 1);
assert.equal(navigator.current(), 1);
assert.equal(navigator.next(), 2);
assert.equal(navigator.current(), 2);
assert.equal(navigator.previous(), 1);
assert.equal(navigator.current(), 1);
assert.equal(navigator.previous(), 12);
assert.equal(navigator.previous(), 11);
assert.equal(navigator.previous(), 10);
assert.equal(navigator.previous(), 0);
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
test('should skip collapsed nodes', () => {
tree.setChildren(null, [
{
element: 0, collapsed: true, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
const navigator = tree.navigate();
assert.equal(navigator.current(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 1);
assert.equal(navigator.next(), 2);
assert.equal(navigator.next(), null);
assert.equal(navigator.previous(), 2);
assert.equal(navigator.previous(), 1);
assert.equal(navigator.previous(), 0);
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
test('should skip filtered elements', () => {
filter = el => el % 2 === 0;
tree.setChildren(null, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
const navigator = tree.navigate();
assert.equal(navigator.current(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.next(), 12);
assert.equal(navigator.next(), 2);
assert.equal(navigator.next(), null);
assert.equal(navigator.previous(), 2);
assert.equal(navigator.previous(), 12);
assert.equal(navigator.previous(), 10);
assert.equal(navigator.previous(), 0);
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
test('should be able to start from node', () => {
tree.setChildren(null, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
const navigator = tree.navigate(1);
assert.equal(navigator.current(), 1);
assert.equal(navigator.next(), 2);
assert.equal(navigator.current(), 2);
assert.equal(navigator.previous(), 1);
assert.equal(navigator.current(), 1);
assert.equal(navigator.previous(), 12);
assert.equal(navigator.previous(), 11);
assert.equal(navigator.previous(), 10);
assert.equal(navigator.previous(), 0);
assert.equal(navigator.previous(), null);
assert.equal(navigator.next(), 0);
assert.equal(navigator.next(), 10);
assert.equal(navigator.first(), 0);
assert.equal(navigator.last(), 2);
});
});
test('traits are preserved according to string identity', function () {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const delegate = new class implements IListVirtualDelegate<number> {
getHeight() { return 20; }
getTemplateId(): string { return 'default'; }
};
const renderer = new class implements ITreeRenderer<number, void, HTMLElement> {
readonly templateId = 'default';
renderTemplate(container: HTMLElement): HTMLElement {
return container;
}
renderElement(element: ITreeNode<number, void>, index: number, templateData: HTMLElement): void {
templateData.textContent = `${element.element}`;
}
disposeTemplate(): void { }
};
const identityProvider = new class implements IIdentityProvider<number> {
getId(element: number): { toString(): string; } {
return `${element % 100}`;
}
};
const tree = new ObjectTree<number>('test', container, delegate, [renderer], { identityProvider });
tree.layout(200);
tree.setChildren(null, [{ element: 0 }, { element: 1 }, { element: 2 }, { element: 3 }]);
tree.setFocus([1]);
assert.deepStrictEqual(tree.getFocus(), [1]);
tree.setChildren(null, [{ element: 100 }, { element: 101 }, { element: 102 }, { element: 103 }]);
assert.deepStrictEqual(tree.getFocus(), [101]);
});
});
function toArray(list: NodeList): Node[] {
const result: Node[] = [];
list.forEach(node => result.push(node));
return result;
}
suite('CompressibleObjectTree', function () {
class Delegate implements IListVirtualDelegate<number> {
getHeight() { return 20; }
getTemplateId(): string { return 'default'; }
}
class Renderer implements ICompressibleTreeRenderer<number, void, HTMLElement> {
readonly templateId = 'default';
renderTemplate(container: HTMLElement): HTMLElement {
return container;
}
renderElement(node: ITreeNode<number, void>, _: number, templateData: HTMLElement): void {
templateData.textContent = `${node.element}`;
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<number>, void>, _: number, templateData: HTMLElement): void {
templateData.textContent = `${node.element.elements.join('/')}`;
}
disposeTemplate(): void { }
}
test('empty', function () {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
const rows = toArray(container.querySelectorAll('.monaco-tl-contents'));
assert.equal(rows.length, 0);
});
test('simple', function () {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
tree.setChildren(null, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
const rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['0', '10', '11', '12', '1', '2']);
});
test('compressed', () => {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
tree.setChildren(null, [
{
element: 1, children: [{
element: 11, children: [{
element: 111, children: [
{ element: 1111 },
{ element: 1112 },
{ element: 1113 },
]
}]
}]
}
]);
let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
tree.setChildren(11, [
{ element: 111 },
{ element: 112 },
{ element: 113 },
]);
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113']);
tree.setChildren(113, [
{ element: 1131 }
]);
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131']);
tree.setChildren(1131, [
{ element: 1132 }
]);
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131/1132']);
tree.setChildren(1131, [
{ element: 1132 },
{ element: 1133 },
]);
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11', '111', '112', '113/1131', '1132', '1133']);
});
test('enableCompression', () => {
const container = document.createElement('div');
container.style.width = '200px';
container.style.height = '200px';
const tree = new CompressibleObjectTree<number>('test', container, new Delegate(), [new Renderer()]);
tree.layout(200);
tree.setChildren(null, [
{
element: 1, children: [{
element: 11, children: [{
element: 111, children: [
{ element: 1111 },
{ element: 1112 },
{ element: 1113 },
]
}]
}]
}
]);
let rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
tree.updateOptions({ compressionEnabled: false });
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1', '11', '111', '1111', '1112', '1113']);
tree.updateOptions({ compressionEnabled: true });
rows = toArray(container.querySelectorAll('.monaco-tl-contents')).map(row => row.textContent);
assert.deepEqual(rows, ['1/11/111', '1111', '1112', '1113']);
});
});

View File

@@ -0,0 +1,276 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ITreeNode, ITreeFilter, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
function toList<T>(arr: T[]): IList<T> {
return {
splice(start: number, deleteCount: number, elements: T[]): void {
// console.log(`splice (${start}, ${deleteCount}, ${elements.length} [${elements.join(', ')}] )`); // debugging
arr.splice(start, deleteCount, ...elements);
},
updateElementHeight() { }
};
}
function toArray<T>(list: ITreeNode<T>[]): T[] {
return list.map(i => i.element);
}
suite('ObjectTreeModel', function () {
test('ctor', () => {
const list: ITreeNode<number>[] = [];
const model = new ObjectTreeModel<number>('test', toList(list));
assert(model);
assert.equal(list.length, 0);
assert.equal(model.size, 0);
});
test('flat', () => {
const list: ITreeNode<number>[] = [];
const model = new ObjectTreeModel<number>('test', toList(list));
model.setChildren(null, [
{ element: 0 },
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(toArray(list), [0, 1, 2]);
assert.equal(model.size, 3);
model.setChildren(null, [
{ element: 3 },
{ element: 4 },
{ element: 5 },
]);
assert.deepEqual(toArray(list), [3, 4, 5]);
assert.equal(model.size, 3);
model.setChildren(null);
assert.deepEqual(toArray(list), []);
assert.equal(model.size, 0);
});
test('nested', () => {
const list: ITreeNode<number>[] = [];
const model = new ObjectTreeModel<number>('test', toList(list));
model.setChildren(null, [
{
element: 0, children: [
{ element: 10 },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(toArray(list), [0, 10, 11, 12, 1, 2]);
assert.equal(model.size, 6);
model.setChildren(12, [
{ element: 120 },
{ element: 121 }
]);
assert.deepEqual(toArray(list), [0, 10, 11, 12, 120, 121, 1, 2]);
assert.equal(model.size, 8);
model.setChildren(0);
assert.deepEqual(toArray(list), [0, 1, 2]);
assert.equal(model.size, 3);
model.setChildren(null);
assert.deepEqual(toArray(list), []);
assert.equal(model.size, 0);
});
test('setChildren on collapsed node', () => {
const list: ITreeNode<number>[] = [];
const model = new ObjectTreeModel<number>('test', toList(list));
model.setChildren(null, [
{ element: 0, collapsed: true }
]);
assert.deepEqual(toArray(list), [0]);
model.setChildren(0, [
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(toArray(list), [0]);
model.setCollapsed(0, false);
assert.deepEqual(toArray(list), [0, 1, 2]);
});
test('setChildren on expanded, unrevealed node', () => {
const list: ITreeNode<number>[] = [];
const model = new ObjectTreeModel<number>('test', toList(list));
model.setChildren(null, [
{
element: 1, collapsed: true, children: [
{ element: 11, collapsed: false }
]
},
{ element: 2 }
]);
assert.deepEqual(toArray(list), [1, 2]);
model.setChildren(11, [
{ element: 111 },
{ element: 112 }
]);
assert.deepEqual(toArray(list), [1, 2]);
model.setCollapsed(1, false);
assert.deepEqual(toArray(list), [1, 11, 111, 112, 2]);
});
test('collapse state is preserved with strict identity', () => {
const list: ITreeNode<string>[] = [];
const model = new ObjectTreeModel<string>('test', toList(list), { collapseByDefault: true });
const data = [{ element: 'father', children: [{ element: 'child' }] }];
model.setChildren(null, data);
assert.deepEqual(toArray(list), ['father']);
model.setCollapsed('father', false);
assert.deepEqual(toArray(list), ['father', 'child']);
model.setChildren(null, data);
assert.deepEqual(toArray(list), ['father', 'child']);
const data2 = [{ element: 'father', children: [{ element: 'child' }] }, { element: 'uncle' }];
model.setChildren(null, data2);
assert.deepEqual(toArray(list), ['father', 'child', 'uncle']);
model.setChildren(null, [{ element: 'uncle' }]);
assert.deepEqual(toArray(list), ['uncle']);
model.setChildren(null, data2);
assert.deepEqual(toArray(list), ['father', 'uncle']);
model.setChildren(null, data);
assert.deepEqual(toArray(list), ['father']);
});
test('sorter', () => {
let compare: (a: string, b: string) => number = (a, b) => a < b ? -1 : 1;
const list: ITreeNode<string>[] = [];
const model = new ObjectTreeModel<string>('test', toList(list), { sorter: { compare(a, b) { return compare(a, b); } } });
const data = [
{ element: 'cars', children: [{ element: 'sedan' }, { element: 'convertible' }, { element: 'compact' }] },
{ element: 'airplanes', children: [{ element: 'passenger' }, { element: 'jet' }] },
{ element: 'bicycles', children: [{ element: 'dutch' }, { element: 'mountain' }, { element: 'electric' }] },
];
model.setChildren(null, data);
assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'compact', 'convertible', 'sedan']);
});
test('resort', () => {
let compare: (a: string, b: string) => number = () => 0;
const list: ITreeNode<string>[] = [];
const model = new ObjectTreeModel<string>('test', toList(list), { sorter: { compare(a, b) { return compare(a, b); } } });
const data = [
{ element: 'cars', children: [{ element: 'sedan' }, { element: 'convertible' }, { element: 'compact' }] },
{ element: 'airplanes', children: [{ element: 'passenger' }, { element: 'jet' }] },
{ element: 'bicycles', children: [{ element: 'dutch' }, { element: 'mountain' }, { element: 'electric' }] },
];
model.setChildren(null, data);
assert.deepEqual(toArray(list), ['cars', 'sedan', 'convertible', 'compact', 'airplanes', 'passenger', 'jet', 'bicycles', 'dutch', 'mountain', 'electric']);
// lexicographical
compare = (a, b) => a < b ? -1 : 1;
// non-recursive
model.resort(null, false);
assert.deepEqual(toArray(list), ['airplanes', 'passenger', 'jet', 'bicycles', 'dutch', 'mountain', 'electric', 'cars', 'sedan', 'convertible', 'compact']);
// recursive
model.resort();
assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'compact', 'convertible', 'sedan']);
// reverse
compare = (a, b) => a < b ? 1 : -1;
// scoped
model.resort('cars');
assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'sedan', 'convertible', 'compact']);
// recursive
model.resort();
assert.deepEqual(toArray(list), ['cars', 'sedan', 'convertible', 'compact', 'bicycles', 'mountain', 'electric', 'dutch', 'airplanes', 'passenger', 'jet']);
});
test('expandTo', () => {
const list: ITreeNode<number>[] = [];
const model = new ObjectTreeModel<number>('test', toList(list), { collapseByDefault: true });
model.setChildren(null, [
{
element: 0, children: [
{ element: 10, children: [{ element: 100, children: [{ element: 1000 }] }] },
{ element: 11 },
{ element: 12 },
]
},
{ element: 1 },
{ element: 2 }
]);
assert.deepEqual(toArray(list), [0, 1, 2]);
model.expandTo(1000);
assert.deepEqual(toArray(list), [0, 10, 100, 1000, 11, 12, 1, 2]);
});
test('issue #95641', () => {
const list: ITreeNode<string>[] = [];
let fn = (_: string) => true;
const filter = new class implements ITreeFilter<string> {
filter(element: string, parentVisibility: TreeVisibility): TreeVisibility {
if (element === 'file') {
return TreeVisibility.Recurse;
}
return fn(element) ? TreeVisibility.Visible : parentVisibility;
}
};
const model = new ObjectTreeModel<string>('test', toList(list), { filter });
model.setChildren(null, [{ element: 'file', children: [{ element: 'hello' }] }]);
assert.deepEqual(toArray(list), ['file', 'hello']);
fn = (el: string) => el === 'world';
model.refilter();
assert.deepEqual(toArray(list), []);
model.setChildren('file', [{ element: 'world' }]);
assert.deepEqual(toArray(list), ['file', 'world']);
model.setChildren('file', [{ element: 'hello' }]);
assert.deepEqual(toArray(list), []);
model.setChildren('file', [{ element: 'world' }]);
assert.deepEqual(toArray(list), ['file', 'world']);
});
});