(core) Moving client and common tests to core

Summary:
- Moved /test/client and /test/common to core.
- Moved two files (CircularArray and RecentItems) from app/common to core/app/common.
- Moved resetOrg test to gen-server.
- `testrun.sh` is now invoking common and client test from core.
- Added missing packages to core's package.json (and revealed underscore as it is used in the main app).
- Removed Coord.js as it is not used anywhere.

Test Plan: Existing tests

Reviewers: paulfitz

Reviewed By: paulfitz

Subscribers: paulfitz

Differential Revision: https://phab.getgrist.com/D3590
This commit is contained in:
Jarosław Sadziński
2022-08-18 23:08:39 +02:00
parent e06f0bc1d8
commit a52d56f613
78 changed files with 11700 additions and 7 deletions

View File

@@ -0,0 +1,170 @@
/* global describe, beforeEach, afterEach, it */
var assert = require('chai').assert;
var clientUtil = require('../clientUtil');
var dom = require('app/client/lib/dom');
var Layout = require('app/client/components/Layout');
describe('Layout', function() {
clientUtil.setTmpMochaGlobals();
var layout;
var sampleData = {
children: [{
children: [{
children: [{
leaf: 1
}, {
leaf: 2
}]
}, {
children: [{
children: [{
leaf: 3
}, {
leaf: 4
}]
}, {
leaf: 5
}]
}]
}, {
leaf: 6
}]
};
function createLeaf(leafId) {
return dom('div.layout_leaf_test', "#" + leafId);
}
beforeEach(function() {
layout = Layout.Layout.create(sampleData, createLeaf);
});
afterEach(function() {
layout.dispose();
layout = null;
});
function getClasses(node) {
return Array.prototype.slice.call(node.classList, 0).sort();
}
it("should generate same layout spec as it was built with", function() {
assert.deepEqual(layout.getLayoutSpec(), sampleData);
assert.deepEqual(layout.getAllLeafIds().sort(), [1, 2, 3, 4, 5, 6]);
});
it("should generate nested DOM structure", function() {
var rootBox = layout.rootElem.querySelector('.layout_box');
assert(rootBox);
assert.strictEqual(rootBox, layout.rootBox().dom);
assert.deepEqual(getClasses(rootBox), ["layout_box", "layout_last_child",
"layout_vbox"]);
var rows = rootBox.children;
assert.equal(rows.length, 2);
assert.equal(rows[0].children.length, 2);
assert.deepEqual(getClasses(rows[0]), ["layout_box", "layout_hbox"]);
assert.deepEqual(getClasses(rows[0].children[0]), ["layout_box", "layout_vbox"]);
assert.deepEqual(getClasses(rows[0].children[1]), ["layout_box", "layout_last_child",
"layout_vbox"]);
assert.equal(rows[1].children.length, 1);
assert.includeMembers(getClasses(rows[1]), ["layout_box", "layout_hbox",
"layout_last_child", "layout_leaf"]);
});
it("should correctly handle removing boxes", function() {
layout.getLeafBox(4).removeFromParent();
layout.getLeafBox(1).removeFromParent();
assert.deepEqual(layout.getAllLeafIds().sort(), [2, 3, 5, 6]);
assert.deepEqual(layout.getLayoutSpec(), {
children: [{
children: [{
leaf: 2
}, {
children: [{
leaf: 3
}, {
leaf: 5
}]
}]
}, {
leaf: 6
}]
});
// Here we get into a rare situation with a single child (to allow root box to be split
// vertically).
layout.getLeafBox(6).removeFromParent();
assert.deepEqual(layout.getLayoutSpec(), {
children: [{
children: [{
leaf: 2
}, {
children: [{
leaf: 3
}, {
leaf: 5
}]
}]
}]
});
assert.deepEqual(layout.getAllLeafIds().sort(), [2, 3, 5]);
// Here the special single-child box should collapse
layout.getLeafBox(2).removeFromParent();
assert.deepEqual(layout.getLayoutSpec(), {
children: [{
leaf: 3
}, {
leaf: 5
}]
});
layout.getLeafBox(3).removeFromParent();
assert.deepEqual(layout.getLayoutSpec(), {
leaf: 5
});
assert.deepEqual(layout.getAllLeafIds().sort(), [5]);
});
it("should correctly handle adding child and sibling boxes", function() {
// In this test, we'll build up the sample layout from scratch, trying to exercise all code
// paths.
layout = Layout.Layout.create({ leaf: 1 }, createLeaf);
assert.deepEqual(layout.getLayoutSpec(), { leaf: 1 });
assert.deepEqual(layout.getAllLeafIds().sort(), [1]);
function makeBox(leafId) {
return layout.buildLayoutBox({leaf: leafId});
}
assert.strictEqual(layout.rootBox(), layout.getLeafBox(1));
layout.getLeafBox(1).addSibling(makeBox(5), true);
assert.deepEqual(layout.getLayoutSpec(), {children: [{
children: [{ leaf: 1 }, { leaf: 5 }]
}]});
assert.notStrictEqual(layout.rootBox(), layout.getLeafBox(1));
// An extra little check to add a sibling to a vertically-split root (in which case the split
// is really a level lower, and that's where the sibling should be added).
layout.rootBox().addSibling(makeBox("foo"), true);
assert.deepEqual(layout.getLayoutSpec(), {children: [{
children: [{ leaf: 1 }, { leaf: 5 }, { leaf: "foo" }]
}]});
assert.deepEqual(layout.getAllLeafIds().sort(), [1, 5, "foo"]);
layout.getLeafBox("foo").dispose();
assert.deepEqual(layout.getAllLeafIds().sort(), [1, 5]);
layout.getLeafBox(1).parentBox().addSibling(makeBox(6), true);
layout.getLeafBox(5).addChild(makeBox(3), false);
layout.getLeafBox(3).addChild(makeBox(4), true);
layout.getLeafBox(1).addChild(makeBox(2), true);
assert.deepEqual(layout.getLayoutSpec(), sampleData);
assert.deepEqual(layout.getAllLeafIds().sort(), [1, 2, 3, 4, 5, 6]);
});
});

