UNPKG

@platform/state

Version:

A small, simple, strongly typed, [rx/observable] state-machine.

458 lines (457 loc) 25.1 kB
"use strict"; 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); }); }); });