mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
219 lines
7.4 KiB
JavaScript
219 lines
7.4 KiB
JavaScript
|
/* global describe, beforeEach, before, after, it */
|
||
|
|
||
|
var _ = require('underscore');
|
||
|
var sinon = require('sinon');
|
||
|
var assert = require('chai').assert;
|
||
|
var ko = require('knockout');
|
||
|
var Mousetrap = require('app/client/lib/Mousetrap');
|
||
|
var commands = require('app/client/components/commands');
|
||
|
var clientUtil = require('../clientUtil');
|
||
|
|
||
|
describe('commands', function() {
|
||
|
|
||
|
clientUtil.setTmpMochaGlobals();
|
||
|
|
||
|
before(function() {
|
||
|
sinon.stub(Mousetrap, "bind");
|
||
|
sinon.stub(Mousetrap, "unbind");
|
||
|
});
|
||
|
|
||
|
after(function() {
|
||
|
Mousetrap.bind.restore();
|
||
|
Mousetrap.unbind.restore();
|
||
|
});
|
||
|
|
||
|
beforeEach(function() {
|
||
|
commands.init([{
|
||
|
group: "Foo",
|
||
|
commands: [{
|
||
|
name: "cmd1",
|
||
|
keys: ["Ctrl+a", "Ctrl+b"],
|
||
|
desc: "Command 1"
|
||
|
}, {
|
||
|
name: "cmd2",
|
||
|
keys: ["Ctrl+c"],
|
||
|
desc: "Command 2"
|
||
|
}, {
|
||
|
name: "cmd3",
|
||
|
keys: ["Ctrl+a"],
|
||
|
desc: "Command 1B"
|
||
|
}]
|
||
|
}]);
|
||
|
});
|
||
|
|
||
|
describe("activate", function() {
|
||
|
it("should invoke Mousetrap.bind/unbind", function() {
|
||
|
var obj = {};
|
||
|
var spy = sinon.spy();
|
||
|
var cmdGroup = commands.createGroup({ cmd1: spy }, obj, true);
|
||
|
sinon.assert.callCount(Mousetrap.bind, 2);
|
||
|
sinon.assert.calledWith(Mousetrap.bind, "ctrl+a");
|
||
|
sinon.assert.calledWith(Mousetrap.bind, "ctrl+b");
|
||
|
Mousetrap.bind.reset();
|
||
|
Mousetrap.unbind.reset();
|
||
|
|
||
|
commands.allCommands.cmd1.run();
|
||
|
sinon.assert.callCount(spy, 1);
|
||
|
sinon.assert.calledOn(spy, obj);
|
||
|
|
||
|
cmdGroup.activate(false);
|
||
|
sinon.assert.callCount(Mousetrap.bind, 0);
|
||
|
sinon.assert.callCount(Mousetrap.unbind, 2);
|
||
|
sinon.assert.calledWith(Mousetrap.unbind, "ctrl+a");
|
||
|
sinon.assert.calledWith(Mousetrap.unbind, "ctrl+b");
|
||
|
Mousetrap.bind.reset();
|
||
|
Mousetrap.unbind.reset();
|
||
|
|
||
|
commands.allCommands.cmd1.run();
|
||
|
sinon.assert.callCount(spy, 1);
|
||
|
|
||
|
cmdGroup.activate(true);
|
||
|
sinon.assert.callCount(Mousetrap.bind, 2);
|
||
|
sinon.assert.calledWith(Mousetrap.bind, "ctrl+a");
|
||
|
sinon.assert.calledWith(Mousetrap.bind, "ctrl+b");
|
||
|
sinon.assert.callCount(Mousetrap.unbind, 0);
|
||
|
|
||
|
commands.allCommands.cmd1.run();
|
||
|
sinon.assert.callCount(spy, 2);
|
||
|
|
||
|
cmdGroup.dispose();
|
||
|
sinon.assert.callCount(Mousetrap.unbind, 2);
|
||
|
sinon.assert.calledWith(Mousetrap.unbind, "ctrl+a");
|
||
|
sinon.assert.calledWith(Mousetrap.unbind, "ctrl+b");
|
||
|
|
||
|
commands.allCommands.cmd1.run();
|
||
|
sinon.assert.callCount(spy, 2);
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* For an object of the form { group1: { cmd1: sinon.spy() } }, goes through all spys, and
|
||
|
* returns a mapping of call counts: {'group1:cmd1': spyCallCount}.
|
||
|
*/
|
||
|
function getCallCounts(groups) {
|
||
|
var counts = {};
|
||
|
_.each(groups, function(group, grpName) {
|
||
|
_.each(group, function(cmdSpy, cmdName) {
|
||
|
counts[grpName + ":" + cmdName] = cmdSpy.callCount;
|
||
|
});
|
||
|
});
|
||
|
return counts;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Diffs two sets of call counts as produced by getCallCounts and returns the difference.
|
||
|
*/
|
||
|
function diffCallCounts(callCounts1, callCounts2) {
|
||
|
return _.chain(callCounts2).mapObject(function(count, name) {
|
||
|
return count - callCounts1[name];
|
||
|
})
|
||
|
.pick(function(count, name) {
|
||
|
return count > 0;
|
||
|
})
|
||
|
.value();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Invokes the given command, and makes sure the difference of call counts before and after is
|
||
|
* as expected.
|
||
|
*/
|
||
|
function assertCallCounts(groups, cmdOrFunc, expectedCounts) {
|
||
|
var before = getCallCounts(groups);
|
||
|
if (typeof cmdOrFunc === 'string') {
|
||
|
commands.allCommands[cmdOrFunc].run();
|
||
|
} else if (cmdOrFunc === null) {
|
||
|
// nothing
|
||
|
} else {
|
||
|
cmdOrFunc();
|
||
|
}
|
||
|
var after = getCallCounts(groups);
|
||
|
assert.deepEqual(diffCallCounts(before, after), expectedCounts);
|
||
|
}
|
||
|
|
||
|
it("should respect order of CommandGroups", function() {
|
||
|
var groups = {
|
||
|
group1: { cmd1: sinon.spy(), cmd3: sinon.spy() },
|
||
|
group2: { cmd1: sinon.spy(), cmd2: sinon.spy() },
|
||
|
group3: { cmd3: sinon.spy() },
|
||
|
};
|
||
|
var cmdGroup1 = commands.createGroup(groups.group1, null, true);
|
||
|
var cmdGroup2 = commands.createGroup(groups.group2, null, true);
|
||
|
var cmdGroup3 = commands.createGroup(groups.group3, null, false);
|
||
|
|
||
|
assertCallCounts(groups, 'cmd1', {'group2:cmd1': 1});
|
||
|
assertCallCounts(groups, 'cmd2', {'group2:cmd2': 1});
|
||
|
assertCallCounts(groups, 'cmd3', {'group1:cmd3': 1});
|
||
|
|
||
|
cmdGroup2.activate(false);
|
||
|
assertCallCounts(groups, 'cmd1', {'group1:cmd1': 1});
|
||
|
assertCallCounts(groups, 'cmd2', {});
|
||
|
assertCallCounts(groups, 'cmd3', {'group1:cmd3': 1});
|
||
|
|
||
|
cmdGroup3.activate(true);
|
||
|
cmdGroup1.activate(false);
|
||
|
assertCallCounts(groups, 'cmd1', {});
|
||
|
assertCallCounts(groups, 'cmd2', {});
|
||
|
assertCallCounts(groups, 'cmd3', {'group3:cmd3': 1});
|
||
|
|
||
|
cmdGroup2.activate(true);
|
||
|
assertCallCounts(groups, 'cmd1', {'group2:cmd1': 1});
|
||
|
assertCallCounts(groups, 'cmd2', {'group2:cmd2': 1});
|
||
|
assertCallCounts(groups, 'cmd3', {'group3:cmd3': 1});
|
||
|
});
|
||
|
|
||
|
it("should allow use of observable for activation flag", function() {
|
||
|
var groups = {
|
||
|
groupFoo: { cmd1: sinon.spy() },
|
||
|
};
|
||
|
var isActive = ko.observable(false);
|
||
|
commands.createGroup(groups.groupFoo, null, isActive);
|
||
|
assertCallCounts(groups, 'cmd1', {});
|
||
|
isActive(true);
|
||
|
assertCallCounts(groups, 'cmd1', {'groupFoo:cmd1': 1});
|
||
|
// Check that subsequent calls continue working.
|
||
|
assertCallCounts(groups, 'cmd1', {'groupFoo:cmd1': 1});
|
||
|
isActive(false);
|
||
|
assertCallCounts(groups, 'cmd1', {});
|
||
|
});
|
||
|
|
||
|
function getFuncForShortcut(shortcut) {
|
||
|
function argsIncludeShortcut(args) {
|
||
|
return Array.isArray(args[0]) ? _.contains(args[0], shortcut) : (args[0] === shortcut);
|
||
|
}
|
||
|
var b = _.findLastIndex(Mousetrap.bind.args, argsIncludeShortcut);
|
||
|
var u = _.findLastIndex(Mousetrap.unbind.args, argsIncludeShortcut);
|
||
|
if (b < 0) {
|
||
|
return null;
|
||
|
} else if (u < 0) {
|
||
|
return Mousetrap.bind.args[b][1];
|
||
|
} else if (Mousetrap.bind.getCall(b).calledBefore(Mousetrap.unbind.getCall(u))) {
|
||
|
return null;
|
||
|
} else {
|
||
|
return Mousetrap.bind.args[b][1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
it("should allow same keys used for different commands", function() {
|
||
|
// Both cmd1 and cmd3 use "Ctrl+a" shortcut, so cmd3 should win when group3 is active.
|
||
|
Mousetrap.bind.reset();
|
||
|
Mousetrap.unbind.reset();
|
||
|
var groups = {
|
||
|
group1: { cmd1: sinon.spy() },
|
||
|
group3: { cmd3: sinon.spy() },
|
||
|
};
|
||
|
var cmdGroup1 = commands.createGroup(groups.group1, null, true);
|
||
|
var cmdGroup3 = commands.createGroup(groups.group3, null, true);
|
||
|
assertCallCounts(groups, getFuncForShortcut('ctrl+a'), {'group3:cmd3': 1});
|
||
|
assertCallCounts(groups, getFuncForShortcut('ctrl+b'), {'group1:cmd1': 1});
|
||
|
cmdGroup3.activate(false);
|
||
|
assertCallCounts(groups, getFuncForShortcut('ctrl+a'), {'group1:cmd1': 1});
|
||
|
assertCallCounts(groups, getFuncForShortcut('ctrl+b'), {'group1:cmd1': 1});
|
||
|
cmdGroup1.activate(false);
|
||
|
assertCallCounts(groups, getFuncForShortcut('ctrl+a'), {});
|
||
|
assertCallCounts(groups, getFuncForShortcut('ctrl+b'), {});
|
||
|
cmdGroup3.activate(true);
|
||
|
assertCallCounts(groups, getFuncForShortcut('ctrl+a'), {'group3:cmd3': 1});
|
||
|
assertCallCounts(groups, getFuncForShortcut('ctrl+b'), {});
|
||
|
});
|
||
|
});
|
||
|
});
|