Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* 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 { ActionBar, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action, Separator } from 'vs/base/common/actions';
suite('Actionbar', () => {
test('prepareActions()', function () {
let a1 = new Separator();
let a2 = new Separator();
let a3 = new Action('a3');
let a4 = new Separator();
let a5 = new Separator();
let a6 = new Action('a6');
let a7 = new Separator();
let actions = prepareActions([a1, a2, a3, a4, a5, a6, a7]);
assert.strictEqual(actions.length, 3); // duplicate separators get removed
assert(actions[0] === a3);
assert(actions[1] === a5);
assert(actions[2] === a6);
});
test('hasAction()', function () {
const container = document.createElement('div');
const actionbar = new ActionBar(container);
let a1 = new Action('a1');
let a2 = new Action('a2');
actionbar.push(a1);
assert.equal(actionbar.hasAction(a1), true);
assert.equal(actionbar.hasAction(a2), false);
actionbar.pull(0);
assert.equal(actionbar.hasAction(a1), false);
actionbar.push(a1, { index: 1 });
actionbar.push(a2, { index: 0 });
assert.equal(actionbar.hasAction(a1), true);
assert.equal(actionbar.hasAction(a2), true);
actionbar.pull(0);
assert.equal(actionbar.hasAction(a1), true);
assert.equal(actionbar.hasAction(a2), false);
actionbar.pull(0);
assert.equal(actionbar.hasAction(a1), false);
assert.equal(actionbar.hasAction(a2), false);
actionbar.push(a1);
assert.equal(actionbar.hasAction(a1), true);
actionbar.clear();
assert.equal(actionbar.hasAction(a1), false);
});
});

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* 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 { isMacintosh, isWindows } from 'vs/base/common/platform';
suite('Browsers', () => {
test('all', () => {
assert(!(isWindows && isMacintosh));
});
});

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { renderCodicons } from 'vs/base/browser/codicons';
import * as assert from 'assert';
suite('renderCodicons', () => {
test('no codicons', () => {
const result = renderCodicons(' hello World .');
assert.equal(elementsToString(result), ' hello World .');
});
test('codicon only', () => {
const result = renderCodicons('$(alert)');
assert.equal(elementsToString(result), '<span class="codicon codicon-alert"></span>');
});
test('codicon and non-codicon strings', () => {
const result = renderCodicons(` $(alert) Unresponsive`);
assert.equal(elementsToString(result), ' <span class="codicon codicon-alert"></span> Unresponsive');
});
test('multiple codicons', () => {
const result = renderCodicons('$(check)$(error)');
assert.equal(elementsToString(result), '<span class="codicon codicon-check"></span><span class="codicon codicon-error"></span>');
});
test('escaped codicon', () => {
const result = renderCodicons('\\$(escaped)');
assert.equal(elementsToString(result), '$(escaped)');
});
test('codicon with animation', () => {
const result = renderCodicons('$(zip~anim)');
assert.equal(elementsToString(result), '<span class="codicon codicon-zip codicon-animation-anim"></span>');
});
const elementsToString = (elements: Array<HTMLElement | string>): string => {
return elements
.map(elem => elem instanceof HTMLElement ? elem.outerHTML : elem)
.reduce((a, b) => a + b, '');
};
});

View File

