1
0
mirror of https://github.com/adobe/brackets.git synced 2024-11-20 18:02:54 +01:00
brackets/test/spec/KeyBindingManager-test.js

1013 lines
45 KiB
JavaScript

/*
* Copyright (c) 2012 - present Adobe Systems Incorporated. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
/*global describe, beforeEach, afterEach, it, expect, spyOn, runs, waits */
/*unittests: KeyBindingManager */
define(function (require, exports, module) {
'use strict';
require("utils/Global");
// Load dependent modules
var CommandManager = require("command/CommandManager"),
Dialogs = require("widgets/Dialogs"),
KeyBindingManager = require("command/KeyBindingManager"),
KeyEvent = require("utils/KeyEvent"),
SpecRunnerUtils = require("spec/SpecRunnerUtils"),
Strings = require("strings"),
_ = require("thirdparty/lodash");
var platform = brackets.platform;
var testPath = SpecRunnerUtils.getTestPath("/spec/KeyBindingManager-test-files");
var executed,
testCommandFn = function () { executed = true; };
var defaultKeyBindings = {
"Ctrl-L": "edit.selectLine",
"Ctrl-Alt-L": "edit.splitSelIntoLines",
"Alt-Shift-Down": "edit.addCursorToNextLine",
"Alt-Shift-Up": "edit.addCursorToPrevLine",
"F8": "navigate.gotoFirstProblem",
"Ctrl-Alt-O": "file.openFolder",
"Ctrl-Alt-H": "view.toggleSidebar",
"Ctrl-Shift-O": "navigate.quickOpen",
"Ctrl-T": "navigate.gotoDefinition"
},
macDefaultKeyBindings = {
"Ctrl-L": "edit.selectLine",
"Alt-Cmd-L": "edit.splitSelIntoLines",
"Alt-Shift-Down": "edit.addCursorToNextLine",
"Alt-Shift-Up": "edit.addCursorToPrevLine",
"Cmd-'": "navigate.gotoFirstProblem",
"Alt-Cmd-O": "file.openFolder",
"Shift-Cmd-H": "view.toggleSidebar",
"Shift-Cmd-O": "navigate.quickOpen",
"Cmd-T": "navigate.gotoDefinition"
};
function key(k, displayKey, explicitPlatform) {
return {
key : k,
displayKey : displayKey || k,
explicitPlatform : explicitPlatform
};
}
function keyBinding(k, commandID, displayKey, explicitPlatform) {
var obj = key(k, displayKey, explicitPlatform);
obj.commandID = commandID;
return obj;
}
function keyMap(keyBindings) {
var map = {};
keyBindings.forEach(function (k) {
map[k.key] = k;
});
return map;
}
function populateDefaultKeyMap() {
var defaults = (platform === "mac") ? macDefaultKeyBindings : defaultKeyBindings,
index = 0;
_.forEach(defaults, function (commandID, key) {
++index;
CommandManager.register("test command" + index.toString(), commandID, testCommandFn);
});
}
function getDefaultKeyMap() {
var bindings = [],
defaults = (platform === "mac") ? macDefaultKeyBindings : defaultKeyBindings,
displayKey = "",
explicitPlatform;
_.forEach(defaults, function (commandID, key) {
displayKey = KeyBindingManager._getDisplayKey(key);
if (platform === "mac") {
explicitPlatform = undefined;
if (commandID === "edit.selectLine" || commandID === "view.toggleSidebar" ||
commandID === "navigate.gotoFirstProblem") {
explicitPlatform = "mac";
}
}
bindings.push(keyBinding(key, commandID, displayKey, explicitPlatform));
});
return keyMap(bindings);
}
describe("KeyBindingManager", function () {
beforeEach(function () {
CommandManager._testReset();
KeyBindingManager._reset();
brackets.platform = "test";
});
afterEach(function () {
CommandManager._testRestore();
brackets.platform = platform;
});
describe("addBinding", function () {
it("should require command and key binding arguments", function () {
KeyBindingManager.addBinding();
expect(KeyBindingManager.getKeymap()).toEqual({});
KeyBindingManager.addBinding("test.foo");
expect(KeyBindingManager.getKeymap()).toEqual({});
expect(KeyBindingManager.getKeyBindings("test.foo")).toEqual([]);
});
it("should ignore invalid bindings", function () {
expect(KeyBindingManager.addBinding("test.foo", "Ktrl-Shift-A")).toBeNull();
expect(KeyBindingManager.addBinding("test.foo", "Ctrl+R")).toBeNull();
expect(KeyBindingManager.getKeymap()).toEqual({});
});
it("should add single bindings to the keymap", function () {
var result = KeyBindingManager.addBinding("test.foo", "Ctrl-A"),
keyTest = key("Ctrl-A");
expect(result).toEqual(keyTest);
expect(KeyBindingManager.getKeyBindings("test.foo")).toEqual([keyTest]);
result = KeyBindingManager.addBinding("test.bar", "Ctrl-B");
keyTest = key("Ctrl-B");
expect(result).toEqual(keyTest);
expect(KeyBindingManager.getKeyBindings("test.bar")).toEqual([keyTest]);
result = KeyBindingManager.addBinding("test.cat", "Ctrl-C", "bark");
expect(result).toBeNull();
result = KeyBindingManager.addBinding("test.dog", "Ctrl-D", "test");
keyTest = key("Ctrl-D", null, "test");
expect(result).toEqual(keyTest);
expect(KeyBindingManager.getKeyBindings("test.dog")).toEqual([keyTest]);
// only "test" platform bindings
var expected = keyMap([
keyBinding("Ctrl-A", "test.foo"),
keyBinding("Ctrl-B", "test.bar"),
keyBinding("Ctrl-D", "test.dog", null, "test")
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
it("should use displayKey to override display of the shortcut", function () {
KeyBindingManager.addBinding("test.foo", key("Ctrl-=", "Ctrl-+"));
// only "test" platform bindings
var expected = keyMap([
keyBinding("Ctrl-=", "test.foo", "Ctrl-+")
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
it("should add multiple bindings to the keymap", function () {
// use a fake platform
brackets.platform = "test1";
var results = KeyBindingManager.addBinding(
"test.foo",
[ {key: "Ctrl-A", platform: "test1"}, {key: "Ctrl-1", platform: "all"} ]
);
expect(results).toEqual([
key("Ctrl-A", null, "test1"),
key("Ctrl-1", null, "all")
]);
expect(KeyBindingManager.getKeyBindings("test.foo")).toEqual([
key("Ctrl-A", null, "test1"),
key("Ctrl-1", null, "all")
]);
results = KeyBindingManager.addBinding("test.bar", [{key: "Ctrl-B"}, {key: "Ctrl-2", platform: "test2"}]);
expect(results).toEqual([
key("Ctrl-B")
]);
expect(KeyBindingManager.getKeyBindings("test.bar")).toEqual([
key("Ctrl-B")
]);
// only "test1" platform and cross-platform bindings
var expected = keyMap([
keyBinding("Ctrl-A", "test.foo", null, "test1"),
keyBinding("Ctrl-1", "test.foo", null, "all"),
keyBinding("Ctrl-B", "test.bar")
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
it("should allow the command argument to be a string or an object", function () {
var result = KeyBindingManager.addBinding("test.foo", "Ctrl-A"),
keyTest = key("Ctrl-A");
expect(result).toEqual(keyTest);
expect(KeyBindingManager.getKeyBindings("test.foo")).toEqual([keyTest]);
var commandObj = CommandManager.register("Bar", "test.bar", function () { return; });
result = KeyBindingManager.addBinding(commandObj, "Ctrl-B");
keyTest = key("Ctrl-B");
expect(result).toEqual(keyTest);
expect(KeyBindingManager.getKeyBindings("test.bar")).toEqual([keyTest]);
// only "test" platform bindings
var expected = keyMap([
keyBinding("Ctrl-A", "test.foo"),
keyBinding("Ctrl-B", "test.bar")
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
it("should not allow a generic key binding to be replaced with another generic binding", function () {
KeyBindingManager.addBinding("test.foo", "Ctrl-A");
KeyBindingManager.addBinding("test.bar", "Ctrl-A");
var expected = keyMap([
keyBinding("Ctrl-A", "test.foo")
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
it("should allow a platform-specific key binding to override a generic binding", function () {
KeyBindingManager.addBinding("test.foo", "Ctrl-A");
KeyBindingManager.addBinding("test.bar", "Ctrl-A", "test");
var expected = keyMap([
keyBinding("Ctrl-A", "test.bar", null, "test")
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
it("should keep a platform-specific key binding if a generic binding is added later", function () {
KeyBindingManager.addBinding("test.foo", "Ctrl-A", "test");
KeyBindingManager.addBinding("test.bar", "Ctrl-A");
var expected = keyMap([
keyBinding("Ctrl-A", "test.foo", null, "test")
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
it("should allow a command to map to multiple key bindings", function () {
KeyBindingManager.addBinding("test.foo", "Ctrl-A");
KeyBindingManager.addBinding("test.foo", "Ctrl-B");
// only "test1" platform and cross-platform bindings
var expected = keyMap([
keyBinding("Ctrl-A", "test.foo"),
keyBinding("Ctrl-B", "test.foo")
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
it("should support the Ctrl key on mac", function () {
brackets.platform = "mac";
KeyBindingManager.addBinding("test.cmd", "Cmd-A", "mac");
KeyBindingManager.addBinding("test.ctrl", "Ctrl-A", "mac");
KeyBindingManager.addBinding("test.ctrlAlt", "Ctrl-Alt-A", "mac");
KeyBindingManager.addBinding("test.cmdCtrlAlt", "Cmd-Ctrl-A", "mac");
var expected = keyMap([
keyBinding("Cmd-A", "test.cmd", null, "mac"),
keyBinding("Ctrl-A", "test.ctrl", null, "mac"),
keyBinding("Ctrl-Alt-A", "test.ctrlAlt", null, "mac"),
keyBinding("Ctrl-Cmd-A", "test.cmdCtrlAlt", null, "mac") // KeyBindingManager changes the order
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
it("should use windows key bindings on linux", function () {
var original = KeyBindingManager.useWindowsCompatibleBindings;
this.after(function () {
KeyBindingManager.useWindowsCompatibleBindings = original;
});
KeyBindingManager.useWindowsCompatibleBindings = true;
brackets.platform = "linux";
// create a windows-specific binding
KeyBindingManager.addBinding("test.cmd", "Ctrl-A", "win");
var expected = keyMap([
keyBinding("Ctrl-A", "test.cmd", null, "win")
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
// create a generic binding to replace the windows binding
KeyBindingManager.addBinding("test.cmd", "Ctrl-B");
expected = keyMap([
keyBinding("Ctrl-B", "test.cmd", null)
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
it("should support windows compatible bindings", function () {
var original = KeyBindingManager.useWindowsCompatibleBindings;
this.after(function () {
KeyBindingManager.useWindowsCompatibleBindings = original;
});
KeyBindingManager.useWindowsCompatibleBindings = true;
brackets.platform = "linux";
// create a generic binding
KeyBindingManager.addBinding("test.cmd", "Ctrl-A");
var expected = keyMap([
keyBinding("Ctrl-A", "test.cmd")
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
// create a linux-only binding to replace the windows binding
KeyBindingManager.addBinding("test.cmd", "Ctrl-B", "linux");
expected = keyMap([
keyBinding("Ctrl-B", "test.cmd", null, "linux")
]);
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
});
describe("removeBinding", function () {
it("should handle an empty keymap gracefully", function () {
KeyBindingManager.removeBinding("Ctrl-A");
expect(KeyBindingManager.getKeymap()).toEqual({});
});
it("should require a key to remove", function () {
KeyBindingManager.addBinding("test.foo", "Ctrl-A");
KeyBindingManager.addBinding("test.bar", "Ctrl-B");
// keymap should be unchanged
var expected = keyMap([
keyBinding("Ctrl-A", "test.foo"),
keyBinding("Ctrl-B", "test.bar")
]);
KeyBindingManager.removeBinding();
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
it("should remove a key from the key map", function () {
KeyBindingManager.addBinding("test.foo", "Ctrl-A");
KeyBindingManager.addBinding("test.foo", "Ctrl-B");
// Ctrl-A should be removed
var expected = keyMap([
keyBinding("Ctrl-B", "test.foo")
]);
KeyBindingManager.removeBinding("Ctrl-A");
expect(KeyBindingManager.getKeymap()).toEqual(expected);
expect(KeyBindingManager.getKeyBindings("test.foo")).toEqual([key("Ctrl-B")]);
KeyBindingManager.removeBinding("Ctrl-B");
expect(KeyBindingManager.getKeyBindings("test.foo")).toEqual([]);
});
it("should remove a key from the key map for the specified platform", function () {
brackets.platform = "test1";
KeyBindingManager.addBinding("test.foo", "Ctrl-A", "test1");
// remove Ctrl-A, only for platform "test1"
KeyBindingManager.removeBinding("Ctrl-A", "test1");
expect(KeyBindingManager.getKeymap()).toEqual({});
});
it("should exclude a specified platform key binding for a cross-platform command", function () {
brackets.platform = "test1";
// all platforms
KeyBindingManager.addBinding("test.foo", "Ctrl-B");
var expected = keyMap([
keyBinding("Ctrl-B", "test.foo")
]);
// remove Ctrl-B, only for platform "test2"
KeyBindingManager.removeBinding("Ctrl-B", "test2");
expect(KeyBindingManager.getKeymap()).toEqual(expected);
});
});
describe("Load User Key Map", function () {
it("should show an error when loading an image file as a user key map file", function () {
runs(function () {
var imageTestFilesPath = SpecRunnerUtils.getTestPath("/spec/test-image-files");
KeyBindingManager._setUserKeyMapFilePath(imageTestFilesPath + "/eye.jpg");
KeyBindingManager._loadUserKeyMap();
waits(300);
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
expect(message).toEqual(Strings.ERROR_LOADING_KEYMAP);
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
expect(Dialogs.showModalDialog).toHaveBeenCalled();
});
});
it("should show an error when loading a corrupted key map file", function () {
runs(function () {
KeyBindingManager._setUserKeyMapFilePath(testPath + "/invalid.json");
KeyBindingManager._loadUserKeyMap();
waits(300);
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
expect(message).toEqual(Strings.ERROR_KEYMAP_CORRUPT);
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
expect(Dialogs.showModalDialog).toHaveBeenCalled();
});
});
it("should show an error when loading a key map file with only whitespaces", function () {
runs(function () {
KeyBindingManager._setUserKeyMapFilePath(testPath + "/whitespace.json");
KeyBindingManager._loadUserKeyMap();
waits(300);
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
expect(message).toEqual(Strings.ERROR_KEYMAP_CORRUPT);
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
expect(Dialogs.showModalDialog).toHaveBeenCalled();
});
});
it("should NOT show any error when loading a user key map file with an empty object", function () {
runs(function () {
KeyBindingManager._initCommandAndKeyMaps();
KeyBindingManager._setUserKeyMapFilePath(testPath + "/empty.json");
KeyBindingManager._loadUserKeyMap();
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
expect(Dialogs.showModalDialog).not.toHaveBeenCalled();
});
});
it("should NOT show any error when loading a zero-byte user key map file", function () {
runs(function () {
KeyBindingManager._initCommandAndKeyMaps();
KeyBindingManager._setUserKeyMapFilePath(testPath + "/blank.json");
KeyBindingManager._loadUserKeyMap();
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
expect(Dialogs.showModalDialog).not.toHaveBeenCalled();
});
});
it("should show an error when attempting to reassign a special command", function () {
runs(function () {
CommandManager.register("test copy command", "edit.copy", testCommandFn);
KeyBindingManager._initCommandAndKeyMaps();
KeyBindingManager._setUserKeyMapFilePath(testPath + "/reassignCopy.json");
KeyBindingManager._loadUserKeyMap();
waits(300);
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
var msgPrefix = Strings.ERROR_RESTRICTED_COMMANDS.replace("{0}", "");
expect(message).toMatch(msgPrefix);
expect(message).toMatch("edit.copy");
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
expect(Dialogs.showModalDialog).toHaveBeenCalled();
});
});
it("should show an error when attempting to reassign a restricted shortcut (either bind to a special command or a mac system shortcut)", function () {
var testFilePath = (platform === "mac") ? (testPath + "/macRestrictedShortcut.json") : (testPath + "/restrictedShortcut.json");
runs(function () {
brackets.platform = platform;
populateDefaultKeyMap();
KeyBindingManager._initCommandAndKeyMaps();
KeyBindingManager._setUserKeyMapFilePath(testFilePath);
KeyBindingManager._loadUserKeyMap();
waits(300);
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
var msgPrefix = Strings.ERROR_RESTRICTED_SHORTCUTS.replace("{0}", "");
expect(message).toMatch(msgPrefix);
if (platform === "mac") {
expect(message).toMatch("cmd-z");
expect(message).toMatch("Cmd-m");
expect(message).toMatch("cmd-h");
} else {
expect(message).toMatch("ctrl-z");
}
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
expect(Dialogs.showModalDialog).toHaveBeenCalled();
});
});
it("should show an error when attempting to assign multiple shortcuts to the same command", function () {
runs(function () {
brackets.platform = platform;
populateDefaultKeyMap();
KeyBindingManager._initCommandAndKeyMaps();
KeyBindingManager._setUserKeyMapFilePath(testPath + "/multipleShortcuts.json");
KeyBindingManager._loadUserKeyMap();
waits(300);
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
var msgPrefix = Strings.ERROR_MULTIPLE_SHORTCUTS.replace("{0}", "");
expect(message).toMatch(msgPrefix);
expect(message).toMatch("file.openFolder");
expect(message).toMatch("view.toggleSidebar");
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
expect(Dialogs.showModalDialog).toHaveBeenCalled();
});
});
it("should show an error when attempting to set duplicate shortcuts", function () {
runs(function () {
brackets.platform = platform;
populateDefaultKeyMap();
KeyBindingManager._setUserKeyMapFilePath(testPath + "/duplicateShortcuts.json");
KeyBindingManager._initCommandAndKeyMaps();
KeyBindingManager._loadUserKeyMap();
waits(300);
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
var msgPrefix = Strings.ERROR_DUPLICATE_SHORTCUTS.replace("{0}", "");
expect(message).toMatch(msgPrefix);
expect(message).toMatch("Ctrl-2");
expect(message).toMatch("Alt-Ctrl-4");
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
expect(Dialogs.showModalDialog).toHaveBeenCalled();
});
});
it("should show an error when parsing invalid shortcuts", function () {
runs(function () {
brackets.platform = platform;
populateDefaultKeyMap();
KeyBindingManager._initCommandAndKeyMaps();
KeyBindingManager._setUserKeyMapFilePath(testPath + "/invalidKeys.json");
KeyBindingManager._loadUserKeyMap();
waits(300);
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
var msgPrefix = Strings.ERROR_INVALID_SHORTCUTS.replace("{0}", "");
expect(message).toMatch(msgPrefix);
expect(message).toMatch("command-2");
expect(message).toMatch("Option-Cmd-Backspace");
expect(message).toMatch("ctrl-kk");
expect(message).toMatch("cmd-Del");
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
expect(Dialogs.showModalDialog).toHaveBeenCalled();
});
});
it("should show an error when attempting to set shortcuts to non-existent commands", function () {
runs(function () {
// The command map has to be empty for this test case. So we are intentionally NOT calling
// populateDefaultKeyMap() before loading the user key map.
KeyBindingManager._initCommandAndKeyMaps();
KeyBindingManager._setUserKeyMapFilePath(testPath + "/keymap.json");
KeyBindingManager._loadUserKeyMap();
waits(300);
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
var msgPrefix = Strings.ERROR_NONEXISTENT_COMMANDS.replace("{0}", "");
expect(message).toMatch(msgPrefix);
expect(message).toMatch("file.openFolder");
expect(message).toMatch("view.toggleSidebar");
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
expect(Dialogs.showModalDialog).toHaveBeenCalled();
});
});
it("should update key map with the user specified key bindings", function () {
var testFilePath = (platform === "mac") ? (testPath + "/macKeymap.json") : (testPath + "/keymap.json");
runs(function () {
brackets.platform = platform;
var defKeyMap = getDefaultKeyMap();
populateDefaultKeyMap();
KeyBindingManager._initCommandAndKeyMaps();
expect(KeyBindingManager.getKeymap()).toEqual(defKeyMap);
KeyBindingManager._setUserKeyMapFilePath(testFilePath);
KeyBindingManager._loadUserKeyMap();
waits(300);
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
var keymap = KeyBindingManager.getKeymap(),
reassignedKey1 = (platform === "mac") ? "Alt-Cmd-Backspace" : "Ctrl-Alt-Backspace",
reassignedKey2 = (platform === "mac") ? "Cmd-T" : "Ctrl-T";
expect(Dialogs.showModalDialog).not.toHaveBeenCalled();
expect(keymap["Ctrl-2"].commandID).toEqual("file.openFolder");
expect(keymap["Alt-Cmd-O"]).toBeFalsy();
expect(keymap["Alt-Ctrl-O"]).toBeFalsy();
expect(keymap[reassignedKey1].commandID).toEqual("view.toggleSidebar");
expect(keymap["Shift-Cmd-H"]).toBeFalsy();
expect(keymap["Alt-Ctrl-H"]).toBeFalsy();
expect(keymap["Ctrl-L"].commandID).toEqual("navigate.gotoDefinition");
expect(keymap[reassignedKey2]).toBeFalsy();
expect(keymap["Alt-Cmd-L"]).toBeFalsy();
expect(keymap["Alt-Ctrl-L"]).toBeFalsy();
});
});
it("should restore original key bindings when the user key map is updated", function () {
var testFilePath1 = (platform === "mac") ? (testPath + "/macKeymap.json") : (testPath + "/keymap.json"),
testFilePath2 = (platform === "mac") ? (testPath + "/macKeymap1.json") : (testPath + "/keymap1.json");
runs(function () {
brackets.platform = platform;
var defKeyMap = getDefaultKeyMap();
populateDefaultKeyMap();
KeyBindingManager._initCommandAndKeyMaps();
expect(KeyBindingManager.getKeymap()).toEqual(defKeyMap);
KeyBindingManager._setUserKeyMapFilePath(testFilePath1);
KeyBindingManager._loadUserKeyMap();
waits(300);
// Loading a different key map file to simulate the user updating an existing key map.
KeyBindingManager._setUserKeyMapFilePath(testFilePath2);
KeyBindingManager._loadUserKeyMap();
waits(300);
spyOn(Dialogs, 'showModalDialog').andCallFake(function (dlgClass, title, message, buttons) {
return {done: function (callback) { callback(Dialogs.DIALOG_BTN_OK); } };
});
});
runs(function () {
var keymap = KeyBindingManager.getKeymap(),
reassignedKey1 = (platform === "mac") ? "Alt-Cmd-Backspace" : "Ctrl-Alt-Backspace",
reassignedKey2 = (platform === "mac") ? "Alt-Cmd-O" : "Ctrl-Alt-O",
reassignedKey3 = (platform === "mac") ? "Cmd-T" : "Ctrl-T";
expect(Dialogs.showModalDialog).not.toHaveBeenCalled();
expect(keymap["Ctrl-2"].commandID).toEqual("view.toggleSidebar");
// Previous user key binding to "view.toggleSidebar" is gone.
expect(keymap[reassignedKey1]).toBeFalsy();
// Default key binding for "file.openFolder" is restored.
expect(keymap[reassignedKey2].commandID).toEqual("file.openFolder");
expect(keymap["Ctrl-L"].commandID).toEqual("navigate.gotoDefinition");
expect(keymap[reassignedKey3]).toBeFalsy();
});
});
});
describe("handleKey", function () {
it("should execute a command", function () {
var fooCalled = false;
CommandManager.register("Foo", "test.foo", function () {
fooCalled = true;
});
KeyBindingManager.addBinding("test.foo", "Ctrl-A");
expect(fooCalled).toBe(false);
KeyBindingManager._handleKey("Ctrl-A");
expect(fooCalled).toBe(true);
});
});
describe("handle AltGr key", function () {
var commandCalled, ctrlAlt1Event, ctrlAltEvents;
var ctrlEvent = {
ctrlKey: true,
keyIdentifier: "Control",
keyCode: KeyEvent.DOM_VK_CONTROL,
immediatePropagationStopped: false,
propagationStopped: false,
defaultPrevented: false,
stopImmediatePropagation: function () {
this.immediatePropagationStopped = true;
},
stopPropagation: function () {
this.propagationStopped = true;
},
preventDefault: function () {
this.defaultPrevented = true;
}
};
function makeCtrlAltKeyEvents() {
var altGrEvents = [],
altEvent = _.cloneDeep(ctrlEvent);
altEvent.keyIdentifier = "Alt";
altEvent.altKey = true;
altEvent.keyCode = KeyEvent.DOM_VK_ALT;
altGrEvents.push(_.cloneDeep(ctrlEvent));
altGrEvents.push(altEvent);
return altGrEvents;
}
function makeCtrlAlt1KeyEvent() {
return {
ctrlKey: true,
altKey: true,
keyCode: "1".charCodeAt(0),
immediatePropagationStopped: false,
propagationStopped: false,
defaultPrevented: false,
stopImmediatePropagation: function () {
this.immediatePropagationStopped = true;
},
stopPropagation: function () {
this.propagationStopped = true;
},
preventDefault: function () {
this.defaultPrevented = true;
}
};
}
beforeEach(function () {
commandCalled = false;
ctrlAlt1Event = makeCtrlAlt1KeyEvent();
ctrlAltEvents = makeCtrlAltKeyEvents();
CommandManager.register("FakeUnitTestCommand", "unittest.fakeCommand", function () {
commandCalled = true;
});
KeyBindingManager.addBinding("unittest.fakeCommand", "Ctrl-Alt-1");
// Modify platform to "win" since right Alt key detection is done only on Windows.
brackets.platform = "win";
});
afterEach(function () {
// Restore the platform.
brackets.platform = "test";
});
it("should block command execution if right Alt key is pressed", function () {
// Simulate a right Alt key down with the specific sequence of keydown events.
ctrlAltEvents.forEach(function (e) {
e.timeStamp = new Date();
KeyBindingManager._handleKeyEvent(e);
});
// Simulate the command shortcut
KeyBindingManager._handleKeyEvent(ctrlAlt1Event);
expect(commandCalled).toBe(false);
// In this case, the event should not have been stopped, because KBM didn't handle it.
expect(ctrlAlt1Event.immediatePropagationStopped).toBe(false);
expect(ctrlAlt1Event.propagationStopped).toBe(false);
expect(ctrlAlt1Event.defaultPrevented).toBe(false);
// Now explicitly remove the keyup event listener created by _detectAltGrKeyDown function.
KeyBindingManager._onCtrlUp(ctrlEvent);
});
it("should block command execution when right Alt key is pressed following a Ctrl shortcut execution", function () {
var ctrlEvent1 = _.cloneDeep(ctrlEvent);
// Simulate holding down Ctrl key and execute a Ctrl shortcut in native shell code
// No need to call the actual Ctrl shortcut since it is not handled in KBM anyway.
KeyBindingManager._handleKeyEvent(ctrlEvent1);
ctrlEvent1.repeat = true;
KeyBindingManager._handleKeyEvent(ctrlEvent1);
KeyBindingManager._handleKeyEvent(ctrlEvent1);
// Simulate a right Alt key down with the specific sequence of keydown events.
ctrlAltEvents.forEach(function (e) {
e.timeStamp = new Date();
e.repeat = false;
KeyBindingManager._handleKeyEvent(e);
});
// Simulate the command shortcut
KeyBindingManager._handleKeyEvent(ctrlAlt1Event);
expect(commandCalled).toBe(false);
// In this case, the event should not have been stopped, because KBM didn't handle it.
expect(ctrlAlt1Event.immediatePropagationStopped).toBe(false);
expect(ctrlAlt1Event.propagationStopped).toBe(false);
expect(ctrlAlt1Event.defaultPrevented).toBe(false);
// Now explicitly remove the keyup event listener created by _detectAltGrKeyDown function.
KeyBindingManager._onCtrlUp(ctrlEvent);
});
it("should not block command execution if interval between Ctrl & Alt events are more than 30 ms.", function () {
var lastTS;
// Simulate a Ctrl-Alt keys down with the specific sequence of keydown events.
ctrlAltEvents.forEach(function (e) {
if (!lastTS) {
e.timeStamp = new Date();
lastTS = e.timeStamp;
} else {
e.timeStamp = lastTS + 50;
}
KeyBindingManager._handleKeyEvent(e);
});
// Simulate the command shortcut
KeyBindingManager._handleKeyEvent(ctrlAlt1Event);
expect(commandCalled).toBe(true);
// In this case, the event should have been stopped (but not immediately) because
// KBM handled it.
expect(ctrlAlt1Event.immediatePropagationStopped).toBe(false);
expect(ctrlAlt1Event.propagationStopped).toBe(true);
expect(ctrlAlt1Event.defaultPrevented).toBe(true);
});
it("should not block command execution when the right Alt key is not used", function () {
// Simulate the command shortcut
KeyBindingManager._handleKeyEvent(ctrlAlt1Event);
expect(commandCalled).toBe(true);
// In this case, the event should have been stopped (but not immediately) because
// KBM handled it.
expect(ctrlAlt1Event.immediatePropagationStopped).toBe(false);
expect(ctrlAlt1Event.propagationStopped).toBe(true);
expect(ctrlAlt1Event.defaultPrevented).toBe(true);
});
});
describe("global hooks", function () {
var commandCalled, hook1Called, hook2Called, ctrlAEvent;
function keydownHook1(event) {
hook1Called = true;
return true;
}
function keydownHook2(event) {
hook2Called = true;
return true;
}
function makeKeyEvent() {
// We don't create a real native event object here--just a fake
// object with enough info for the key translation to work
// properly--since our mock hooks don't actually look at it
// anyway.
return {
ctrlKey: true,
keyCode: "A".charCodeAt(0),
immediatePropagationStopped: false,
propagationStopped: false,
defaultPrevented: false,
stopImmediatePropagation: function () {
this.immediatePropagationStopped = true;
},
stopPropagation: function () {
this.propagationStopped = true;
},
preventDefault: function () {
this.defaultPrevented = true;
}
};
}
beforeEach(function () {
commandCalled = false;
hook1Called = false;
hook2Called = false;
ctrlAEvent = makeKeyEvent();
CommandManager.register("FakeUnitTestCommand", "unittest.fakeCommand", function () {
commandCalled = true;
});
KeyBindingManager.addBinding("unittest.fakeCommand", "Ctrl-A");
});
it("should block command execution if a global hook is added that prevents it", function () {
KeyBindingManager.addGlobalKeydownHook(keydownHook1);
KeyBindingManager._handleKeyEvent(ctrlAEvent);
expect(hook1Called).toBe(true);
expect(commandCalled).toBe(false);
// In this case, the event should not have been stopped, because our hook didn't stop it
// and KBM didn't handle it.
expect(ctrlAEvent.immediatePropagationStopped).toBe(false);
expect(ctrlAEvent.propagationStopped).toBe(false);
expect(ctrlAEvent.defaultPrevented).toBe(false);
});
it("should not block command execution if a global hook is added then removed", function () {
KeyBindingManager.addGlobalKeydownHook(keydownHook1);
KeyBindingManager.removeGlobalKeydownHook(keydownHook1);
KeyBindingManager._handleKeyEvent(ctrlAEvent);
expect(hook1Called).toBe(false);
expect(commandCalled).toBe(true);
// In this case, the event should have been stopped (but not immediately) because
// KBM handled it.
expect(ctrlAEvent.immediatePropagationStopped).toBe(false);
expect(ctrlAEvent.propagationStopped).toBe(true);
expect(ctrlAEvent.defaultPrevented).toBe(true);
});
it("should call the most recently added hook first", function () {
KeyBindingManager.addGlobalKeydownHook(keydownHook1);
KeyBindingManager.addGlobalKeydownHook(keydownHook2);
KeyBindingManager._handleKeyEvent(ctrlAEvent);
expect(hook2Called).toBe(true);
expect(hook1Called).toBe(false);
expect(commandCalled).toBe(false);
// In this case, the event should not have been stopped, because our hook didn't stop it
// and KBM didn't handle it.
expect(ctrlAEvent.immediatePropagationStopped).toBe(false);
expect(ctrlAEvent.propagationStopped).toBe(false);
expect(ctrlAEvent.defaultPrevented).toBe(false);
});
});
});
});