mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
215 lines
7.1 KiB
JavaScript
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);
|
||
|
});
|
||
|
});
|
||
|
});
|