gristlabs_grist-core/test/client/lib/dom.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

395 lines
14 KiB
JavaScript

var assert = require('chai').assert;
var sinon = require('sinon');
var Promise = require('bluebird');
var ko = require('knockout');
var dom = require('app/client/lib/dom');
var clientUtil = require('../clientUtil');
var G = require('app/client/lib/browserGlobals').get('DocumentFragment');
var utils = require('../../utils');
describe('dom', function() {
clientUtil.setTmpMochaGlobals();
describe("dom construction", function() {
it("should create elements with the right tag name, class and ID", function() {
var elem = dom('div', "Hello world");
assert.equal(elem.tagName, "DIV");
assert(!elem.className);
assert(!elem.id);
assert.equal(elem.textContent, "Hello world");
elem = dom('span#foo.bar.baz', "Hello world");
assert.equal(elem.tagName, "SPAN");
assert.equal(elem.className, "bar baz");
assert.equal(elem.id, "foo");
assert.equal(elem.textContent, "Hello world");
});
it("should set attributes", function() {
var elem = dom('a', { title: "foo", id: "bar" });
assert.equal(elem.title, "foo");
assert.equal(elem.id, "bar");
});
it("should set children", function() {
var elem = dom('div',
"foo", dom('a#a'),
[dom('a#b'), "bar", dom('a#c')],
dom.frag(dom('a#d'), "baz", dom('a#e')));
assert.equal(elem.childNodes.length, 8);
assert.equal(elem.childNodes[0].data, "foo");
assert.equal(elem.childNodes[1].id, "a");
assert.equal(elem.childNodes[2].id, "b");
assert.equal(elem.childNodes[3].data, "bar");
assert.equal(elem.childNodes[4].id, "c");
assert.equal(elem.childNodes[5].id, "d");
assert.equal(elem.childNodes[6].data, "baz");
assert.equal(elem.childNodes[7].id, "e");
});
it('should flatten nested arrays and arrays returned from functions', function() {
var values = ['apple', 'orange', ['banana', 'mango']];
var elem = dom('ul',
values.map(function(value, index) {
return dom('li', value);
}),
[
dom('li', 'pear'),
[
dom('li', 'peach'),
dom('li', 'cranberry'),
],
dom('li', 'date')
]
);
assert.equal(elem.outerHTML, "<ul><li>apple</li><li>orange</li>" +
"<li>bananamango</li><li>pear</li><li>peach</li><li>cranberry</li>" +
"<li>date</li></ul>");
elem = dom('ul',
function(innerElem) {
return [
dom('li', 'plum'),
dom('li', 'pomegranate')
];
},
function(innerElem) {
return function(moreInnerElem) {
return [
dom('li', 'strawberry'),
dom('li', 'blueberry')
];
};
}
);
assert.equal(elem.outerHTML, "<ul><li>plum</li><li>pomegranate</li>" +
"<li>strawberry</li><li>blueberry</li></ul>");
});
it("should append append values returned from functions except undefined", function() {
var elem = dom('div',
function(divElem) {
divElem.classList.add('yogurt');
return dom('div', 'sneakers');
},
dom('span', 'melon')
);
assert.equal(elem.classList[0], 'yogurt',
'function shold have applied new class to outer div');
assert.equal(elem.childNodes.length, 2);
assert.equal(elem.childNodes[0].innerHTML, "sneakers");
assert.equal(elem.childNodes[1].innerHTML, "melon");
elem = dom('div',
function(divElem) {
return undefined;
}
);
assert.equal(elem.childNodes.length, 0,
"undefined returned from a function should not be added to the DOM tree");
});
it('should not append nulls', function() {
var elem = dom('div',
[
"hello",
null,
"world",
null,
"jazz"
],
'hands',
null
);
assert.equal(elem.childNodes.length, 4,
"undefined returned from a function should not be added to the DOM tree");
assert.equal(elem.childNodes[0].data, "hello");
assert.equal(elem.childNodes[1].data, "world");
assert.equal(elem.childNodes[2].data, "jazz");
assert.equal(elem.childNodes[3].data, "hands");
});
});
utils.timing.describe("dom", function() {
var built, child;
before(function() {
child = dom('bar');
});
utils.timing.it(40, "should be fast", function() {
built = utils.repeat(100, function() {
return dom('div#id1.class1.class2', {disabled: 'disabled'},
'foo',
child,
['hello', 'world'],
function(elem) {
return 'test';
}
);
});
});
utils.timing.it(40, "should be fast", function() {
utils.repeat(100, function() {
dom('div#id1.class1.class2.class3');
dom('div#id1.class1.class2.class3');
dom('div#id1.class1.class2.class3');
dom('div#id1.class1.class2.class3');
dom('div#id1.class1.class2.class3');
});
});
after(function() {
assert.equal(built.getAttribute('disabled'), 'disabled');
assert.equal(built.tagName, 'DIV');
assert.equal(built.className, 'class1 class2');
assert.equal(built.childNodes.length, 5);
assert.equal(built.childNodes[0].data, 'foo');
assert.equal(built.childNodes[1], child);
assert.equal(built.childNodes[2].data, 'hello');
assert.equal(built.childNodes[3].data, 'world');
assert.equal(built.childNodes[4].data, 'test');
});
});
describe("dom.frag", function() {
it("should create DocumentFragments", function() {
var elem1 = dom.frag(["hello", "world"]);
assert(elem1 instanceof G.DocumentFragment);
assert.equal(elem1.childNodes.length, 2);
assert.equal(elem1.childNodes[0].data, "hello");
assert.equal(elem1.childNodes[1].data, "world");
var elem2 = dom.frag("hello", "world");
assert(elem2 instanceof G.DocumentFragment);
assert.equal(elem2.childNodes.length, 2);
assert.equal(elem2.childNodes[0].data, "hello");
assert.equal(elem2.childNodes[1].data, "world");
var elem3 = dom.frag(dom("div"), [dom("span"), "hello"], "world");
assert.equal(elem3.childNodes.length, 4);
assert.equal(elem3.childNodes[0].tagName, "DIV");
assert.equal(elem3.childNodes[1].tagName, "SPAN");
assert.equal(elem3.childNodes[2].data, "hello");
assert.equal(elem3.childNodes[3].data, "world");
});
});
describe("inlineable", function() {
it("should return a function suitable for use as dom argument", function() {
var ctx = {a:1}, b = dom('span'), c = {c:1};
var spy = sinon.stub().returns(c);
var inlinable = dom.inlineable(spy);
// When the first argument is a Node, then calling inlineable is the same as calling spy.
inlinable.call(ctx, b, c, 1, "asdf");
sinon.assert.calledOnce(spy);
sinon.assert.calledOn(spy, ctx);
sinon.assert.calledWithExactly(spy, b, c, 1, "asdf");
assert.strictEqual(spy.returnValues[0], c);
spy.reset();
spy.returns(c);
// When the first Node argument is omitted, then the call is deferred. Check that it works
// correctly.
var func = inlinable.call(ctx, c, 1, "asdf");
sinon.assert.notCalled(spy);
assert.equal(typeof func, 'function');
assert.deepEqual(spy.returnValues, []);
let r = func(b);
sinon.assert.calledOnce(spy);
sinon.assert.calledOn(spy, ctx);
sinon.assert.calledWithExactly(spy, b, c, 1, "asdf");
assert.deepEqual(r, c);
assert.strictEqual(spy.returnValues[0], c);
});
});
utils.timing.describe("dom.inlinable", function() {
var elem, spy, inlinableCounter, inlinableSpy, count = 0;
before(function() {
elem = dom('span');
spy = sinon.stub();
inlinableCounter = dom.inlinable(function(elem, a, b) {
count++;
});
inlinableSpy = dom.inlinable(spy);
});
utils.timing.it(25, "should be fast", function() {
utils.repeat(10000, function() {
inlinableCounter(1, "asdf")(elem);
inlinableCounter(1, "asdf")(elem);
inlinableCounter(1, "asdf")(elem);
inlinableCounter(1, "asdf")(elem);
inlinableCounter(1, "asdf")(elem);
});
inlinableSpy()(elem);
inlinableSpy(1)(elem);
inlinableSpy(1, "asdf")(elem);
inlinableSpy(1, "asdf", 56)(elem);
inlinableSpy(1, "asdf", 56, "hello")(elem);
});
after(function() {
assert.equal(count, 50000);
sinon.assert.callCount(spy, 5);
assert.deepEqual(spy.args[0], [elem]);
assert.deepEqual(spy.args[1], [elem, 1]);
assert.deepEqual(spy.args[2], [elem, 1, "asdf"]);
assert.deepEqual(spy.args[3], [elem, 1, "asdf", 56]);
assert.deepEqual(spy.args[4], [elem, 1, "asdf", 56, "hello"]);
});
});
describe("dom.defer", function() {
it("should call supplied function after the current call stack", function() {
var obj = {};
var spy1 = sinon.spy();
var spy2 = sinon.spy();
var div, span;
dom('div',
span = dom('span', dom.defer(spy1, obj)),
div = dom('div', spy2)
);
sinon.assert.calledOnce(spy2);
sinon.assert.calledWithExactly(spy2, div);
sinon.assert.notCalled(spy1);
return Promise.delay(0).then(function() {
sinon.assert.calledOnce(spy2);
sinon.assert.calledOnce(spy1);
assert(spy2.calledBefore(spy1));
sinon.assert.calledOn(spy1, obj);
sinon.assert.calledWithExactly(spy1, span);
});
});
});
describe("dom.onDispose", function() {
it("should call supplied function when an element is cleaned up", function() {
var obj = {};
var spy1 = sinon.spy();
var spy2 = sinon.spy();
var div, span;
div = dom('div',
span = dom('span', dom.onDispose(spy1, obj)),
dom.onDispose(spy2)
);
sinon.assert.notCalled(spy1);
sinon.assert.notCalled(spy2);
ko.virtualElements.emptyNode(div);
sinon.assert.notCalled(spy2);
sinon.assert.calledOnce(spy1);
sinon.assert.calledOn(spy1, obj);
sinon.assert.calledWithExactly(spy1, span);
ko.removeNode(div);
sinon.assert.calledOnce(spy1);
sinon.assert.calledOnce(spy2);
sinon.assert.calledOn(spy2, undefined);
sinon.assert.calledWithExactly(spy2, div);
});
});
describe("dom.autoDispose", function() {
it("should call dispose the supplied value when an element is cleaned up", function() {
var obj = { dispose: sinon.spy() };
var div = dom('div', dom.autoDispose(obj));
ko.cleanNode(div);
sinon.assert.calledOnce(obj.dispose);
sinon.assert.calledOn(obj.dispose, obj);
sinon.assert.calledWithExactly(obj.dispose);
});
});
describe("dom.findLastChild", function() {
it("should return last matching child", function() {
var el = dom('div', dom('div.a.b'), dom('div.b.c'), dom('div.c.d'));
assert.equal(dom.findLastChild(el, '.b').className, 'b c');
assert.equal(dom.findLastChild(el, '.f'), null);
assert.equal(dom.findLastChild(el, '.c.d').className, 'c d');
assert.equal(dom.findLastChild(el, '.b.a').className, 'a b');
function filter(elem) { return elem.classList.length === 2; }
assert.equal(dom.findLastChild(el, filter).className, 'c d');
});
});
describe("dom.findAncestor", function() {
var el1, el2, el3, el4;
before(function() {
el1 = dom('div.foo.bar',
el2 = dom('div.foo',
el3 = dom('div.baz')
),
el4 = dom('div.foo.bar2')
);
});
function assertSameElem(elem1, elem2) {
assert(elem1 === elem2, "Expected " + elem1 + " to be " + elem2);
}
it("should return the child itself if it matches", function() {
assertSameElem(dom.findAncestor(el3, null, '.baz'), el3);
assertSameElem(dom.findAncestor(el3, el3, '.baz'), el3);
});
it("should stop at the nearest match", function() {
assertSameElem(dom.findAncestor(el3, null, '.foo'), el2);
assertSameElem(dom.findAncestor(el3, el1, '.foo'), el2);
assertSameElem(dom.findAncestor(el3, el2, '.foo'), el2);
assertSameElem(dom.findAncestor(el3, el3, '.foo'), null);
});
it("should not go past container", function() {
assertSameElem(dom.findAncestor(el3, null, '.bar'), el1);
assertSameElem(dom.findAncestor(el3, el1, '.bar'), el1);
assertSameElem(dom.findAncestor(el3, el2, '.bar'), null);
assertSameElem(dom.findAncestor(el3, el3, '.bar'), null);
});
it("should fail if child is outside of container", function() {
assertSameElem(dom.findAncestor(el3, el4, '.foo'), null);
assertSameElem(dom.findAncestor(el2, el3, '.foo'), null);
});
it("should return null for no matches", function() {
assertSameElem(dom.findAncestor(el3, null, '.blah'), null);
assertSameElem(dom.findAncestor(el3, el1, '.blah'), null);
assertSameElem(dom.findAncestor(el3, el2, '.blah'), null);
assertSameElem(dom.findAncestor(el3, el3, '.blah'), null);
});
function filter(elem) { return elem.classList.length === 2; }
it("should handle a custom filter function", function() {
assertSameElem(dom.findAncestor(el3, null, filter), el1);
assertSameElem(dom.findAncestor(el3, el1, filter), el1);
assertSameElem(dom.findAncestor(el3, el2, filter), null);
assertSameElem(dom.findAncestor(el3, el3, filter), null);
assertSameElem(dom.findAncestor(el3, el4, filter), null);
});
});
});