UNPKG

52.7 kBJavaScriptView Raw
1import { Subject } from 'rxjs';
2import { TreeState } from '.';
3import { expect } from '../test';
4import { TreeIdentity } from '../TreeIdentity';
5import { TreeQuery } from '../TreeQuery';
6import { helpers } from './helpers';
7import { isDraft } from 'immer';
8const query = TreeQuery.create;
9const create = TreeState.create;
10describe('TreeState', () => {
11 describe('create', () => {
12 it('without parent', () => {
13 const root = { id: 'root' };
14 const tree = create({ root });
15 expect(tree.state).to.not.equal(root);
16 expect(tree.parent).to.eql(undefined);
17 expect(tree.children).to.eql([]);
18 expect(tree.id).to.eql(tree.state.id);
19 expect(tree.key).to.eql('root');
20 expect(tree.namespace.length).to.greaterThan(10);
21 });
22 it('with parent', () => {
23 const root = { id: 'myLeaf' };
24 const tree = create({ root, parent: 'myParent' });
25 expect(tree.parent).to.eql('myParent');
26 });
27 it('create with no id (defaults to "node")', () => {
28 const tree = create();
29 expect(helpers.id.stripNamespace(tree.id)).to.eql('node');
30 expect(helpers.id.namespace(tree.id)).to.eql(tree.namespace);
31 });
32 it('from root id (string)', () => {
33 const tree = create({ root: 'foo' });
34 const id = `${tree.namespace}:foo`;
35 expect(tree.id).to.eql(id);
36 expect(tree.state.id).to.eql(id);
37 });
38 it('from root id (parses <namespace>:<id>)', () => {
39 const tree = create({ root: 'ns:foo' });
40 expect(tree.namespace).to.eql('ns');
41 expect(tree.id).to.eql('ns:foo');
42 });
43 it('readonly', () => {
44 const root = { id: 'root' };
45 const tree = create({ root });
46 expect(tree.readonly).to.equal(tree);
47 });
48 it('throw: id contains "/" character', () => {
49 const fn = () => create({ root: 'foo/bar' });
50 expect(fn).to.throw(/Tree node IDs cannot contain the "\/" character/);
51 });
52 });
53 describe('dispose', () => {
54 it('dispose', () => {
55 const tree = create();
56 expect(tree.isDisposed).to.eql(false);
57 let count = 0;
58 tree.dispose$.subscribe((e) => count++);
59 expect(tree.isDisposed).to.eql(false);
60 tree.dispose();
61 tree.dispose();
62 expect(tree.isDisposed).to.eql(true);
63 });
64 it('dispose: event', () => {
65 const tree = create();
66 tree.change((root, ctx) => ctx.props(root, (p) => (p.label = 'foo')));
67 const fired = [];
68 tree.event.$.subscribe((e) => fired.push(e));
69 tree.dispose();
70 tree.dispose();
71 expect(fired.length).to.eql(1);
72 const event = fired[0];
73 expect(event.type).to.eql('TreeState/disposed');
74 expect(event.payload.final).to.eql(tree.state);
75 });
76 it('disposes of all children', () => {
77 const tree = create();
78 expect(tree.isDisposed).to.eql(false);
79 const child1 = tree.add({ root: 'foo' });
80 const child2 = child1.add({ root: 'bar' });
81 const child3 = child2.add({ root: 'zoo' });
82 child2.dispose();
83 expect(child3.isDisposed).to.eql(true);
84 tree.dispose();
85 expect(tree.isDisposed).to.eql(true);
86 expect(child1.isDisposed).to.eql(true);
87 expect(child2.isDisposed).to.eql(true);
88 expect(child3.isDisposed).to.eql(true);
89 });
90 it('takes a [dispose$] within constructor', () => {
91 const dispose$ = new Subject();
92 const tree = create({ dispose$ });
93 expect(tree.isDisposed).to.eql(false);
94 let count = 0;
95 tree.dispose$.subscribe(() => count++);
96 dispose$.next();
97 dispose$.next();
98 dispose$.next();
99 expect(tree.isDisposed).to.eql(true);
100 expect(count).to.eql(1);
101 });
102 });
103 describe('static', () => {
104 it('isInstance', () => {
105 const test = (input, expected) => {
106 expect(TreeState.isInstance(input)).to.eql(expected);
107 };
108 const instance = create({ root: 'foo' });
109 test(instance, true);
110 test(undefined, false);
111 test(null, false);
112 test('', false);
113 test({ id: 'foo' }, false);
114 });
115 it('identity', () => {
116 expect(TreeState.identity).to.equal(TreeIdentity);
117 });
118 });
119 describe('rewrite IDs with namespace prefix', () => {
120 it('simple', () => {
121 const root = { id: 'root' };
122 const tree = create({ root });
123 const start = `${tree.namespace}:`;
124 expect(tree.id.startsWith(start)).to.eql(true);
125 expect(tree.state.id.startsWith(start)).to.eql(true);
126 });
127 it('deep', () => {
128 const root = {
129 id: 'root',
130 children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-2-1' }] }],
131 };
132 const tree = create({ root });
133 const ids = [];
134 const start = `${tree.namespace}:`;
135 query(tree.state).walkDown((e) => ids.push(e.node.id));
136 expect(ids.length).to.eql(4);
137 expect(ids.every((id) => id.startsWith(start))).to.eql(true);
138 });
139 });
140 describe('add', () => {
141 it('add: root as {object} (TreeNode)', () => {
142 const root = { id: 'root' };
143 const tree = create({ root });
144 expect(tree.children).to.eql([]);
145 const child = tree.add({ root: { id: 'foo' } });
146 expect(tree.children.length).to.eql(1);
147 expect(tree.children[0]).to.equal(child);
148 expect(child.parent).to.eql(`${tree.namespace}:root`);
149 });
150 it('add: root as string ("id")', () => {
151 const root = { id: 'root' };
152 const tree = create({ root });
153 const child = tree.add({ root: 'foo' });
154 expect(tree.children.length).to.eql(1);
155 expect(child.id).to.eql(`${child.namespace}:foo`);
156 });
157 it('add (pre-existing): { root: [TreeState] }', () => {
158 const tree = create({ root: 'root' });
159 expect(tree.state.children).to.eql(undefined);
160 const child = create({ root: 'foo' });
161 expect(tree.namespace).to.not.eql(child.namespace);
162 tree.add({ root: child });
163 expect(helpers.children(tree.state)[0].id).to.eql(child.id);
164 expect(tree.children.includes(child)).to.eql(true);
165 });
166 it('add (pre-existing): [TreeState] as base argument', () => {
167 const tree = create({ root: 'root' });
168 expect(tree.state.children).to.eql(undefined);
169 const child = create({ root: 'foo' });
170 expect(tree.namespace).to.not.eql(child.namespace);
171 tree.add(child);
172 expect(tree.children.includes(child)).to.eql(true);
173 expect(helpers.children(tree.state)[0].id).to.eql(child.id);
174 });
175 it('add (pre-existing): [TreeState] as base argument', () => {
176 const tree = create({ root: 'root' });
177 expect(tree.state.children).to.eql(undefined);
178 const child = create({ root: 'foo' });
179 expect(tree.namespace).to.not.eql(child.namespace);
180 tree.add(child);
181 expect(tree.children.includes(child)).to.eql(true);
182 expect(helpers.children(tree.state)[0].id).to.eql(child.id);
183 });
184 it('add (pre-existing): within sub-node of parent', () => {
185 var _a;
186 const tree = create({ root: { id: 'root', children: [{ id: 'foo' }] } });
187 const subnode = tree.query.find((e) => e.key === 'foo');
188 expect(subnode === null || subnode === void 0 ? void 0 : subnode.id.endsWith(':foo')).to.eql(true);
189 const child = create({ root: 'child' });
190 tree.add({ root: child, parent: subnode === null || subnode === void 0 ? void 0 : subnode.id });
191 expect((_a = tree.children.find((e) => e.id === child.id)) === null || _a === void 0 ? void 0 : _a.id).to.eql(child.id);
192 const children = helpers.children(tree.state);
193 expect(children.length).to.eql(1);
194 const grandchildren = children[0].children || [];
195 expect(grandchildren.length).to.eql(1);
196 expect(grandchildren[0].id).to.eql(child.id);
197 });
198 it('add: no parent id (root id assumed)', () => {
199 const root = { id: 'root' };
200 const tree = create({ root });
201 const child = tree.add({ root: 'foo' });
202 expect(tree.children.length).to.eql(1);
203 expect(child.id).to.eql(`${child.namespace}:foo`);
204 });
205 it('adds multiple children with same id (geneated namespaces differs)', () => {
206 const root = { id: 'root' };
207 const tree = create({ root });
208 const child1 = tree.add({ root: { id: 'foo' } });
209 const child2 = tree.add({ root: { id: 'foo' } });
210 expect(child1.id).to.not.eql(child2.id);
211 });
212 it('inserts node into parent state-tree data', () => {
213 const root = { id: 'root', children: [{ id: 'mary' }] };
214 const tree = create({ root });
215 expect((tree.state.children || []).length).to.eql(1);
216 const fired = [];
217 tree.event
218 .payload('TreeState/changed')
219 .subscribe((e) => fired.push(e));
220 const child1 = tree.add({ root: { id: 'foo' } });
221 const children = tree.state.children || [];
222 expect(children.length).to.eql(2);
223 expect(children[0].id).to.match(/\:mary$/);
224 expect(children[1].id).to.eql(child1.id);
225 expect(fired.length).to.eql(1);
226 expect((fired[0].from.children || []).length).to.eql(1);
227 expect((fired[0].to.children || []).length).to.eql(2);
228 });
229 it('child added to more than one parent [StateTree]', () => {
230 const state1 = create({ root: 'root-1' });
231 const state2 = create({ root: 'root-2' });
232 const child = create({ root: 'child' });
233 expect(state1.namespace).to.not.eql(state2.namespace);
234 state1.add(child);
235 state2.add(child);
236 const childAt = (state, i) => helpers.children(state.state)[i];
237 const firstChild = (state) => childAt(state, 0);
238 expect(firstChild(state1).id).to.eql(child.id);
239 expect(firstChild(state2).id).to.eql(child.id);
240 child.change((draft, ctx) => {
241 ctx.props(draft).label = 'hello';
242 });
243 expect(firstChild(state1).props).to.eql({ label: 'hello' });
244 expect(firstChild(state2).props).to.eql({ label: 'hello' });
245 state1.remove(child);
246 expect(firstChild(state1)).to.eql(undefined);
247 expect(firstChild(state2).id).to.eql(child.id);
248 child.dispose();
249 expect(firstChild(state1)).to.eql(undefined);
250 expect(firstChild(state2)).to.eql(undefined);
251 });
252 it('event: added', () => {
253 const root = { id: 'root' };
254 const tree = create({ root });
255 const fired = [];
256 tree.event
257 .payload('TreeState/child/added')
258 .subscribe((e) => fired.push(e));
259 const child1 = tree.add({ root: { id: 'foo' } });
260 const child2 = tree.add({ root: { id: 'foo' } });
261 expect(fired.length).to.eql(2);
262 expect(fired[0].child).to.equal(child1);
263 expect(fired[1].child).to.equal(child2);
264 expect(fired[0].parent).to.equal(tree);
265 expect(fired[1].parent).to.equal(tree);
266 });
267 it('throw: "parent" does not exist', () => {
268 const tree = create({ root: { id: 'root' } });
269 const fn = () => tree.add({ parent: '404', root: { id: 'foo' } });
270 expect(fn).to.throw(/parent node '404' does not exist/);
271 });
272 it('throw: (pre-existing) "parent" does not exist', () => {
273 const tree = create({ root: { id: 'root' } });
274 const child = create({ root: 'child' });
275 const fn = () => tree.add({ parent: '404', root: child });
276 expect(fn).to.throw(new RegExp(`parent sub-node '404' within '${tree.id}' does not exist`));
277 });
278 it('throw: child already added', () => {
279 const tree = create({ root: 'root' });
280 const child = tree.add({ root: 'child' });
281 expect(tree.children.length).to.eql(1);
282 expect(tree.namespace).to.not.eql(child.namespace);
283 const fn = () => tree.add({ root: child });
284 expect(fn).to.throw(/already exists/);
285 });
286 });
287 describe('remove', () => {
288 it('removes (but does not dispose)', () => {
289 const root = { id: 'root' };
290 const tree = create({ root });
291 const fired = [];
292 tree.event
293 .payload('TreeState/child/removed')
294 .subscribe((e) => fired.push(e));
295 const child1 = tree.add({ parent: 'root', root: { id: 'foo' } });
296 const child2 = tree.add({ parent: 'root', root: { id: 'foo' } });
297 expect(tree.children.length).to.eql(2);
298 tree.remove(child1);
299 expect(tree.children.length).to.eql(1);
300 tree.remove(child2);
301 expect(tree.children.length).to.eql(0);
302 expect(child1.isDisposed).to.eql(false);
303 expect(child2.isDisposed).to.eql(false);
304 expect(fired.length).to.eql(2);
305 expect(fired[0].child).to.eql(child1);
306 expect(fired[1].child).to.eql(child2);
307 });
308 it('removes on [child.dispose()]', () => {
309 const root = { id: 'root' };
310 const tree = create({ root });
311 const fired = [];
312 tree.event
313 .payload('TreeState/child/removed')
314 .subscribe((e) => fired.push(e));
315 const child1 = tree.add({ parent: 'root', root: { id: 'foo' } });
316 const child2 = tree.add({ parent: 'root', root: 'foo' });
317 expect(tree.children.length).to.eql(2);
318 child1.dispose();
319 expect(tree.children.length).to.eql(1);
320 child2.dispose();
321 expect(tree.children.length).to.eql(0);
322 expect(fired.length).to.eql(2);
323 expect(fired[0].child).to.eql(child1);
324 expect(fired[1].child).to.eql(child2);
325 });
326 it('removes node from parent state-tree data', () => {
327 const root = { id: 'root' };
328 const tree = create({ root });
329 const child1 = tree.add({ parent: 'root', root: 'foo' });
330 const child2 = tree.add({ parent: 'root', root: 'foo' });
331 const children = () => tree.state.children || [];
332 const count = () => children().length;
333 const includes = (id) => (tree.state.children || []).some((c) => c.id === id);
334 expect(count()).to.eql(2);
335 expect(includes(child1.id)).to.eql(true);
336 expect(includes(child2.id)).to.eql(true);
337 child1.dispose();
338 expect(count()).to.eql(1);
339 expect(includes(child1.id)).to.eql(false);
340 expect(includes(child2.id)).to.eql(true);
341 tree.remove(child2);
342 expect(count()).to.eql(0);
343 expect(includes(child1.id)).to.eql(false);
344 expect(includes(child2.id)).to.eql(false);
345 });
346 it('throw: remove child that does not exist', () => {
347 const root = { id: 'root' };
348 const tree = create({ root });
349 const child1 = tree.add({ parent: 'root', root: { id: 'foo' } });
350 const child2 = tree.add({ parent: 'root', root: { id: 'foo' } });
351 const test = (child) => {
352 const tree = create({ root });
353 const fn = () => tree.remove(child);
354 expect(fn).to.throw(/Cannot remove child-state as it does not exist in the parent/);
355 };
356 test(child1.state.id);
357 test(child1.id);
358 test(child2);
359 });
360 });
361 describe('clear', () => {
362 it('empty', () => {
363 const root = { id: 'root' };
364 const tree = create({ root });
365 expect(tree.children.length).to.eql(0);
366 tree.clear();
367 expect(tree.children.length).to.eql(0);
368 });
369 it('removes all children', () => {
370 const root = { id: 'root' };
371 const tree = create({ root });
372 const fired = [];
373 tree.event.childRemoved$.subscribe((e) => fired.push(e));
374 const parent = 'root';
375 tree.add({ parent, root: 'foo' });
376 tree.add({ parent, root: 'bar' });
377 expect(tree.children.length).to.eql(2);
378 tree.clear();
379 expect(tree.children.length).to.eql(0);
380 expect(fired.length).to.eql(2);
381 });
382 });
383 describe('change', () => {
384 const root = {
385 id: 'root',
386 children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-2-1' }] }],
387 };
388 it('simple', () => {
389 var _a, _b, _c, _d, _e;
390 const tree = create({ root });
391 const res = tree.change((draft, ctx) => {
392 ctx.props(draft, (p) => {
393 p.label = 'Hello!';
394 p.icon = 'face';
395 });
396 });
397 expect((_a = tree.state.props) === null || _a === void 0 ? void 0 : _a.label).to.eql('Hello!');
398 expect((_b = tree.state.props) === null || _b === void 0 ? void 0 : _b.icon).to.eql('face');
399 expect(res.op).to.eql('update');
400 expect(res.cid.length).to.greaterThan(10);
401 expect((_c = res.changed) === null || _c === void 0 ? void 0 : _c.from.props).to.eql(undefined);
402 expect((_e = (_d = res.changed) === null || _d === void 0 ? void 0 : _d.to.props) === null || _e === void 0 ? void 0 : _e.label).to.eql('Hello!');
403 const { prev, next } = res.patches;
404 expect(prev.length).to.eql(1);
405 expect(next.length).to.eql(1);
406 expect(prev[0]).to.eql({ op: 'remove', path: 'props' });
407 expect(next[0]).to.eql({
408 op: 'add',
409 path: 'props',
410 value: { label: 'Hello!', icon: 'face' },
411 });
412 });
413 it('child array: insert (updates id namespaces)', () => {
414 const tree = create({ root: 'root' });
415 tree.change((draft, ctx) => {
416 const children = TreeState.children(draft);
417 children.push(...[{ id: 'foo', children: [{ id: 'foo.1' }] }, { id: 'bar' }]);
418 });
419 const ns = TreeState.identity.hasNamespace;
420 const children = tree.state.children || [];
421 expect(children.length).to.eql(2);
422 expect(ns(children[0].id)).to.eql(true);
423 expect(ns(children[1].id)).to.eql(true);
424 expect(ns((children[0].children || [])[0].id)).to.eql(true);
425 });
426 it('updates parent state-tree when child changes', () => {
427 const tree = create({ root });
428 const child1 = tree.add({ root: 'foo' });
429 const child2 = tree.add({ root: 'bar' });
430 const children = () => tree.state.children || [];
431 const count = () => children().length;
432 expect(count()).to.eql(4);
433 expect(children()[2].props).to.eql(undefined);
434 child1.change((draft, ctx) => ctx.props(draft, (p) => (p.label = 'foo')));
435 expect(children()[2].props).to.eql({ label: 'foo' });
436 child1.dispose();
437 child1.change((draft, ctx) => ctx.props(draft, (p) => (p.label = 'bar')));
438 expect(count()).to.eql(3);
439 child2.change((root, ctx) => ctx.props(root, (p) => (p.label = 'hello')));
440 expect(children()[2].props).to.eql({ label: 'hello' });
441 });
442 it('updates parent state-tree when child changes (deep)', () => {
443 const tree = create({ root: { id: 'root' } });
444 const child1 = tree.add({ root: 'foo' });
445 const child2 = child1.add({ root: 'bar' });
446 const children = (node) => node.children || [];
447 const grandchild = () => children(children(tree.state)[0])[0];
448 expect(grandchild().props).to.eql(undefined);
449 child2.change((draft, ctx) => (ctx.props(draft).label = 'hello'));
450 expect(grandchild().props).to.eql({ label: 'hello' });
451 });
452 it('updates from found/queried node', () => {
453 var _a, _b;
454 const tree = create({ root });
455 expect((_a = tree.query.findById('child-1')) === null || _a === void 0 ? void 0 : _a.props).to.eql(undefined);
456 tree.change((draft, ctx) => {
457 const child = ctx.findById('child-1');
458 if (child) {
459 ctx.props(child, (p) => (p.label = 'hello'));
460 }
461 });
462 expect((_b = tree.query.findById('child-1')) === null || _b === void 0 ? void 0 : _b.props).to.eql({ label: 'hello' });
463 });
464 });
465 describe('change (events)', () => {
466 const root = {
467 id: 'root',
468 children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-2-1' }] }],
469 };
470 it('event: changed', () => {
471 var _a, _b;
472 const tree = create({ root });
473 const fired = [];
474 tree.event
475 .payload('TreeState/changed')
476 .subscribe((e) => fired.push(e));
477 const res = tree.change((root, ctx) => {
478 ctx.props(root, (p) => (p.label = 'foo'));
479 });
480 expect(fired.length).to.eql(1);
481 const event = fired[0];
482 expect((_a = event.from.props) === null || _a === void 0 ? void 0 : _a.label).to.eql(undefined);
483 expect((_b = event.to.props) === null || _b === void 0 ? void 0 : _b.label).to.eql('foo');
484 expect(event.patches).to.eql(res.patches);
485 });
486 it('event: changed (fires from root when child changes)', () => {
487 const tree = create({ root });
488 const child = tree.add({ root: 'foo' });
489 const firedRoot = [];
490 tree.event
491 .payload('TreeState/changed')
492 .subscribe((e) => firedRoot.push(e));
493 const firedChild = [];
494 child.event
495 .payload('TreeState/changed')
496 .subscribe((e) => firedChild.push(e));
497 child.change((draft, ctx) => ctx.props(draft, (p) => (p.label = 'foo')));
498 expect(firedRoot.length).to.eql(1);
499 expect(firedChild.length).to.eql(1);
500 expect(firedRoot[0].patches.next[0].op).to.eql('replace');
501 expect(firedChild[0].patches.next[0].op).to.eql('add');
502 });
503 it('event: patched', () => {
504 const tree = create({ root });
505 const fired = [];
506 tree.event
507 .payload('TreeState/patched')
508 .subscribe((e) => fired.push(e));
509 const res = tree.change((root, ctx) => {
510 ctx.props(root, (p) => (p.label = 'foo'));
511 });
512 expect(fired.length).to.eql(1);
513 const event = fired[0];
514 expect(event.prev).to.eql(res.patches.prev);
515 expect(event.next).to.eql(res.patches.next);
516 });
517 it('event: does not fire when nothing changes', () => {
518 const tree = create({ root });
519 const fired = [];
520 tree.event
521 .payload('TreeState/changed')
522 .subscribe((e) => fired.push(e));
523 const res = tree.change((root) => {
524 });
525 expect(fired.length).to.eql(0);
526 expect(res.changed).to.eql(undefined);
527 });
528 });
529 describe('change: ctx (context)', () => {
530 const root = {
531 id: 'root',
532 children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-2-1' }] }],
533 };
534 it('toObject', () => {
535 const tree = create({ root });
536 tree.change((draft, ctx) => {
537 const child = ctx.findById('child-2-1');
538 expect(child).to.exist;
539 if (child) {
540 expect(isDraft(draft)).to.eql(true);
541 expect(isDraft(child)).to.eql(true);
542 expect(isDraft(ctx.toObject(draft))).to.eql(false);
543 expect(isDraft(ctx.toObject(child))).to.eql(false);
544 }
545 });
546 });
547 });
548 describe('query', () => {
549 const root = {
550 id: 'root',
551 children: [{ id: 'child-1' }, { id: 'child-2', children: [{ id: 'child-2-1' }] }],
552 };
553 it('has query', () => {
554 const tree = create({ root });
555 const query = tree.query;
556 expect(query.root).to.eql(tree.state);
557 expect(query.namespace).to.eql(tree.namespace);
558 });
559 describe('walkDown', () => {
560 it('walkDown', () => {
561 const tree = create({ root });
562 const walked = [];
563 tree.query.walkDown((e) => {
564 expect(e.namespace).to.eql(tree.namespace);
565 expect(e.node.id.endsWith(`:${e.key}`)).to.eql(true);
566 walked.push(e);
567 });
568 expect(walked.length).to.eql(4);
569 expect(walked[0].key).to.eql('root');
570 expect(walked[3].key).to.eql('child-2-1');
571 });
572 it('walkDown: stop', () => {
573 const tree = create({ root });
574 const walked = [];
575 tree.query.walkDown((e) => {
576 walked.push(e);
577 if (e.key === 'child-1') {
578 e.stop();
579 }
580 });
581 expect(walked.length).to.eql(2);
582 expect(walked[0].key).to.eql('root');
583 expect(walked[1].key).to.eql('child-1');
584 });
585 it('walkDown: skip (children)', () => {
586 const tree = create({ root });
587 const walked = [];
588 tree.query.walkDown((e) => {
589 walked.push(e);
590 if (e.key === 'child-2') {
591 e.skip();
592 }
593 });
594 expect(walked.length).to.eql(3);
595 expect(walked[0].key).to.eql('root');
596 expect(walked[1].key).to.eql('child-1');
597 expect(walked[2].key).to.eql('child-2');
598 });
599 it('walkDown: does not walk down into child namespace', () => {
600 var _a;
601 const tree = create({ root });
602 const child = tree.add({ parent: 'child-2-1', root: { id: 'foo' } });
603 expect(child.namespace).to.not.eql(tree.namespace);
604 expect((_a = query(tree.state).findById(child.id)) === null || _a === void 0 ? void 0 : _a.id).to.eql(child.id);
605 const walked = [];
606 tree.query.walkDown((e) => walked.push(e));
607 const ids = walked.map((e) => e.id);
608 expect(ids.length).to.greaterThan(0);
609 expect(ids.includes('foo')).to.eql(false);
610 });
611 });
612 describe('walkUp', () => {
613 it('walkUp', () => {
614 const tree = create({ root });
615 const test = (startAt) => {
616 const walked = [];
617 tree.query.walkUp(startAt, (e) => walked.push(e));
618 expect(walked.map((e) => e.key)).to.eql(['child-2-1', 'child-2', 'root']);
619 };
620 test('child-2-1');
621 test(tree.query.findById('child-2-1'));
622 });
623 it('walkUp: startAt not found', () => {
624 const tree = create({ root });
625 const test = (startAt) => {
626 const walked = [];
627 tree.query.walkUp(startAt, (e) => walked.push(e));
628 expect(walked).to.eql([]);
629 };
630 test();
631 test('404');
632 test({ id: '404' });
633 });
634 it('walkUp: does not walk up into parent namespace', () => {
635 const tree = create({ root });
636 const child = tree.add({
637 parent: 'child-2-1',
638 root: { id: 'foo', children: [{ id: 'foo.child' }] },
639 });
640 const fooChild = child.query.findById('foo.child');
641 expect(fooChild).to.exist;
642 const test = (startAt) => {
643 const walked = [];
644 child.query.walkUp(startAt, (e) => walked.push(e));
645 expect(walked.map((e) => e.key)).to.eql(['foo.child', 'foo']);
646 };
647 test(fooChild);
648 test(fooChild === null || fooChild === void 0 ? void 0 : fooChild.id);
649 test('foo.child');
650 });
651 it('walkUp: not within namespace', () => {
652 const tree = create({ root });
653 const child = tree.add({
654 parent: 'child-2-1',
655 root: { id: 'foo', children: [{ id: 'foo.child' }] },
656 });
657 const fooChild = child.query.findById('foo.child');
658 expect(fooChild).to.exist;
659 const walked = [];
660 tree.query.walkUp(fooChild, (e) => walked.push(e));
661 expect(walked.map((e) => e.id)).to.eql([]);
662 });
663 });
664 describe('find', () => {
665 it('find', () => {
666 const tree = create({ root });
667 const walked = [];
668 tree.query.walkDown((e) => walked.push(e));
669 const res1 = tree.query.findById('404');
670 const res2 = tree.query.findById('root');
671 const res3 = tree.query.findById('child-2-1');
672 expect(res1).to.eql(undefined);
673 expect(res2).to.eql(walked[0].node);
674 expect(res3).to.eql(walked[3].node);
675 });
676 it('find: root (immediate)', () => {
677 const tree = create({ root: 'root' });
678 const res = tree.query.findById('root');
679 expect(res === null || res === void 0 ? void 0 : res.id).to.eql(tree.id);
680 });
681 it('find: does not walk down into child namespace', () => {
682 var _a, _b, _c, _d;
683 const tree = create({ root });
684 const child = tree.add({ parent: 'child-2-1', root: { id: 'foo' } });
685 expect(child.namespace).to.not.eql(tree.namespace);
686 expect((_a = query(tree.state).findById(child.id)) === null || _a === void 0 ? void 0 : _a.id).to.eql(child.id);
687 expect(tree.query.findById('foo')).to.eql(undefined);
688 expect(TreeIdentity.key((_b = tree.query.findById('child-2-1')) === null || _b === void 0 ? void 0 : _b.id)).to.eql('child-2-1');
689 expect((_c = child.query.findById('foo')) === null || _c === void 0 ? void 0 : _c.id).to.eql(child.id);
690 expect((_d = child.query.findById('child-2-1')) === null || _d === void 0 ? void 0 : _d.id).to.eql(undefined);
691 });
692 });
693 describe('exists', () => {
694 it('does exist', () => {
695 const tree = create({ root });
696 const res = tree.query.findById('child-2-1');
697 expect(TreeState.identity.parse(res === null || res === void 0 ? void 0 : res.id).key).to.eql('child-2-1');
698 });
699 it('does not exist', () => {
700 const tree = create({ root });
701 const res = tree.query.findById('404');
702 expect(res).to.eql(undefined);
703 });
704 });
705 });
706 describe('child (find)', () => {
707 it('empty', () => {
708 const root = { id: 'root' };
709 const tree = create({ root });
710 const list = [];
711 const res = tree.find((e) => {
712 list.push(e);
713 return false;
714 });
715 expect(res).to.eql(undefined);
716 expect(list).to.eql([]);
717 });
718 it('undefined/null', () => {
719 const root = { id: 'root' };
720 const tree = create({ root });
721 expect(tree.find()).to.eql(undefined);
722 expect(tree.find(null)).to.eql(undefined);
723 });
724 it('deep', () => {
725 const tree = create();
726 const child1 = tree.add({ root: 'child-1' });
727 const child2a = child1.add({ root: 'child-2a' });
728 child1.add({ root: 'child-2a' });
729 const child3 = child2a.add({ root: 'child-3' });
730 const list = [];
731 const res = tree.find((e) => {
732 list.push(e);
733 return e.key === 'child-3';
734 });
735 expect(list.length).to.eql(3);
736 expect(res === null || res === void 0 ? void 0 : res.id).to.equal(child3.id);
737 expect(list[0].tree.id).to.eql(child1.id);
738 expect(list[1].tree.id).to.eql(child2a.id);
739 expect(list[2].tree.id).to.eql(child3.id);
740 });
741 it('flat', () => {
742 const root = { id: 'root' };
743 const tree = create({ root });
744 const child1 = tree.add({ root: 'child-1' });
745 const child2 = tree.add({ root: 'child-2' });
746 const child3 = tree.add({ root: 'child-3' });
747 const list = [];
748 const res = tree.find((e) => {
749 list.push(e);
750 return e.key === 'child-3';
751 });
752 expect(res === null || res === void 0 ? void 0 : res.id).to.equal(child3.id);
753 expect(list.length).to.eql(3);
754 expect(list[0].tree.id).to.eql(child1.id);
755 expect(list[1].tree.id).to.eql(child2.id);
756 expect(list[2].tree.id).to.eql(child3.id);
757 });
758 it('via node-identifier (param)', () => {
759 const tree = create();
760 const child1 = tree.add({ root: 'child-1' });
761 const child2a = child1.add({ root: 'child-2a' });
762 expect(tree.find(child2a.id)).to.equal(child2a);
763 expect(tree.find(child2a)).to.equal(child2a);
764 expect(tree.find('404')).to.equal(undefined);
765 });
766 it('node-identifer descendent of child module', () => {
767 const tree = create();
768 const child1 = tree.add({ root: 'child-1' });
769 const child2a = child1.add({ root: { id: 'child-2a', children: [{ id: 'foo' }] } });
770 const query = TreeQuery.create({ root: tree.state });
771 const node = query.find((e) => e.key === 'foo');
772 expect(node).to.exist;
773 expect(tree.find(node)).to.equal(child2a);
774 expect(tree.find(node === null || node === void 0 ? void 0 : node.id)).to.equal(child2a);
775 });
776 it('toString => fully qualified identifier (<namespace>:<id>)', () => {
777 const tree = create();
778 const child1 = tree.add({ root: 'child-1' });
779 child1.add({ root: 'ns:child-2a' });
780 const res1 = tree.find((e) => e.toString() === 'ns:child-2a');
781 const res2 = tree.find((e) => e.id === 'ns:child-2a');
782 expect(res1 === null || res1 === void 0 ? void 0 : res1.id).to.eql('ns:child-2a');
783 expect(res2 === null || res2 === void 0 ? void 0 : res2.id).to.eql('ns:child-2a');
784 });
785 it('stop (walking)', () => {
786 const tree = create();
787 const child1 = tree.add({ root: 'child-1' });
788 const child2a = child1.add({ root: 'child-2a' });
789 child1.add({ root: 'child-2b' });
790 child2a.add({ root: 'child-3' });
791 const list = [];
792 const res = tree.find((e) => {
793 list.push(e);
794 if (e.key === 'child-2a') {
795 e.stop();
796 }
797 return e.key === 'child-3';
798 });
799 expect(list.map((e) => e.key)).to.eql(['child-1', 'child-2a']);
800 expect(res).to.eql(undefined);
801 });
802 });
803 describe('contains', () => {
804 const tree = create();
805 const child1 = tree.add({ root: 'ns1:child-1' });
806 const child2a = child1.add({
807 root: { id: 'child-2a', children: [{ id: 'foo' }, { id: 'ns2:bar' }] },
808 });
809 const child3 = create({ root: 'child-3' });
810 const query = TreeQuery.create({ root: tree.state });
811 const foo = query.find((e) => e.key === 'foo');
812 const bar = query.find((e) => e.key === 'bar');
813 it('empty', () => {
814 expect(tree.contains('')).to.eql(false);
815 expect(tree.contains(' ')).to.eql(false);
816 expect(tree.contains(undefined)).to.eql(false);
817 expect(tree.contains(null)).to.eql(false);
818 });
819 it('does not contain', () => {
820 expect(tree.contains('404')).to.eql(false);
821 expect(tree.contains({ id: '404' })).to.eql(false);
822 expect(tree.contains((e) => false)).to.eql(false);
823 expect(tree.contains(child3)).to.eql(false);
824 expect(tree.contains(tree)).to.eql(false);
825 });
826 it('does contain (via match function)', () => {
827 const res = tree.contains((e) => e.id === child2a.id);
828 expect(res).to.eql(true);
829 });
830 it('does contain (via node-identifier)', () => {
831 expect(foo).to.exist;
832 expect(bar).to.exist;
833 expect(tree.contains(child2a.id)).to.eql(true);
834 expect(tree.contains(child2a)).to.eql(true);
835 expect(tree.contains(foo)).to.eql(true);
836 expect(tree.contains(foo === null || foo === void 0 ? void 0 : foo.id)).to.eql(true);
837 });
838 it('does not contain child nodes within different descendent namespace', () => {
839 expect(tree.contains(bar)).to.eql(false);
840 expect(tree.contains(bar === null || bar === void 0 ? void 0 : bar.id)).to.eql(false);
841 });
842 });
843 describe('walkDown', () => {
844 const tree = create({ root: 'root' });
845 const child1 = tree.add({ root: { id: 'child-1' } });
846 const child2 = child1.add({ root: { id: 'child-2' } });
847 const child3 = child1.add({ root: { id: 'child-3' } });
848 const child4 = child3.add({ root: { id: 'child-4' } });
849 it('walkDown: no children (visits root)', () => {
850 const tree = create({ root: 'root' });
851 const items = [];
852 tree.walkDown((e) => items.push(e));
853 expect(items.length).to.eql(1);
854 expect(items[0].id).to.eql(tree.id);
855 expect(items[0].key).to.eql(tree.key);
856 expect(items[0].namespace).to.eql(tree.namespace);
857 expect(items[0].level).to.eql(0);
858 expect(items[0].index).to.eql(-1);
859 });
860 it('walkDown: deep', () => {
861 var _a;
862 const items = [];
863 tree.walkDown((e) => items.push(e));
864 expect(items.length).to.eql(5);
865 expect(items[0].id).to.eql(tree.id);
866 expect(items[1].id).to.eql(child1.id);
867 expect(items[2].id).to.eql(child2.id);
868 expect(items[3].id).to.eql(child3.id);
869 expect(items[4].id).to.eql(child4.id);
870 expect(items[0].level).to.eql(0);
871 expect(items[1].level).to.eql(1);
872 expect(items[2].level).to.eql(2);
873 expect(items[3].level).to.eql(2);
874 expect(items[4].level).to.eql(3);
875 expect(items[0].index).to.eql(-1);
876 expect(items[1].index).to.eql(0);
877 expect(items[2].index).to.eql(0);
878 expect(items[3].index).to.eql(1);
879 expect(items[4].index).to.eql(0);
880 expect(items[0].parent).to.eql(undefined);
881 expect((_a = items[1].parent) === null || _a === void 0 ? void 0 : _a.id).to.eql(tree.id);
882 });
883 it('walkDown: stop', () => {
884 const items = [];
885 tree.walkDown((e) => {
886 if (e.level > 0) {
887 e.stop();
888 }
889 items.push(e);
890 });
891 expect(items.length).to.eql(2);
892 expect(items[0].id).to.eql(tree.id);
893 expect(items[1].id).to.eql(child1.id);
894 });
895 it('walkDown: skip', () => {
896 const items = [];
897 tree.walkDown((e) => {
898 if (e.key === 'child-3') {
899 e.skip();
900 }
901 items.push(e);
902 });
903 expect(items.length).to.eql(4);
904 expect(items.map((e) => e.key)).to.not.include('child-4');
905 });
906 });
907 describe('syncFrom', () => {
908 const tree1 = {
909 id: 'foo:tree',
910 children: [{ id: 'foo:child-1' }, { id: 'foo:child-2' }],
911 };
912 const tree2 = {
913 id: 'bar:tree',
914 props: { label: 'hello' },
915 children: [{ id: 'bar:child-1' }, { id: 'bar:child-2' }],
916 };
917 const tree3 = {
918 id: 'zoo:tree',
919 children: [{ id: 'zoo:child-1' }, { id: 'zoo:child-2' }],
920 };
921 it('inserts within parent (new node)', () => {
922 var _a, _b;
923 const state1 = create({ root: tree1 });
924 const state2 = create({ root: tree2, parent: 'foo:child-1' });
925 const state3 = create({ root: tree3, parent: 'foo:child-1' });
926 expect((_a = state1.query.findById('foo:child-1')) === null || _a === void 0 ? void 0 : _a.children).to.eql(undefined);
927 const res1 = state1.syncFrom({ source: state2 });
928 const res2 = state1.syncFrom({ source: state3 });
929 expect(res1.parent).to.eql('foo:child-1');
930 expect(res2.parent).to.eql('foo:child-1');
931 expect(state1.query.findById('foo:child-2')).to.eql({ id: 'foo:child-2' });
932 const node = state1.query.findById('foo:child-1');
933 expect(node === null || node === void 0 ? void 0 : node.props).to.eql(undefined);
934 expect((_b = node === null || node === void 0 ? void 0 : node.children) === null || _b === void 0 ? void 0 : _b.length).to.eql(2);
935 expect(((node === null || node === void 0 ? void 0 : node.children) || [])[0]).to.eql(state2.state);
936 expect(((node === null || node === void 0 ? void 0 : node.children) || [])[1]).to.eql(state3.state);
937 });
938 it('inserts within parent (replace existing node)', () => {
939 var _a;
940 const state1 = create({ root: tree1 });
941 const state2 = create({ root: tree2, parent: 'foo:child-1' });
942 const state3 = create({ root: tree3, parent: 'foo:child-1' });
943 state1.change((draft, ctx) => {
944 const node = ctx.findById('foo:child-1');
945 if (node) {
946 ctx.children(node, (children) => {
947 children.push({ id: 'zoo:tree', props: { label: 'banging' } });
948 });
949 }
950 });
951 const res1 = state1.syncFrom({ source: state2 });
952 const res2 = state1.syncFrom({ source: state3 });
953 expect(res1.parent).to.eql('foo:child-1');
954 expect(res2.parent).to.eql('foo:child-1');
955 const node = state1.query.findById('foo:child-1');
956 expect(node === null || node === void 0 ? void 0 : node.props).to.eql(undefined);
957 expect((_a = node === null || node === void 0 ? void 0 : node.children) === null || _a === void 0 ? void 0 : _a.length).to.eql(2);
958 expect(((node === null || node === void 0 ? void 0 : node.children) || [])[0]).to.eql(state3.state);
959 expect(((node === null || node === void 0 ? void 0 : node.children) || [])[1]).to.eql(state2.state);
960 });
961 it('no initial value inserted into target (observable passed rather than [TreeState])]', () => {
962 var _a, _b;
963 const state1 = create({ root: tree1 });
964 const state2 = create({ root: tree2, parent: 'foo:child-1' });
965 expect(((_a = state1.query.findById('foo:child-1')) === null || _a === void 0 ? void 0 : _a.children) || []).to.eql([]);
966 const res = state1.syncFrom({ source: { event$: state2.event.$, parent: 'foo:child-1' } });
967 expect(res.parent).to.eql('foo:child-1');
968 expect(((_b = state1.query.findById('foo:child-1')) === null || _b === void 0 ? void 0 : _b.children) || []).to.eql([]);
969 });
970 it('stays in sync', () => {
971 var _a, _b, _c, _d, _e;
972 const state1 = create({ root: tree1 });
973 const state2 = create({ root: tree2, parent: 'foo:child-1' });
974 const state3 = create({ root: tree3, parent: 'foo:child-1' });
975 state1.syncFrom({ source: state2 });
976 state1.syncFrom({ source: state3 });
977 const getChildren = () => { var _a; return ((_a = state1.query.findById('foo:child-1')) === null || _a === void 0 ? void 0 : _a.children) || []; };
978 let children = getChildren();
979 expect(children).to.eql([state2.state, state3.state]);
980 state2.change((draft, ctx) => {
981 ctx.children(draft, (children) => {
982 ctx.props(draft, (props) => (props.label = 'derp'));
983 ctx.props(children[0], (props) => (props.label = 'boo'));
984 children.pop();
985 });
986 });
987 children = getChildren();
988 expect((_a = children[0].props) === null || _a === void 0 ? void 0 : _a.label).to.eql('derp');
989 expect((_b = children[0].children) === null || _b === void 0 ? void 0 : _b.length).to.eql(1);
990 expect((_c = (children[0].children || [])[0].props) === null || _c === void 0 ? void 0 : _c.label).to.eql('boo');
991 state3.change((draft, ctx) => {
992 ctx.props(draft, (props) => (props.label = 'barry'));
993 });
994 children = getChildren();
995 expect((_d = children[0].props) === null || _d === void 0 ? void 0 : _d.label).to.eql('derp');
996 expect((_e = children[1].props) === null || _e === void 0 ? void 0 : _e.label).to.eql('barry');
997 });
998 it('stops syncing on dispose()', () => {
999 var _a, _b, _c;
1000 const state1 = create({ root: tree1 });
1001 const state2 = create({ root: tree2, parent: 'foo:child-1' });
1002 const state3 = create({ root: tree3, parent: 'foo:child-1' });
1003 const res1 = state1.syncFrom({ source: state2 });
1004 const res2 = state1.syncFrom({ source: state3 });
1005 const disposed = { res1: false, res2: false };
1006 res1.dispose$.subscribe(() => (disposed.res1 = true));
1007 res2.dispose$.subscribe(() => (disposed.res2 = true));
1008 const getChildren = () => { var _a; return ((_a = state1.query.findById('foo:child-1')) === null || _a === void 0 ? void 0 : _a.children) || []; };
1009 expect(disposed.res1).to.eql(false);
1010 expect(disposed.res2).to.eql(false);
1011 res1.dispose();
1012 expect(disposed.res1).to.eql(true);
1013 expect(disposed.res2).to.eql(false);
1014 state2.change((draft, ctx) => {
1015 ctx.props(draft, (props) => (props.label = 'change-1'));
1016 });
1017 state3.change((draft, ctx) => {
1018 ctx.props(draft, (props) => (props.label = 'change-2'));
1019 });
1020 let children = getChildren();
1021 expect((_a = children[0].props) === null || _a === void 0 ? void 0 : _a.label).to.eql('hello');
1022 expect((_b = children[1].props) === null || _b === void 0 ? void 0 : _b.label).to.eql('change-2');
1023 res2.dispose();
1024 expect(disposed.res2).to.eql(true);
1025 state3.change((draft, ctx) => {
1026 ctx.props(draft, (props) => (props.label = 'change-3'));
1027 });
1028 children = getChildren();
1029 expect((_c = children[1].props) === null || _c === void 0 ? void 0 : _c.label).to.eql('change-2');
1030 });
1031 it('stops syncing when [until$] fires', () => {
1032 var _a, _b;
1033 const state1 = create({ root: tree1 });
1034 const state2 = create({ root: tree2, parent: 'foo:child-1' });
1035 const until$ = new Subject();
1036 state1.syncFrom({ source: state2, until$ });
1037 const getChildren = () => { var _a; return ((_a = state1.query.findById('foo:child-1')) === null || _a === void 0 ? void 0 : _a.children) || []; };
1038 state2.change((draft, ctx) => {
1039 ctx.props(draft, (props) => (props.label = 'change-1'));
1040 });
1041 let children = getChildren();
1042 expect((_a = children[0].props) === null || _a === void 0 ? void 0 : _a.label).to.eql('change-1');
1043 until$.next();
1044 state2.change((draft, ctx) => {
1045 ctx.props(draft, (props) => (props.label = 'change-2'));
1046 });
1047 children = getChildren();
1048 expect((_b = children[0].props) === null || _b === void 0 ? void 0 : _b.label).to.eql('change-1');
1049 });
1050 it('throw: parent not given', () => {
1051 const test = (source) => {
1052 const tree = create({ root: tree1 });
1053 const fn = () => tree.syncFrom({ source });
1054 expect(fn).to.throw(/parent node not specified/);
1055 };
1056 test(create({ root: tree2, parent: '' }));
1057 test(create({ root: tree2, parent: ' ' }));
1058 const tree = create({ root: tree2 });
1059 test({ event$: tree.event.$, parent: '' });
1060 test({ event$: tree.event.$, parent: ' ' });
1061 });
1062 it('throw: parent does not exist', () => {
1063 const state1 = create({ root: tree1 });
1064 const state2 = create({ root: tree2, parent: '404' });
1065 const fn = () => state1.syncFrom({ source: state2 });
1066 expect(fn).to.throw(/parent node '404' does not exist in tree/);
1067 });
1068 });
1069 describe('path', () => {
1070 const tree = create({ root: 'root' });
1071 const child1 = tree.add({ root: { id: 'child-1' } });
1072 const child2 = child1.add({ root: { id: 'child-2' } });
1073 const child3 = child1.add({ root: { id: 'child-3' } });
1074 it('path.from: empty', () => {
1075 expect(tree.path.from('404')).to.eql('');
1076 expect(tree.path.from(' ')).to.eql('');
1077 expect(tree.path.from(undefined)).to.eql('');
1078 expect(tree.path.from(null)).to.eql('');
1079 });
1080 it('path.from: shallow (root)', () => {
1081 expect(tree.path.from(tree)).to.eql(tree.id);
1082 expect(tree.path.from(tree.id)).to.eql(tree.id);
1083 });
1084 it('path.from: deep', () => {
1085 const path1 = tree.path.from(child1.id);
1086 const path2 = tree.path.from(child2.id);
1087 const path3 = tree.path.from(child3.id);
1088 expect(path1).to.eql(`${tree.id}/${child1.id}`);
1089 expect(path2).to.eql(`${tree.id}/${child1.id}/${child2.id}`);
1090 expect(path3).to.eql(`${tree.id}/${child1.id}/${child3.id}`);
1091 });
1092 it('path.get: not found (undefined)', () => {
1093 expect(tree.path.get('foo/404')).to.eql(undefined);
1094 });
1095 it('path.get: root', () => {
1096 const path = tree.path.from(tree);
1097 const res = tree.path.get(path);
1098 expect(res === null || res === void 0 ? void 0 : res.id).to.eql(tree.id);
1099 });
1100 it('path.get: deep', () => {
1101 const path = tree.path.from(child2.id);
1102 const res = tree.path.get(path);
1103 expect(res === null || res === void 0 ? void 0 : res.id).to.eql(child2.id);
1104 });
1105 });
1106});