UNPKG

@platform/state

Version:

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

953 lines 62.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var rxjs_1 = require("rxjs"); var _1 = require("."); var test_1 = require("../test"); var TreeIdentity_1 = require("../TreeIdentity"); var TreeQuery_1 = require("../TreeQuery"); var helpers_1 = require("./helpers"); var immer_1 = require("immer"); var query = TreeQuery_1.TreeQuery.create; var create = _1.TreeState.create; describe('TreeState', function () { describe('create', function () { it('without parent', function () { var root = { id: 'root' }; var tree = create({ root: root }); (0, test_1.expect)(tree.state).to.not.equal(root); (0, test_1.expect)(tree.parent).to.eql(undefined); (0, test_1.expect)(tree.children).to.eql([]); (0, test_1.expect)(tree.id).to.eql(tree.state.id); (0, test_1.expect)(tree.key).to.eql('root'); (0, test_1.expect)(tree.namespace.length).to.greaterThan(10); }); it('with parent', function () { var root = { id: 'myLeaf' }; var tree = create({ root: root, parent: 'myParent' }); (0, test_1.expect)(tree.parent).to.eql('myParent'); }); it('create with no id (defaults to "node")', function () { var tree = create(); (0, test_1.expect)(helpers_1.helpers.id.stripNamespace(tree.id)).to.eql('node'); (0, test_1.expect)(helpers_1.helpers.id.namespace(tree.id)).to.eql(tree.namespace); }); it('from root id (string)', function () { var tree = create({ root: 'foo' }); var id = "".concat(tree.namespace, ":foo"); (0, test_1.expect)(tree.id).to.eql(id); (0, test_1.expect)(tree.state.id).to.eql(id); }); it('from root id (parses <namespace>:<id>)', function () { var tree = create({ root: 'ns:foo' }); (0, test_1.expect)(tree.namespace).to.eql('ns'); (0, test_1.expect)(tree.id).to.eql('ns:foo'); }); it('readonly', function () { var root = { id: 'root' }; var tree = create({ root: root }); (0, test_1.expect)(tree.readonly).to.equal(tree); }); it('throw: id contains "/" character', function () { var fn = function () { return create({ root: 'foo/bar' }); }; (0, test_1.expect)(fn).to.throw(/Tree node IDs cannot contain the "\/" character/); }); }); describe('dispose', function () { it('dispose', function () { var tree = create(); (0, test_1.expect)(tree.isDisposed).to.eql(false); var count = 0; tree.dispose$.subscribe(function (e) { return count++; }); (0, test_1.expect)(tree.isDisposed).to.eql(false); tree.dispose(); tree.dispose(); (0, test_1.expect)(tree.isDisposed).to.eql(true); }); it('dispose: event', function () { var tree = create(); tree.change(function (root, ctx) { return ctx.props(root, function (p) { return (p.label = 'foo'); }); }); var fired = []; tree.event.$.subscribe(function (e) { return fired.push(e); }); tree.dispose(); tree.dispose(); (0, test_1.expect)(fired.length).to.eql(1); var event = fired[0]; (0, test_1.expect)(event.type).to.eql('TreeState/disposed'); (0, test_1.expect)(event.payload.final).to.eql(tree.state); }); it('disposes of all children', function () { var tree = create(); (0, test_1.expect)(tree.isDisposed).to.eql(false); var child1 = tree.add({ root: 'foo' }); var child2 = child1.add({ root: 'bar' }); var child3 = child2.add({ root: 'zoo' }); child2.dispose(); (0, test_1.expect)(child3.isDisposed).to.eql(true); tree.dispose(); (0, test_1.expect)(tree.isDisposed).to.eql(true); (0, test_1.expect)(child1.isDisposed).to.eql(true); (0, test_1.expect)(child2.isDisposed).to.eql(true); (0, test_1.expect)(child3.isDisposed).to.eql(true); }); it('takes a [dispose$] within constructor', function () { var dispose$ = new rxjs_1.Subject(); var tree = create({ dispose$: dispose$ }); (0, test_1.expect)(tree.isDisposed).to.eql(false); var count = 0; tree.dispose$.subscribe(function () { return count++; }); dispose$.next(); dispose$.next(); dispose$.next(); (0, test_1.expect)(tree.isDisposed).to.eql(true); (0, test_1.expect)(count).to.eql(1); }); }); describe('static', function () { it('isInstance', function () { var test = function (input, expected) { (0, test_1.expect)(_1.TreeState.isInstance(input)).to.eql(expected); }; var instance = create({ root: 'foo' }); test(instance, true); test(undefined, false); test(null, false); test('', false); test({ id: 'foo' }, false); }); it('identity', function () { (0, test_1.expect)(_1.TreeState.identity).to.equal(TreeIdentity_1.TreeIdentity); }); }); describe('rewrite IDs with namespace prefix', function () { it('simple', function () { var root = { id: 'root' }; var tree = create({ root: root }); var start = "".concat(tree.namespace, ":"); (0, test_1.expect)(tree.id.startsWith(start)).to.eql(true); (0, test_1.expect)(tree.state.id.startsWith(start)).to.eql(true); }); it('deep', function () { var root = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-2-1' }] }], }; var tree = create({ root: root }); var ids = []; var start = "".concat(tree.namespace, ":"); query(tree.state).walkDown(function (e) { return ids.push(e.node.id); }); (0, test_1.expect)(ids.length).to.eql(4); (0, test_1.expect)(ids.every(function (id) { return id.startsWith(start); })).to.eql(true); }); }); describe('add', function () { it('add: root as {object} (TreeNode)', function () { var root = { id: 'root' }; var tree = create({ root: root }); (0, test_1.expect)(tree.children).to.eql([]); var child = tree.add({ root: { id: 'foo' } }); (0, test_1.expect)(tree.children.length).to.eql(1); (0, test_1.expect)(tree.children[0]).to.equal(child); (0, test_1.expect)(child.parent).to.eql("".concat(tree.namespace, ":root")); }); it('add: root as string ("id")', function () { var root = { id: 'root' }; var tree = create({ root: root }); var child = tree.add({ root: 'foo' }); (0, test_1.expect)(tree.children.length).to.eql(1); (0, test_1.expect)(child.id).to.eql("".concat(child.namespace, ":foo")); }); it('add (pre-existing): { root: [TreeState] }', function () { var tree = create({ root: 'root' }); (0, test_1.expect)(tree.state.children).to.eql(undefined); var child = create({ root: 'foo' }); (0, test_1.expect)(tree.namespace).to.not.eql(child.namespace); tree.add({ root: child }); (0, test_1.expect)(helpers_1.helpers.children(tree.state)[0].id).to.eql(child.id); (0, test_1.expect)(tree.children.includes(child)).to.eql(true); }); it('add (pre-existing): [TreeState] as base argument', function () { var tree = create({ root: 'root' }); (0, test_1.expect)(tree.state.children).to.eql(undefined); var child = create({ root: 'foo' }); (0, test_1.expect)(tree.namespace).to.not.eql(child.namespace); tree.add(child); (0, test_1.expect)(tree.children.includes(child)).to.eql(true); (0, test_1.expect)(helpers_1.helpers.children(tree.state)[0].id).to.eql(child.id); }); it('add (pre-existing): [TreeState] as base argument', function () { var tree = create({ root: 'root' }); (0, test_1.expect)(tree.state.children).to.eql(undefined); var child = create({ root: 'foo' }); (0, test_1.expect)(tree.namespace).to.not.eql(child.namespace); tree.add(child); (0, test_1.expect)(tree.children.includes(child)).to.eql(true); (0, test_1.expect)(helpers_1.helpers.children(tree.state)[0].id).to.eql(child.id); }); it('add (pre-existing): within sub-node of parent', function () { var _a; var tree = create({ root: { id: 'root', children: [{ id: 'foo' }] } }); var subnode = tree.query.find(function (e) { return e.key === 'foo'; }); (0, test_1.expect)(subnode === null || subnode === void 0 ? void 0 : subnode.id.endsWith(':foo')).to.eql(true); var child = create({ root: 'child' }); tree.add({ root: child, parent: subnode === null || subnode === void 0 ? void 0 : subnode.id }); (0, test_1.expect)((_a = tree.children.find(function (e) { return e.id === child.id; })) === null || _a === void 0 ? void 0 : _a.id).to.eql(child.id); var children = helpers_1.helpers.children(tree.state); (0, test_1.expect)(children.length).to.eql(1); var grandchildren = children[0].children || []; (0, test_1.expect)(grandchildren.length).to.eql(1); (0, test_1.expect)(grandchildren[0].id).to.eql(child.id); }); it('add: no parent id (root id assumed)', function () { var root = { id: 'root' }; var tree = create({ root: root }); var child = tree.add({ root: 'foo' }); (0, test_1.expect)(tree.children.length).to.eql(1); (0, test_1.expect)(child.id).to.eql("".concat(child.namespace, ":foo")); }); it('adds multiple children with same id (geneated namespaces differs)', function () { var root = { id: 'root' }; var tree = create({ root: root }); var child1 = tree.add({ root: { id: 'foo' } }); var child2 = tree.add({ root: { id: 'foo' } }); (0, test_1.expect)(child1.id).to.not.eql(child2.id); }); it('inserts node into parent state-tree data', function () { var root = { id: 'root', children: [{ id: 'mary' }] }; var tree = create({ root: root }); (0, test_1.expect)((tree.state.children || []).length).to.eql(1); var fired = []; tree.event .payload('TreeState/changed') .subscribe(function (e) { return fired.push(e); }); var child1 = tree.add({ root: { id: 'foo' } }); var children = tree.state.children || []; (0, test_1.expect)(children.length).to.eql(2); (0, test_1.expect)(children[0].id).to.match(/:mary$/); (0, test_1.expect)(children[1].id).to.eql(child1.id); (0, test_1.expect)(fired.length).to.eql(1); (0, test_1.expect)((fired[0].from.children || []).length).to.eql(1); (0, test_1.expect)((fired[0].to.children || []).length).to.eql(2); }); it('child added to more than one parent [StateTree]', function () { var state1 = create({ root: 'root-1' }); var state2 = create({ root: 'root-2' }); var child = create({ root: 'child' }); (0, test_1.expect)(state1.namespace).to.not.eql(state2.namespace); state1.add(child); state2.add(child); var childAt = function (state, i) { return helpers_1.helpers.children(state.state)[i]; }; var firstChild = function (state) { return childAt(state, 0); }; (0, test_1.expect)(firstChild(state1).id).to.eql(child.id); (0, test_1.expect)(firstChild(state2).id).to.eql(child.id); child.change(function (draft, ctx) { ctx.props(draft).label = 'hello'; }); (0, test_1.expect)(firstChild(state1).props).to.eql({ label: 'hello' }); (0, test_1.expect)(firstChild(state2).props).to.eql({ label: 'hello' }); state1.remove(child); (0, test_1.expect)(firstChild(state1)).to.eql(undefined); (0, test_1.expect)(firstChild(state2).id).to.eql(child.id); child.dispose(); (0, test_1.expect)(firstChild(state1)).to.eql(undefined); (0, test_1.expect)(firstChild(state2)).to.eql(undefined); }); it('event: added', function () { var root = { id: 'root' }; var tree = create({ root: root }); var fired = []; tree.event .payload('TreeState/child/added') .subscribe(function (e) { return fired.push(e); }); var child1 = tree.add({ root: { id: 'foo' } }); var child2 = tree.add({ root: { id: 'foo' } }); (0, test_1.expect)(fired.length).to.eql(2); (0, test_1.expect)(fired[0].child).to.equal(child1); (0, test_1.expect)(fired[1].child).to.equal(child2); (0, test_1.expect)(fired[0].parent).to.equal(tree); (0, test_1.expect)(fired[1].parent).to.equal(tree); }); it('throw: "parent" does not exist', function () { var tree = create({ root: { id: 'root' } }); var fn = function () { return tree.add({ parent: '404', root: { id: 'foo' } }); }; (0, test_1.expect)(fn).to.throw(/parent node '404' does not exist/); }); it('throw: (pre-existing) "parent" does not exist', function () { var tree = create({ root: { id: 'root' } }); var child = create({ root: 'child' }); var fn = function () { return tree.add({ parent: '404', root: child }); }; (0, test_1.expect)(fn).to.throw(new RegExp("parent sub-node '404' within '".concat(tree.id, "' does not exist"))); }); it('throw: child already added', function () { var tree = create({ root: 'root' }); var child = tree.add({ root: 'child' }); (0, test_1.expect)(tree.children.length).to.eql(1); (0, test_1.expect)(tree.namespace).to.not.eql(child.namespace); var fn = function () { return tree.add({ root: child }); }; (0, test_1.expect)(fn).to.throw(/already exists/); }); }); describe('remove', function () { it('removes (but does not dispose)', function () { var root = { id: 'root' }; var tree = create({ root: root }); var fired = []; tree.event .payload('TreeState/child/removed') .subscribe(function (e) { return fired.push(e); }); var child1 = tree.add({ parent: 'root', root: { id: 'foo' } }); var child2 = tree.add({ parent: 'root', root: { id: 'foo' } }); (0, test_1.expect)(tree.children.length).to.eql(2); tree.remove(child1); (0, test_1.expect)(tree.children.length).to.eql(1); tree.remove(child2); (0, test_1.expect)(tree.children.length).to.eql(0); (0, test_1.expect)(child1.isDisposed).to.eql(false); (0, test_1.expect)(child2.isDisposed).to.eql(false); (0, test_1.expect)(fired.length).to.eql(2); (0, test_1.expect)(fired[0].child).to.eql(child1); (0, test_1.expect)(fired[1].child).to.eql(child2); }); it('removes on [child.dispose()]', function () { var root = { id: 'root' }; var tree = create({ root: root }); var fired = []; tree.event .payload('TreeState/child/removed') .subscribe(function (e) { return fired.push(e); }); var child1 = tree.add({ parent: 'root', root: { id: 'foo' } }); var child2 = tree.add({ parent: 'root', root: 'foo' }); (0, test_1.expect)(tree.children.length).to.eql(2); child1.dispose(); (0, test_1.expect)(tree.children.length).to.eql(1); child2.dispose(); (0, test_1.expect)(tree.children.length).to.eql(0); (0, test_1.expect)(fired.length).to.eql(2); (0, test_1.expect)(fired[0].child).to.eql(child1); (0, test_1.expect)(fired[1].child).to.eql(child2); }); it('removes node from parent state-tree data', function () { var root = { id: 'root' }; var tree = create({ root: root }); var child1 = tree.add({ parent: 'root', root: 'foo' }); var child2 = tree.add({ parent: 'root', root: 'foo' }); var children = function () { return tree.state.children || []; }; var count = function () { return children().length; }; var includes = function (id) { return (tree.state.children || []).some(function (c) { return c.id === id; }); }; (0, test_1.expect)(count()).to.eql(2); (0, test_1.expect)(includes(child1.id)).to.eql(true); (0, test_1.expect)(includes(child2.id)).to.eql(true); child1.dispose(); (0, test_1.expect)(count()).to.eql(1); (0, test_1.expect)(includes(child1.id)).to.eql(false); (0, test_1.expect)(includes(child2.id)).to.eql(true); tree.remove(child2); (0, test_1.expect)(count()).to.eql(0); (0, test_1.expect)(includes(child1.id)).to.eql(false); (0, test_1.expect)(includes(child2.id)).to.eql(false); }); it('throw: remove child that does not exist', function () { var root = { id: 'root' }; var tree = create({ root: root }); var child1 = tree.add({ parent: 'root', root: { id: 'foo' } }); var child2 = tree.add({ parent: 'root', root: { id: 'foo' } }); var test = function (child) { var tree = create({ root: root }); var fn = function () { return tree.remove(child); }; (0, test_1.expect)(fn).to.throw(/Cannot remove child-state as it does not exist in the parent/); }; test(child1.state.id); test(child1.id); test(child2); }); }); describe('clear', function () { it('empty', function () { var root = { id: 'root' }; var tree = create({ root: root }); (0, test_1.expect)(tree.children.length).to.eql(0); tree.clear(); (0, test_1.expect)(tree.children.length).to.eql(0); }); it('removes all children', function () { var root = { id: 'root' }; var tree = create({ root: root }); var fired = []; tree.event.childRemoved$.subscribe(function (e) { return fired.push(e); }); var parent = 'root'; tree.add({ parent: parent, root: 'foo' }); tree.add({ parent: parent, root: 'bar' }); (0, test_1.expect)(tree.children.length).to.eql(2); tree.clear(); (0, test_1.expect)(tree.children.length).to.eql(0); (0, test_1.expect)(fired.length).to.eql(2); }); }); describe('change', function () { var root = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-2-1' }] }], }; it('simple', function () { var _a, _b, _c, _d, _e, _f, _g; var tree = create({ root: root }); (0, test_1.expect)((_a = tree.state.props) === null || _a === void 0 ? void 0 : _a.label).to.eql(undefined); (0, test_1.expect)((_b = tree.state.props) === null || _b === void 0 ? void 0 : _b.icon).to.eql(undefined); var res = tree.change(function (draft, ctx) { ctx.props(draft, function (p) { p.label = 'Hello!'; p.icon = 'face'; }); }); (0, test_1.expect)((_c = tree.state.props) === null || _c === void 0 ? void 0 : _c.label).to.eql('Hello!'); (0, test_1.expect)((_d = tree.state.props) === null || _d === void 0 ? void 0 : _d.icon).to.eql('face'); (0, test_1.expect)(res.op).to.eql('update'); (0, test_1.expect)(res.cid.length).to.eql(8); (0, test_1.expect)((_e = res.changed) === null || _e === void 0 ? void 0 : _e.from.props).to.eql(undefined); (0, test_1.expect)((_g = (_f = res.changed) === null || _f === void 0 ? void 0 : _f.to.props) === null || _g === void 0 ? void 0 : _g.label).to.eql('Hello!'); var _h = res.patches, prev = _h.prev, next = _h.next; (0, test_1.expect)(prev.length).to.eql(1); (0, test_1.expect)(next.length).to.eql(1); (0, test_1.expect)(prev[0]).to.eql({ op: 'remove', path: 'props' }); (0, test_1.expect)(next[0]).to.eql({ op: 'add', path: 'props', value: { label: 'Hello!', icon: 'face' }, }); }); it('child array: insert (updates id namespaces)', function () { var tree = create({ root: 'root' }); tree.change(function (draft, ctx) { var children = _1.TreeState.children(draft); children.push.apply(children, [{ id: 'foo', children: [{ id: 'foo.1' }] }, { id: 'bar' }]); }); var ns = _1.TreeState.identity.hasNamespace; var children = tree.state.children || []; (0, test_1.expect)(children.length).to.eql(2); (0, test_1.expect)(ns(children[0].id)).to.eql(true); (0, test_1.expect)(ns(children[1].id)).to.eql(true); (0, test_1.expect)(ns((children[0].children || [])[0].id)).to.eql(true); }); it('updates parent state-tree when child changes', function () { var tree = create({ root: root }); var child1 = tree.add({ root: 'foo' }); var child2 = tree.add({ root: 'bar' }); var children = function () { return tree.state.children || []; }; var count = function () { return children().length; }; (0, test_1.expect)(count()).to.eql(4); (0, test_1.expect)(children()[2].props).to.eql(undefined); child1.change(function (draft, ctx) { return ctx.props(draft, function (p) { return (p.label = 'foo'); }); }); (0, test_1.expect)(children()[2].props).to.eql({ label: 'foo' }); child1.dispose(); child1.change(function (draft, ctx) { return ctx.props(draft, function (p) { return (p.label = 'bar'); }); }); (0, test_1.expect)(count()).to.eql(3); child2.change(function (root, ctx) { return ctx.props(root, function (p) { return (p.label = 'hello'); }); }); (0, test_1.expect)(children()[2].props).to.eql({ label: 'hello' }); }); it('updates parent state-tree when child changes (deep)', function () { var tree = create({ root: { id: 'root' } }); var child1 = tree.add({ root: 'foo' }); var child2 = child1.add({ root: 'bar' }); var children = function (node) { return node.children || []; }; var grandchild = function () { return children(children(tree.state)[0])[0]; }; (0, test_1.expect)(grandchild().props).to.eql(undefined); child2.change(function (draft, ctx) { return (ctx.props(draft).label = 'hello'); }); (0, test_1.expect)(grandchild().props).to.eql({ label: 'hello' }); }); it('updates from found/queried node', function () { var _a, _b; var tree = create({ root: root }); (0, test_1.expect)((_a = tree.query.findById('child-1')) === null || _a === void 0 ? void 0 : _a.props).to.eql(undefined); tree.change(function (draft, ctx) { var child = ctx.findById('child-1'); if (child) { ctx.props(child, function (p) { return (p.label = 'hello'); }); } }); (0, test_1.expect)((_b = tree.query.findById('child-1')) === null || _b === void 0 ? void 0 : _b.props).to.eql({ label: 'hello' }); }); it('changeAsync', function () { return tslib_1.__awaiter(void 0, void 0, void 0, function () { var tree, res, _a, prev, next; var _b, _c, _d, _e, _f, _g, _h; return tslib_1.__generator(this, function (_j) { switch (_j.label) { case 0: tree = create({ root: root }); (0, test_1.expect)((_b = tree.state.props) === null || _b === void 0 ? void 0 : _b.label).to.eql(undefined); (0, test_1.expect)((_c = tree.state.props) === null || _c === void 0 ? void 0 : _c.icon).to.eql(undefined); return [4, tree.changeAsync(function (draft, ctx) { 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(5)]; case 1: _a.sent(); ctx.props(draft, function (p) { p.label = 'Hello!'; p.icon = 'face'; }); return [2]; } }); }); })]; case 1: res = _j.sent(); (0, test_1.expect)((_d = tree.state.props) === null || _d === void 0 ? void 0 : _d.label).to.eql('Hello!'); (0, test_1.expect)((_e = tree.state.props) === null || _e === void 0 ? void 0 : _e.icon).to.eql('face'); (0, test_1.expect)(res.op).to.eql('update'); (0, test_1.expect)(res.cid.length).to.eql(8); (0, test_1.expect)((_f = res.changed) === null || _f === void 0 ? void 0 : _f.from.props).to.eql(undefined); (0, test_1.expect)((_h = (_g = res.changed) === null || _g === void 0 ? void 0 : _g.to.props) === null || _h === void 0 ? void 0 : _h.label).to.eql('Hello!'); _a = res.patches, prev = _a.prev, next = _a.next; (0, test_1.expect)(prev.length).to.eql(1); (0, test_1.expect)(next.length).to.eql(1); (0, test_1.expect)(prev[0]).to.eql({ op: 'remove', path: 'props' }); (0, test_1.expect)(next[0]).to.eql({ op: 'add', path: 'props', value: { label: 'Hello!', icon: 'face' }, }); return [2]; } }); }); }); }); describe('change (events)', function () { var root = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-2-1' }] }], }; it('event: changed', function () { var _a, _b; var tree = create({ root: root }); var fired = []; tree.event .payload('TreeState/changed') .subscribe(function (e) { return fired.push(e); }); var res = tree.change(function (root, ctx) { ctx.props(root, function (p) { return (p.label = 'foo'); }); }); (0, test_1.expect)(fired.length).to.eql(1); var event = fired[0]; (0, test_1.expect)((_a = event.from.props) === null || _a === void 0 ? void 0 : _a.label).to.eql(undefined); (0, test_1.expect)((_b = event.to.props) === null || _b === void 0 ? void 0 : _b.label).to.eql('foo'); (0, test_1.expect)(event.patches).to.eql(res.patches); }); it('event: changed (fires from root when child changes)', function () { var tree = create({ root: root }); var child = tree.add({ root: 'foo' }); var firedRoot = []; tree.event .payload('TreeState/changed') .subscribe(function (e) { return firedRoot.push(e); }); var firedChild = []; child.event .payload('TreeState/changed') .subscribe(function (e) { return firedChild.push(e); }); child.change(function (draft, ctx) { return ctx.props(draft, function (p) { return (p.label = 'foo'); }); }); (0, test_1.expect)(firedRoot.length).to.eql(1); (0, test_1.expect)(firedChild.length).to.eql(1); (0, test_1.expect)(firedRoot[0].patches.next[0].op).to.eql('replace'); (0, test_1.expect)(firedChild[0].patches.next[0].op).to.eql('add'); }); it('event: patched', function () { var tree = create({ root: root }); var fired = []; tree.event .payload('TreeState/patched') .subscribe(function (e) { return fired.push(e); }); var res = tree.change(function (root, ctx) { ctx.props(root, function (p) { return (p.label = 'foo'); }); }); (0, test_1.expect)(fired.length).to.eql(1); var event = fired[0]; (0, test_1.expect)(event.prev).to.eql(res.patches.prev); (0, test_1.expect)(event.next).to.eql(res.patches.next); }); it('event: does not fire when nothing changes', function () { var tree = create({ root: root }); var fired = []; tree.event .payload('TreeState/changed') .subscribe(function (e) { return fired.push(e); }); var res = tree.change(function (root) { }); (0, test_1.expect)(fired.length).to.eql(0); (0, test_1.expect)(res.changed).to.eql(undefined); }); }); describe('change: ctx (context)', function () { var root = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-2-1' }] }], }; it('toObject', function () { var tree = create({ root: root }); tree.change(function (draft, ctx) { var child = ctx.findById('child-2-1'); (0, test_1.expect)(child).to.exist; if (child) { (0, test_1.expect)((0, immer_1.isDraft)(draft)).to.eql(true); (0, test_1.expect)((0, immer_1.isDraft)(child)).to.eql(true); (0, test_1.expect)((0, immer_1.isDraft)(ctx.toObject(draft))).to.eql(false); (0, test_1.expect)((0, immer_1.isDraft)(ctx.toObject(child))).to.eql(false); } }); }); }); describe('query', function () { var root = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-2-1' }] }], }; it('has query', function () { var tree = create({ root: root }); var query = tree.query; (0, test_1.expect)(query.root).to.eql(tree.state); (0, test_1.expect)(query.namespace).to.eql(tree.namespace); }); describe('walkDown', function () { it('walkDown', function () { var tree = create({ root: root }); var walked = []; tree.query.walkDown(function (e) { (0, test_1.expect)(e.namespace).to.eql(tree.namespace); (0, test_1.expect)(e.node.id.endsWith(":".concat(e.key))).to.eql(true); walked.push(e); }); (0, test_1.expect)(walked.length).to.eql(4); (0, test_1.expect)(walked[0].key).to.eql('root'); (0, test_1.expect)(walked[3].key).to.eql('child-2-1'); }); it('walkDown: stop', function () { var tree = create({ root: root }); var walked = []; tree.query.walkDown(function (e) { walked.push(e); if (e.key === 'child-1') { e.stop(); } }); (0, test_1.expect)(walked.length).to.eql(2); (0, test_1.expect)(walked[0].key).to.eql('root'); (0, test_1.expect)(walked[1].key).to.eql('child-1'); }); it('walkDown: skip (children)', function () { var tree = create({ root: root }); var walked = []; tree.query.walkDown(function (e) { walked.push(e); if (e.key === 'child-2') { e.skip(); } }); (0, test_1.expect)(walked.length).to.eql(3); (0, test_1.expect)(walked[0].key).to.eql('root'); (0, test_1.expect)(walked[1].key).to.eql('child-1'); (0, test_1.expect)(walked[2].key).to.eql('child-2'); }); it('walkDown: does not walk down into child namespace', function () { var _a; var tree = create({ root: root }); var child = tree.add({ parent: 'child-2-1', root: { id: 'foo' } }); (0, test_1.expect)(child.namespace).to.not.eql(tree.namespace); (0, test_1.expect)((_a = query(tree.state).findById(child.id)) === null || _a === void 0 ? void 0 : _a.id).to.eql(child.id); var walked = []; tree.query.walkDown(function (e) { return walked.push(e); }); var ids = walked.map(function (e) { return e.id; }); (0, test_1.expect)(ids.length).to.greaterThan(0); (0, test_1.expect)(ids.includes('foo')).to.eql(false); }); }); describe('walkUp', function () { it('walkUp', function () { var tree = create({ root: root }); var test = function (startAt) { var walked = []; tree.query.walkUp(startAt, function (e) { return walked.push(e); }); (0, test_1.expect)(walked.map(function (e) { return e.key; })).to.eql(['child-2-1', 'child-2', 'root']); }; test('child-2-1'); test(tree.query.findById('child-2-1')); }); it('walkUp: startAt not found', function () { var tree = create({ root: root }); var test = function (startAt) { var walked = []; tree.query.walkUp(startAt, function (e) { return walked.push(e); }); (0, test_1.expect)(walked).to.eql([]); }; test(); test('404'); test({ id: '404' }); }); it('walkUp: does not walk up into parent namespace', function () { var tree = create({ root: root }); var child = tree.add({ parent: 'child-2-1', root: { id: 'foo', children: [{ id: 'foo.child' }] }, }); var fooChild = child.query.findById('foo.child'); (0, test_1.expect)(fooChild).to.exist; var test = function (startAt) { var walked = []; child.query.walkUp(startAt, function (e) { return walked.push(e); }); (0, test_1.expect)(walked.map(function (e) { return e.key; })).to.eql(['foo.child', 'foo']); }; test(fooChild); test(fooChild === null || fooChild === void 0 ? void 0 : fooChild.id); test('foo.child'); }); it('walkUp: not within namespace', function () { var tree = create({ root: root }); var child = tree.add({ parent: 'child-2-1', root: { id: 'foo', children: [{ id: 'foo.child' }] }, }); var fooChild = child.query.findById('foo.child'); (0, test_1.expect)(fooChild).to.exist; var walked = []; tree.query.walkUp(fooChild, function (e) { return walked.push(e); }); (0, test_1.expect)(walked.map(function (e) { return e.id; })).to.eql([]); }); }); describe('find', function () { it('find', function () { var tree = create({ root: root }); var walked = []; tree.query.walkDown(function (e) { return walked.push(e); }); var res1 = tree.query.findById('404'); var res2 = tree.query.findById('root'); var res3 = tree.query.findById('child-2-1'); (0, test_1.expect)(res1).to.eql(undefined); (0, test_1.expect)(res2).to.eql(walked[0].node); (0, test_1.expect)(res3).to.eql(walked[3].node); }); it('find: root (immediate)', function () { var tree = create({ root: 'root' }); var res = tree.query.findById('root'); (0, test_1.expect)(res === null || res === void 0 ? void 0 : res.id).to.eql(tree.id); }); it('find: does not walk down into child namespace', function () { var _a, _b, _c, _d; var tree = create({ root: root }); var child = tree.add({ parent: 'child-2-1', root: { id: 'foo' } }); (0, test_1.expect)(child.namespace).to.not.eql(tree.namespace); (0, test_1.expect)((_a = query(tree.state).findById(child.id)) === null || _a === void 0 ? void 0 : _a.id).to.eql(child.id); (0, test_1.expect)(tree.query.findById('foo')).to.eql(undefined); (0, test_1.expect)(TreeIdentity_1.TreeIdentity.key((_b = tree.query.findById('child-2-1')) === null || _b === void 0 ? void 0 : _b.id)).to.eql('child-2-1'); (0, test_1.expect)((_c = child.query.findById('foo')) === null || _c === void 0 ? void 0 : _c.id).to.eql(child.id); (0, test_1.expect)((_d = child.query.findById('child-2-1')) === null || _d === void 0 ? void 0 : _d.id).to.eql(undefined); }); }); describe('exists', function () { it('does exist', function () { var tree = create({ root: root }); var res = tree.query.findById('child-2-1'); (0, test_1.expect)(_1.TreeState.identity.parse(res === null || res === void 0 ? void 0 : res.id).key).to.eql('child-2-1'); }); it('does not exist', function () { var tree = create({ root: root }); var res = tree.query.findById('404'); (0, test_1.expect)(res).to.eql(undefined); }); }); }); describe('child (find)', function () { it('empty', function () { var root = { id: 'root' }; var tree = create({ root: root }); var list = []; var res = tree.find(function (e) { list.push(e); return false; }); (0, test_1.expect)(res).to.eql(undefined); (0, test_1.expect)(list).to.eql([]); }); it('undefined/null', function () { var root = { id: 'root' }; var tree = create({ root: root }); (0, test_1.expect)(tree.find()).to.eql(undefined); (0, test_1.expect)(tree.find(null)).to.eql(undefined); }); it('deep', function () { var tree = create(); var child1 = tree.add({ root: 'child-1' }); var child2a = child1.add({ root: 'child-2a' }); child1.add({ root: 'child-2a' }); var child3 = child2a.add({ root: 'child-3' }); var list = []; var res = tree.find(function (e) { list.push(e); return e.key === 'child-3'; }); (0, test_1.expect)(list.length).to.eql(3); (0, test_1.expect)(res === null || res === void 0 ? void 0 : res.id).to.equal(child3.id); (0, test_1.expect)(list[0].tree.id).to.eql(child1.id); (0, test_1.expect)(list[1].tree.id).to.eql(child2a.id); (0, test_1.expect)(list[2].tree.id).to.eql(child3.id); }); it('flat', function () { var root = { id: 'root' }; var tree = create({ root: root }); var child1 = tree.add({ root: 'child-1' }); var child2 = tree.add({ root: 'child-2' }); var child3 = tree.add({ root: 'child-3' }); var list = []; var res = tree.find(function (e) { list.push(e); return e.key === 'child-3'; }); (0, test_1.expect)(res === null || res === void 0 ? void 0 : res.id).to.equal(child3.id); (0, test_1.expect)(list.length).to.eql(3); (0, test_1.expect)(list[0].tree.id).to.eql(child1.id); (0, test_1.expect)(list[1].tree.id).to.eql(child2.id); (0, test_1.expect)(list[2].tree.id).to.eql(child3.id); }); it('via node-identifier (param)', function () { var tree = create(); var child1 = tree.add({ root: 'child-1' }); var child2a = child1.add({ root: 'child-2a' }); (0, test_1.expect)(tree.find(child2a.id)).to.equal(child2a); (0, test_1.expect)(tree.find(child2a)).to.equal(child2a); (0, test_1.expect)(tree.find('404')).to.equal(undefined); }); it('node-identifer descendent of child module', function () { var tree = create(); var child1 = tree.add({ root: 'child-1' }); var child2a = child1.add({ root: { id: 'child-2a', children: [{ id: 'foo' }] } }); var query = TreeQuery_1.TreeQuery.create({ root: tree.state }); var node = query.find(function (e) { return e.key === 'foo'; }); (0, test_1.expect)(node).to.exist; (0, test_1.expect)(tree.find(node)).to.equal(child2a); (0, test_1.expect)(tree.find(node === null || node === void 0 ? void 0 : node.id)).to.equal(child2a); }); it('toString => fully qualified identifier (<namespace>:<id>)', function () { var tree = create(); var child1 = tree.add({ root: 'child-1' }); child1.add({ root: 'ns:child-2a' }); var res1 = tree.find(function (e) { return e.toString() === 'ns:child-2a'; }); var res2 = tree.find(function (e) { return e.id === 'ns:child-2a'; }); (0, test_1.expect)(res1 === null || res1 === void 0 ? void 0 : res1.id).to.eql('ns:child-2a'); (0, test_1.expect)(res2 === null || res2 === void 0 ? void 0 : res2.id).to.eql('ns:child-2a'); }); it('stop (walking)', function () { var tree = create(); var child1 = tree.add({ root: 'child-1' }); var child2a = child1.add({ root: 'child-2a' }); child1.add({ root: 'child-2b' }); child2a.add({ root: 'child-3' }); var list = []; var res = tree.find(function (e) { list.push(e); if (e.key === 'child-2a') { e.stop(); } return e.key === 'child-3'; }); (0, test_1.expect)(list.map(function (e) { return e.key; })).to.eql(['child-1', 'child-2a']); (0, test_1.expect)(res).to.eql(undefined); }); }); describe('contains', function () { var tree = create(); var child1 = tree.add({ root: 'ns1:child-1' }); var child2a = child1.add({ root: { id: 'child-2a', children: [{ id: 'foo' }, { id: 'ns2:bar' }] }, }); var child3 = create({ root: 'child-3' }); var query = TreeQuery_1.TreeQuery.create({ root: tree.state }); var foo = query.find(function (e) { return e.key === 'foo'; }); var bar = query.find(function (e) { return e.key === 'bar'; }); it('empty', function () { (0, test_1.expect)(tree.contains('')).to.eql(false); (0, test_1.expect)(tree.contains(' ')).to.eql(false); (0, test_1.expect)(tree.contains(undefined)).to.eql(false); (0, test_1.expect)(tree.contains(null)).to.eql(false); }); it('does not contain', function () { (0, test_1.expect)(tree.contains('404')).to.eql(false); (0, test_1.expect)(tree.contains({ id: '404' })).to.eql(false); (0, test_1.expect)(tree.contains(function (e) { return false; })).to.eql(false); (0, test_1.expect)(tree.contains(child3)).to.eql(false); (0, test_1.expect)(tree.contains(tree)).to.eql(false); }); it('does contain (via match function)', function () { var res = tree.contains(function (e) { return e.id === child2a.id; }); (0, test_1.expect)(res).to.eql(true); }); it('does contain (via node-identifier)', function () { (0, test_1.expect)(foo).to.exist; (0, test_1.expect)(bar).to.exist; (0, test_1.expect)(tree.contains(child2a.id)).to.eql(true); (0, test_1.expect)(tree.contains(child2a)).to.eql(true); (0, test_1.expect)(tree.contains(foo)).to.eql(true); (0, test_1.expect)(tree.contains(foo === null || foo === void 0 ? void 0 : foo.id)).to.eql(true); }); it('does not contain child nodes within different descendent namespace', function () { (0, test_1.expect)(tree.contains(bar)).to.eql(false); (0, test_1.expect)(tree.contains(bar === null || bar === void 0 ? void 0 : bar.id)).to.eql(false); }); }); describe('walkDown', function () { var tree = create({ root: 'root' }); var child1 = tree.add({ root: { id: 'child-1' } }); var child2 = child1.add({ root: { id: 'child-2' } }); var child3 = child1.add({ root: { id: 'child-3' } }); var child4 = child3.add({ root: { id: 'child-4' } }); it('walkDown: no children (visits root)', function () { var tree = create({ root: 'root' }); var items = []; tree.walkDown(function (e) { return items.push(e); }); (0, test_1.expect)(items.length).to.eql(1); (0, test_1.expect)(items[0].id).to.eql(tree.id); (0, test_1.expect)(items[0].key).to.eql(tree.key); (0, test_1.expect)(items[0].namespace).to.eql(tree.namespace); (0, test_1.expect)(items[0].level).to.eql(0); (0, test_1.expect)(items[0].index).to.eql(-1); }); it('walkDown: deep', function () { var _a; var items = []; tree.walkDown(function (e) { return items.push(e); }); (0, test_1.expect)(items.length).to.eql(5); (0, test_1.expect)(items[0].id).to.eql(tree.id); (0, test_1.expect)(items[1].id).to.eql(child1.id); (0, test_1.expect)(items[2].id).to.eql(child2.id); (0, test_1.expect)(items[3].id).to.eql(child3.id); (0, test_1.expect)(items[4].id).to.eql(child4.id); (0, test_1.expect)(items[0].level).to.eql(0); (0, test_1.expect)(items[1].level).to.eql(1); (0, test_1.expect)(items[2].level).to.eql(2); (0, test_1.expect)(items[3].level).to.eql(2); (0, test_1.expect)(items[4].level).to.eql(3); (0, test_1.expect)(items[0].index).to.eql(-1); (0, test_1.expect)(items[1].index).to.eql(0); (0, test_1.expect)(items[2].index).to.eql(0); (0, test_1.expect)(items[3].index).to.eql(1); (0, test_1.expect)(items[4].index).to.eql(0); (0, test_1.expect)(items[0].parent).to.eql(undefined); (0, test_1.expect)((_a = items[1].parent) === null || _a === void 0 ? void 0 : _a.id).to.eql(tree.id); }); it('walkDown: stop', function () { var items = []; tree.walkDown(function (e) { if (e.level > 0) { e.stop(); } items.push(e); }); (0, test_1.expect)(items.length).to.eql(2); (0, test_1.expect)(items[0].id).to.eql(tree.id); (0, test_1.expect)(items[1].id).to.eql(child1.id); }); it('walkDown: skip', function () { var items = []; tree.walkDown(function (e) { if (e.key === 'child-3') { e.skip(); } items.push(e); }); (0, test_1.expect)(items.length).to.eql(4); (0, test_1.expect)(items.map(function (e) { return e.key; })).to.not.inclu