UNPKG

@platform/state

Version:

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

463 lines (462 loc) 20.8 kB
import { expect } from '../test'; import { TreeQuery } from '.'; const create = TreeQuery.create; describe('TreeQuery', () => { describe('create', () => { it('with root (default node type)', () => { const root = { id: 'root' }; const query = create(root); expect(query.root).to.equal(root); expect(query.namespace).to.eql(''); }); it('with root (specific node type)', () => { const root = { id: 'root', count: 0 }; const query = create(root); expect(query.root).to.equal(root); expect(query.namespace).to.eql(''); }); it('with namespace', () => { const test = (namespace, expected) => { const root = { id: 'root' }; const query = create({ root, namespace }); expect(query.namespace).to.eql(expected); }; test('', ''); test(' ', ''); test('foo', 'foo'); test(' foo ', 'foo'); }); }); describe('children (static)', () => { it('no arguments', () => { let count = 0; const res = TreeQuery.children(undefined, () => count++); expect(res).to.eql([]); expect(count).to.eql(1); }); it('no childen => empty array (and assigns by default)', () => { const node = { id: 'root' }; expect(node.children).to.eql(undefined); const res = TreeQuery.children(node); expect(res).to.eql([]); expect(node.children).to.equal(res); }); it('no childen => empty array (and does not assign)', () => { const node = { id: 'root' }; expect(node.children).to.eql(undefined); const res1 = TreeQuery.children(node, { assign: false }); expect(res1).to.eql([]); expect(node.children).to.equal(undefined); const res2 = TreeQuery.children(node, undefined, { assign: false }); expect(res2).to.eql([]); expect(node.children).to.equal(undefined); }); it('returns child array (instance)', () => { const node = { id: 'root', children: [{ id: 'child' }] }; expect(TreeQuery.children(node)).to.eql([{ id: 'child' }]); }); it('runs visitor function', () => { const node = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2' }] }; const visits = []; TreeQuery.children(node, (children) => children.forEach((child) => visits.push(child))); expect(visits.map((c) => c.id)).to.eql(['child-1', 'child-2']); }); }); describe('hasChild (static)', () => { const root = { id: 'A', children: [{ id: 'B' }, { id: 'C' }] }; it('does have child', () => { expect(TreeQuery.hasChild(root, 'B')).to.eql(true); expect(TreeQuery.hasChild(root, 'C')).to.eql(true); expect(TreeQuery.hasChild(root, { id: 'C' })).to.eql(true); }); it('does not have child', () => { expect(TreeQuery.hasChild(root, 'A')).to.eql(false); expect(TreeQuery.hasChild(root, 'NO_MATCH')).to.eql(false); expect(TreeQuery.hasChild(root, undefined)).to.eql(false); expect(TreeQuery.hasChild(undefined, undefined)).to.eql(false); }); }); describe('walkDown', () => { it('walkDown from root', () => { const tree = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2' }], }; const query = create(tree); const items = []; query.walkDown((e) => items.push(e)); expect(items.length).to.eql(3); expect(items[0].node).to.equal(tree); expect(items[1].node).to.equal(tree.children && tree.children[0]); expect(items[2].node).to.equal(tree.children && tree.children[1]); expect(items.every((m) => m.namespace === '')).to.eql(true); }); it('walkDown from root (with namespace)', () => { const tree = { id: 'ns:root', children: [{ id: 'ns:child-1' }, { id: 'ns:child-2' }], }; const query = create(tree); const items = []; query.walkDown((e) => items.push(e)); expect(items.length).to.eql(3); expect(items.every((m) => m.namespace === 'ns')).to.eql(true); expect(items[0].key).to.equal('root'); expect(items[0].id).to.equal('ns:root'); expect(items[0].node.id).to.equal('ns:root'); expect(items[1].key).to.equal('child-1'); expect(items[1].id).to.equal('ns:child-1'); expect(items[1].node.id).to.equal('ns:child-1'); expect(items[2].key).to.equal('child-2'); expect(items[2].id).to.equal('ns:child-2'); expect(items[2].node.id).to.equal('ns:child-2'); }); it('walkDown: skip (children)', () => { const tree = { id: 'root', children: [ { id: 'child-1', children: [{ id: 'child-1.1' }, { id: 'child-1.1' }] }, { id: 'child-2' }, ], }; const query = create(tree); const nodes = []; query.walkDown((e) => { nodes.push(e.node); if (e.node.id === 'child-1') { e.skip(); } }); expect(nodes.length).to.eql(3); expect(nodes[0].id).to.eql('root'); expect(nodes[1].id).to.eql('child-1'); expect(nodes[2].id).to.eql('child-2'); }); it('walkDown: stop mid-way', () => { const root = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-2.1' }] }], }; const state = create(root); const walked = []; state.walkDown((e) => { walked.push(e); if (e.id === 'child-1') { e.stop(); } }); expect(walked.length).to.eql(2); expect(walked[0].id).to.eql('root'); expect(walked[1].id).to.eql('child-1'); }); it('walkDown: passes level/parent to visitor', () => { const grandchild = { id: 'grandchild' }; const child = { id: 'child', children: [grandchild] }; const root = { id: 'root', children: [child] }; const query = create(root); const items = []; query.walkDown((e) => items.push(e)); expect(items.length).to.eql(3); expect(items[0].level).to.eql(0); expect(items[1].level).to.eql(1); expect(items[2].level).to.eql(2); expect(items[0].parent).to.eql(undefined); expect(items[1].parent).to.eql(root); expect(items[2].parent).to.eql(child); }); it('walkDown: passes index to visitor (sibling position)', () => { const root = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2' }], }; const query = create(root); const items = []; query.walkDown((e) => items.push(e)); expect(items[0].index).to.eql(-1); expect(items[1].index).to.eql(0); expect(items[2].index).to.eql(1); }); it('walkDown: does not walk down into child namespace', () => { var _a; const root = { id: 'ns1:root', children: [{ id: 'ns1:child-1' }, { id: 'ns1:child-2', children: [{ id: 'ns2:foo' }] }], }; const query = create({ root, namespace: 'ns1' }); expect(query.namespace).to.eql('ns1'); expect((_a = create(root).findById('ns2:foo')) === null || _a === void 0 ? void 0 : _a.id).to.eql('ns2:foo'); const walked = []; query.walkDown((e) => walked.push(e)); const ids = walked.map((e) => e.id); expect(ids.length).to.eql(3); expect(ids.includes('ns2:foo')).to.eql(false); }); }); describe('walkUp', () => { it('walkUp: to root', () => { const tree = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'grandchild-1' }] }], }; const query = create(tree); const start = query.findById('grandchild-1'); const items = []; query.walkUp(start, (e) => items.push(e)); expect(items.length).to.eql(3); expect(items.map((e) => e.node.id)).to.eql(['grandchild-1', 'child-2', 'root']); expect(items.every((m) => m.namespace === '')).to.eql(true); expect(items[0].parent && items[0].parent.id).to.eql('child-2'); expect(items[1].parent && items[1].parent.id).to.eql('root'); expect(items[2].parent && items[2].parent.id).to.eql(undefined); expect(items[0].index).to.eql(0); expect(items[1].index).to.eql(1); expect(items[2].index).to.eql(-1); }); it('walkUp: to root (with namespace)', () => { const tree = { id: 'ns:root', children: [ { id: 'ns:child-1' }, { id: 'ns:child-2', children: [{ id: 'ns:grandchild-1' }] }, ], }; const query = create(tree); const start = query.findById('ns:grandchild-1'); const items = []; query.walkUp(start, (e) => items.push(e)); expect(items.length).to.eql(3); expect(items.every((m) => m.namespace === 'ns')).to.eql(true); expect(items.map((e) => e.key)).to.eql(['grandchild-1', 'child-2', 'root']); expect(items.map((e) => e.node.id)).to.eql(['ns:grandchild-1', 'ns:child-2', 'ns:root']); }); it('walkUp: stop mid-way', () => { const tree = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'grandchild-1' }] }], }; const query = create(tree); const start = query.findById('grandchild-1'); const list = []; query.walkUp(start, (e) => { list.push(e); if (e.node.id === 'child-2') { e.stop(); } }); expect(list.length).to.eql(2); expect(list.map((e) => e.id)).to.eql(['grandchild-1', 'child-2']); }); it('walkUp: startAt not found', () => { const tree = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'grandchild-1' }] }], }; const query = create(tree); const test = (startAt) => { const walked = []; query.walkUp(startAt, (e) => walked.push(e)); expect(walked).to.eql([]); }; test(); test('404'); test({ id: '404' }); }); it('walkUp: passes level (from start of ascent)', () => { const tree = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'grandchild-1' }] }], }; const query = create(tree); const walked = []; query.walkUp('grandchild-1', (e) => walked.push(e)); expect(walked.map((e) => e.level)).to.eql([0, 1, 2]); }); it('walkUp: does not walk up into parent namespace', () => { const tree = { id: 'ns1:root', children: [ { id: 'ns1:child-1' }, { id: 'ns2:child-2', children: [{ id: 'ns2:child-2.1' }] }, ], }; const root = create(tree).findById('ns2:child-2'); const child = create(tree).findById('ns2:child-2.1'); expect(child).to.exist; const query = create({ root, namespace: 'ns2' }); const test = (startAt) => { const walked = []; query.walkUp(startAt, (e) => walked.push(e)); expect(walked.map((e) => e.key)).to.eql(['child-2.1', 'child-2']); }; test(child); test(child === null || child === void 0 ? void 0 : child.id); test('child-2.1'); }); }); describe('find', () => { it('no namespace', () => { const tree = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-3' }] }], }; const query = create(tree); const res1 = query.find((e) => e.node.id === 'child-3'); const res2 = query.find((e) => e.node.id === 'NO_EXIT'); expect(res1).to.eql({ id: 'child-3' }); expect(res2).to.eql(undefined); }); it('with namespace', () => { const tree = { id: 'ns:root', children: [{ id: 'child-1' }, { id: 'ns:child-2', children: [{ id: 'ns:child-3' }] }], }; const query = create(tree); const res1a = query.find((e) => e.namespace === 'ns' && e.key === 'child-3'); const res1b = query.find((e) => e.id === 'ns:child-3'); const res2 = query.find((e) => e.namespace === 'foo' && e.key === 'child-3'); expect(res1a).to.eql({ id: 'ns:child-3' }); expect(res1b).to.eql({ id: 'ns:child-3' }); expect(res2).to.eql(undefined); }); it('root with namespace', () => { const tree = { id: 'root', children: [ { id: 'child-1' }, { id: 'ns:child-2', children: [{ id: 'ns:child-2.1' }, { id: 'ns:child-2.1' }] }, ], }; const query = create(tree); const res = query.find((e) => e.namespace === 'ns'); expect(res === null || res === void 0 ? void 0 : res.id).to.eql('ns:child-2'); }); }); describe('findById', () => { it('no namespace', () => { const tree = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-3' }] }], }; const query = create(tree); const res1 = query.findById('child-3'); const res2 = query.findById('NO_EXIST'); const res3 = query.findById(undefined); const res4 = query.findById({ id: 'child-2' }); expect(res1).to.eql({ id: 'child-3' }); expect(res2).to.eql(undefined); expect(res3).to.eql(undefined); expect(res4).to.eql({ id: 'child-2', children: [{ id: 'child-3' }] }); }); it('within namespace', () => { var _a, _b, _c, _d, _e, _f, _g, _h, _j; const root = { id: 'ns1:root', children: [{ id: 'ns1:child-1' }, { id: 'ns2:child-2' }, { id: 'foo' }], }; const query = create({ root }); const ns1 = create({ root, namespace: 'ns1' }); expect((_a = query.findById('root')) === null || _a === void 0 ? void 0 : _a.id).to.eql('ns1:root'); expect(query.findById('foo:root')).to.eql(undefined); expect((_b = query.findById('ns1:root')) === null || _b === void 0 ? void 0 : _b.id).to.eql('ns1:root'); expect((_c = query.findById('ns1:child-1')) === null || _c === void 0 ? void 0 : _c.id).to.eql('ns1:child-1'); expect((_d = query.findById('ns2:child-2')) === null || _d === void 0 ? void 0 : _d.id).to.eql('ns2:child-2'); expect((_e = query.findById('foo')) === null || _e === void 0 ? void 0 : _e.id).to.eql('foo'); expect((_f = ns1.findById('root')) === null || _f === void 0 ? void 0 : _f.id).to.eql('ns1:root'); expect((_g = ns1.findById('ns1:root')) === null || _g === void 0 ? void 0 : _g.id).to.eql('ns1:root'); expect(ns1.findById('foo:root')).to.eql(undefined); expect(ns1.findById('404')).to.eql(undefined); expect(ns1.findById('ns1:404')).to.eql(undefined); expect((_h = ns1.findById('child-1')) === null || _h === void 0 ? void 0 : _h.id).to.eql('ns1:child-1'); expect((_j = ns1.findById('ns1:child-1')) === null || _j === void 0 ? void 0 : _j.id).to.eql('ns1:child-1'); expect(ns1.findById('child-2')).to.eql(undefined); expect(ns1.findById('ns2:child-2')).to.eql(undefined); expect(ns1.findById('foo')).to.eql(undefined); }); }); describe('parent', () => { it('has a parent', () => { const grandchild = { id: 'grandchild' }; const child = { id: 'child', children: [grandchild] }; const root = { id: 'root', children: [child] }; const query = create(root); expect(query.parent(child)).to.equal(root); expect(query.parent(grandchild)).to.equal(child); }); it('has no parent', () => { const grandchild = { id: 'grandchild' }; const child = { id: 'child', children: [grandchild] }; const root = { id: 'root', children: [] }; const query = create(root); expect(query.parent(child)).to.equal(undefined); expect(query.parent(grandchild)).to.equal(undefined); }); }); describe('ancestor', () => { const tree = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-3' }] }], }; it('matches self (first)', () => { const query = create(tree); const node = query.findById('child-3'); const res = query.ancestor(node, (e) => e.node.id === 'child-3'); expect(res && res.id).to.eql('child-3'); }); it('finds matching ancestor', () => { const query = create(tree); const node = query.findById('child-3'); const res1 = query.ancestor(node, (e) => e.node.id === 'child-2'); const res2 = query.ancestor(node, (e) => e.node.id === 'root'); expect(res1 && res1.id).to.eql('child-2'); expect(res2 && res2.id).to.eql('root'); }); it('no match', () => { const query = create(tree); const node = query.findById('root'); const res = query.ancestor(node, (e) => e.node.id === 'child-1'); expect(res).to.eql(undefined); }); }); describe('depth', () => { const root = { id: 'A', children: [{ id: 'B' }, { id: 'C', children: [{ id: 'D' }] }], }; it('retrieves depth', () => { const query = create(root); expect(query.depth('A')).to.eql(0); expect(query.depth({ id: 'A' })).to.eql(0); expect(query.depth('B')).to.eql(1); expect(query.depth('C')).to.eql(1); expect(query.depth('D')).to.eql(2); expect(query.depth({ id: 'D' })).to.eql(2); }); it('-1', () => { const query = create({ id: 'root' }); expect(query.depth('C')).to.eql(-1); expect(query.depth(undefined)).to.eql(-1); expect(query.depth('NO_EXIST')).to.eql(-1); expect(query.depth({ id: 'NO_EXIST' })).to.eql(-1); }); }); describe('exists', () => { const tree = { id: 'root', children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-3' }] }], }; it('exists', () => { const query = create(tree); expect(query.exists('root')).to.eql(true); expect(query.exists('child-3')).to.eql(true); expect(query.exists({ id: 'root' })).to.eql(true); expect(query.exists({ id: 'child-3' })).to.eql(true); expect(query.exists((e) => e.id === 'child-3')).to.eql(true); }); it('does not exist', () => { const query = create(tree); expect(query.exists('404')).to.eql(false); expect(query.exists({ id: '404' })).to.eql(false); expect(query.exists((e) => e.id === '404')).to.eql(false); }); }); });