gristlabs_grist-core/test/client/components/commands.js
Paul Fitzpatrick bcbf57d590 (core) bump mocha version to allow parallel tests; move more tests to core
Summary:
This uses a newer version of mocha in grist-core so that tests can be run in parallel. That allows more tests to be moved without slowing things down overall. Tests moved are venerable browser tests; only the ones that "just work" or worked without too much trouble to are moved, in order to keep the diff from growing too large. Will wrestle with more in follow up.

Parallelism is at the file level, rather than the individual test.

The newer version of mocha isn't needed for grist-saas repo; tests are parallelized in our internal CI by other means. I've chosen to allocate files to workers in a cruder way than our internal CI, based on initial characters rather than an automated process. The automated process would need some reworking to be compatible with mocha running in parallel mode.

Test Plan: this diff was tested first on grist-core, then ported to grist-saas so saas repo history will correctly track history of moved files.

Reviewers: jarek

Reviewed By: jarek

Subscribers: jarek

Differential Revision: https://phab.getgrist.com/D3927
2023-06-27 02:55:34 -04:00

217 lines
7.4 KiB
JavaScript

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