@@ -0,0 +1,285 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { compareFileNames, compareFileExtensions, compareFileNamesDefault, compareFileExtensionsDefault } from 'vs/base/common/comparers';
import * as assert from 'assert';
const compareLocale = (a: string, b: string) => a.localeCompare(b);
const compareLocaleNumeric = (a: string, b: string) => a.localeCompare(b, undefined, { numeric: true });
suite('Comparers', () => {
test('compareFileNames', () => {
//
// Comparisons with the same results as compareFileNamesDefault
//
// name-only comparisons
assert(compareFileNames(null, null) === 0, 'null should be equal');
assert(compareFileNames(null, 'abc') < 0, 'null should be come before real values');
assert(compareFileNames('', '') === 0, 'empty should be equal');
assert(compareFileNames('abc', 'abc') === 0, 'equal names should be equal');
assert(compareFileNames('z', 'A') > 0, 'z comes is after A regardless of case');
assert(compareFileNames('Z', 'a') > 0, 'Z comes after a regardless of case');
// name plus extension comparisons
assert(compareFileNames('bbb.aaa', 'aaa.bbb') > 0, 'files with extensions are compared first by filename');
assert(compareFileNames('aggregate.go', 'aggregate_repo.go') > 0, 'compares the whole name all at once by locale');
// dotfile comparisons
assert(compareFileNames('.abc', '.abc') === 0, 'equal dotfile names should be equal');
assert(compareFileNames('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly');
assert(compareFileNames('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
assert(compareFileNames('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
assert(compareFileNames('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot');
// dotfile vs non-dotfile comparisons
assert(compareFileNames(null, '.abc') < 0, 'null should come before dotfiles');
assert(compareFileNames('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
assert(compareFileNames('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
assert(compareFileNames('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
assert(compareFileNames('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
// numeric comparisons
assert(compareFileNames('1', '1') === 0, 'numerically equal full names should be equal');
assert(compareFileNames('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
assert(compareFileNames('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
assert(compareFileNames('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long');
assert(compareFileNames('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
assert(compareFileNames('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
//
// Comparisons with different results than compareFileNamesDefault
//
// name-only comparisons
assert(compareFileNames('a', 'A') !== compareLocale('a', 'A'), 'the same letter does not sort by locale');
assert(compareFileNames('â', 'Â') !== compareLocale('â', 'Â'), 'the same accented letter does not sort by locale');
assert.notDeepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNames), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order');
assert.notDeepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNames), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents do not sort in locale order');
// numeric comparisons
assert(compareFileNames('abc02.txt', 'abc002.txt') > 0, 'filenames with equivalent numbers and leading zeros sort in unicode order');
assert(compareFileNames('abc.txt1', 'abc.txt01') > 0, 'same name plus extensions with equal numbers sort in unicode order');
assert(compareFileNames('art01', 'Art01') !== 'art01'.localeCompare('Art01', undefined, { numeric: true }),
'a numerically equivalent word of a different case does not compare numerically based on locale');
});
test('compareFileExtensions', () => {
//
// Comparisons with the same results as compareFileExtensionsDefault
//
// name-only comparisons
assert(compareFileExtensions(null, null) === 0, 'null should be equal');
assert(compareFileExtensions(null, 'abc') < 0, 'null should come before real files without extension');
assert(compareFileExtensions('', '') === 0, 'empty should be equal');
assert(compareFileExtensions('abc', 'abc') === 0, 'equal names should be equal');
assert(compareFileExtensions('z', 'A') > 0, 'z comes after A');
assert(compareFileExtensions('Z', 'a') > 0, 'Z comes after a');
// name plus extension comparisons
assert(compareFileExtensions('file.ext', 'file.ext') === 0, 'equal full names should be equal');
assert(compareFileExtensions('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
assert(compareFileExtensions('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
assert(compareFileExtensions('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extensions even if filenames compare differently');
assert(compareFileExtensions('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names');
assert(compareFileExtensions('agg.go', 'agg_repo.go') < 0, 'shorter names short before longer names even when the longer name contains an underscore');
assert(compareFileExtensions('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name');
// dotfile comparisons
assert(compareFileExtensions('.abc', '.abc') === 0, 'equal dotfiles should be equal');
assert(compareFileExtensions('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case');
// dotfile vs non-dotfile comparisons
assert(compareFileExtensions(null, '.abc') < 0, 'null should come before dotfiles');
assert(compareFileExtensions('.env', 'aaa.env') < 0, 'if equal extensions, filenames should be compared, empty filename should come before others');
assert(compareFileExtensions('.MD', 'a.md') < 0, 'if extensions differ in case, files sort by extension in unicode order');
// numeric comparisons
assert(compareFileExtensions('1', '1') === 0, 'numerically equal full names should be equal');
assert(compareFileExtensions('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
assert(compareFileExtensions('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensions('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long');
assert(compareFileExtensions('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
assert(compareFileExtensions('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
assert(compareFileExtensions('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensions('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal');
assert(compareFileExtensions('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensions('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long');
assert(compareFileExtensions('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared');
assert(compareFileExtensions('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically');
//
// Comparisons with different results from compareFileExtensionsDefault
//
// name-only comparisions
assert(compareFileExtensions('a', 'A') !== compareLocale('a', 'A'), 'the same letter of different case does not sort by locale');
assert(compareFileExtensions('â', 'Â') !== compareLocale('â', 'Â'), 'the same accented letter of different case does not sort by locale');
assert.notDeepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensions), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order');
assert.notDeepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensions), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents do not sort in locale order');
// name plus extension comparisons
assert(compareFileExtensions('a.MD', 'a.md') !== compareLocale('MD', 'md'), 'case differences in extensions do not sort by locale');
assert(compareFileExtensions('a.md', 'A.md') !== compareLocale('a', 'A'), 'case differences in names do not sort by locale');
assert(compareFileExtensions('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order');
// dotfile comparisons
assert(compareFileExtensions('.env', '.aaa.env') < 0, 'a dotfile with an extension is treated as a name plus an extension - equal extensions');
assert(compareFileExtensions('.env', '.env.aaa') > 0, 'a dotfile with an extension is treated as a name plus an extension - unequal extensions');
// dotfile vs non-dotfile comparisons
assert(compareFileExtensions('.env', 'aaa') > 0, 'filenames without extensions come before dotfiles');
assert(compareFileExtensions('.md', 'A.MD') > 0, 'a file with an uppercase extension sorts before a dotfile of the same lowercase extension');
// numeric comparisons
assert(compareFileExtensions('abc.txt01', 'abc.txt1') < 0, 'extensions with equal numbers sort in unicode order');
assert(compareFileExtensions('art01', 'Art01') !== compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case does not compare by locale');
assert(compareFileExtensions('abc02.txt', 'abc002.txt') > 0, 'filenames with equivalent numbers and leading zeros sort in unicode order');
assert(compareFileExtensions('txt.abc01', 'txt.abc1') < 0, 'extensions with equivalent numbers sort in unicode order');
});
test('compareFileNamesDefault', () => {
//
// Comparisons with the same results as compareFileNames
//
// name-only comparisons
assert(compareFileNamesDefault(null, null) === 0, 'null should be equal');
assert(compareFileNamesDefault(null, 'abc') < 0, 'null should be come before real values');
assert(compareFileNamesDefault('', '') === 0, 'empty should be equal');
assert(compareFileNamesDefault('abc', 'abc') === 0, 'equal names should be equal');
assert(compareFileNamesDefault('z', 'A') > 0, 'z comes is after A regardless of case');
assert(compareFileNamesDefault('Z', 'a') > 0, 'Z comes after a regardless of case');
// name plus extension comparisons
assert(compareFileNamesDefault('file.ext', 'file.ext') === 0, 'equal full names should be equal');
assert(compareFileNamesDefault('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
assert(compareFileNamesDefault('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
assert(compareFileNamesDefault('bbb.aaa', 'aaa.bbb') > 0, 'files should be compared by names even if extensions compare differently');
assert(compareFileNamesDefault('aggregate.go', 'aggregate_repo.go') > 0, 'compares the whole filename in locale order');
// dotfile comparisons
assert(compareFileNamesDefault('.abc', '.abc') === 0, 'equal dotfile names should be equal');
assert(compareFileNamesDefault('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly');
assert(compareFileNamesDefault('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
assert(compareFileNamesDefault('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
assert(compareFileNamesDefault('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot');
// dotfile vs non-dotfile comparisons
assert(compareFileNamesDefault(null, '.abc') < 0, 'null should come before dotfiles');
assert(compareFileNamesDefault('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
assert(compareFileNamesDefault('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
assert(compareFileNamesDefault('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
assert(compareFileNamesDefault('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
// numeric comparisons
assert(compareFileNamesDefault('1', '1') === 0, 'numerically equal full names should be equal');
assert(compareFileNamesDefault('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
assert(compareFileNamesDefault('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
assert(compareFileNamesDefault('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long');
assert(compareFileNamesDefault('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
assert(compareFileNamesDefault('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
//
// Comparisons with different results than compareFileNames
//
// name-only comparisons
assert(compareFileNamesDefault('a', 'A') === compareLocale('a', 'A'), 'the same letter sorts by locale');
assert(compareFileNamesDefault('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter sorts by locale');
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNamesDefault), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents sort in locale order');
// numeric comparisons
assert(compareFileNamesDefault('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest number first');
assert(compareFileNamesDefault('abc.txt1', 'abc.txt01') < 0, 'same name plus extensions with equal numbers sort shortest number first');
assert(compareFileNamesDefault('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale');
});
test('compareFileExtensionsDefault', () => {
//
// Comparisons with the same result as compareFileExtensions
//
// name-only comparisons
assert(compareFileExtensionsDefault(null, null) === 0, 'null should be equal');
assert(compareFileExtensionsDefault(null, 'abc') < 0, 'null should come before real files without extensions');
assert(compareFileExtensionsDefault('', '') === 0, 'empty should be equal');
assert(compareFileExtensionsDefault('abc', 'abc') === 0, 'equal names should be equal');
assert(compareFileExtensionsDefault('z', 'A') > 0, 'z comes after A');
assert(compareFileExtensionsDefault('Z', 'a') > 0, 'Z comes after a');
// name plus extension comparisons
assert(compareFileExtensionsDefault('file.ext', 'file.ext') === 0, 'equal full filenames should be equal');
assert(compareFileExtensionsDefault('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
assert(compareFileExtensionsDefault('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
assert(compareFileExtensionsDefault('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extension first');
assert(compareFileExtensionsDefault('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names');
assert(compareFileExtensionsDefault('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name');
// dotfile comparisons
assert(compareFileExtensionsDefault('.abc', '.abc') === 0, 'equal dotfiles should be equal');
assert(compareFileExtensionsDefault('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case');
// dotfile vs non-dotfile comparisons
assert(compareFileExtensionsDefault(null, '.abc') < 0, 'null should come before dotfiles');
assert(compareFileExtensionsDefault('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
assert(compareFileExtensionsDefault('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
// numeric comparisons
assert(compareFileExtensionsDefault('1', '1') === 0, 'numerically equal full names should be equal');
assert(compareFileExtensionsDefault('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
assert(compareFileExtensionsDefault('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensionsDefault('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order');
assert(compareFileExtensionsDefault('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
assert(compareFileExtensionsDefault('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
assert(compareFileExtensionsDefault('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensionsDefault('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal');
assert(compareFileExtensionsDefault('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensionsDefault('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long');
assert(compareFileExtensionsDefault('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared');
assert(compareFileExtensionsDefault('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically');
//
// Comparisons with different results than compareFileExtensions
//
// name-only comparisons
assert(compareFileExtensionsDefault('a', 'A') === compareLocale('a', 'A'), 'the same letter of different case sorts by locale');
assert(compareFileExtensionsDefault('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter of different case sorts by locale');
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsDefault), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensionsDefault), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents sort in locale order');
// name plus extension comparisons
assert(compareFileExtensionsDefault('a.MD', 'a.md') === compareLocale('MD', 'md'), 'case differences in extensions sort by locale');
assert(compareFileExtensionsDefault('a.md', 'A.md') === compareLocale('a', 'A'), 'case differences in names sort by locale');
assert(compareFileExtensionsDefault('aggregate.go', 'aggregate_repo.go') > 0, 'names with the same extension sort in full filename locale order');
// dotfile comparisons
assert(compareFileExtensionsDefault('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
assert(compareFileExtensionsDefault('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
// dotfile vs non-dotfile comparisons
assert(compareFileExtensionsDefault('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
assert(compareFileExtensionsDefault('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
// numeric comparisons
assert(compareFileExtensionsDefault('abc.txt01', 'abc.txt1') > 0, 'extensions with equal numbers should be in shortest-first order');
assert(compareFileExtensionsDefault('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale');
assert(compareFileExtensionsDefault('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest string first');
assert(compareFileExtensionsDefault('txt.abc01', 'txt.abc1') > 0, 'extensions with equivalent numbers sort shortest extension first');
});
});

View File

@@ -0,0 +1,131 @@
/*---------------------------------------------------------------------------------------------
* 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 * as dom from 'vs/base/browser/dom';
const $ = dom.$;
suite('dom', () => {
test('hasClass', () => {
let element = document.createElement('div');
element.className = 'foobar boo far';
assert(element.classList.contains('foobar'));
assert(element.classList.contains('boo'));
assert(element.classList.contains('far'));
assert(!element.classList.contains('bar'));
assert(!element.classList.contains('foo'));
assert(!element.classList.contains(''));
});
test('removeClass', () => {
let element = document.createElement('div');
element.className = 'foobar boo far';
element.classList.remove('boo');
assert(element.classList.contains('far'));
assert(!element.classList.contains('boo'));
assert(element.classList.contains('foobar'));
assert.equal(element.className, 'foobar far');
element = document.createElement('div');
element.className = 'foobar boo far';
element.classList.remove('far');
assert(!element.classList.contains('far'));
assert(element.classList.contains('boo'));
assert(element.classList.contains('foobar'));
assert.equal(element.className, 'foobar boo');
element.classList.remove('boo');
assert(!element.classList.contains('far'));
assert(!element.classList.contains('boo'));
assert(element.classList.contains('foobar'));
assert.equal(element.className, 'foobar');
element.classList.remove('foobar');
assert(!element.classList.contains('far'));
assert(!element.classList.contains('boo'));
assert(!element.classList.contains('foobar'));
assert.equal(element.className, '');
});
test('removeClass should consider hyphens', function () {
let element = document.createElement('div');
element.classList.add('foo-bar');
element.classList.add('bar');
assert(element.classList.contains('foo-bar'));
assert(element.classList.contains('bar'));
element.classList.remove('bar');
assert(element.classList.contains('foo-bar'));
assert(!element.classList.contains('bar'));
element.classList.remove('foo-bar');
assert(!element.classList.contains('foo-bar'));
assert(!element.classList.contains('bar'));
});
test('multibyteAwareBtoa', () => {
assert.equal(dom.multibyteAwareBtoa('hello world'), dom.multibyteAwareBtoa('hello world'));
assert.ok(dom.multibyteAwareBtoa('平仮名'));
});
suite('$', () => {
test('should build simple nodes', () => {
const div = $('div');
assert(div);
assert(div instanceof HTMLElement);
assert.equal(div.tagName, 'DIV');
assert(!div.firstChild);
});
test('should buld nodes with id', () => {
const div = $('div#foo');
assert(div);
assert(div instanceof HTMLElement);
assert.equal(div.tagName, 'DIV');
assert.equal(div.id, 'foo');
});
test('should buld nodes with class-name', () => {
const div = $('div.foo');
assert(div);
assert(div instanceof HTMLElement);
assert.equal(div.tagName, 'DIV');
assert.equal(div.className, 'foo');
});
test('should build nodes with attributes', () => {
let div = $('div', { class: 'test' });
assert.equal(div.className, 'test');
div = $('div', undefined);
assert.equal(div.className, '');
});
test('should build nodes with children', () => {
let div = $('div', undefined, $('span', { id: 'demospan' }));
let firstChild = div.firstChild as HTMLElement;
assert.equal(firstChild.tagName, 'SPAN');
assert.equal(firstChild.id, 'demospan');
div = $('div', undefined, 'hello');
assert.equal(div.firstChild && div.firstChild.textContent, 'hello');
});
test('should build nodes with text children', () => {
let div = $('div', undefined, 'foobar');
let firstChild = div.firstChild as HTMLElement;
assert.equal(firstChild.tagName, undefined);
assert.equal(firstChild.textContent, 'foobar');
});
});
});

View File

@@ -0,0 +1,104 @@
/*---------------------------------------------------------------------------------------------
* 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 { renderText, renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
import { DisposableStore } from 'vs/base/common/lifecycle';
suite('FormattedTextRenderer', () => {
const store = new DisposableStore();
setup(() => {
store.clear();
});
teardown(() => {
store.clear();
});
test('render simple element', () => {
let result: HTMLElement = renderText('testing');
assert.strictEqual(result.nodeType, document.ELEMENT_NODE);
assert.strictEqual(result.textContent, 'testing');
assert.strictEqual(result.tagName, 'DIV');
});
test('render element with class', () => {
let result: HTMLElement = renderText('testing', {
className: 'testClass'
});
assert.strictEqual(result.nodeType, document.ELEMENT_NODE);
assert.strictEqual(result.className, 'testClass');
});
test('simple formatting', () => {
let result: HTMLElement = renderFormattedText('**bold**');
assert.strictEqual(result.children.length, 1);
assert.strictEqual(result.firstChild!.textContent, 'bold');
assert.strictEqual((<HTMLElement>result.firstChild).tagName, 'B');
assert.strictEqual(result.innerHTML, '<b>bold</b>');
result = renderFormattedText('__italics__');
assert.strictEqual(result.innerHTML, '<i>italics</i>');
result = renderFormattedText('this string has **bold** and __italics__');
assert.strictEqual(result.innerHTML, 'this string has <b>bold</b> and <i>italics</i>');
});
test('no formatting', () => {
let result: HTMLElement = renderFormattedText('this is just a string');
assert.strictEqual(result.innerHTML, 'this is just a string');
});
test('preserve newlines', () => {
let result: HTMLElement = renderFormattedText('line one\nline two');
assert.strictEqual(result.innerHTML, 'line one<br>line two');
});
test('action', () => {
let callbackCalled = false;
let result: HTMLElement = renderFormattedText('[[action]]', {
actionHandler: {
callback(content) {
assert.strictEqual(content, '0');
callbackCalled = true;
},
disposeables: store
}
});
assert.strictEqual(result.innerHTML, '<a href="#">action</a>');
let event: MouseEvent = <any>document.createEvent('MouseEvent');
event.initEvent('click', true, true);
result.firstChild!.dispatchEvent(event);
assert.strictEqual(callbackCalled, true);
});
test('fancy action', () => {
let callbackCalled = false;
let result: HTMLElement = renderFormattedText('__**[[action]]**__', {
actionHandler: {
callback(content) {
assert.strictEqual(content, '0');
callbackCalled = true;
},
disposeables: store
}
});
assert.strictEqual(result.innerHTML, '<i><b><a href="#">action</a></b></i>');
let event: MouseEvent = <any>document.createEvent('MouseEvent');
event.initEvent('click', true, true);
result.firstChild!.firstChild!.firstChild!.dispatchEvent(event);
assert.strictEqual(callbackCalled, true);
});
test('escaped formatting', () => {
let result: HTMLElement = renderFormattedText('\\*\\*bold\\*\\*');
assert.strictEqual(result.children.length, 0);
assert.strictEqual(result.innerHTML, '**bold**');
});
});

View File

@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* 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 { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
suite('HighlightedLabel', () => {
let label: HighlightedLabel;
setup(() => {
label = new HighlightedLabel(document.createElement('div'), true);
});
test('empty label', function () {
assert.equal(label.element.innerHTML, '');
});
test('no decorations', function () {
label.set('hello');
assert.equal(label.element.innerHTML, '<span>hello</span>');
});
test('escape html', function () {
label.set('hel<lo');
assert.equal(label.element.innerHTML, '<span>hel&lt;lo</span>');
});
test('everything highlighted', function () {
label.set('hello', [{ start: 0, end: 5 }]);
assert.equal(label.element.innerHTML, '<span class="highlight">hello</span>');
});
test('beginning highlighted', function () {
label.set('hellothere', [{ start: 0, end: 5 }]);
assert.equal(label.element.innerHTML, '<span class="highlight">hello</span><span>there</span>');
});
test('ending highlighted', function () {
label.set('goodbye', [{ start: 4, end: 7 }]);
assert.equal(label.element.innerHTML, '<span>good</span><span class="highlight">bye</span>');
});
test('middle highlighted', function () {
label.set('foobarfoo', [{ start: 3, end: 6 }]);
assert.equal(label.element.innerHTML, '<span>foo</span><span class="highlight">bar</span><span>foo</span>');
});
test('escapeNewLines', () => {
let highlights = [{ start: 0, end: 5 }, { start: 7, end: 9 }, { start: 11, end: 12 }];// before,after,after
let escaped = HighlightedLabel.escapeNewLines('ACTION\r\n_TYPE2', highlights);
assert.equal(escaped, 'ACTION\u23CE_TYPE2');
assert.deepEqual(highlights, [{ start: 0, end: 5 }, { start: 6, end: 8 }, { start: 10, end: 11 }]);
highlights = [{ start: 5, end: 9 }, { start: 11, end: 12 }];//overlap,after
escaped = HighlightedLabel.escapeNewLines('ACTION\r\n_TYPE2', highlights);
assert.equal(escaped, 'ACTION\u23CE_TYPE2');
assert.deepEqual(highlights, [{ start: 5, end: 8 }, { start: 10, end: 11 }]);
});
});

View File

@@ -0,0 +1,118 @@
/*---------------------------------------------------------------------------------------------
* 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 * as marked from 'vs/base/common/marked/marked';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
import { MarkdownString, IMarkdownString } from 'vs/base/common/htmlContent';
import { URI } from 'vs/base/common/uri';
import { parse } from 'vs/base/common/marshalling';
suite('MarkdownRenderer', () => {
suite('Images', () => {
test('image rendering conforms to default', () => {
const markdown = { value: `![image](someimageurl 'caption')` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image rendering conforms to default without title', () => {
const markdown = { value: `![image](someimageurl)` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image width from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
});
test('image height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
});
test('image width and height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
});
});
suite('ThemeIcons Support On', () => {
test('render appendText', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendText('$(zap) $(not a theme icon) $(add)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);
});
test('render appendMarkdown', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendMarkdown('$(zap) $(not a theme icon) $(add)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-zap"></span> $(not a theme icon) <span class="codicon codicon-add"></span></p>`);
});
test('render appendMarkdown with escaped icon', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) <span class="codicon codicon-add"></span></p>`);
});
});
suite('ThemeIcons Support Off', () => {
test('render appendText', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
mds.appendText('$(zap) $(not a theme icon) $(add)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);
});
test('render appendMarkdown with escaped icon', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
mds.appendMarkdown('\\$(zap) $(not a theme icon) $(add)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(not a theme icon) $(add)</p>`);
});
});
test('npm Hover Run Script not working #90855', function () {
const md: IMarkdownString = JSON.parse('{"value":"[Run Script](command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D \\"Run the script as a task\\")","supportThemeIcons":false,"isTrusted":true,"uris":{"__uri_e49443":{"$mid":1,"fsPath":"c:\\\\Users\\\\jrieken\\\\Code\\\\_sample\\\\foo\\\\package.json","_sep":1,"external":"file:///c%3A/Users/jrieken/Code/_sample/foo/package.json","path":"/c:/Users/jrieken/Code/_sample/foo/package.json","scheme":"file"},"command:npm.runScriptFromHover?%7B%22documentUri%22%3A%7B%22%24mid%22%3A1%2C%22fsPath%22%3A%22c%3A%5C%5CUsers%5C%5Cjrieken%5C%5CCode%5C%5C_sample%5C%5Cfoo%5C%5Cpackage.json%22%2C%22_sep%22%3A1%2C%22external%22%3A%22file%3A%2F%2F%2Fc%253A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22path%22%3A%22%2Fc%3A%2FUsers%2Fjrieken%2FCode%2F_sample%2Ffoo%2Fpackage.json%22%2C%22scheme%22%3A%22file%22%7D%2C%22script%22%3A%22echo%22%7D":{"$mid":1,"path":"npm.runScriptFromHover","scheme":"command","query":"{\\"documentUri\\":\\"__uri_e49443\\",\\"script\\":\\"echo\\"}"}}}');
const element = renderMarkdown(md);
const anchor = element.querySelector('a')!;
assert.ok(anchor);
assert.ok(anchor.dataset['href']);
const uri = URI.parse(anchor.dataset['href']!);
const data = <{ script: string, documentUri: URI }>parse(decodeURIComponent(uri.query));
assert.ok(data);
assert.equal(data.script, 'echo');
assert.ok(data.documentUri.toString().startsWith('file:///c%3A/'));
});
});

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* 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 { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
suite('ProgressBar', () => {
let fixture: HTMLElement;
setup(() => {
fixture = document.createElement('div');
document.body.appendChild(fixture);
});
teardown(() => {
document.body.removeChild(fixture);
});
test('Progress Bar', function () {
const bar = new ProgressBar(fixture);
assert(bar.infinite());
assert(bar.total(100));
assert(bar.worked(50));
assert(bar.setWorked(70));
assert(bar.worked(30));
assert(bar.done());
bar.dispose();
});
});

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* 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 { layout, LayoutAnchorPosition } from 'vs/base/browser/ui/contextview/contextview';
suite('Contextview', function () {
test('layout', () => {
assert.equal(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.Before }), 0);
assert.equal(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.Before }), 50);
assert.equal(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.Before }), 180);
assert.equal(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.After }), 0);
assert.equal(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.After }), 30);
assert.equal(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.After }), 180);
assert.equal(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.Before }), 50);
assert.equal(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.Before }), 100);
assert.equal(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.Before }), 130);
assert.equal(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.After }), 50);
assert.equal(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.After }), 30);
assert.equal(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.After }), 130);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,225 @@
/*---------------------------------------------------------------------------------------------
* 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 { $ } from 'vs/base/browser/dom';
import { GridView, IView, Sizing } from 'vs/base/browser/ui/grid/gridview';
import { nodesToArrays, TestView } from './util';
suite('Gridview', function () {
let gridview: GridView;
setup(function () {
gridview = new GridView();
const container = $('.container');
container.style.position = 'absolute';
container.style.width = `${200}px`;
container.style.height = `${200}px`;
container.appendChild(gridview.element);
});
test('empty gridview is empty', function () {
assert.deepEqual(nodesToArrays(gridview.getView()), []);
gridview.dispose();
});
test('gridview addView', function () {
const view = new TestView(20, 20, 20, 20);
assert.throws(() => gridview.addView(view, 200, []), 'empty location');
assert.throws(() => gridview.addView(view, 200, [1]), 'index overflow');
assert.throws(() => gridview.addView(view, 200, [0, 0]), 'hierarchy overflow');
const views = [
new TestView(20, 20, 20, 20),
new TestView(20, 20, 20, 20),
new TestView(20, 20, 20, 20)
];
gridview.addView(views[0], 200, [0]);
gridview.addView(views[1], 200, [1]);
gridview.addView(views[2], 200, [2]);
assert.deepEqual(nodesToArrays(gridview.getView()), views);
gridview.dispose();
});
test('gridview addView nested', function () {
const views = [
new TestView(20, 20, 20, 20),
[
new TestView(20, 20, 20, 20),
new TestView(20, 20, 20, 20)
]
];
gridview.addView(views[0] as IView, 200, [0]);
gridview.addView((views[1] as TestView[])[0] as IView, 200, [1]);
gridview.addView((views[1] as TestView[])[1] as IView, 200, [1, 1]);
assert.deepEqual(nodesToArrays(gridview.getView()), views);
gridview.dispose();
});
test('gridview addView deep nested', function () {
const view1 = new TestView(20, 20, 20, 20);
gridview.addView(view1 as IView, 200, [0]);
assert.deepEqual(nodesToArrays(gridview.getView()), [view1]);
const view2 = new TestView(20, 20, 20, 20);
gridview.addView(view2 as IView, 200, [1]);
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, view2]);
const view3 = new TestView(20, 20, 20, 20);
gridview.addView(view3 as IView, 200, [1, 0]);
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view3, view2]]);
const view4 = new TestView(20, 20, 20, 20);
gridview.addView(view4 as IView, 200, [1, 0, 0]);
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [[view4, view3], view2]]);
const view5 = new TestView(20, 20, 20, 20);
gridview.addView(view5 as IView, 200, [1, 0]);
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2]]);
const view6 = new TestView(20, 20, 20, 20);
gridview.addView(view6 as IView, 200, [2]);
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view4, view3], view2], view6]);
const view7 = new TestView(20, 20, 20, 20);
gridview.addView(view7 as IView, 200, [1, 1]);
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, view7, [view4, view3], view2], view6]);
const view8 = new TestView(20, 20, 20, 20);
gridview.addView(view8 as IView, 200, [1, 1, 0]);
assert.deepEqual(nodesToArrays(gridview.getView()), [view1, [view5, [view8, view7], [view4, view3], view2], view6]);
gridview.dispose();
});
test('simple layout', function () {
gridview.layout(800, 600);
const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view1, 200, [0]);
assert.deepEqual(view1.size, [800, 600]);
assert.deepEqual(gridview.getViewSize([0]), { width: 800, height: 600 });
const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view2, 200, [0]);
assert.deepEqual(view1.size, [800, 400]);
assert.deepEqual(gridview.getViewSize([1]), { width: 800, height: 400 });
assert.deepEqual(view2.size, [800, 200]);
assert.deepEqual(gridview.getViewSize([0]), { width: 800, height: 200 });
const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view3, 200, [1, 1]);
assert.deepEqual(view1.size, [600, 400]);
assert.deepEqual(gridview.getViewSize([1, 0]), { width: 600, height: 400 });
assert.deepEqual(view2.size, [800, 200]);
assert.deepEqual(gridview.getViewSize([0]), { width: 800, height: 200 });
assert.deepEqual(view3.size, [200, 400]);
assert.deepEqual(gridview.getViewSize([1, 1]), { width: 200, height: 400 });
const view4 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view4, 200, [0, 0]);
assert.deepEqual(view1.size, [600, 400]);
assert.deepEqual(gridview.getViewSize([1, 0]), { width: 600, height: 400 });
assert.deepEqual(view2.size, [600, 200]);
assert.deepEqual(gridview.getViewSize([0, 1]), { width: 600, height: 200 });
assert.deepEqual(view3.size, [200, 400]);
assert.deepEqual(gridview.getViewSize([1, 1]), { width: 200, height: 400 });
assert.deepEqual(view4.size, [200, 200]);
assert.deepEqual(gridview.getViewSize([0, 0]), { width: 200, height: 200 });
const view5 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view5, 100, [1, 0, 1]);
assert.deepEqual(view1.size, [600, 300]);
assert.deepEqual(gridview.getViewSize([1, 0, 0]), { width: 600, height: 300 });
assert.deepEqual(view2.size, [600, 200]);
assert.deepEqual(gridview.getViewSize([0, 1]), { width: 600, height: 200 });
assert.deepEqual(view3.size, [200, 400]);
assert.deepEqual(gridview.getViewSize([1, 1]), { width: 200, height: 400 });
assert.deepEqual(view4.size, [200, 200]);
assert.deepEqual(gridview.getViewSize([0, 0]), { width: 200, height: 200 });
assert.deepEqual(view5.size, [600, 100]);
assert.deepEqual(gridview.getViewSize([1, 0, 1]), { width: 600, height: 100 });
});
test('simple layout with automatic size distribution', function () {
gridview.layout(800, 600);
const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view1, Sizing.Distribute, [0]);
assert.deepEqual(view1.size, [800, 600]);
assert.deepEqual(gridview.getViewSize([0]), { width: 800, height: 600 });
const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view2, Sizing.Distribute, [0]);
assert.deepEqual(view1.size, [800, 300]);
assert.deepEqual(view2.size, [800, 300]);
const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
assert.deepEqual(view1.size, [400, 300]);
assert.deepEqual(view2.size, [800, 300]);
assert.deepEqual(view3.size, [400, 300]);
const view4 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view4, Sizing.Distribute, [0, 0]);
assert.deepEqual(view1.size, [400, 300]);
assert.deepEqual(view2.size, [400, 300]);
assert.deepEqual(view3.size, [400, 300]);
assert.deepEqual(view4.size, [400, 300]);
const view5 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view5, Sizing.Distribute, [1, 0, 1]);
assert.deepEqual(view1.size, [400, 150]);
assert.deepEqual(view2.size, [400, 300]);
assert.deepEqual(view3.size, [400, 300]);
assert.deepEqual(view4.size, [400, 300]);
assert.deepEqual(view5.size, [400, 150]);
});
test('addviews before layout call 1', function () {
const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view1, 200, [0]);
const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view2, 200, [0]);
const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view3, 200, [1, 1]);
gridview.layout(800, 600);
assert.deepEqual(view1.size, [400, 300]);
assert.deepEqual(view2.size, [800, 300]);
assert.deepEqual(view3.size, [400, 300]);
});
test('addviews before layout call 2', function () {
const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view1, 200, [0]);
const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view2, 200, [0]);
const view3 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY);
gridview.addView(view3, 200, [0, 0]);
gridview.layout(800, 600);
assert.deepEqual(view1.size, [800, 300]);
assert.deepEqual(view2.size, [400, 300]);
assert.deepEqual(view3.size, [400, 300]);
});
});

View File

@@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* 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 { Emitter, Event } from 'vs/base/common/event';
import { GridNode, isGridBranchNode } from 'vs/base/browser/ui/grid/gridview';
import { IView } from 'vs/base/browser/ui/grid/grid';
export class TestView implements IView {
private readonly _onDidChange = new Emitter<{ width: number; height: number; } | undefined>();
readonly onDidChange = this._onDidChange.event;
get minimumWidth(): number { return this._minimumWidth; }
set minimumWidth(size: number) { this._minimumWidth = size; this._onDidChange.fire(undefined); }
get maximumWidth(): number { return this._maximumWidth; }
set maximumWidth(size: number) { this._maximumWidth = size; this._onDidChange.fire(undefined); }
get minimumHeight(): number { return this._minimumHeight; }
set minimumHeight(size: number) { this._minimumHeight = size; this._onDidChange.fire(undefined); }
get maximumHeight(): number { return this._maximumHeight; }
set maximumHeight(size: number) { this._maximumHeight = size; this._onDidChange.fire(undefined); }
private _element: HTMLElement = document.createElement('div');
get element(): HTMLElement { this._onDidGetElement.fire(); return this._element; }
private readonly _onDidGetElement = new Emitter<void>();
readonly onDidGetElement = this._onDidGetElement.event;
private _width = 0;
get width(): number { return this._width; }
private _height = 0;
get height(): number { return this._height; }
get size(): [number, number] { return [this.width, this.height]; }
private readonly _onDidLayout = new Emitter<{ width: number; height: number; }>();
readonly onDidLayout: Event<{ width: number; height: number; }> = this._onDidLayout.event;
private readonly _onDidFocus = new Emitter<void>();
readonly onDidFocus: Event<void> = this._onDidFocus.event;
constructor(
private _minimumWidth: number,
private _maximumWidth: number,
private _minimumHeight: number,
private _maximumHeight: number
) {
assert(_minimumWidth <= _maximumWidth, 'gridview view minimum width must be <= maximum width');
assert(_minimumHeight <= _maximumHeight, 'gridview view minimum height must be <= maximum height');
}
layout(width: number, height: number): void {
this._width = width;
this._height = height;
this._onDidLayout.fire({ width, height });
}
focus(): void {
this._onDidFocus.fire();
}
dispose(): void {
this._onDidChange.dispose();
this._onDidGetElement.dispose();
this._onDidLayout.dispose();
this._onDidFocus.dispose();
}
}
export function nodesToArrays(node: GridNode): any {
if (isGridBranchNode(node)) {
return node.children.map(nodesToArrays);
} else {
return node.view;
}
}

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* 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 { ListView } from 'vs/base/browser/ui/list/listView';
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
import { range } from 'vs/base/common/arrays';
suite('ListView', function () {
test('all rows get disposed', function () {
const element = document.createElement('div');
element.style.height = '200px';
element.style.width = '200px';
const delegate: IListVirtualDelegate<number> = {
getHeight() { return 20; },
getTemplateId() { return 'template'; }
};
let templatesCount = 0;
const renderer: IListRenderer<number, void> = {
templateId: 'template',
renderTemplate() { templatesCount++; },
renderElement() { },
disposeTemplate() { templatesCount--; }
};
const listView = new ListView<number>(element, delegate, [renderer]);
listView.layout(200);
assert.equal(templatesCount, 0, 'no templates have been allocated');
listView.splice(0, 0, range(100));
assert.equal(templatesCount, 10, 'some templates have been allocated');
listView.dispose();
assert.equal(templatesCount, 0, 'all templates have been disposed');
});
});

View File

@@ -0,0 +1,345 @@
/*---------------------------------------------------------------------------------------------
* 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 { RangeMap, groupIntersect, consolidate } from 'vs/base/browser/ui/list/rangeMap';
import { Range } from 'vs/base/common/range';
suite('RangeMap', () => {
let rangeMap: RangeMap;
setup(() => {
rangeMap = new RangeMap();
});
test('intersection', () => {
assert.deepEqual(Range.intersect({ start: 0, end: 0 }, { start: 0, end: 0 }), { start: 0, end: 0 });
assert.deepEqual(Range.intersect({ start: 0, end: 0 }, { start: 5, end: 5 }), { start: 0, end: 0 });
assert.deepEqual(Range.intersect({ start: 0, end: 1 }, { start: 5, end: 6 }), { start: 0, end: 0 });
assert.deepEqual(Range.intersect({ start: 5, end: 6 }, { start: 0, end: 1 }), { start: 0, end: 0 });
assert.deepEqual(Range.intersect({ start: 0, end: 5 }, { start: 2, end: 2 }), { start: 0, end: 0 });
assert.deepEqual(Range.intersect({ start: 0, end: 1 }, { start: 0, end: 1 }), { start: 0, end: 1 });
assert.deepEqual(Range.intersect({ start: 0, end: 10 }, { start: 0, end: 5 }), { start: 0, end: 5 });
assert.deepEqual(Range.intersect({ start: 0, end: 5 }, { start: 0, end: 10 }), { start: 0, end: 5 });
assert.deepEqual(Range.intersect({ start: 0, end: 10 }, { start: 5, end: 10 }), { start: 5, end: 10 });
assert.deepEqual(Range.intersect({ start: 5, end: 10 }, { start: 0, end: 10 }), { start: 5, end: 10 });
assert.deepEqual(Range.intersect({ start: 0, end: 10 }, { start: 2, end: 8 }), { start: 2, end: 8 });
assert.deepEqual(Range.intersect({ start: 2, end: 8 }, { start: 0, end: 10 }), { start: 2, end: 8 });
assert.deepEqual(Range.intersect({ start: 0, end: 10 }, { start: 5, end: 15 }), { start: 5, end: 10 });
assert.deepEqual(Range.intersect({ start: 5, end: 15 }, { start: 0, end: 10 }), { start: 5, end: 10 });
});
test('multiIntersect', () => {
assert.deepEqual(
groupIntersect(
{ start: 0, end: 0 },
[{ range: { start: 0, end: 10 }, size: 1 }]
),
[]
);
assert.deepEqual(
groupIntersect(
{ start: 10, end: 20 },
[{ range: { start: 0, end: 10 }, size: 1 }]
),
[]
);
assert.deepEqual(
groupIntersect(
{ start: 2, end: 8 },
[{ range: { start: 0, end: 10 }, size: 1 }]
),
[{ range: { start: 2, end: 8 }, size: 1 }]
);
assert.deepEqual(
groupIntersect(
{ start: 2, end: 8 },
[{ range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 5 }]
),
[{ range: { start: 2, end: 8 }, size: 1 }]
);
assert.deepEqual(
groupIntersect(
{ start: 12, end: 18 },
[{ range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 5 }]
),
[{ range: { start: 12, end: 18 }, size: 5 }]
);
assert.deepEqual(
groupIntersect(
{ start: 2, end: 18 },
[{ range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 5 }]
),
[{ range: { start: 2, end: 10 }, size: 1 }, { range: { start: 10, end: 18 }, size: 5 }]
);
assert.deepEqual(
groupIntersect(
{ start: 2, end: 28 },
[{ range: { start: 0, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 5 }, { range: { start: 20, end: 30 }, size: 10 }]
),
[{ range: { start: 2, end: 10 }, size: 1 }, { range: { start: 10, end: 20 }, size: 5 }, { range: { start: 20, end: 28 }, size: 10 }]
);
});
test('consolidate', () => {
assert.deepEqual(consolidate([]), []);
assert.deepEqual(
consolidate([{ range: { start: 0, end: 10 }, size: 1 }]),
[{ range: { start: 0, end: 10 }, size: 1 }]
);
assert.deepEqual(
consolidate([
{ range: { start: 0, end: 10 }, size: 1 },
{ range: { start: 10, end: 20 }, size: 1 }
]),
[{ range: { start: 0, end: 20 }, size: 1 }]
);
assert.deepEqual(
consolidate([
{ range: { start: 0, end: 10 }, size: 1 },
{ range: { start: 10, end: 20 }, size: 1 },
{ range: { start: 20, end: 100 }, size: 1 }
]),
[{ range: { start: 0, end: 100 }, size: 1 }]
);
assert.deepEqual(
consolidate([
{ range: { start: 0, end: 10 }, size: 1 },
{ range: { start: 10, end: 20 }, size: 5 },
{ range: { start: 20, end: 30 }, size: 10 }
]),
[
{ range: { start: 0, end: 10 }, size: 1 },
{ range: { start: 10, end: 20 }, size: 5 },
{ range: { start: 20, end: 30 }, size: 10 }
]
);
assert.deepEqual(
consolidate([
{ range: { start: 0, end: 10 }, size: 1 },
{ range: { start: 10, end: 20 }, size: 2 },
{ range: { start: 20, end: 100 }, size: 2 }
]),
[
{ range: { start: 0, end: 10 }, size: 1 },
{ range: { start: 10, end: 100 }, size: 2 }
]
);
});
test('empty', () => {
assert.equal(rangeMap.size, 0);
assert.equal(rangeMap.count, 0);
});
const one = { size: 1 };
const two = { size: 2 };
const three = { size: 3 };
const five = { size: 5 };
const ten = { size: 10 };
test('length & count', () => {
rangeMap.splice(0, 0, [one]);
assert.equal(rangeMap.size, 1);
assert.equal(rangeMap.count, 1);
});
test('length & count #2', () => {
rangeMap.splice(0, 0, [one, one, one, one, one]);
assert.equal(rangeMap.size, 5);
assert.equal(rangeMap.count, 5);
});
test('length & count #3', () => {
rangeMap.splice(0, 0, [five]);
assert.equal(rangeMap.size, 5);
assert.equal(rangeMap.count, 1);
});
test('length & count #4', () => {
rangeMap.splice(0, 0, [five, five, five, five, five]);
assert.equal(rangeMap.size, 25);
assert.equal(rangeMap.count, 5);
});
test('insert', () => {
rangeMap.splice(0, 0, [five, five, five, five, five]);
assert.equal(rangeMap.size, 25);
assert.equal(rangeMap.count, 5);
rangeMap.splice(0, 0, [five, five, five, five, five]);
assert.equal(rangeMap.size, 50);
assert.equal(rangeMap.count, 10);
rangeMap.splice(5, 0, [ten, ten]);
assert.equal(rangeMap.size, 70);
assert.equal(rangeMap.count, 12);
rangeMap.splice(12, 0, [{ size: 200 }]);
assert.equal(rangeMap.size, 270);
assert.equal(rangeMap.count, 13);
});
test('delete', () => {
rangeMap.splice(0, 0, [five, five, five, five, five,
five, five, five, five, five,
five, five, five, five, five,
five, five, five, five, five]);
assert.equal(rangeMap.size, 100);
assert.equal(rangeMap.count, 20);
rangeMap.splice(10, 5);
assert.equal(rangeMap.size, 75);
assert.equal(rangeMap.count, 15);
rangeMap.splice(0, 1);
assert.equal(rangeMap.size, 70);
assert.equal(rangeMap.count, 14);
rangeMap.splice(1, 13);
assert.equal(rangeMap.size, 5);
assert.equal(rangeMap.count, 1);
rangeMap.splice(1, 1);
assert.equal(rangeMap.size, 5);
assert.equal(rangeMap.count, 1);
});
test('insert & delete', () => {
assert.equal(rangeMap.size, 0);
assert.equal(rangeMap.count, 0);
rangeMap.splice(0, 0, [one]);
assert.equal(rangeMap.size, 1);
assert.equal(rangeMap.count, 1);
rangeMap.splice(0, 1);
assert.equal(rangeMap.size, 0);
assert.equal(rangeMap.count, 0);
});
test('insert & delete #2', () => {
rangeMap.splice(0, 0, [one, one, one, one, one,
one, one, one, one, one]);
rangeMap.splice(2, 6);
assert.equal(rangeMap.count, 4);
assert.equal(rangeMap.size, 4);
});
test('insert & delete #3', () => {
rangeMap.splice(0, 0, [one, one, one, one, one,
one, one, one, one, one,
two, two, two, two, two,
two, two, two, two, two]);
rangeMap.splice(8, 4);
assert.equal(rangeMap.count, 16);
assert.equal(rangeMap.size, 24);
});
test('insert & delete #3', () => {
rangeMap.splice(0, 0, [one, one, one, one, one,
one, one, one, one, one,
two, two, two, two, two,
two, two, two, two, two]);
rangeMap.splice(5, 0, [three, three, three, three, three]);
assert.equal(rangeMap.count, 25);
assert.equal(rangeMap.size, 45);
rangeMap.splice(4, 7);
assert.equal(rangeMap.count, 18);
assert.equal(rangeMap.size, 28);
});
suite('indexAt, positionAt', () => {
test('empty', () => {
assert.equal(rangeMap.indexAt(0), 0);
assert.equal(rangeMap.indexAt(10), 0);
assert.equal(rangeMap.indexAt(-1), -1);
assert.equal(rangeMap.positionAt(0), -1);
assert.equal(rangeMap.positionAt(10), -1);
assert.equal(rangeMap.positionAt(-1), -1);
});
test('simple', () => {
rangeMap.splice(0, 0, [one]);
assert.equal(rangeMap.indexAt(0), 0);
assert.equal(rangeMap.indexAt(1), 1);
assert.equal(rangeMap.positionAt(0), 0);
assert.equal(rangeMap.positionAt(1), -1);
});
test('simple #2', () => {
rangeMap.splice(0, 0, [ten]);
assert.equal(rangeMap.indexAt(0), 0);
assert.equal(rangeMap.indexAt(5), 0);
assert.equal(rangeMap.indexAt(9), 0);
assert.equal(rangeMap.indexAt(10), 1);
assert.equal(rangeMap.positionAt(0), 0);
assert.equal(rangeMap.positionAt(1), -1);
});
test('insert', () => {
rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one]);
assert.equal(rangeMap.indexAt(0), 0);
assert.equal(rangeMap.indexAt(1), 1);
assert.equal(rangeMap.indexAt(5), 5);
assert.equal(rangeMap.indexAt(9), 9);
assert.equal(rangeMap.indexAt(10), 10);
assert.equal(rangeMap.indexAt(11), 10);
rangeMap.splice(10, 0, [one, one, one, one, one, one, one, one, one, one]);
assert.equal(rangeMap.indexAt(10), 10);
assert.equal(rangeMap.indexAt(19), 19);
assert.equal(rangeMap.indexAt(20), 20);
assert.equal(rangeMap.indexAt(21), 20);
assert.equal(rangeMap.positionAt(0), 0);
assert.equal(rangeMap.positionAt(1), 1);
assert.equal(rangeMap.positionAt(19), 19);
assert.equal(rangeMap.positionAt(20), -1);
});
test('delete', () => {
rangeMap.splice(0, 0, [one, one, one, one, one, one, one, one, one, one]);
rangeMap.splice(2, 6);
assert.equal(rangeMap.indexAt(0), 0);
assert.equal(rangeMap.indexAt(1), 1);
assert.equal(rangeMap.indexAt(3), 3);
assert.equal(rangeMap.indexAt(4), 4);
assert.equal(rangeMap.indexAt(5), 4);
assert.equal(rangeMap.positionAt(0), 0);
assert.equal(rangeMap.positionAt(1), 1);
assert.equal(rangeMap.positionAt(3), 3);
assert.equal(rangeMap.positionAt(4), -1);
});
test('delete #2', () => {
rangeMap.splice(0, 0, [ten, ten, ten, ten, ten, ten, ten, ten, ten, ten]);
rangeMap.splice(2, 6);
assert.equal(rangeMap.indexAt(0), 0);
assert.equal(rangeMap.indexAt(1), 0);
assert.equal(rangeMap.indexAt(30), 3);
assert.equal(rangeMap.indexAt(40), 4);
assert.equal(rangeMap.indexAt(50), 4);
assert.equal(rangeMap.positionAt(0), 0);
assert.equal(rangeMap.positionAt(1), 10);
assert.equal(rangeMap.positionAt(2), 20);
assert.equal(rangeMap.positionAt(3), 30);
assert.equal(rangeMap.positionAt(4), -1);
});
});
});

View File

@@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* 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 { $ } from 'vs/base/browser/dom';
import { MenuBar } from 'vs/base/browser/ui/menu/menubar';
function getButtonElementByAriaLabel(menubarElement: HTMLElement, ariaLabel: string): HTMLElement | null {
let i;
for (i = 0; i < menubarElement.childElementCount; i++) {
if (menubarElement.children[i].getAttribute('aria-label') === ariaLabel) {
return menubarElement.children[i] as HTMLElement;
}
}
return null;
}
function getTitleDivFromButtonDiv(menuButtonElement: HTMLElement): HTMLElement | null {
let i;
for (i = 0; i < menuButtonElement.childElementCount; i++) {
if (menuButtonElement.children[i].classList.contains('menubar-menu-title')) {
return menuButtonElement.children[i] as HTMLElement;
}
}
return null;
}
function getMnemonicFromTitleDiv(menuTitleDiv: HTMLElement): string | null {
let i;
for (i = 0; i < menuTitleDiv.childElementCount; i++) {
if (menuTitleDiv.children[i].tagName.toLocaleLowerCase() === 'mnemonic') {
return menuTitleDiv.children[i].textContent;
}
}
return null;
}
function validateMenuBarItem(menubar: MenuBar, menubarContainer: HTMLElement, label: string, readableLabel: string, mnemonic: string) {
menubar.push([
{
actions: [],
label: label
}
]);
const buttonElement = getButtonElementByAriaLabel(menubarContainer, readableLabel);
assert(buttonElement !== null, `Button element not found for ${readableLabel} button.`);
const titleDiv = getTitleDivFromButtonDiv(buttonElement!);
assert(titleDiv !== null, `Title div not found for ${readableLabel} button.`);
const mnem = getMnemonicFromTitleDiv(titleDiv!);
assert.equal(mnem, mnemonic, 'Mnemonic not correct');
}
suite('Menubar', () => {
const container = $('.container');
const menubar = new MenuBar(container, {
enableMnemonics: true,
visibility: 'visible'
});
test('English File menu renders mnemonics', function () {
validateMenuBarItem(menubar, container, '&File', 'File', 'F');
});
test('Russian File menu renders mnemonics', function () {
validateMenuBarItem(menubar, container, '&Файл', 'Файл', 'Ф');
});
test('Chinese File menu renders mnemonics', function () {
validateMenuBarItem(menubar, container, '文件(&F)', '文件', 'F');
});
});

View File

@@ -0,0 +1,524 @@
/*---------------------------------------------------------------------------------------------
* 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 { MouseWheelClassifier } from 'vs/base/browser/ui/scrollbar/scrollableElement';
export type IMouseWheelEvent = [number, number, number];
suite('MouseWheelClassifier', () => {
test('OSX - Apple Magic Mouse', () => {
const testData: IMouseWheelEvent[] = [
[1503409622410, -0.025, 0],
[1503409622435, -0.175, 0],
[1503409622446, -0.225, 0],
[1503409622489, -0.65, 0],
[1503409622514, -1.225, 0],
[1503409622537, -1.025, 0],
[1503409622543, -0.55, 0],
[1503409622587, -0.75, 0],
[1503409622623, -1.45, 0],
[1503409622641, -1.325, 0],
[1503409622663, -0.6, 0],
[1503409622681, -1.125, 0],
[1503409622703, -0.5166666666666667, 0],
[1503409622721, -0.475, 0],
[1503409622822, -0.425, 0],
[1503409622871, -1.9916666666666667, 0],
[1503409622933, -0.7, 0],
[1503409622991, -0.725, 0],
[1503409623032, -0.45, 0],
[1503409623083, -0.25, 0],
[1503409623122, -0.4, 0],
[1503409623176, -0.2, 0],
[1503409623197, -0.225, 0],
[1503409623219, -0.05, 0],
[1503409623249, -0.1, 0],
[1503409623278, -0.1, 0],
[1503409623292, -0.025, 0],
[1503409623315, -0.025, 0],
[1503409623324, -0.05, 0],
[1503409623356, -0.025, 0],
[1503409623415, -0.025, 0],
[1503409623443, -0.05, 0],
[1503409623452, -0.025, 0],
];
const classifier = new MouseWheelClassifier();
for (let i = 0, len = testData.length; i < len; i++) {
const [timestamp, deltaY, deltaX] = testData[i];
classifier.accept(timestamp, deltaX, deltaY);
const actual = classifier.isPhysicalMouseWheel();
assert.equal(actual, false);
}
});
test('OSX - Apple Touch Pad', () => {
const testData: IMouseWheelEvent[] = [
[1503409780792, 0.025, 0],
[1503409780808, 0.175, -0.025],
[1503409780811, 0.35, -0.05],
[1503409780816, 0.55, -0.075],
[1503409780836, 0.825, -0.1],
[1503409780840, 0.725, -0.075],
[1503409780842, 1.5, -0.125],
[1503409780848, 1.1, -0.1],
[1503409780877, 2.05, -0.1],
[1503409780882, 3.9, 0],
[1503409780908, 3.825, 0],
[1503409780915, 3.65, 0],
[1503409780940, 3.45, 0],
[1503409780949, 3.25, 0],
[1503409780979, 3.075, 0],
[1503409780982, 2.9, 0],
[1503409781016, 2.75, 0],
[1503409781018, 2.625, 0],
[1503409781051, 2.5, 0],
[1503409781071, 2.4, 0],
[1503409781089, 2.3, 0],
[1503409781111, 2.175, 0],
[1503409781140, 3.975, 0],
[1503409781165, 1.8, 0],
[1503409781183, 3.3, 0],
[1503409781202, 1.475, 0],
[1503409781223, 1.375, 0],
[1503409781244, 1.275, 0],
[1503409781269, 2.25, 0],
[1503409781285, 1.025, 0],
[1503409781300, 0.925, 0],
[1503409781303, 0.875, 0],
[1503409781321, 0.8, 0],
[1503409781333, 0.725, 0],
[1503409781355, 0.65, 0],
[1503409781370, 0.6, 0],
[1503409781384, 0.55, 0],
[1503409781410, 0.5, 0],
[1503409781422, 0.475, 0],
[1503409781435, 0.425, 0],
[1503409781454, 0.4, 0],
[1503409781470, 0.35, 0],
[1503409781486, 0.325, 0],
[1503409781501, 0.3, 0],
[1503409781519, 0.275, 0],
[1503409781534, 0.25, 0],
[1503409781553, 0.225, 0],
[1503409781569, 0.2, 0],
[1503409781589, 0.2, 0],
[1503409781601, 0.175, 0],
[1503409781621, 0.15, 0],
[1503409781631, 0.15, 0],
[1503409781652, 0.125, 0],
[1503409781667, 0.125, 0],
[1503409781685, 0.125, 0],
[1503409781703, 0.1, 0],
[1503409781715, 0.1, 0],
[1503409781734, 0.1, 0],
[1503409781753, 0.075, 0],
[1503409781768, 0.075, 0],
[1503409781783, 0.075, 0],
[1503409781801, 0.075, 0],
[1503409781815, 0.05, 0],
[1503409781836, 0.05, 0],
[1503409781850, 0.05, 0],
[1503409781865, 0.05, 0],
[1503409781880, 0.05, 0],
[1503409781899, 0.025, 0],
[1503409781916, 0.025, 0],
[1503409781933, 0.025, 0],
[1503409781952, 0.025, 0],
[1503409781965, 0.025, 0],
[1503409781996, 0.025, 0],
[1503409782015, 0.025, 0],
[1503409782045, 0.025, 0],
];
const classifier = new MouseWheelClassifier();
for (let i = 0, len = testData.length; i < len; i++) {
const [timestamp, deltaY, deltaX] = testData[i];
classifier.accept(timestamp, deltaX, deltaY);
const actual = classifier.isPhysicalMouseWheel();
assert.equal(actual, false);
}
});
test('OSX - Razer Physical Mouse Wheel', () => {
const testData: IMouseWheelEvent[] = [
[1503409880776, -1, 0],
[1503409880791, -1, 0],
[1503409880810, -4, 0],
[1503409880820, -5, 0],
[1503409880848, -6, 0],
[1503409880876, -7, 0],
[1503409881319, -1, 0],
[1503409881387, -1, 0],
[1503409881407, -2, 0],
[1503409881443, -4, 0],
[1503409881444, -5, 0],
[1503409881470, -6, 0],
[1503409881496, -7, 0],
[1503409881812, -1, 0],
[1503409881829, -1, 0],
[1503409881850, -4, 0],
[1503409881871, -5, 0],
[1503409881896, -13, 0],
[1503409881914, -16, 0],
[1503409882551, -1, 0],
[1503409882589, -1, 0],
[1503409882625, -2, 0],
[1503409883035, -1, 0],
[1503409883098, -1, 0],
[1503409883143, -2, 0],
[1503409883217, -2, 0],
[1503409883270, -3, 0],
[1503409883388, -3, 0],
[1503409883531, -3, 0],
[1503409884095, -1, 0],
[1503409884122, -1, 0],
[1503409884160, -3, 0],
[1503409884208, -4, 0],
[1503409884292, -4, 0],
[1503409884447, -1, 0],
[1503409884788, -1, 0],
[1503409884835, -1, 0],
[1503409884898, -2, 0],
[1503409884965, -3, 0],
[1503409885085, -2, 0],
[1503409885552, -1, 0],
[1503409885619, -1, 0],
[1503409885670, -1, 0],
[1503409885733, -2, 0],
[1503409885784, -4, 0],
[1503409885916, -3, 0],
];
const classifier = new MouseWheelClassifier();
for (let i = 0, len = testData.length; i < len; i++) {
const [timestamp, deltaY, deltaX] = testData[i];
classifier.accept(timestamp, deltaX, deltaY);
const actual = classifier.isPhysicalMouseWheel();
assert.equal(actual, true);
}
});
test('Windows - Microsoft Arc Touch', () => {
const testData: IMouseWheelEvent[] = [
[1503418316909, -2, 0],
[1503418316985, -2, 0],
[1503418316988, -4, 0],
[1503418317034, -2, 0],
[1503418317071, -2, 0],
[1503418317094, -2, 0],
[1503418317133, -2, 0],
[1503418317170, -2, 0],
[1503418317192, -2, 0],
[1503418317265, -2, 0],
[1503418317289, -2, 0],
[1503418317365, -2, 0],
[1503418317414, -2, 0],
[1503418317458, -2, 0],
[1503418317513, -2, 0],
[1503418317583, -2, 0],
[1503418317637, -2, 0],
[1503418317720, -2, 0],
[1503418317786, -2, 0],
[1503418317832, -2, 0],
[1503418317933, -2, 0],
[1503418318037, -2, 0],
[1503418318134, -2, 0],
[1503418318267, -2, 0],
[1503418318411, -2, 0],
];
const classifier = new MouseWheelClassifier();
for (let i = 0, len = testData.length; i < len; i++) {
const [timestamp, deltaY, deltaX] = testData[i];
classifier.accept(timestamp, deltaX, deltaY);
const actual = classifier.isPhysicalMouseWheel();
assert.equal(actual, true);
}
});
test('Windows - SurfaceBook TouchPad', () => {
const testData: IMouseWheelEvent[] = [
[1503418499174, -3.35, 0],
[1503418499177, -0.9333333333333333, 0],
[1503418499222, -2.091666666666667, 0],
[1503418499238, -1.5666666666666667, 0],
[1503418499242, -1.8, 0],
[1503418499271, -2.5166666666666666, 0],
[1503418499283, -0.7666666666666667, 0],
[1503418499308, -2.033333333333333, 0],
[1503418499320, -2.85, 0],
[1503418499372, -1.5333333333333334, 0],
[1503418499373, -2.8, 0],
[1503418499411, -1.6166666666666667, 0],
[1503418499413, -1.9166666666666667, 0],
[1503418499443, -0.9333333333333333, 0],
[1503418499446, -0.9833333333333333, 0],
[1503418499458, -0.7666666666666667, 0],
[1503418499482, -0.9666666666666667, 0],
[1503418499485, -0.36666666666666664, 0],
[1503418499508, -0.5833333333333334, 0],
[1503418499532, -0.48333333333333334, 0],
[1503418499541, -0.6333333333333333, 0],
[1503418499571, -0.18333333333333332, 0],
[1503418499573, -0.4, 0],
[1503418499595, -0.15, 0],
[1503418499608, -0.23333333333333334, 0],
[1503418499625, -0.18333333333333332, 0],
[1503418499657, -0.13333333333333333, 0],
[1503418499674, -0.15, 0],
[1503418499676, -0.03333333333333333, 0],
[1503418499691, -0.016666666666666666, 0],
];
const classifier = new MouseWheelClassifier();
for (let i = 0, len = testData.length; i < len; i++) {
const [timestamp, deltaY, deltaX] = testData[i];
classifier.accept(timestamp, deltaX, deltaY);
const actual = classifier.isPhysicalMouseWheel();
assert.equal(actual, false);
}
});
test('Windows - Razer physical wheel', () => {
const testData: IMouseWheelEvent[] = [
[1503418638271, -2, 0],
[1503418638317, -2, 0],
[1503418638336, -2, 0],
[1503418638350, -2, 0],
[1503418638360, -2, 0],
[1503418638366, -2, 0],
[1503418638407, -2, 0],
[1503418638694, -2, 0],
[1503418638742, -2, 0],
[1503418638744, -2, 0],
[1503418638746, -2, 0],
[1503418638780, -2, 0],
[1503418638782, -2, 0],
[1503418638810, -2, 0],
[1503418639127, -2, 0],
[1503418639168, -2, 0],
[1503418639194, -2, 0],
[1503418639197, -4, 0],
[1503418639244, -2, 0],
[1503418639248, -2, 0],
[1503418639586, -2, 0],
[1503418639653, -2, 0],
[1503418639667, -4, 0],
[1503418639677, -2, 0],
[1503418639681, -2, 0],
[1503418639728, -2, 0],
[1503418639997, -2, 0],
[1503418640034, -2, 0],
[1503418640039, -2, 0],
[1503418640065, -2, 0],
[1503418640080, -2, 0],
[1503418640097, -2, 0],
[1503418640141, -2, 0],
[1503418640413, -2, 0],
[1503418640456, -2, 0],
[1503418640490, -2, 0],
[1503418640492, -4, 0],
[1503418640494, -2, 0],
[1503418640546, -2, 0],
[1503418640781, -2, 0],
[1503418640823, -2, 0],
[1503418640824, -2, 0],
[1503418640829, -2, 0],
[1503418640864, -2, 0],
[1503418640874, -2, 0],
[1503418640876, -2, 0],
[1503418641168, -2, 0],
[1503418641203, -2, 0],
[1503418641224, -2, 0],
[1503418641240, -2, 0],
[1503418641254, -4, 0],
[1503418641270, -2, 0],
[1503418641546, -2, 0],
[1503418641612, -2, 0],
[1503418641625, -6, 0],
[1503418641634, -2, 0],
[1503418641680, -2, 0],
[1503418641961, -2, 0],
[1503418642004, -2, 0],
[1503418642016, -4, 0],
[1503418642044, -2, 0],
[1503418642065, -2, 0],
[1503418642083, -2, 0],
[1503418642349, -2, 0],
[1503418642378, -2, 0],
[1503418642390, -2, 0],
[1503418642408, -2, 0],
[1503418642413, -2, 0],
[1503418642448, -2, 0],
[1503418642468, -2, 0],
[1503418642746, -2, 0],
[1503418642800, -2, 0],
[1503418642814, -4, 0],
[1503418642816, -2, 0],
[1503418642857, -2, 0],
];
const classifier = new MouseWheelClassifier();
for (let i = 0, len = testData.length; i < len; i++) {
const [timestamp, deltaY, deltaX] = testData[i];
classifier.accept(timestamp, deltaX, deltaY);
const actual = classifier.isPhysicalMouseWheel();
assert.equal(actual, true);
}
});
test('Windows - Logitech physical wheel', () => {
const testData: IMouseWheelEvent[] = [
[1503418872930, -2, 0],
[1503418872952, -2, 0],
[1503418872969, -2, 0],
[1503418873022, -2, 0],
[1503418873042, -2, 0],
[1503418873076, -2, 0],
[1503418873368, -2, 0],
[1503418873393, -2, 0],
[1503418873404, -2, 0],
[1503418873425, -2, 0],
[1503418873479, -2, 0],
[1503418873520, -2, 0],
[1503418873758, -2, 0],
[1503418873759, -2, 0],
[1503418873762, -2, 0],
[1503418873807, -2, 0],
[1503418873830, -4, 0],
[1503418873850, -2, 0],
[1503418874076, -2, 0],
[1503418874116, -2, 0],
[1503418874136, -4, 0],
[1503418874148, -2, 0],
[1503418874150, -2, 0],
[1503418874409, -2, 0],
[1503418874452, -2, 0],
[1503418874472, -2, 0],
[1503418874474, -4, 0],
[1503418874543, -2, 0],
[1503418874566, -2, 0],
[1503418874778, -2, 0],
[1503418874780, -2, 0],
[1503418874801, -2, 0],
[1503418874822, -2, 0],
[1503418874832, -2, 0],
[1503418874845, -2, 0],
[1503418875122, -2, 0],
[1503418875158, -2, 0],
[1503418875180, -2, 0],
[1503418875195, -4, 0],
[1503418875239, -2, 0],
[1503418875260, -2, 0],
[1503418875490, -2, 0],
[1503418875525, -2, 0],
[1503418875547, -4, 0],
[1503418875556, -4, 0],
[1503418875630, -2, 0],
[1503418875852, -2, 0],
[1503418875895, -2, 0],
[1503418875935, -2, 0],
[1503418875941, -4, 0],
[1503418876198, -2, 0],
[1503418876242, -2, 0],
[1503418876270, -4, 0],
[1503418876279, -2, 0],
[1503418876333, -2, 0],
[1503418876342, -2, 0],
[1503418876585, -2, 0],
[1503418876609, -2, 0],
[1503418876623, -2, 0],
[1503418876644, -2, 0],
[1503418876646, -2, 0],
[1503418876678, -2, 0],
[1503418877330, -2, 0],
[1503418877354, -2, 0],
[1503418877368, -2, 0],
[1503418877397, -2, 0],
[1503418877411, -2, 0],
[1503418877748, -2, 0],
[1503418877756, -2, 0],
[1503418877778, -2, 0],
[1503418877793, -2, 0],
[1503418877807, -2, 0],
[1503418878091, -2, 0],
[1503418878133, -2, 0],
[1503418878137, -4, 0],
[1503418878181, -2, 0],
];
const classifier = new MouseWheelClassifier();
for (let i = 0, len = testData.length; i < len; i++) {
const [timestamp, deltaY, deltaX] = testData[i];
classifier.accept(timestamp, deltaX, deltaY);
const actual = classifier.isPhysicalMouseWheel();
assert.equal(actual, true);
}
});
test('Windows - Microsoft basic v2 physical wheel', () => {
const testData: IMouseWheelEvent[] = [
[1503418994564, -2, 0],
[1503418994643, -2, 0],
[1503418994676, -2, 0],
[1503418994691, -2, 0],
[1503418994727, -2, 0],
[1503418994799, -2, 0],
[1503418994850, -2, 0],
[1503418995259, -2, 0],
[1503418995321, -2, 0],
[1503418995328, -2, 0],
[1503418995343, -2, 0],
[1503418995402, -2, 0],
[1503418995454, -2, 0],
[1503418996052, -2, 0],
[1503418996095, -2, 0],
[1503418996107, -2, 0],
[1503418996120, -2, 0],
[1503418996146, -2, 0],
[1503418996471, -2, 0],
[1503418996530, -2, 0],
[1503418996549, -2, 0],
[1503418996561, -2, 0],
[1503418996571, -2, 0],
[1503418996636, -2, 0],
[1503418996936, -2, 0],
[1503418997002, -2, 0],
[1503418997006, -2, 0],
[1503418997043, -2, 0],
[1503418997045, -2, 0],
[1503418997092, -2, 0],
[1503418997357, -2, 0],
[1503418997394, -2, 0],
[1503418997410, -2, 0],
[1503418997426, -2, 0],
[1503418997442, -2, 0],
[1503418997486, -2, 0],
[1503418997757, -2, 0],
[1503418997807, -2, 0],
[1503418997813, -2, 0],
[1503418997850, -2, 0],
];
const classifier = new MouseWheelClassifier();
for (let i = 0, len = testData.length; i < len; i++) {
const [timestamp, deltaY, deltaX] = testData[i];
classifier.accept(timestamp, deltaX, deltaY);
const actual = classifier.isPhysicalMouseWheel();
assert.equal(actual, true);
}
});
});

View File

@@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* 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 { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
suite('ScrollbarState', () => {
test('inflates slider size', () => {
let actual = new ScrollbarState(0, 14, 0, 339, 42423, 32787);
assert.equal(actual.getArrowSize(), 0);
assert.equal(actual.getScrollPosition(), 32787);
assert.equal(actual.getRectangleLargeSize(), 339);
assert.equal(actual.getRectangleSmallSize(), 14);
assert.equal(actual.isNeeded(), true);
assert.equal(actual.getSliderSize(), 20);
assert.equal(actual.getSliderPosition(), 249);
assert.equal(actual.getDesiredScrollPositionFromOffset(259), 32849);
actual.setScrollPosition(32849);
assert.equal(actual.getArrowSize(), 0);
assert.equal(actual.getScrollPosition(), 32849);
assert.equal(actual.getRectangleLargeSize(), 339);
assert.equal(actual.getRectangleSmallSize(), 14);
assert.equal(actual.isNeeded(), true);
assert.equal(actual.getSliderSize(), 20);
assert.equal(actual.getSliderPosition(), 249);
});
test('inflates slider size with arrows', () => {
let actual = new ScrollbarState(12, 14, 0, 339, 42423, 32787);
assert.equal(actual.getArrowSize(), 12);
assert.equal(actual.getScrollPosition(), 32787);
assert.equal(actual.getRectangleLargeSize(), 339);
assert.equal(actual.getRectangleSmallSize(), 14);
assert.equal(actual.isNeeded(), true);
assert.equal(actual.getSliderSize(), 20);
assert.equal(actual.getSliderPosition(), 230);
assert.equal(actual.getDesiredScrollPositionFromOffset(240 + 12), 32811);
actual.setScrollPosition(32811);
assert.equal(actual.getArrowSize(), 12);
assert.equal(actual.getScrollPosition(), 32811);
assert.equal(actual.getRectangleLargeSize(), 339);
assert.equal(actual.getRectangleSmallSize(), 14);
assert.equal(actual.isNeeded(), true);
assert.equal(actual.getSliderSize(), 20);
assert.equal(actual.getSliderPosition(), 230);
});
});

View File

@@ -0,0 +1,549 @@
/*---------------------------------------------------------------------------------------------
* 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 { Emitter } from 'vs/base/common/event';
import { SplitView, IView, Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview';
import { Sash, SashState } from 'vs/base/browser/ui/sash/sash';
class TestView implements IView<number> {
private readonly _onDidChange = new Emitter<number | undefined>();
readonly onDidChange = this._onDidChange.event;
get minimumSize(): number { return this._minimumSize; }
set minimumSize(size: number) { this._minimumSize = size; this._onDidChange.fire(undefined); }
get maximumSize(): number { return this._maximumSize; }
set maximumSize(size: number) { this._maximumSize = size; this._onDidChange.fire(undefined); }
private _element: HTMLElement = document.createElement('div');
get element(): HTMLElement { this._onDidGetElement.fire(); return this._element; }
private readonly _onDidGetElement = new Emitter<void>();
readonly onDidGetElement = this._onDidGetElement.event;
private _size = 0;
get size(): number { return this._size; }
private _orthogonalSize: number | undefined = 0;
get orthogonalSize(): number | undefined { return this._orthogonalSize; }
private readonly _onDidLayout = new Emitter<{ size: number; orthogonalSize: number | undefined }>();
readonly onDidLayout = this._onDidLayout.event;
private readonly _onDidFocus = new Emitter<void>();
readonly onDidFocus = this._onDidFocus.event;
constructor(
private _minimumSize: number,
private _maximumSize: number,
readonly priority: LayoutPriority = LayoutPriority.Normal
) {
assert(_minimumSize <= _maximumSize, 'splitview view minimum size must be <= maximum size');
}
layout(size: number, _offset: number, orthogonalSize: number | undefined): void {
this._size = size;
this._orthogonalSize = orthogonalSize;
this._onDidLayout.fire({ size, orthogonalSize });
}
focus(): void {
this._onDidFocus.fire();
}
dispose(): void {
this._onDidChange.dispose();
this._onDidGetElement.dispose();
this._onDidLayout.dispose();
this._onDidFocus.dispose();
}
}
function getSashes(splitview: SplitView): Sash[] {
return (splitview as any).sashItems.map((i: any) => i.sash) as Sash[];
}
suite('Splitview', () => {
let container: HTMLElement;
setup(() => {
container = document.createElement('div');
container.style.position = 'absolute';
container.style.width = `${200}px`;
container.style.height = `${200}px`;
});
test('empty splitview has empty DOM', () => {
const splitview = new SplitView(container);
assert.equal(container.firstElementChild!.firstElementChild!.childElementCount, 0, 'split view should be empty');
splitview.dispose();
});
test('has views and sashes as children', () => {
const view1 = new TestView(20, 20);
const view2 = new TestView(20, 20);
const view3 = new TestView(20, 20);
const splitview = new SplitView(container);
splitview.addView(view1, 20);
splitview.addView(view2, 20);
splitview.addView(view3, 20);
let viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
assert.equal(viewQuery.length, 3, 'split view should have 3 views');
let sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash');
assert.equal(sashQuery.length, 2, 'split view should have 2 sashes');
splitview.removeView(2);
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
assert.equal(viewQuery.length, 2, 'split view should have 2 views');
sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash');
assert.equal(sashQuery.length, 1, 'split view should have 1 sash');
splitview.removeView(0);
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
assert.equal(viewQuery.length, 1, 'split view should have 1 view');
sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash');
assert.equal(sashQuery.length, 0, 'split view should have no sashes');
splitview.removeView(0);
viewQuery = container.querySelectorAll('.monaco-split-view2 > .split-view-container > .split-view-view');
assert.equal(viewQuery.length, 0, 'split view should have no views');
sashQuery = container.querySelectorAll('.monaco-split-view2 > .sash-container > .monaco-sash');
assert.equal(sashQuery.length, 0, 'split view should have no sashes');
splitview.dispose();
view1.dispose();
view2.dispose();
view3.dispose();
});
test('calls view methods on addView and removeView', () => {
const view = new TestView(20, 20);
const splitview = new SplitView(container);
let didLayout = false;
const layoutDisposable = view.onDidLayout(() => didLayout = true);
const renderDisposable = view.onDidGetElement(() => undefined);
splitview.addView(view, 20);
assert.equal(view.size, 20, 'view has right size');
assert(didLayout, 'layout is called');
assert(didLayout, 'render is called');
splitview.dispose();
layoutDisposable.dispose();
renderDisposable.dispose();
view.dispose();
});
test('stretches view to viewport', () => {
const view = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view, 20);
assert.equal(view.size, 200, 'view is stretched');
splitview.layout(200);
assert.equal(view.size, 200, 'view stayed the same');
splitview.layout(100);
assert.equal(view.size, 100, 'view is collapsed');
splitview.layout(20);
assert.equal(view.size, 20, 'view is collapsed');
splitview.layout(10);
assert.equal(view.size, 20, 'view is clamped');
splitview.layout(200);
assert.equal(view.size, 200, 'view is stretched');
splitview.dispose();
view.dispose();
});
test('can resize views', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, 20);
splitview.addView(view2, 20);
splitview.addView(view3, 20);
assert.equal(view1.size, 160, 'view1 is stretched');
assert.equal(view2.size, 20, 'view2 size is 20');
assert.equal(view3.size, 20, 'view3 size is 20');
splitview.resizeView(1, 40);
assert.equal(view1.size, 140, 'view1 is collapsed');
assert.equal(view2.size, 40, 'view2 is stretched');
assert.equal(view3.size, 20, 'view3 stays the same');
splitview.resizeView(0, 70);
assert.equal(view1.size, 70, 'view1 is collapsed');
assert.equal(view2.size, 40, 'view2 stays the same');
assert.equal(view3.size, 90, 'view3 is stretched');
splitview.resizeView(2, 40);
assert.equal(view1.size, 70, 'view1 stays the same');
assert.equal(view2.size, 90, 'view2 is collapsed');
assert.equal(view3.size, 40, 'view3 is stretched');
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('reacts to view changes', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, 20);
splitview.addView(view2, 20);
splitview.addView(view3, 20);
assert.equal(view1.size, 160, 'view1 is stretched');
assert.equal(view2.size, 20, 'view2 size is 20');
assert.equal(view3.size, 20, 'view3 size is 20');
view1.maximumSize = 20;
assert.equal(view1.size, 20, 'view1 is collapsed');
assert.equal(view2.size, 20, 'view2 stays the same');
assert.equal(view3.size, 160, 'view3 is stretched');
view3.maximumSize = 40;
assert.equal(view1.size, 20, 'view1 stays the same');
assert.equal(view2.size, 140, 'view2 is stretched');
assert.equal(view3.size, 40, 'view3 is collapsed');
view2.maximumSize = 200;
assert.equal(view1.size, 20, 'view1 stays the same');
assert.equal(view2.size, 140, 'view2 stays the same');
assert.equal(view3.size, 40, 'view3 stays the same');
view3.maximumSize = Number.POSITIVE_INFINITY;
view3.minimumSize = 100;
assert.equal(view1.size, 20, 'view1 is collapsed');
assert.equal(view2.size, 80, 'view2 is collapsed');
assert.equal(view3.size, 100, 'view3 is stretched');
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('sashes are properly enabled/disabled', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, Sizing.Distribute);
splitview.addView(view2, Sizing.Distribute);
splitview.addView(view3, Sizing.Distribute);
let sashes = getSashes(splitview);
assert.equal(sashes.length, 2, 'there are two sashes');
assert.equal(sashes[0].state, SashState.Enabled, 'first sash is enabled');
assert.equal(sashes[1].state, SashState.Enabled, 'second sash is enabled');
splitview.layout(60);
assert.equal(sashes[0].state, SashState.Disabled, 'first sash is disabled');
assert.equal(sashes[1].state, SashState.Disabled, 'second sash is disabled');
splitview.layout(20);
assert.equal(sashes[0].state, SashState.Disabled, 'first sash is disabled');
assert.equal(sashes[1].state, SashState.Disabled, 'second sash is disabled');
splitview.layout(200);
assert.equal(sashes[0].state, SashState.Enabled, 'first sash is enabled');
assert.equal(sashes[1].state, SashState.Enabled, 'second sash is enabled');
view1.maximumSize = 20;
assert.equal(sashes[0].state, SashState.Disabled, 'first sash is disabled');
assert.equal(sashes[1].state, SashState.Enabled, 'second sash is enabled');
view2.maximumSize = 20;
assert.equal(sashes[0].state, SashState.Disabled, 'first sash is disabled');
assert.equal(sashes[1].state, SashState.Disabled, 'second sash is disabled');
view1.maximumSize = 300;
assert.equal(sashes[0].state, SashState.Minimum, 'first sash is enabled');
assert.equal(sashes[1].state, SashState.Minimum, 'second sash is enabled');
view2.maximumSize = 200;
assert.equal(sashes[0].state, SashState.Minimum, 'first sash is enabled');
assert.equal(sashes[1].state, SashState.Minimum, 'second sash is enabled');
splitview.resizeView(0, 40);
assert.equal(sashes[0].state, SashState.Enabled, 'first sash is enabled');
assert.equal(sashes[1].state, SashState.Enabled, 'second sash is enabled');
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('issue #35497', () => {
const view1 = new TestView(160, Number.POSITIVE_INFINITY);
const view2 = new TestView(66, 66);
const splitview = new SplitView(container);
splitview.layout(986);
splitview.addView(view1, 142, 0);
assert.equal(view1.size, 986, 'first view is stretched');
view2.onDidGetElement(() => {
assert.throws(() => splitview.resizeView(1, 922));
assert.throws(() => splitview.resizeView(1, 922));
});
splitview.addView(view2, 66, 0);
assert.equal(view2.size, 66, 'second view is fixed');
assert.equal(view1.size, 986 - 66, 'first view is collapsed');
const viewContainers = container.querySelectorAll('.split-view-view');
assert.equal(viewContainers.length, 2, 'there are two view containers');
assert.equal((viewContainers.item(0) as HTMLElement).style.height, '66px', 'second view container is 66px');
assert.equal((viewContainers.item(1) as HTMLElement).style.height, `${986 - 66}px`, 'first view container is 66px');
splitview.dispose();
view2.dispose();
view1.dispose();
});
test('automatic size distribution', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, Sizing.Distribute);
assert.equal(view1.size, 200);
splitview.addView(view2, 50);
assert.deepEqual([view1.size, view2.size], [150, 50]);
splitview.addView(view3, Sizing.Distribute);
assert.deepEqual([view1.size, view2.size, view3.size], [66, 66, 68]);
splitview.removeView(1, Sizing.Distribute);
assert.deepEqual([view1.size, view3.size], [100, 100]);
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('add views before layout', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.addView(view1, 100);
splitview.addView(view2, 75);
splitview.addView(view3, 25);
splitview.layout(200);
assert.deepEqual([view1.size, view2.size, view3.size], [67, 67, 66]);
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('split sizing', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, Sizing.Distribute);
assert.equal(view1.size, 200);
splitview.addView(view2, Sizing.Split(0));
assert.deepEqual([view1.size, view2.size], [100, 100]);
splitview.addView(view3, Sizing.Split(1));
assert.deepEqual([view1.size, view2.size, view3.size], [100, 50, 50]);
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('split sizing 2', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, Sizing.Distribute);
assert.equal(view1.size, 200);
splitview.addView(view2, Sizing.Split(0));
assert.deepEqual([view1.size, view2.size], [100, 100]);
splitview.addView(view3, Sizing.Split(0));
assert.deepEqual([view1.size, view2.size, view3.size], [50, 100, 50]);
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('proportional layout', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, Sizing.Distribute);
splitview.addView(view2, Sizing.Distribute);
assert.deepEqual([view1.size, view2.size], [100, 100]);
splitview.layout(100);
assert.deepEqual([view1.size, view2.size], [50, 50]);
splitview.dispose();
view2.dispose();
view1.dispose();
});
test('disable proportional layout', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container, { proportionalLayout: false });
splitview.layout(200);
splitview.addView(view1, Sizing.Distribute);
splitview.addView(view2, Sizing.Distribute);
assert.deepEqual([view1.size, view2.size], [100, 100]);
splitview.layout(100);
assert.deepEqual([view1.size, view2.size], [80, 20]);
splitview.dispose();
view2.dispose();
view1.dispose();
});
test('high layout priority', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.High);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container, { proportionalLayout: false });
splitview.layout(200);
splitview.addView(view1, Sizing.Distribute);
splitview.addView(view2, Sizing.Distribute);
splitview.addView(view3, Sizing.Distribute);
assert.deepEqual([view1.size, view2.size, view3.size], [66, 68, 66]);
splitview.layout(180);
assert.deepEqual([view1.size, view2.size, view3.size], [66, 48, 66]);
splitview.layout(124);
assert.deepEqual([view1.size, view2.size, view3.size], [66, 20, 38]);
splitview.layout(60);
assert.deepEqual([view1.size, view2.size, view3.size], [20, 20, 20]);
splitview.layout(200);
assert.deepEqual([view1.size, view2.size, view3.size], [20, 160, 20]);
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('low layout priority', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low);
const splitview = new SplitView(container, { proportionalLayout: false });
splitview.layout(200);
splitview.addView(view1, Sizing.Distribute);
splitview.addView(view2, Sizing.Distribute);
splitview.addView(view3, Sizing.Distribute);
assert.deepEqual([view1.size, view2.size, view3.size], [66, 68, 66]);
splitview.layout(180);
assert.deepEqual([view1.size, view2.size, view3.size], [66, 48, 66]);
splitview.layout(132);
assert.deepEqual([view1.size, view2.size, view3.size], [46, 20, 66]);
splitview.layout(60);
assert.deepEqual([view1.size, view2.size, view3.size], [20, 20, 20]);
splitview.layout(200);
assert.deepEqual([view1.size, view2.size, view3.size], [20, 160, 20]);
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('context propagates to views', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low);
const splitview = new SplitView<number>(container, { proportionalLayout: false });
splitview.layout(200);
splitview.addView(view1, Sizing.Distribute);
splitview.addView(view2, Sizing.Distribute);
splitview.addView(view3, Sizing.Distribute);
splitview.layout(200, 100);
assert.deepEqual([view1.orthogonalSize, view2.orthogonalSize, view3.orthogonalSize], [100, 100, 100]);
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
});

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']);
});
});