keybindingEditing.test.ts 14.0 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

8 9 10 11
import * as assert from 'assert';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs';
12 13 14 15 16 17
import * as json from 'vs/base/common/json';
import { OS } from 'vs/base/common/platform';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
import { TPromise } from 'vs/base/common/winjs.base';
import { KeyCode, SimpleKeybinding, ChordKeybinding } from 'vs/base/common/keyCodes';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
18
import * as extfs from 'vs/base/node/extfs';
B
Benjamin Pasero 已提交
19
import { TestTextFileService, TestLifecycleService, TestBackupFileService, TestContextService, TestTextResourceConfigurationService, TestHashService, TestEnvironmentService, TestStorageService, TestEditorGroupsService, TestEditorService } from 'vs/workbench/test/workbenchTestServices';
B
Benjamin Pasero 已提交
20 21
import { INextEditorGroupsService } from 'vs/workbench/services/group/common/nextEditorGroupsService';
import { INextEditorService } from 'vs/workbench/services/editor/common/nextEditorService';
22
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
23
import { IWorkspaceContextService, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
24
import * as uuid from 'vs/base/common/uuid';
25
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
26
import { FileService } from 'vs/workbench/services/files/electron-browser/fileService';
27 28 29 30 31 32 33 34 35
import { IFileService } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
36
import { ITextModelService } from 'vs/editor/common/services/resolverService';
37 38 39 40 41
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
I
isidor 已提交
42
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
43 44 45
import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
B
Benjamin Pasero 已提交
46
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
R
Ramya Achutha Rao 已提交
47
import { IHashService } from 'vs/workbench/services/hash/common/hashService';
48
import { mkdirp } from 'vs/base/node/pfs';
I
isidor 已提交
49
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
50 51 52 53 54 55 56 57 58 59 60 61

interface Modifiers {
	metaKey?: boolean;
	ctrlKey?: boolean;
	altKey?: boolean;
	shiftKey?: boolean;
}

suite('Keybindings Editing', () => {

	let instantiationService: TestInstantiationService;
	let testObject: KeybindingsEditingService;
B
Benjamin Pasero 已提交
62
	let testDir: string;
63
	let keybindingsFile: string;
64 65 66 67 68 69 70 71 72

	setup(() => {
		return setUpWorkspace().then(() => {
			keybindingsFile = path.join(testDir, 'keybindings.json');

			instantiationService = new TestInstantiationService();

			instantiationService.stub(IEnvironmentService, { appKeybindingsPath: keybindingsFile });
			instantiationService.stub(IConfigurationService, ConfigurationService);
S
Sandeep Somavarapu 已提交
73
			instantiationService.stub(IConfigurationService, 'getValue', { 'eol': '\n' });
74
			instantiationService.stub(IConfigurationService, 'onDidUpdateConfiguration', () => { });
B
Benjamin Pasero 已提交
75
			instantiationService.stub(IConfigurationService, 'onDidChangeConfiguration', () => { });
S
Sandeep Somavarapu 已提交
76
			instantiationService.stub(IWorkspaceContextService, new TestContextService());
77 78
			const lifecycleService = new TestLifecycleService();
			instantiationService.stub(ILifecycleService, lifecycleService);
I
isidor 已提交
79
			instantiationService.stub(IContextKeyService, <IContextKeyService>instantiationService.createInstance(MockContextKeyService));
R
Ramya Achutha Rao 已提交
80
			instantiationService.stub(IHashService, new TestHashService());
B
Benjamin Pasero 已提交
81 82
			instantiationService.stub(INextEditorGroupsService, new TestEditorGroupsService());
			instantiationService.stub(INextEditorService, new TestEditorService());
83 84 85
			instantiationService.stub(ITelemetryService, NullTelemetryService);
			instantiationService.stub(IModeService, ModeServiceImpl);
			instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl));
86 87 88 89 90 91 92 93 94 95
			instantiationService.stub(IFileService, new FileService(
				new TestContextService(new Workspace(testDir, testDir, toWorkspaceFolders([{ path: testDir }]))),
				TestEnvironmentService,
				new TestTextResourceConfigurationService(),
				new TestConfigurationService(),
				lifecycleService,
				new TestStorageService(),
				new TestNotificationService(),
				{ disableWatcher: true })
			);
96 97
			instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService));
			instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
98
			instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
99 100 101 102 103 104
			instantiationService.stub(IBackupFileService, new TestBackupFileService());

			testObject = instantiationService.createInstance(KeybindingsEditingService);
		});
	});

105 106 107
	function setUpWorkspace(): TPromise<boolean> {
		testDir = path.join(os.tmpdir(), 'vsctests', uuid.generateUuid());
		return mkdirp(testDir, 493);
108 109 110 111 112 113 114 115 116 117 118 119 120 121
	}

	teardown(() => {
		return new TPromise<void>((c, e) => {
			if (testDir) {
				extfs.del(testDir, os.tmpdir(), () => c(null), () => c(null));
			} else {
				c(null);
			}
		}).then(() => testDir = null);
	});

	test('errors cases - parse errors', () => {
		fs.writeFileSync(keybindingsFile, ',,,,,,,,,,,,,,');
S
Sandeep Somavarapu 已提交
122
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }))
123
			.then(() => assert.fail('Should fail with parse errors'),
M
Matt Bierner 已提交
124
				error => assert.equal(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.'));
125 126 127 128
	});

	test('errors cases - parse errors 2', () => {
		fs.writeFileSync(keybindingsFile, '[{"key": }]');
S
Sandeep Somavarapu 已提交
129
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }))
130
			.then(() => assert.fail('Should fail with parse errors'),
M
Matt Bierner 已提交
131
				error => assert.equal(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.'));
132 133 134 135
	});

	test('errors cases - dirty', () => {
		instantiationService.stub(ITextFileService, 'isDirty', true);
S
Sandeep Somavarapu 已提交
136
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }))
137
			.then(() => assert.fail('Should fail with dirty error'),
M
Matt Bierner 已提交
138
				error => assert.equal(error.message, 'Unable to write because the keybindings configuration file is dirty. Please save it first and then try again.'));
