@platform/state
Version:
A small, simple, strongly typed, [rx/observable] state-machine.
458 lines (457 loc) • 25.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var immer_1 = require("immer");
var rxjs_1 = require("rxjs");
var _1 = require(".");
var test_1 = require("../test");
var StateObject_1 = require("./StateObject");
describe('StateObject', function () {
describe('lifecycle', function () {
it('create: store initial state', function () {
var initial = { count: 1 };
var obj = _1.StateObject.create(initial);
(0, test_1.expect)(obj.state).to.eql(initial);
(0, test_1.expect)(obj.original).to.eql(initial);
(0, test_1.expect)(obj.state).to.not.equal(initial);
(0, test_1.expect)(obj.original).to.not.equal(initial);
});
it('create: readonly version', function () {
var obj = _1.StateObject.create({ count: 0 });
var readonly = obj.readonly;
(0, test_1.expect)(readonly).to.be.an.instanceof(StateObject_1.StateObject);
(0, test_1.expect)(readonly).to.equal(obj);
(0, test_1.expect)(_1.StateObject.readonly(obj)).to.be.an.instanceof(StateObject_1.StateObject);
(0, test_1.expect)(_1.StateObject.readonly(readonly)).to.be.an.instanceof(StateObject_1.StateObject);
(0, test_1.expect)(_1.StateObject.readonly(obj)).to.equal(obj);
(0, test_1.expect)(_1.StateObject.readonly(readonly)).to.equal(obj);
});
it('dispose', function () {
var obj = _1.StateObject.create({ count: 1 });
var count = 0;
obj.dispose$.subscribe(function (e) { return count++; });
(0, test_1.expect)(obj.isDisposed).to.eql(false);
obj.dispose();
obj.dispose();
obj.dispose();
(0, test_1.expect)(obj.isDisposed).to.eql(true);
(0, test_1.expect)(count).to.eql(1);
});
it('dispose: events cease firing', function () {
var obj = _1.StateObject.create({ count: 1 });
var fired = [];
obj.event.$.subscribe(function (e) { return fired.push(e); });
obj.change(function (draft) { return (draft.message = 'hello'); });
(0, test_1.expect)(fired.length).to.eql(2);
obj.dispose();
obj.dispose();
obj.dispose();
(0, test_1.expect)(obj.isDisposed).to.eql(true);
(0, test_1.expect)(fired.length).to.eql(3);
var event = fired[2];
(0, test_1.expect)(event.type).to.eql('StateObject/disposed');
(0, test_1.expect)(event.payload.original).to.eql({ count: 1 });
(0, test_1.expect)(event.payload.final).to.eql({ count: 1, message: 'hello' });
obj.change(function (draft) { return (draft.message = 'hello'); });
(0, test_1.expect)(fired.length).to.eql(3);
});
});
describe('static', function () {
describe('toObject', function () {
it('object', function () {
var initial = { count: 0 };
var obj = _1.StateObject.create(initial);
var original;
obj.change(function (draft) {
draft.count = 123;
(0, test_1.expect)(draft.count).to.eql(123);
original = _1.StateObject.toObject(draft);
});
(0, test_1.expect)((0, immer_1.isDraft)(original)).to.eql(false);
(0, test_1.expect)(original === null || original === void 0 ? void 0 : original.count).to.eql(0);
(0, test_1.expect)(obj.state.count).to.eql(123);
});
it('array', function () {
var initial = { count: 0, items: [] };
var obj = _1.StateObject.create(initial);
var list;
obj.change(function (draft) {
draft.items = [{ id: 1 }, { items: [[{ msg: 'hello' }]] }];
});
obj.change(function (draft) {
var items = draft.items || [];
(0, test_1.expect)(items.length).to.eql(2);
(0, test_1.expect)((0, immer_1.isDraft)(items[0])).to.eql(true);
(0, test_1.expect)((0, immer_1.isDraft)(items[1])).to.eql(true);
(0, test_1.expect)(draft.items).to.eql([{ id: 1 }, { items: [[{ msg: 'hello' }]] }]);
list = _1.StateObject.toObject(draft.items);
});
(0, test_1.expect)(list).to.eql([{ id: 1 }, { items: [[{ msg: 'hello' }]] }]);
(0, test_1.expect)((0, immer_1.isDraft)(list[0])).to.eql(false);
(0, test_1.expect)((0, immer_1.isDraft)(list[1])).to.eql(false);
});
it('undefined', function () {
var initial = { count: 0 };
var obj = _1.StateObject.create(initial);
var list;
obj.change(function (draft) {
list = _1.StateObject.toObject(draft.items);
});
(0, test_1.expect)(list).to.eql(undefined);
});
});
it('isStateObject', function () {
var test = function (input, expected) {
var res = _1.StateObject.isStateObject(input);
(0, test_1.expect)(res).to.eql(expected);
};
test(undefined, false);
test(null, false);
test('', false);
test(123, false);
test(true, false);
test({}, false);
var obj = _1.StateObject.create({ count: 0 });
test(obj, true);
});
it('isProxy', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
var test, obj;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
test = function (input, expected) {
var res = _1.StateObject.isProxy(input);
(0, test_1.expect)(res).to.eql(expected);
};
obj = _1.StateObject.create({ count: 0 });
test(undefined, false);
test(null, false);
test('', false);
test(123, false);
test(true, false);
test({}, false);
test(obj, false);
obj.change(function (draft) { return test(draft, true); });
return [4, obj.changeAsync(function (draft) { return tslib_1.__awaiter(void 0, void 0, void 0, function () { return tslib_1.__generator(this, function (_a) {
return [2, test(draft, true)];
}); }); })];
case 1:
_a.sent();
return [2];
}
});
}); });
});
describe('change', function () {
it('sync/change: update (via function)', function () {
var _a, _b, _c, _d;
var initial = { count: 1 };
var obj = _1.StateObject.create(initial);
(0, test_1.expect)(obj.state).to.equal(obj.original);
var res1 = obj.change(function (draft) {
draft.count += 2;
draft.message = 'hello';
});
var res2 = obj.change(function (draft) { return (draft.count += 1); });
(0, test_1.expect)(res1.op).to.eql('update');
(0, test_1.expect)(res1.cid.length).to.greaterThan(4);
(0, test_1.expect)(res2.cid.length).to.greaterThan(4);
(0, test_1.expect)(res1.cid).to.not.eql(res2.cid);
(0, test_1.expect)(obj.state).to.eql({ count: 4, message: 'hello' });
(0, test_1.expect)(obj.state).to.not.equal(obj.original);
(0, test_1.expect)((_a = res1.changed) === null || _a === void 0 ? void 0 : _a.from).to.eql({ count: 1 });
(0, test_1.expect)((_b = res1.changed) === null || _b === void 0 ? void 0 : _b.to).to.eql({ count: 3, message: 'hello' });
(0, test_1.expect)(res1.cancelled).to.eql(undefined);
(0, test_1.expect)(res2.op).to.eql('update');
(0, test_1.expect)((_c = res2.changed) === null || _c === void 0 ? void 0 : _c.from).to.eql({ count: 3, message: 'hello' });
(0, test_1.expect)((_d = res2.changed) === null || _d === void 0 ? void 0 : _d.to).to.eql({ count: 4, message: 'hello' });
(0, test_1.expect)(res2.cancelled).to.eql(undefined);
(0, test_1.expect)(obj.original).to.eql(initial);
});
it('sync/change: replace (via {object} value)', function () {
var _a, _b, _c, _d;
var initial = { count: 1 };
var obj = _1.StateObject.create(initial);
(0, test_1.expect)(obj.state).to.equal(obj.original);
var res1 = obj.change({ count: 2, message: 'hello' });
(0, test_1.expect)(res1.op).to.eql('replace');
(0, test_1.expect)((_a = res1.changed) === null || _a === void 0 ? void 0 : _a.from).to.eql({ count: 1 });
(0, test_1.expect)((_b = res1.changed) === null || _b === void 0 ? void 0 : _b.to).to.eql({ count: 2, message: 'hello' });
(0, test_1.expect)(obj.state).to.eql({ count: 2, message: 'hello' });
var res2 = obj.change({ count: 3 });
(0, test_1.expect)(res2.op).to.eql('replace');
(0, test_1.expect)((_c = res2.changed) === null || _c === void 0 ? void 0 : _c.from).to.eql({ count: 2, message: 'hello' });
(0, test_1.expect)((_d = res2.changed) === null || _d === void 0 ? void 0 : _d.to).to.eql({ count: 3 });
(0, test_1.expect)(obj.state).to.eql({ count: 3 });
(0, test_1.expect)(obj.original).to.eql(initial);
});
it('sync/change: disconnected method function can be passed around (bound)', function () {
var initial = { count: 1 };
var obj = _1.StateObject.create(initial);
var change = obj.change;
var increment = function () { return change(function (draft) { return draft.count++; }); };
(0, test_1.expect)(obj.state.count).to.eql(1);
change(function (draft) { return (draft.count -= 1); });
(0, test_1.expect)(obj.state.count).to.eql(0);
change({ count: 99 });
(0, test_1.expect)(obj.state.count).to.eql(99);
increment();
increment();
(0, test_1.expect)(obj.state.count).to.eql(101);
(0, test_1.expect)(obj.original).to.eql(initial);
});
it('change (async)', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
var initial, obj, changeAsync, res;
var _a, _b;
return tslib_1.__generator(this, function (_c) {
switch (_c.label) {
case 0:
initial = { count: 1 };
obj = _1.StateObject.create(initial);
changeAsync = obj.changeAsync;
return [4, changeAsync(function (draft) { return tslib_1.__awaiter(void 0, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4, test_1.time.wait(10)];
case 1:
_a.sent();
draft.count++;
return [2, 'NO EFFECT'];
}
});
}); })];
case 1:
res = _c.sent();
(0, test_1.expect)(res.op).to.eql('update');
(0, test_1.expect)((_a = res.changed) === null || _a === void 0 ? void 0 : _a.from).to.eql({ count: 1 });
(0, test_1.expect)((_b = res.changed) === null || _b === void 0 ? void 0 : _b.to).to.eql({ count: 2 });
(0, test_1.expect)(res.patches.prev[0]).to.eql({ op: 'replace', path: 'count', value: 1 });
(0, test_1.expect)(res.patches.next[0]).to.eql({ op: 'replace', path: 'count', value: 2 });
return [2];
}
});
}); });
it('no change (does not fire events)', function () {
var initial = { count: 0 };
var obj = _1.StateObject.create(initial);
var changing = 0;
var changed = 0;
obj.event.changing$.subscribe(function (e) { return changing++; });
obj.event.changed$.subscribe(function (e) { return changed++; });
var test = function (changer) {
var res = obj.change(changer);
(0, test_1.expect)(changing).to.eql(0);
(0, test_1.expect)(changed).to.eql(0);
(0, test_1.expect)(obj.state).to.eql(initial);
(0, test_1.expect)(res.changed).to.eql(undefined);
(0, test_1.expect)(res.cancelled).to.eql(undefined);
};
test(function (draft) { return undefined; });
test(function (draft) { return (draft.count = 0); });
});
it('throw: when property name contains "/"', function () {
var obj = _1.StateObject.create({});
var fn = function () { return obj.change(function (draft) { return (draft['foo/bar'] = 123); }); };
(0, test_1.expect)(fn).to.throw(/Property names cannot contain the "\/" character/);
});
});
describe('patches', function () {
it('no change', function () {
var obj = _1.StateObject.create({ count: 1 });
var res = obj.change(function (draft) { return undefined; });
(0, test_1.expect)(obj.state.count).to.eql(1);
(0, test_1.expect)(res.patches.next).to.eql([]);
(0, test_1.expect)(res.patches.prev).to.eql([]);
});
it('update', function () {
var _a, _b;
var initial = { foo: { count: 1 } };
var obj = _1.StateObject.create(initial);
var res = obj.change(function (draft) {
draft.foo.count++;
draft.foo.message = 'hello';
draft.bar = { count: 9 };
return 123;
});
(0, test_1.expect)(res.op).to.eql('update');
(0, test_1.expect)((_a = res.changed) === null || _a === void 0 ? void 0 : _a.to.foo.count).to.eql(2);
(0, test_1.expect)((_b = res.changed) === null || _b === void 0 ? void 0 : _b.to.foo.message).to.eql('hello');
var _c = res.patches, next = _c.next, prev = _c.prev;
(0, test_1.expect)(next.length).to.eql(3);
(0, test_1.expect)(prev.length).to.eql(3);
(0, test_1.expect)(prev[0]).to.eql({ op: 'replace', path: 'foo/count', value: 1 });
(0, test_1.expect)(prev[1]).to.eql({ op: 'remove', path: 'foo/message' });
(0, test_1.expect)(prev[2]).to.eql({ op: 'remove', path: 'bar' });
(0, test_1.expect)(next[0]).to.eql({ op: 'replace', path: 'foo/count', value: 2 });
(0, test_1.expect)(next[1]).to.eql({ op: 'add', path: 'foo/message', value: 'hello' });
(0, test_1.expect)(next[2]).to.eql({ op: 'add', path: 'bar', value: { count: 9 } });
});
it('replace', function () {
var obj = _1.StateObject.create({ count: 0 });
var res = obj.change({ count: 888 });
(0, test_1.expect)(res.op).to.eql('replace');
obj.change({ count: 5, message: 'hello' });
var _a = res.patches, next = _a.next, prev = _a.prev;
(0, test_1.expect)(next.length).to.eql(1);
(0, test_1.expect)(prev.length).to.eql(1);
(0, test_1.expect)(prev[0]).to.eql({ op: 'replace', path: '', value: { count: 0 } });
(0, test_1.expect)(next[0]).to.eql({ op: 'replace', path: '', value: { count: 888 } });
});
});
describe('events', function () {
it('event: changing', function () {
var initial = { count: 1 };
var obj = _1.StateObject.create(initial);
var events = [];
var changing = [];
obj.event.$.subscribe(function (e) { return events.push(e); });
obj.event.changing$.subscribe(function (e) { return changing.push(e); });
var res = obj.change(function (draft) { return (draft.count += 1); });
(0, test_1.expect)(obj.state.count).to.eql(2);
(0, test_1.expect)(events.length).to.eql(2);
(0, test_1.expect)(changing.length).to.eql(1);
(0, test_1.expect)(events[0].type).to.eql('StateObject/changing');
(0, test_1.expect)(events[1].type).to.eql('StateObject/changed');
var event = changing[0];
(0, test_1.expect)(event.op).to.eql('update');
(0, test_1.expect)(event.cancelled).to.eql(false);
(0, test_1.expect)(event.from).to.eql(initial);
(0, test_1.expect)(event.to).to.eql({ count: 2 });
(0, test_1.expect)(event.patches).to.eql(res.patches);
});
it('event: changing (cancelled)', function () {
var initial = { count: 1 };
var obj = _1.StateObject.create(initial);
var cancelled = [];
var changing = [];
var changed = [];
obj.event.cancelled$.subscribe(function (e) { return cancelled.push(e); });
obj.event.changed$.subscribe(function (e) { return changed.push(e); });
obj.event.changing$.subscribe(function (e) {
changing.push(e);
e.cancel();
});
obj.change(function (draft) { return (draft.count += 1); });
(0, test_1.expect)(changing.length).to.eql(1);
(0, test_1.expect)(cancelled.length).to.eql(1);
(0, test_1.expect)(changed.length).to.eql(0);
obj.change({ count: 2 });
(0, test_1.expect)(changing.length).to.eql(2);
(0, test_1.expect)(cancelled.length).to.eql(2);
(0, test_1.expect)(changed.length).to.eql(0);
(0, test_1.expect)(obj.state).to.eql(initial);
(0, test_1.expect)(obj.state).to.equal(obj.original);
});
it('event: changed', function () {
var initial = { count: 1 };
var obj = _1.StateObject.create(initial);
var events = [];
var changing = [];
var changed = [];
obj.event.$.subscribe(function (e) { return events.push(e); });
obj.event.changing$.subscribe(function (e) { return changing.push(e); });
obj.event.changed$.subscribe(function (e) { return changed.push(e); });
var res = obj.change(function (draft) { return draft.count++; });
(0, test_1.expect)(events.length).to.eql(2);
(0, test_1.expect)(changed.length).to.eql(1);
var event = changed[0];
(0, test_1.expect)(event.op).to.eql('update');
(0, test_1.expect)(event.from).to.eql(initial);
(0, test_1.expect)(event.to).to.eql({ count: 2 });
(0, test_1.expect)(event.to).to.equal(obj.state);
(0, test_1.expect)(event.patches).to.eql(res.patches);
(0, test_1.expect)(changing[0].cid).to.eql(changed[0].cid);
});
it('event: changing/changed (via "replace" operation)', function () {
var obj = _1.StateObject.create({ count: 1 });
var changing = [];
var changed = [];
obj.event.changing$.subscribe(function (e) { return changing.push(e); });
obj.event.changed$.subscribe(function (e) { return changed.push(e); });
obj.change({ count: 888 });
(0, test_1.expect)(changing.length).to.eql(1);
(0, test_1.expect)(changed.length).to.eql(1);
(0, test_1.expect)(changing[0].op).to.eql('replace');
(0, test_1.expect)(changed[0].op).to.eql('replace');
(0, test_1.expect)(changing[0].patches).to.eql(changed[0].patches);
var patches = changed[0].patches;
(0, test_1.expect)(patches.prev[0]).to.eql({ op: 'replace', path: '', value: { count: 1 } });
(0, test_1.expect)(patches.next[0]).to.eql({ op: 'replace', path: '', value: { count: 888 } });
});
it('event: changedPatches', function () {
var obj = _1.StateObject.create({ count: 1 });
var patches = [];
obj.event.patched$.subscribe(function (e) { return patches.push(e); });
obj.change({ count: 888 });
(0, test_1.expect)(patches.length).to.eql(1);
var e = patches[0];
(0, test_1.expect)(e.op).to.eql('replace');
(0, test_1.expect)(e.prev[0]).to.eql({ op: 'replace', path: '', value: { count: 1 } });
(0, test_1.expect)(e.next[0]).to.eql({ op: 'replace', path: '', value: { count: 888 } });
});
});
describe('combine', function () {
it('create: from initial {object} values', function () {
var initial = { foo: { count: 0 }, bar: {} };
var combined = _1.StateObject.combine(initial);
(0, test_1.expect)(combined.store.state).to.eql(combined.state);
(0, test_1.expect)(combined.state).to.eql(initial);
});
it('create: from initial {state-object} values', function () {
var foo = _1.StateObject.create({ count: 123 });
var bar = _1.StateObject.create({ isEnabled: true });
var combined = _1.StateObject.combine({ foo: foo, bar: bar });
(0, test_1.expect)(combined.state.foo).to.eql({ count: 123 });
(0, test_1.expect)(combined.state.bar).to.eql({ isEnabled: true });
foo.change(function (draft) { return draft.count++; });
(0, test_1.expect)(combined.state.foo.count).to.eql(124);
});
it('exposes [changed$] events', function () {
var combined = _1.StateObject.combine({ foo: { count: 0 }, bar: {} });
(0, test_1.expect)(combined.changed$).to.equal(combined.store.event.changed$);
});
it('add: sync values', function () {
var initial = { foo: { count: 0 }, bar: {} };
var bar = _1.StateObject.create({ isEnabled: true });
var combined = _1.StateObject.combine(initial);
(0, test_1.expect)(combined.state.bar.isEnabled).to.eql(undefined);
combined.add('bar', bar);
(0, test_1.expect)(combined.store.state).to.eql(combined.state);
(0, test_1.expect)(combined.state.bar.isEnabled).to.eql(true);
});
it('change: sync values', function () {
var initial = { foo: { count: 0 }, bar: {} };
var foo = _1.StateObject.create({ count: 1 });
var bar = _1.StateObject.create({});
var combined = _1.StateObject.combine(initial).add('bar', bar).add('foo', foo);
(0, test_1.expect)(combined.state).to.eql({ foo: { count: 1 }, bar: {} });
foo.change(function (draft) {
draft.count = 123;
draft.message = 'hello';
});
bar.change({ isEnabled: true });
(0, test_1.expect)(combined.state.foo).to.eql({ count: 123, message: 'hello' });
(0, test_1.expect)(combined.state.bar).to.eql({ isEnabled: true });
});
it('dispose$ (param)', function () {
var dispose$ = new rxjs_1.Subject();
var combined = _1.StateObject.combine({ foo: { count: 0 }, bar: {} }, dispose$);
(0, test_1.expect)(combined.store.isDisposed).to.eql(false);
dispose$.next();
(0, test_1.expect)(combined.store.isDisposed).to.eql(true);
});
it('stop syncing on [store.dispose]', function () {
var initial = { foo: { count: 0 }, bar: {} };
var foo = _1.StateObject.create({ count: 1 });
var bar = _1.StateObject.create({});
var combined = _1.StateObject.combine(initial).add('bar', bar).add('foo', foo);
foo.change(function (draft) { return draft.count++; });
bar.change(function (draft) { return (draft.isEnabled = !draft.isEnabled); });
(0, test_1.expect)(combined.state.foo.count).to.eql(2);
(0, test_1.expect)(combined.state.bar.isEnabled).to.eql(true);
combined.dispose();
foo.change(function (draft) { return draft.count++; });
(0, test_1.expect)(combined.state.foo.count).to.eql(2);
});
});
});