View File

@@ -0,0 +1,63 @@
import {assert} from 'chai';
import {MethodAccess} from 'app/client/components/WidgetFrame';
import {AccessLevel} from 'app/common/CustomWidget';
describe('WidgetFrame', function () {
it('should define access level per method', function () {
class SampleApi {
public none() {
return true;
}
public read_table() {
return true;
}
public full() {}
public notMentioned() {}
}
const checker = new MethodAccess<SampleApi>()
.require(AccessLevel.none, 'none')
.require(AccessLevel.read_table, 'read_table')
.require(AccessLevel.full, 'full');
const directTest = () => {
assert.isTrue(checker.check(AccessLevel.none, 'none'));
assert.isFalse(checker.check(AccessLevel.none, 'read_table'));
assert.isFalse(checker.check(AccessLevel.none, 'full'));
assert.isTrue(checker.check(AccessLevel.read_table, 'none'));
assert.isTrue(checker.check(AccessLevel.read_table, 'read_table'));
assert.isFalse(checker.check(AccessLevel.read_table, 'full'));
assert.isTrue(checker.check(AccessLevel.full, 'none'));
assert.isTrue(checker.check(AccessLevel.full, 'read_table'));
assert.isTrue(checker.check(AccessLevel.full, 'full'));
};
directTest();
// Check that for any other method, access is denied.
assert.isFalse(checker.check(AccessLevel.none, 'notMentioned'));
assert.isFalse(checker.check(AccessLevel.read_table, 'notMentioned'));
// Even though access is full, the method was not mentioned, so it should be denied.
assert.isFalse(checker.check(AccessLevel.full, 'notMentioned'));
// Now add a default rule.
checker.require(AccessLevel.none, '*');
assert.isTrue(checker.check(AccessLevel.none, 'notMentioned'));
assert.isTrue(checker.check(AccessLevel.read_table, 'notMentioned'));
assert.isTrue(checker.check(AccessLevel.full, 'notMentioned'));
directTest();
checker.require(AccessLevel.read_table, '*');
assert.isFalse(checker.check(AccessLevel.none, 'notMentioned'));
assert.isTrue(checker.check(AccessLevel.read_table, 'notMentioned'));
assert.isTrue(checker.check(AccessLevel.full, 'notMentioned'));
directTest();
checker.require(AccessLevel.full, '*');
assert.isFalse(checker.check(AccessLevel.none, 'notMentioned'));
assert.isFalse(checker.check(AccessLevel.read_table, 'notMentioned'));
assert.isTrue(checker.check(AccessLevel.full, 'notMentioned'));
directTest();
});
});

View File

@@ -0,0 +1,218 @@
/* 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'), {});
});
});
});

View File

@@ -0,0 +1,80 @@
var dom = require('app/client/lib/dom');
var kd = require('app/client/lib/koDom');
var kf = require('app/client/lib/koForm');
var Layout = require('app/client/components/Layout');
var LayoutEditor = require('app/client/components/LayoutEditor');
function createTestTab() {
return kf.topTab('Layout',
kf.label("Layout Editor")
);
}
exports.createTestTab = createTestTab;
var sampleData = {
children: [{
children: [{
children: [{
leaf: 1
}, {
leaf: 2
}, {
leaf: 7
}, {
leaf: 8
}]
}, {
children: [{
children: [{
leaf: 3
}, {
leaf: 4
}, {
leaf: 9
}, {
leaf: 10
}]
}, {
leaf: 5
}]
}]
}, {
leaf: 6
}]
};
function getMaxLeaf(spec) {
var maxChild = spec.children ? Math.max.apply(Math, spec.children.map(getMaxLeaf)) : -Infinity;
return Math.max(maxChild, spec.leaf || -Infinity);
}
function createLeaf(leafId) {
return dom('div.layout_leaf_test', "#" + leafId,
kd.toggleClass('layout_leaf_test_big', leafId % 2 === 0)
);
}
function createTestPane() {
var layout = Layout.Layout.create(sampleData, createLeaf);
var layoutEditor = LayoutEditor.LayoutEditor.create(layout);
var maxLeaf = getMaxLeaf(sampleData);
return dom('div',
dom.autoDispose(layoutEditor),
dom.autoDispose(layout),
dom('div',
dom('div.layout_new.pull-left', '+ Add New',
dom.on('mousedown', function(event) {
layoutEditor.dragInNewBox(event, ++maxLeaf);
return false;
})
),
dom('div.layout_trash.pull-right',
dom('span.glyphicon.glyphicon-trash')
),
dom('div.clearfix')
),
layout.rootElem
);
}
exports.createTestPane = createTestPane;