@platform/state
Version:
A small, simple, strongly typed, [rx/observable] state-machine.
953 lines • 62.1 kB
JavaScript
"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