gristlabs_grist-core/test/client/models/modelUtil.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

215 lines
7.1 KiB
JavaScript

var assert = require('assert');
var ko = require('knockout');
var modelUtil = require('app/client/models/modelUtil');
var sinon = require('sinon');
describe('modelUtil', function() {
describe("fieldWithDefault", function() {
it("should be an observable with a default", function() {
var foo = modelUtil.createField('foo');
var bar = modelUtil.fieldWithDefault(foo, 'defaultValue');
assert.equal(bar(), 'defaultValue');
foo('test');
assert.equal(bar(), 'test');
bar('hello');
assert.equal(bar(), 'hello');
assert.equal(foo(), 'hello');
foo('');
assert.equal(bar(), 'defaultValue');
assert.equal(foo(), '');
});
it("should exhibit specific behavior when used as a jsonObservable", function() {
var custom = modelUtil.createField('custom');
var common = ko.observable('{"foo": 2, "bar": 3}');
var combined = modelUtil.fieldWithDefault(custom, function() { return common(); });
combined = modelUtil.jsonObservable(combined);
assert.deepEqual(combined(), {"foo": 2, "bar": 3});
// Once the custom object is defined, the common object is not read.
combined({"foo": 20});
assert.deepEqual(combined(), {"foo": 20});
// Setting the custom object to be undefined should make read return the common object again.
combined(undefined);
assert.deepEqual(combined(), {"foo": 2, "bar": 3});
// Setting a property with an undefined custom object should initially copy all defaults from common.
combined(undefined);
combined.prop('foo')(50);
assert.deepEqual(combined(), {"foo": 50, "bar": 3});
// Once the custom object is defined, changes to common should not affect the combined read value.
common('{"bar": 60}');
combined.prop('foo')(70);
assert.deepEqual(combined(), {"foo": 70, "bar": 3});
});
});
describe("jsonObservable", function() {
it("should auto parse and stringify", function() {
var str = ko.observable();
var obj = modelUtil.jsonObservable(str);
assert.deepEqual(obj(), {});
str('{"foo": 1, "bar": "baz"}');
assert.deepEqual(obj(), {foo: 1, bar: "baz"});
obj({foo: 2, baz: "bar"});
assert.equal(str(), '{"foo":2,"baz":"bar"}');
obj.update({foo: 17, bar: null});
assert.equal(str(), '{"foo":17,"baz":"bar","bar":null}');
});
it("should support saving", function() {
var str = ko.observable('{"foo": 1, "bar": "baz"}');
var saved = null;
str.saveOnly = function(value) { saved = value; };
var obj = modelUtil.jsonObservable(str);
obj.saveOnly({foo: 2});
assert.equal(saved, '{"foo":2}');
assert.equal(str(), '{"foo": 1, "bar": "baz"}');
assert.deepEqual(obj(), {"foo": 1, "bar": "baz"});
obj.update({"hello": "world"});
obj.save();
assert.equal(saved, '{"foo":1,"bar":"baz","hello":"world"}');
assert.equal(str(), '{"foo":1,"bar":"baz","hello":"world"}');
assert.deepEqual(obj(), {"foo":1, "bar":"baz", "hello":"world"});
obj.setAndSave({"hello": "world"});
assert.equal(saved, '{"hello":"world"}');
assert.equal(str(), '{"hello":"world"}');
assert.deepEqual(obj(), {"hello":"world"});
});
it("should support property observables", function() {
var str = ko.observable('{"foo": 1, "bar": "baz"}');
var saved = null;
str.saveOnly = function(value) { saved = value; };
var obj = modelUtil.jsonObservable(str);
var foo = obj.prop("foo"), hello = obj.prop("hello");
assert.equal(foo(), 1);
assert.equal(hello(), undefined);
obj.update({"foo": 17});
assert.equal(foo(), 17);
assert.equal(hello(), undefined);
foo(18);
assert.equal(str(), '{"foo":18,"bar":"baz"}');
hello("world");
assert.equal(saved, null);
assert.equal(str(), '{"foo":18,"bar":"baz","hello":"world"}');
assert.deepEqual(obj(), {"foo":18, "bar":"baz", "hello":"world"});
foo.setAndSave(20);
assert.equal(saved, '{"foo":20,"bar":"baz","hello":"world"}');
assert.equal(str(), '{"foo":20,"bar":"baz","hello":"world"}');
assert.deepEqual(obj(), {"foo":20, "bar":"baz", "hello":"world"});
});
});
describe("objObservable", function() {
it("should support property observables", function() {
var objObs = ko.observable({"foo": 1, "bar": "baz"});
var obj = modelUtil.objObservable(objObs);
var foo = obj.prop("foo"), hello = obj.prop("hello");
assert.equal(foo(), 1);
assert.equal(hello(), undefined);
obj.update({"foo": 17});
assert.equal(foo(), 17);
assert.equal(hello(), undefined);
foo(18);
hello("world");
assert.deepEqual(obj(), {"foo":18, "bar":"baz", "hello":"world"});
});
});
it("should support customComputed", function() {
var obs = ko.observable("hello");
var spy = sinon.spy();
var cs = modelUtil.customComputed({
read: () => obs(),
save: (val) => spy(val)
});
// Check that customComputed auto-updates when the underlying value changes.
assert.equal(cs(), "hello");
assert.equal(cs.isSaved(), true);
obs("world2");
assert.equal(cs(), "world2");
assert.equal(cs.isSaved(), true);
// Check that it can be set to something else, and will stop auto-updating.
cs("foo");
assert.equal(cs(), "foo");
assert.equal(cs.isSaved(), false);
obs("world");
assert.equal(cs(), "foo");
assert.equal(cs.isSaved(), false);
// Check that revert works.
cs.revert();
assert.equal(cs(), "world");
assert.equal(cs.isSaved(), true);
// Check that setting to the underlying value is same as revert.
cs("foo");
assert.equal(cs.isSaved(), false);
cs("world");
assert.equal(cs.isSaved(), true);
// Check that save calls the save function.
cs("foo");
assert.equal(cs(), "foo");
assert.equal(cs.isSaved(), false);
return cs.save()
.then(() => {
sinon.assert.calledOnce(spy);
sinon.assert.calledWithExactly(spy, "foo");
// Once saved, the observable should revert.
assert.equal(cs(), "world");
assert.equal(cs.isSaved(), true);
spy.resetHistory();
// Check that saveOnly works similarly to save().
return cs.saveOnly("foo2");
})
.then(() => {
sinon.assert.calledOnce(spy);
sinon.assert.calledWithExactly(spy, "foo2");
assert.equal(cs(), "world");
assert.equal(cs.isSaved(), true);
spy.resetHistory();
// Check that saving the underlying value does NOT call save().
return cs.saveOnly("world");
})
.then(() => {
sinon.assert.notCalled(spy);
assert.equal(cs(), "world");
assert.equal(cs.isSaved(), true);
spy.resetHistory();
return cs.saveOnly("bar");
})
.then(() => {
assert.equal(cs(), "world");
assert.equal(cs.isSaved(), true);
sinon.assert.calledOnce(spy);
sinon.assert.calledWithExactly(spy, "bar");
// If save() updated the underlying value, the customComputed should see it.
obs("bar");
assert.equal(cs(), "bar");
assert.equal(cs.isSaved(), true);
});
});
});