gristlabs_grist-core/test/client/models/modelUtil.js

215 lines
7.1 KiB
JavaScript
Raw Normal View History

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);
});
});
});