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

371 lines
13 KiB
JavaScript

var _ = require('underscore');
var assert = require('assert');
var ko = require('knockout');
var sinon = require('sinon');
var clientUtil = require('../clientUtil');
var koArray = require('app/client/lib/koArray');
describe('koArray', function() {
clientUtil.setTmpMochaGlobals();
it("should emit spliceChange events", function() {
var arr = koArray([1, 2, 3]);
var events = [];
// Whenever we get an event, push it to events.
['change', 'spliceChange'].forEach(function(type) {
arr.subscribe(function(data) {
events.push({ type: type, data: data });
}, null, type);
});
function expectSplice(start, num, deleted, options) {
assert.equal(events.length, 2);
var e = events.shift();
assert.equal(e.type, 'spliceChange');
assert.equal(e.data.start, start);
assert.equal(e.data.added, num);
assert.deepEqual(e.data.deleted, deleted);
e = events.shift();
assert.equal(e.type, 'change');
}
assert.deepEqual(arr.all(), [1, 2, 3]);
// push should work fine.
arr.push("foo");
expectSplice(3, 1, []);
arr.push("bar");
expectSplice(4, 1, []);
assert.deepEqual(arr.all(), [1, 2, 3, "foo", "bar"]);
assert.deepEqual(arr.peek(), [1, 2, 3, "foo", "bar"]);
assert.equal(arr.peekLength, 5);
// insertions via splice should work.
arr.splice(1, 0, "hello", "world");
expectSplice(1, 2, []);
assert.deepEqual(arr.all(), [1, "hello", "world", 2, 3, "foo", "bar"]);
// including using negative indices.
arr.splice(-6, 2, "blah");
expectSplice(1, 1, ["hello", "world"]);
assert.deepEqual(arr.all(), [1, "blah", 2, 3, "foo", "bar"]);
// slice should work but not emit anything.
assert.deepEqual(arr.slice(3, 5), [3, "foo"]);
assert.equal(events.length, 0);
// deletions using splice should work
arr.splice(-2, 1);
expectSplice(4, 0, ["foo"]);
assert.deepEqual(arr.all(), [1, "blah", 2, 3, "bar"]);
// including deletions to the end
arr.splice(1);
expectSplice(1, 0, ["blah", 2, 3, "bar"]);
assert.deepEqual(arr.all(), [1]);
// setting a new array should also produce a splice event.
var newValues = [4, 5, 6];
arr.assign(newValues);
expectSplice(0, 3, [1]);
// Check that koArray does not affect the array passed-in on assignment.
arr.push(7);
expectSplice(3, 1, []);
assert.deepEqual(newValues, [4, 5, 6]);
assert.deepEqual(arr.peek(), [4, 5, 6, 7]);
// We don't support various observableArray() methods. If we do start supporting them, we
// need to make sure they emit correct events.
assert.throws(function() { arr.pop(); }, Error);
assert.throws(function() { arr.remove("b"); }, Error);
});
it("should create dependencies when needed", function() {
var arr = koArray([1, 2, 3]);
var sum = ko.computed(function() {
return arr.all().reduce(function(sum, item) { return sum + item; }, 0);
});
var peekSum = ko.computed(function() {
return arr.peek().reduce(function(sum, item) { return sum + item; }, 0);
});
assert.equal(sum(), 6);
assert.equal(peekSum(), 6);
arr.push(10);
assert.equal(sum(), 16);
assert.equal(peekSum(), 6);
arr.splice(1, 1);
assert.equal(sum(), 14);
assert.equal(peekSum(), 6);
arr.splice(0);
assert.equal(sum(), 0);
assert.equal(peekSum(), 6);
});
describe("#arraySplice", function() {
it("should work similarly to splice", function() {
var arr = koArray([1, 2, 3]);
arr.arraySplice(1, 2, []);
assert.deepEqual(arr.peek(), [1]);
arr.arraySplice(1, 0, [10, 11]);
assert.deepEqual(arr.peek(), [1, 10, 11]);
arr.arraySplice(0, 0, [4, 5]);
assert.deepEqual(arr.peek(), [4, 5, 1, 10, 11]);
});
});
describe("#makeLiveIndex", function() {
it("should be kept valid", function() {
var arr = koArray([1, 2, 3]);
var index = arr.makeLiveIndex();
assert.equal(index(), 0);
index(-1);
assert.equal(index(), 0);
index(null);
assert.equal(index(), 0);
index(100);
assert.equal(index(), 2);
arr.splice(1, 1);
assert.deepEqual(arr.peek(), [1, 3]);
assert.equal(index(), 1);
arr.splice(0, 1, 5, 6, 7);
assert.deepEqual(arr.peek(), [5, 6, 7, 3]);
assert.equal(index(), 3);
arr.push(10);
arr.splice(2, 2);
assert.deepEqual(arr.peek(), [5, 6, 10]);
assert.equal(index(), 2);
arr.splice(2, 1);
assert.deepEqual(arr.peek(), [5, 6]);
assert.equal(index(), 1);
arr.splice(0, 2);
assert.deepEqual(arr.peek(), []);
assert.equal(index(), null);
arr.splice(0, 0, 1, 2, 3);
assert.deepEqual(arr.peek(), [1, 2, 3]);
assert.equal(index(), 0);
});
});
describe("#map", function() {
it("should map immediately and continuously", function() {
var arr = koArray([1, 2, 3]);
var mapped = arr.map(function(orig) { return orig * 10; });
assert.deepEqual(mapped.peek(), [10, 20, 30]);
arr.push(4);
assert.deepEqual(mapped.peek(), [10, 20, 30, 40]);
arr.splice(1, 1);
assert.deepEqual(mapped.peek(), [10, 30, 40]);
arr.splice(0, 1, 5, 6, 7);
assert.deepEqual(mapped.peek(), [50, 60, 70, 30, 40]);
arr.splice(2, 0, 2);
assert.deepEqual(mapped.peek(), [50, 60, 20, 70, 30, 40]);
arr.splice(1, 3);
assert.deepEqual(mapped.peek(), [50, 30, 40]);
arr.splice(0, 0, 1, 2, 3);
assert.deepEqual(mapped.peek(), [10, 20, 30, 50, 30, 40]);
arr.splice(3, 3);
assert.deepEqual(mapped.peek(), [10, 20, 30]);
// Check that `this` argument works correctly.
var foo = { test: function(orig) { return orig * 100; } };
var mapped2 = arr.map(function(orig) { return this.test(orig); }, foo);
assert.deepEqual(mapped2.peek(), [100, 200, 300]);
arr.splice(1, 0, 4, 5);
assert.deepEqual(mapped2.peek(), [100, 400, 500, 200, 300]);
});
});
describe("#syncMap", function() {
it("should keep two arrays in sync", function() {
var arr1 = koArray([1, 2, 3]);
var arr2 = koArray([4, 5, 6]);
var mapped = koArray();
mapped.syncMap(arr1);
assert.deepEqual(mapped.peek(), [1, 2, 3]);
arr1.splice(1, 1, 8, 9);
assert.deepEqual(mapped.peek(), [1, 8, 9, 3]);
mapped.syncMap(arr2, function(x) { return x * 10; });
assert.deepEqual(mapped.peek(), [40, 50, 60]);
arr1.splice(1, 1, 8, 9);
assert.deepEqual(mapped.peek(), [40, 50, 60]);
arr2.push(8, 9);
assert.deepEqual(mapped.peek(), [40, 50, 60, 80, 90]);
});
});
describe('#subscribeForEach', function() {
it('should call onAdd and onRemove callbacks', function() {
var arr1 = koArray([1, 2, 3]);
var seen = [];
function onAdd(x) { seen.push(["add", x]); }
function onRm(x) { seen.push(["rm", x]); }
var sub = arr1.subscribeForEach({ add: onAdd, remove: onRm });
assert.deepEqual(seen, [["add", 1], ["add", 2], ["add", 3]]);
seen = [];
arr1.push(4);
assert.deepEqual(seen, [["add", 4]]);
seen = [];
arr1.splice(1, 2);
assert.deepEqual(seen, [["rm", 2], ["rm", 3]]);
seen = [];
arr1.splice(0, 1, 5, 6);
assert.deepEqual(seen, [["rm", 1], ["add", 5], ["add", 6]]);
// If subscription is disposed, callbacks should no longer get called.
sub.dispose();
seen = [];
arr1.push(10);
assert.deepEqual(seen, []);
});
});
describe('#setAutoDisposeValues', function() {
it('should dispose elements when asked', function() {
var objects = _.range(5).map(function(n) { return { value: n, dispose: sinon.spy() }; });
var arr = koArray(objects.slice(0, 3)).setAutoDisposeValues();
// Just to check what's in the array to start with.
assert.equal(arr.all().length, 3);
assert.strictEqual(arr.at(0), objects[0]);
// Delete two elements: they should get disposed, but the remaining one should not.
var x = arr.splice(0, 2);
assert.equal(arr.all().length, 1);
assert.strictEqual(arr.at(0), objects[2]);
assert.equal(x.length, 2);
sinon.assert.calledOnce(x[0].dispose);
sinon.assert.calledOnce(x[1].dispose);
sinon.assert.notCalled(objects[2].dispose);
// Reassign: the remaining element should now also get disposed.
arr.assign(objects.slice(3, 5));
assert.equal(arr.all().length, 2);
assert.strictEqual(arr.at(0), objects[3]);
sinon.assert.calledOnce(objects[2].dispose);
sinon.assert.notCalled(objects[3].dispose);
sinon.assert.notCalled(objects[4].dispose);
// Dispose the entire array: previously assigned elements should be disposed.
arr.dispose();
sinon.assert.calledOnce(objects[3].dispose);
sinon.assert.calledOnce(objects[4].dispose);
// Check that elements disposed earlier haven't been disposed more than once.
sinon.assert.calledOnce(objects[0].dispose);
sinon.assert.calledOnce(objects[1].dispose);
sinon.assert.calledOnce(objects[2].dispose);
});
});
describe('syncedKoArray', function() {
it("should return array synced to the value of the observable", function() {
var arr1 = koArray(["1", "2", "3"]);
var arr2 = koArray(["foo", "bar"]);
var arr3 = ["hello", "world"];
var obs = ko.observable(arr1);
var combined = koArray.syncedKoArray(obs);
// The values match the array returned by the observable, but mapped using wrap().
assert.deepEqual(combined.all(), ["1", "2", "3"]);
// Changes to the array changes the synced array.
arr1.push("4");
assert.deepEqual(combined.all(), ["1", "2", "3", "4"]);
// Changing the observable changes the synced array; the value may be a plain array.
obs(arr3);
assert.deepEqual(combined.all(), ["hello", "world"]);
// Previously mapped observable array no longer affects the combined one. And of course
// modifying the non-observable array makes no difference either.
arr1.push("4");
arr3.splice(0, 1);
arr3.push("qwer");
assert.deepEqual(combined.all(), ["hello", "world"]);
// Test assigning again to a koArray.
obs(arr2);
assert.deepEqual(combined.all(), ["foo", "bar"]);
arr2.splice(0, 1);
assert.deepEqual(combined.all(), ["bar"]);
arr2.splice(0, 0, "this", "is", "a", "test");
assert.deepEqual(combined.all(), ["this", "is", "a", "test", "bar"]);
arr2.assign(["10", "20"]);
assert.deepEqual(combined.all(), ["10", "20"]);
// Check that only arr2 has a subscriber (not arr1), and that disposing unsubscribes from
// both the observable and the currently active array.
assert.equal(arr1.getObservable().getSubscriptionsCount(), 1);
assert.equal(arr2.getObservable().getSubscriptionsCount(), 2);
assert.equal(obs.getSubscriptionsCount(), 1);
combined.dispose();
assert.equal(obs.getSubscriptionsCount(), 0);
assert.equal(arr2.getObservable().getSubscriptionsCount(), 1);
});
it("should work with a mapper callback", function() {
var arr1 = koArray(["1", "2", "3"]);
var obs = ko.observable();
function wrap(value) { return "x" + value; }
var combined = koArray.syncedKoArray(obs, wrap);
assert.deepEqual(combined.all(), []);
obs(arr1);
assert.deepEqual(combined.all(), ["x1", "x2", "x3"]);
arr1.push("4");
assert.deepEqual(combined.all(), ["x1", "x2", "x3", "x4"]);
obs(["foo", "bar"]);
assert.deepEqual(combined.all(), ["xfoo", "xbar"]);
arr1.splice(1, 1);
obs(arr1);
arr1.splice(1, 1);
assert.deepEqual(combined.all(), ["x1", "x4"]);
});
});
describe("syncedMap", function() {
it("should associate state with each item and dispose it", function() {
var arr = koArray(["1", "2", "3"]);
var constructSpy = sinon.spy(), disposeSpy = sinon.spy();
var map = koArray.syncedMap(arr, (state, val) => {
constructSpy(val);
state.autoDisposeCallback(() => disposeSpy(val));
});
assert.deepEqual(constructSpy.args, [["1"], ["2"], ["3"]]);
assert.deepEqual(disposeSpy.args, []);
arr.splice(1, 0, "4", "5");
assert.deepEqual(arr.peek(), ["1", "4", "5", "2", "3"]);
assert.deepEqual(constructSpy.args, [["1"], ["2"], ["3"], ["4"], ["5"]]);
assert.deepEqual(disposeSpy.args, []);
arr.splice(0, 2);
assert.deepEqual(constructSpy.args, [["1"], ["2"], ["3"], ["4"], ["5"]]);
assert.deepEqual(disposeSpy.args, [["1"], ["4"]]);
map.dispose();
assert.deepEqual(constructSpy.args, [["1"], ["2"], ["3"], ["4"], ["5"]]);
assert.deepEqual(disposeSpy.args, [["1"], ["4"], ["2"], ["3"], ["5"]]);
});
});
});