keybindingEditing.test.ts 15.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';
I
isidor 已提交
19
import { TestTextFileService, TestLifecycleService, TestBackupFileService, TestContextService, TestTextResourceConfigurationService, TestHashService, TestEnvironmentService, TestStorageService, TestEditorGroupsService, TestEditorService, TestLogService } from 'vs/workbench/test/workbenchTestServices';
20 21
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
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';
I
isidor 已提交
50
import { ILogService } from 'vs/platform/log/common/log';
51 52 53 54 55 56 57 58

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

S
Sandeep Somavarapu 已提交
59
suite('KeybindingsEditing', () => {
60 61 62

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

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

			instantiationService = new TestInstantiationService();

S
Sandeep Somavarapu 已提交
72
			instantiationService.stub(IEnvironmentService, <IEnvironmentService>{ appKeybindingsPath: keybindingsFile, appSettingsPath: path.join(testDir, 'settings.json') });
73
			instantiationService.stub(IConfigurationService, ConfigurationService);
S
Sandeep Somavarapu 已提交
74
			instantiationService.stub(IConfigurationService, 'getValue', { 'eol': '\n' });
75
			instantiationService.stub(IConfigurationService, 'onDidUpdateConfiguration', () => { });
B
Benjamin Pasero 已提交
76
			instantiationService.stub(IConfigurationService, 'onDidChangeConfiguration', () => { });
S
Sandeep Somavarapu 已提交
77
			instantiationService.stub(IWorkspaceContextService, new TestContextService());
78 79
			const lifecycleService = new TestLifecycleService();
			instantiationService.stub(ILifecycleService, lifecycleService);
I
isidor 已提交
80
			instantiationService.stub(IContextKeyService, <IContextKeyService>instantiationService.createInstance(MockContextKeyService));
R
Ramya Achutha Rao 已提交
81
			instantiationService.stub(IHashService, new TestHashService());
82 83
			instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService());
			instantiationService.stub(IEditorService, new TestEditorService());
84 85
			instantiationService.stub(ITelemetryService, NullTelemetryService);
			instantiationService.stub(IModeService, ModeServiceImpl);
I
isidor 已提交
86
			instantiationService.stub(ILogService, new TestLogService());
87
			instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl));
88 89 90 91 92 93 94 95 96 97
			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 })
			);
98 99
			instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService));
			instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
100
			instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
101 102 103 104 105 106
			instantiationService.stub(IBackupFileService, new TestBackupFileService());

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

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

	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 已提交
124
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }))
125
			.then(() => assert.fail('Should fail with parse errors'),
M
Matt Bierner 已提交
126
				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.'));
127 128 129 130
	});

	test('errors cases - parse errors 2', () => {
		fs.writeFileSync(keybindingsFile, '[{"key": }]');
S
Sandeep Somavarapu 已提交
131
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }))
132
			.then(() => assert.fail('Should fail with parse errors'),
M
Matt Bierner 已提交
133
				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.'));
134 135 136 137
	});

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

	test('errors cases - did not find an array', () => {
S
Sandeep Somavarapu 已提交
144 145
		fs.writeFileSync(keybindingsFile, '{"key": "alt+c", "command": "hello"}');
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }))
146
			.then(() => assert.fail('Should fail with dirty error'),
M
Matt Bierner 已提交
147
				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.'));
148 149 150 151
	});

	test('edit a default keybinding to an empty file', () => {
		fs.writeFileSync(keybindingsFile, '');
S
Sandeep Somavarapu 已提交
152 153
		const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }))
154 155 156 157 158 159 160 161
			.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 已提交
162 163
		const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
		return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }))
164 165 166 167 168
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

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

	test('edit a default keybinding in an existing array', () => {
		writeToKeybindingsFile({ command: 'b', key: 'shift+c' });
S
Sandeep Somavarapu 已提交
176 177
		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' }))
178 179 180 181
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

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

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

	test('edit an user keybinding with more than one element', () => {
S
Sandeep Somavarapu 已提交
195 196 197
		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 }))
198 199 200 201
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

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

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

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

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

S
Sandeep Somavarapu 已提交
225 226 227 228 229 230 231 232
	test('reset mulitple removed keybindings', () => {
		writeToKeybindingsFile({ key: 'alt+c', command: '-b' });
		writeToKeybindingsFile({ key: 'alt+shift+c', command: '-b' });
		writeToKeybindingsFile({ key: 'escape', command: '-b' });
		return testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', isDefault: false }))
			.then(() => assert.deepEqual(getUserKeybindings(), []));
	});

S
Sandeep Somavarapu 已提交
233 234 235 236 237 238 239
	test('add a new keybinding to unassigned keybinding', () => {
		writeToKeybindingsFile({ key: 'alt+c', command: '-a' });
		const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }, { key: 'shift+alt+c', command: 'a' }];
		return testObject.editKeybinding('shift+alt+c', aResolvedKeybindingItem({ command: 'a', isDefault: false }))
			.then(() => assert.deepEqual(getUserKeybindings(), expected));
	});

240 241 242 243 244 245 246 247
	function writeToKeybindingsFile(...keybindings: IUserFriendlyKeybinding[]) {
		fs.writeFileSync(keybindingsFile, JSON.stringify(keybindings || []));
	}

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

A
Alex Dima 已提交
248
	function aResolvedKeybindingItem({ command, when, isDefault, firstPart, chordPart }: { command?: string, when?: string, isDefault?: boolean, firstPart?: { keyCode: KeyCode, modifiers?: Modifiers }, chordPart?: { keyCode: KeyCode, modifiers?: Modifiers } }): ResolvedKeybindingItem {
249
		const aSimpleKeybinding = function (part: { keyCode: KeyCode, modifiers?: Modifiers }): SimpleKeybinding {
A
Alex Dima 已提交
250
			const { ctrlKey, shiftKey, altKey, metaKey } = part.modifiers || { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false };
251 252 253 254 255 256
			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);
	}

257
});