139 140 141
	});

	test('errors cases - did not find an array', () => {
S
Sandeep Somavarapu 已提交
142 143
		fs.writeFileSync(keybindingsFile, '{"key": "alt+c", "command": "hello"}');
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }))
144
			.then(() => assert.fail('Should fail with dirty error'),
M
Matt Bierner 已提交
145
				error => assert.equal(error.message, 'Unable to write to the keybindings configuration file. It has an object which is not of type Array. Please open the file to clean up and try again.'));
146 147 148 149
	});

	test('edit a default keybinding to an empty file', () => {
		fs.writeFileSync(keybindingsFile, '');
S
Sandeep Somavarapu 已提交
150 151
		const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }))
152 153 154 155 156 157 158 159
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

	test('edit a default keybinding to a non existing keybindings file', () => {
		keybindingsFile = path.join(testDir, 'nonExistingFile.json');
		instantiationService.get(IEnvironmentService).appKeybindingsPath = keybindingsFile;
		testObject = instantiationService.createInstance(KeybindingsEditingService);

S
Sandeep Somavarapu 已提交
160 161
		const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }))
162 163 164 165 166
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

	test('edit a default keybinding to an empty array', () => {
		writeToKeybindingsFile();
S
Sandeep Somavarapu 已提交
167 168
		const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }))
169 170 171 172 173
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

	test('edit a default keybinding in an existing array', () => {
		writeToKeybindingsFile({ command: 'b', key: 'shift+c' });
S
Sandeep Somavarapu 已提交
174 175
		const expected: IUserFriendlyKeybinding[] = [{ key: 'shift+c', command: 'b' }, { key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }))
176 177 178 179
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

	test('add a new default keybinding', () => {
S
Sandeep Somavarapu 已提交
180 181
		const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }];
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ command: 'a' }))
182 183 184 185 186
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

	test('edit an user keybinding', () => {
		writeToKeybindingsFile({ key: 'escape', command: 'b' });
S
Sandeep Somavarapu 已提交
187 188
		const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }];
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }))
189 190 191 192
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

	test('edit an user keybinding with more than one element', () => {
S
Sandeep Somavarapu 已提交
193 194 195
		writeToKeybindingsFile({ key: 'escape', command: 'b' }, { key: 'alt+shift+g', command: 'c' });
		const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }, { key: 'alt+shift+g', command: 'c' }];
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }))
196 197 198 199
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

	test('remove a default keybinding', () => {
S
Sandeep Somavarapu 已提交
200 201
		const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }];
		return testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }))
202 203 204 205
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

	test('remove a user keybinding', () => {
S
Sandeep Somavarapu 已提交
206 207
		writeToKeybindingsFile({ key: 'alt+c', command: 'b' });
		return testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'b', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } }, isDefault: false }))
208 209 210 211
			.then(() => assert.deepEqual(getUserKeybindings(), []));
	});

	test('reset an edited keybinding', () => {
S
Sandeep Somavarapu 已提交
212 213
		writeToKeybindingsFile({ key: 'alt+c', command: 'b' });
		return testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } }, isDefault: false }))
214 215 216 217
			.then(() => assert.deepEqual(getUserKeybindings(), []));
	});

	test('reset a removed keybinding', () => {
S
Sandeep Somavarapu 已提交
218
		writeToKeybindingsFile({ key: 'alt+c', command: '-b' });
219 220 221 222 223 224 225 226 227 228 229 230
		return testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', isDefault: false }))
			.then(() => assert.deepEqual(getUserKeybindings(), []));
	});

	function writeToKeybindingsFile(...keybindings: IUserFriendlyKeybinding[]) {
		fs.writeFileSync(keybindingsFile, JSON.stringify(keybindings || []));
	}

	function getUserKeybindings(): IUserFriendlyKeybinding[] {
		return json.parse(fs.readFileSync(keybindingsFile).toString('utf8'));
	}

A
Alex Dima 已提交
231
	function aResolvedKeybindingItem({ command, when, isDefault, firstPart, chordPart }: { command?: string, when?: string, isDefault?: boolean, firstPart?: { keyCode: KeyCode, modifiers?: Modifiers }, chordPart?: { keyCode: KeyCode, modifiers?: Modifiers } }): ResolvedKeybindingItem {
232
		const aSimpleKeybinding = function (part: { keyCode: KeyCode, modifiers?: Modifiers }): SimpleKeybinding {
A
Alex Dima 已提交
233
			const { ctrlKey, shiftKey, altKey, metaKey } = part.modifiers || { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false };
234 235 236 237 238 239
			return new SimpleKeybinding(ctrlKey, shiftKey, altKey, metaKey, part.keyCode);
		};
		const keybinding = firstPart ? chordPart ? new ChordKeybinding(aSimpleKeybinding(firstPart), aSimpleKeybinding(chordPart)) : aSimpleKeybinding(firstPart) : null;
		return new ResolvedKeybindingItem(keybinding ? new USLayoutResolvedKeybinding(keybinding, OS) : null, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : null, isDefault === void 0 ? true : isDefault);
	}

240
});