UNPKG

atlas-relax

Version:

A minimal, powerful declarative VDOM and reactive programming framework.

1,083 lines (1,078 loc) 181 kB
const { describe, it } = require("mocha") const { expect } = require("chai") const { LCRSRenderer, Tracker } = require("./effects"); const { diff, Frame } = require("../"); const { StemCell } = require("./cases/Frames"); const DeferredTests = require("./DeferredTests") const { has, copy, inject } = require("./util") const addHooks = ["willAdd", "didAdd"]; const updateHooks = ["willUpdate", "didUpdate"]; const allHooks = [...addHooks, ...updateHooks]; const p = StemCell.h const h = (id, hooks, next) => p(id, {hooks}, next); const hooks = (hook, job) => ({[hook]: function(){job(this)}}) const m = id => ({name:"div", data:{id}}); const k = (id, hooks, next) => { // keyed const node = p(id, {hooks}, next); node.key = id; return node; } /* merging consecutive synchronous diffs is done by "rebasing" the current path onto the latter diff. _----_----_ | | | | venn diagram of derived affected regions for first diff (A) | A | | B | and the second diff (B). fill(O1) = A, fill(O2) = B where -____-____- O1 is the originator set for A as O2 is for B. O1 and O2 may be disjoint and |path| = |A U B|. */ // performing an outer-diff during mutation events is strictly not supported. // XXX this code is dripping wet, need to wring it out so it becomes DRY // e.g. 'should not X during Y' tests can be abstracted out into a factory or fn // pretty easy refactor opportunity here, this file should be 100s of lines, not 1000s describe("rebasing (merging a new diff into current diff)", function(){ describe("mounting", function(){ describe("virtual (managed) nodes", function(){ it("should not mount nodes during a constructor", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const temp = h(0, hooks("ctor", f => { const res = diff(m(1), null, f); expect(res).to.be.false; called++ })) diff(temp, null, [renderer, tracker]); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(copy(temp))); expect(events).to.eql([{wA: 0}, {mWA: 0}]) }) it("should not mount nodes during cleanup", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const temp = h(0, hooks("cleanup", f => { const res = diff(m(1), null, f); expect(res).to.be.false; called++ })) const f = diff(temp, null, [renderer, tracker]); diff(null, f); expect(called).to.equal(1); expect(renderer.tree).to.be.null; expect(events).to.eql([{wA: 0}, {mWA: 0}, {mWP: 0}]) }) it("should not mount nodes during add mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); diff(h(0), null, [renderer, tracker, {add: f => { const res = diff(h(1), null, f); expect(res).to.be.false; called++ }}]) expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0))); expect(events).to.eql([{wA: 0}, {mWA: 0}]) }) it("should not mount nodes during remove mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const f = diff(h(0, null, h(1)), null, [renderer, tracker, {remove: (f, p, s, t) => { if (t.data.id === 1){ const res = diff(h(2), null, f); expect(res).to.be.false; called++ } }}]) diff(h(0), f); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0))); expect(events).to.eql([ {wA: 0}, {wA: 1}, {mWA: 0}, {mWA: 1}, {wU: 0}, {mWP: 1}, {mWR: 0} ]) }) it("should not mount nodes during temp mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const f = diff(h(0), null, [renderer, tracker, {temp: f => { const res = diff(h(1), null, f); expect(res).to.be.false; called++ }}]) diff(h(0), f); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0))); expect(events).to.eql([ {wA: 0}, {mWA: 0}, {wU: 0}, {mWR: 0}, ]) }) it("should not mount nodes during move mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const f = diff(h(0, null, [k(1), k(2)]), null, [renderer, tracker, {move: f => { if (f.temp.data.id === 2){ const res = diff(h(3), null, f); expect(res).to.be.false; called++ } }}]) diff(h(0, null, [k(2), k(1)]), f); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, [k(2), k(1)]))); expect(events).to.eql([ {wA: 0}, {wA: 1}, {wA: 2}, {mWA: 0}, {mWA: 1}, {mWA: 2}, {wU: 0}, {wU: 1}, {wU: 2}, {mWR: 0}, {mWR: 2}, {mWM: 2}, {mWR: 1} ]) }) allHooks.forEach(hook => { it(`should immediately return the root's ref during ${hook}`, function(){ const managedTemp = m(1) let called = 0; const temp = h(0, hooks(hook, f => { const managedNode = diff(managedTemp, null, f); expect(managedNode).to.be.an.instanceOf(Frame); expect(managedNode.temp).to.equal(managedTemp); called++; })) const f = diff(temp); if (has(updateHooks, hook)) diff(copy(temp), f); expect(called).to.equal(1); }) it(`should properly mount the node during ${hook}`, function(){ const managedTemp = m(1) const renderer = new LCRSRenderer const temp = h(0, hooks(hook, f => { diff(managedTemp, null, f) })); const f = diff(temp, null, renderer); if (has(updateHooks, hook)) diff(copy(temp), f); expect(renderer.tree).to.eql(renderer.renderStatic(inject(copy(temp), managedTemp))); }) it(`should mount multiple sibling nodes in reverse call order by default during ${hook}`, function(){ const managedChildren = [m(1), m(2), m(3)], reverse = [...managedChildren].reverse(); const renderer = new LCRSRenderer const temp = h(0, hooks(hook, f => { for (let m of managedChildren) diff(m, null, f); })); const f = diff(temp, null, renderer); if (has(updateHooks, hook)) diff(copy(temp), f); expect(renderer.tree).to.eql(renderer.renderStatic(inject(copy(temp), reverse))); }) it(`should otherwise mount multiple sibling nodes in a specified order during ${hook}`, function(){ const managedChildren = [m(1), m(2), m(3)]; const renderer = new LCRSRenderer const temp = h(0, hooks(hook, f => { const first = diff(managedChildren[0], null, f); // first is first child const second = diff(managedChildren[1], null, f, first); // second after first diff(managedChildren[2], null, f, second); // third after second })); const f = diff(temp, null, renderer); if (has(updateHooks, hook)) diff(copy(temp), f); expect(renderer.tree).to.eql(renderer.renderStatic(inject(copy(temp), managedChildren))); }) }) const mount = f => { diff(h(3, hooks("willAdd", f => {})), null, f); diff(h(4, hooks("willAdd", f => {})), null, f); } it("should defer rendering new nodes in reverse order until all updates have run during willUpdate", function(){ const events = [], tracker = new Tracker(events); const temp = h(0, null, [ h(1), h(2, hooks("willUpdate", f => mount(f))) ]) const f = diff(temp, null, tracker); events.length = 0; // we don't care about initial mount diff(copy(temp), f); expect(events).to.eql([ {wU: 0}, {wU: 1}, {wU: 2}, {wA: 4}, {wA: 3}, {mWR: 0}, {mWR: 1}, {mWR: 2}, {mWA: 4}, {mWA: 3} ]) }) it("should defer rendering new nodes in reverse order until after flush during didUpdate", function(){ const events = [], tracker = new Tracker(events); const temp = h(0, null, [ h(1), h(2, hooks("didUpdate", f => mount(f))) ]) const f = diff(temp, null, tracker); events.length = 0; // we don't care about initial mount diff(copy(temp), f); expect(events).to.eql([ {wU: 0}, {wU: 1}, {wU: 2}, {mWR: 0}, {mWR: 1}, {mWR: 2}, {dU: 2}, {wA: 4}, {wA: 3}, {mWA: 4}, {mWA: 3} ]) }) it("should immediately add new nodes in reverse order during willAdd", function(){ const events = [], tracker = new Tracker(events); const temp = h(0, null, [ h(1, hooks("willAdd", f => mount(f))), h(2) ]) const f = diff(temp, null, tracker); expect(events).to.eql([ {wA: 0}, {wA: 1}, {wA: 4}, {wA: 3}, {wA: 2}, {mWA: 0}, {mWA: 1}, {mWA: 4}, {mWA: 3}, {mWA: 2} ]) }) it("should defer adding new nodes in reverse order after flush during didAdd", function(){ const events = [], tracker = new Tracker(events); const temp = h(0, null, [ h(1, hooks("didAdd", f => mount(f))), h(2) ]) const f = diff(temp, null, tracker); expect(events).to.eql([ {wA: 0}, {wA: 1}, {wA: 2}, {mWA: 0}, {mWA: 1}, {mWA: 2}, {dA: 1}, {wA: 4}, {wA: 3}, {mWA: 4}, {mWA: 3}, ]) }) }) describe("free (unmanaged) nodes", function(){ it("should not mount nodes during a constructor", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const temp = h(0, hooks("ctor", f => { const res = diff(m(1), null, [renderer, tracker]); expect(res).to.be.false; called++ })) diff(temp); expect(called).to.equal(1); expect(renderer.tree).to.be.null; expect(events).to.be.empty }) it("should not mount nodes during cleanup", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const temp = h(0, hooks("cleanup", f => { const res = diff(m(1), null, [renderer, tracker]); expect(res).to.be.false; called++ })) const f = diff(temp); diff(null, f); expect(called).to.equal(1); expect(renderer.tree).to.be.null; expect(events).to.be.empty }) it("should not mount nodes during add mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); diff(h(0), null, {add: f => { const res = diff(h(1), null, [renderer, tracker]); expect(res).to.be.false; called++ }}) expect(called).to.equal(1); expect(renderer.tree).to.be.null expect(events).to.be.empty; }) it("should not mount nodes during remove mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const f = diff(h(0, null, h(1)), null, {remove: (f, p, s, t) => { if (t.data.id === 1){ const res = diff(h(2), null, [renderer, tracker]); expect(res).to.be.false; called++ } }}) diff(h(0), f); expect(called).to.equal(1); expect(renderer.tree).to.be.null expect(events).to.be.empty }) it("should not mount nodes during temp mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const f = diff(h(0), null, {temp: f => { const res = diff(h(1), null, [renderer, tracker]); expect(res).to.be.false; called++ }}) diff(h(0), f); expect(called).to.equal(1); expect(renderer.tree).to.be.null expect(events).to.be.empty; }) it("should not mount nodes during move mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const f = diff(h(0, null, [k(1), k(2)]), null, {move: f => { if (f.temp.data.id === 2){ const res = diff(h(3), null, [renderer, tracker]); expect(res).to.be.false; called++ } }}) diff(h(0, null, [k(2), k(1)]), f); expect(called).to.equal(1); expect(renderer.tree).to.be.null; expect(events).to.be.empty; }) allHooks.forEach(hook => { it(`should immediately return the root's ref during ${hook}`, function(){ const managedTemp = m(1) let called = 0; const temp = h(0, hooks(hook, f => { const managedNode = diff(managedTemp); expect(managedNode).to.be.an.instanceOf(Frame); expect(managedNode.temp).to.equal(managedTemp); called++; })) const f = diff(temp); if (has(updateHooks, hook)) diff(copy(temp), f); expect(called).to.equal(1); }) it(`should properly mount the node during ${hook}`, function(){ const managedTemp = m(1) const renderer = new LCRSRenderer const temp = h(0, hooks(hook, f => { diff(managedTemp) })); const f = diff(temp, null, renderer); if (has(updateHooks, hook)) diff(copy(temp), f); expect(renderer.tree).to.eql(renderer.renderStatic(temp)); }) it(`should mount multiple nodes in reverse call order during ${hook}`, function(){ const managedIds = [1, 2, 3]; let order = []; const temp = h(0, hooks(hook, f => { for (let id of managedIds) diff(h(id, hooks("willAdd", f => { order.push(id); }))); })); const f = diff(temp); if (has(updateHooks, hook)) diff(copy(temp), f); expect(order).to.eql([3,2,1]) }) }) const mount = tracker => { diff(h(3, hooks("willAdd", f => {})), null, tracker); diff(h(4, hooks("willAdd", f => {})), null, tracker); } it("should defer rendering new nodes in reverse order until all updates have run during willUpdate", function(){ const events = [], tracker = new Tracker(events); const temp = h(0, null, [ h(1), h(2, hooks("willUpdate", f => mount(tracker))) ]) const f = diff(temp, null, tracker); events.length = 0; // we don't care about initial mount diff(copy(temp), f); expect(events).to.eql([ {wU: 0}, {wU: 1}, {wU: 2}, {wA: 4}, {wA: 3}, {mWR: 0}, {mWR: 1}, {mWR: 2}, {mWA: 3}, {mWA: 4} ]) }) it("should defer rendering new nodes in reverse order until after flush during didUpdate", function(){ const events = [], tracker = new Tracker(events); const temp = h(0, null, [ h(1), h(2, hooks("didUpdate", f => mount(tracker))) ]) const f = diff(temp, null, tracker); events.length = 0; // we don't care about initial mount diff(copy(temp), f); expect(events).to.eql([ {wU: 0}, {wU: 1}, {wU: 2}, {mWR: 0}, {mWR: 1}, {mWR: 2}, {dU: 2}, {wA: 4}, {wA: 3}, {mWA: 3}, {mWA: 4} ]) }) it("should immediately render new nodes in reverse order during willAdd", function(){ const events = [], tracker = new Tracker(events); const temp = h(0, null, [ h(1, hooks("willAdd", f => mount(tracker))), h(2) ]) const f = diff(temp, null, tracker); expect(events).to.eql([ {wA: 0}, {wA: 1}, {wA: 4}, {wA: 3}, {wA: 2}, {mWA: 0}, {mWA: 1}, {mWA: 2}, {mWA: 3}, {mWA: 4} ]) }) it("should defer adding new nodes in reverse order after flush during didAdd", function(){ const events = [], tracker = new Tracker(events); const temp = h(0, null, [ h(1, hooks("didAdd", f => mount(tracker))), h(2) ]) const f = diff(temp, null, tracker); expect(events).to.eql([ {wA: 0}, {wA: 1}, {wA: 2}, {mWA: 0}, {mWA: 1}, {mWA: 2}, {dA: 1}, {wA: 4}, {wA: 3}, {mWA: 3}, {mWA: 4}, ]) }) }) }) describe("unmounting", function(){ describe("virtual (managed) nodes", function(){ it("should not unmount nodes during a constructor", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0, null), null, [renderer, tracker]); const managedR = diff(h(1), null, r); diff(h(2, hooks("ctor", f => { const res = diff(null, managedR); expect(res).to.be.false; called++ }))) expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1)))); expect(events).to.eql([{wA: 0}, {mWA: 0}, {wA: 1}, {mWA: 1}]) }) it("should not unmount nodes during cleanup", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0, null), null, [renderer, tracker]); const managedR = diff(h(1), null, r); const f = diff(h(2, hooks("cleanup", f => { const res = diff(null, managedR); expect(res).to.be.false; called++ }))) diff(null, f); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1)))); expect(events).to.eql([{wA: 0}, {mWA: 0}, {wA: 1}, {mWA: 1}]) }) it("should not unmount nodes during add mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0, null), null, [renderer, tracker]); const m = diff(h(1), null, r); diff(h(2), null, {add: f => { const res = diff(null, m); expect(res).to.be.false; called++ }}) expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1)))); expect(events).to.eql([{wA: 0}, {mWA: 0}, {wA: 1}, {mWA: 1}]) }) it("should not unmount nodes during remove mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0, null), null, [renderer, tracker]); const m = diff(h(1), null, r); const f = diff(h(2), null, {remove: f => { const res = diff(null, m); expect(res).to.be.false; called++ }}) diff(null, f); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1)))); expect(events).to.eql([{wA: 0}, {mWA: 0}, {wA: 1}, {mWA: 1}]) }) it("should not unmount nodes during temp mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0, null), null, [renderer, tracker]); const m = diff(h(1), null, r); const f = diff(h(2), null, {temp: f => { const res = diff(null, m); expect(res).to.be.false; called++ }}) diff(h(2), f) expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1)))); expect(events).to.eql([{wA: 0}, {mWA: 0}, {wA: 1}, {mWA: 1}]) }) it("should not unmount nodes during move mutation event", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0, null), null, [renderer, tracker]); const m = diff(h(1), null, r); const f = diff(h(2, null, [k(3), k(4)]), null, {move: f => { if (f.temp.data.id === 4){ const res = diff(null, m); expect(res).to.be.false; called++ } }}) diff(h(2, null, [k(4), k(3)]), f); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1)))); expect(events).to.eql([{wA: 0}, {mWA: 0}, {wA: 1}, {mWA: 1}]) }) it("should unmount nodes created during willAdd in the next cycle", function(){ const managedTemp = h(1); const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); let called = 0; const hooks = { willAdd: f => {f.managed = diff(managedTemp, null, f)}, willUpdate: f => { expect(f.managed).to.be.an.instanceOf(Frame); expect(f.managed.temp).to.equal(managedTemp); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, hooks, h(1)))) const res = diff(null, f.managed) expect(res).to.be.true; called++; } } const r = diff(h(0, hooks), null, [renderer, tracker]); diff(h(0, hooks), r); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, hooks))) expect(events).to.eql([ { wA: 0 }, { wA: 1 }, { mWA: 0 }, { mWA: 1 }, { wU: 0 }, { mWP: 1 }, { mWR: 0 } ]) }) it("should unmount nodes created during willAdd in didAdd", function(){ const managedTemp = h(1); const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); let called = 0; const hooks = { willAdd: f => {f.managed = diff(managedTemp, null, f)}, didAdd: f => { expect(f.managed).to.be.an.instanceOf(Frame); expect(f.managed.temp).to.equal(managedTemp); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, hooks, h(1)))) const res = diff(null, f.managed) expect(res).to.be.true; called++; } } diff(h(0, hooks), null, [renderer, tracker]); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, hooks))) expect(events).to.eql([ { wA: 0 }, { wA: 1 }, { mWA: 0 }, { mWA: 1 }, {dA: 0}, { mWP: 1 } ]) }) it("should unmount an external node during willAdd", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot = diff(h(1), null, r1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1)))) let called = 0; const temp = h(2, hooks("willAdd", f => { const res = diff(null, managedRoot); expect(res).to.be.true; called++; })); diff(temp, null, tracker); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ { wA: 0 }, { mWA: 0 }, {wA: 1}, { mWA: 1 }, {wA: 2}, { mWP: 1 }, {mWA: 2} ]) }) it("should unmount an external node during didAdd", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot = diff(h(1), null, r1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1)))) let called = 0; const temp = h(2, hooks("didAdd", f => { const res = diff(null, managedRoot); expect(res).to.be.true; called++; })); diff(temp, null, tracker); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ { wA: 0 }, { mWA: 0 }, {wA: 1}, { mWA: 1 }, {wA: 2}, {mWA: 2}, {dA: 2}, { mWP: 1 } ]) }) it("should unmount an external node during willUpdate", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot = diff(h(1), null, r1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1)))) let called = 0; const temp = h(2, hooks("willUpdate", f => { const res = diff(null, managedRoot); expect(res).to.be.true; called++; })); const r2 = diff(temp, null, tracker); events.length = 0; diff(copy(temp), r2); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ {wU: 2}, { mWP: 1 }, {mWR: 2} ]) }) it("should unmount an external node during didUpdate", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot = diff(h(1), null, r1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1)))) let called = 0; const temp = h(2, hooks("didUpdate", f => { const res = diff(null, managedRoot); expect(res).to.be.true; called++; })); const r2 = diff(temp, null, tracker); events.length = 0; diff(copy(temp), r2); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ {wU: 2}, {mWR: 2}, {dU: 2}, {mWP: 1} ]) }) it("should unmount a node that has not yet been rendered during willAdd", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const t = h(0, hooks("willAdd", f => { const r = diff(m(1), null, f); diff(null, r); })) diff(t, null, [renderer, tracker]); expect(renderer.tree).to.eql(renderer.renderStatic(copy(t))); expect(events).to.eql([{ wA: 0 }, { mWA: 0 }]) }) it("should unmount a node that has not yet been rendered during didAdd", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const t = h(0, hooks("didAdd", f => { const r = diff(m(1), null, f); diff(null, r); })) diff(t, null, [renderer, tracker]); expect(renderer.tree).to.eql(renderer.renderStatic(copy(t))); expect(events).to.eql([{ wA: 0 }, { mWA: 0 }, {dA: 0}]) }) it("should unmount a node that has not yet been rendered during willUpdate", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const t = h(0, hooks("willUpdate", f => { const r = diff(m(1), null, f); diff(null, r); })) const r = diff(t, null, [renderer, tracker]); events.length = 0; diff(copy(t), r); expect(renderer.tree).to.eql(renderer.renderStatic(copy(t))); expect(events).to.eql([{ wU: 0 }, { mWR: 0 }]) }) it("should unmount a node that has not yet been rendered during didUpdate", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const t = h(0, hooks("didUpdate", f => { const r = diff(m(1), null, f); diff(null, r); })) const r = diff(t, null, [renderer, tracker]); events.length = 0; diff(copy(t), r); expect(renderer.tree).to.eql(renderer.renderStatic(copy(t))); expect(events).to.eql([{ wU: 0 }, { mWR: 0 }, {dU: 0}]) }) it("should properly unmount itself during willAdd", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0), null, [renderer, tracker]); const temp = h(1, hooks("willAdd", f => diff(null, f))); diff(temp, null, r); expect(renderer.tree).to.eql(renderer.renderStatic(h(0))); expect(events).to.eql([ { wA: 0 }, { mWA: 0 }, { wA: 1 }]) }) it("should properly unmount itself during didAdd", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0), null, [renderer, tracker]); const temp = h(1, hooks("didAdd", f => diff(null, f))); diff(temp, null, r); expect(renderer.tree).to.eql(renderer.renderStatic(h(0))); expect(events).to.eql([ { wA: 0 }, { mWA: 0 }, { wA: 1 }, {mWA: 1}, {dA: 1}, {mWP: 1}]) }) it("should properly unmount itself during willUpdate", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0), null, [renderer, tracker]); const temp = h(1, hooks("willUpdate", f => diff(null, f))); const r2 = diff(temp, null, r); events.length = 0; diff(copy(temp), r2) expect(renderer.tree).to.eql(renderer.renderStatic(h(0))); expect(events).to.eql([ { wU: 1 }, { mWP: 1 }]) }) it("should properly unmount itself during didUpdate", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0), null, [renderer, tracker]); const temp = h(1, hooks("didUpdate", f => diff(null, f))); const r2 = diff(temp, null, r); events.length = 0; diff(copy(temp), r2) expect(renderer.tree).to.eql(renderer.renderStatic(h(0))); expect(events).to.eql([ { wU: 1 }, {mWR: 1}, {dU: 1}, { mWP: 1 }]) }) it("should unmount multiple nodes in default order during willAdd", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot1 = diff(h(1), null, r1); const managedRoot2 = diff(h(2), null, r1, managedRoot1); const managedRoot3 = diff(h(3), null, r1, managedRoot2); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, [h(1), h(2), h(3)]))) let called = 0; const temp = h(4, hooks("willAdd", f => { expect(diff(null, managedRoot1)).to.be.true; expect(diff(null, managedRoot2)).to.be.true; expect(diff(null, managedRoot3)).to.be.true; called++; })); diff(temp, null, tracker); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ { wA: 0 }, { mWA: 0 }, {wA: 1}, { mWA: 1 }, {wA: 2}, {mWA: 2}, {wA: 3}, { mWA: 3}, { wA: 4}, {mWP: 1}, {mWP: 2}, {mWP: 3}, {mWA: 4} ]) }) it("should unmount multiple nodes in default order during didAdd", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot1 = diff(h(1), null, r1); const managedRoot2 = diff(h(2), null, r1, managedRoot1); const managedRoot3 = diff(h(3), null, r1, managedRoot2); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, [h(1), h(2), h(3)]))) let called = 0; const temp = h(4, hooks("didAdd", f => { expect(diff(null, managedRoot1)).to.be.true; expect(diff(null, managedRoot2)).to.be.true; expect(diff(null, managedRoot3)).to.be.true; called++; })); diff(temp, null, tracker); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ { wA: 0 }, { mWA: 0 }, {wA: 1}, { mWA: 1 }, {wA: 2}, {mWA: 2}, {wA: 3}, { mWA: 3}, { wA: 4}, {mWA: 4}, {dA: 4}, {mWP: 1}, {mWP: 2}, {mWP: 3} ]) }) it("should unmount multiple nodes in default order during willUpdate", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot1 = diff(h(1), null, r1); const managedRoot2 = diff(h(2), null, r1, managedRoot1); const managedRoot3 = diff(h(3), null, r1, managedRoot2); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, [h(1), h(2), h(3)]))) let called = 0; const temp = h(4, hooks("willUpdate", f => { expect(diff(null, managedRoot1)).to.be.true; expect(diff(null, managedRoot2)).to.be.true; expect(diff(null, managedRoot3)).to.be.true; called++; })); const r2 = diff(temp, null, tracker); events.length = 0; diff(copy(temp), r2); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ {wU: 4}, { mWP: 1 }, { mWP: 2 }, { mWP: 3 }, {mWR: 4} ]) }) it("should unmount multiple nodes in default order during didUpdate", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot1 = diff(h(1), null, r1); const managedRoot2 = diff(h(2), null, r1, managedRoot1); const managedRoot3 = diff(h(3), null, r1, managedRoot2); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, [h(1), h(2), h(3)]))) let called = 0; const temp = h(4, hooks("didUpdate", f => { expect(diff(null, managedRoot1)).to.be.true; expect(diff(null, managedRoot2)).to.be.true; expect(diff(null, managedRoot3)).to.be.true; called++; })); const r2 = diff(temp, null, tracker); events.length = 0; diff(copy(temp), r2); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ {wU: 4}, {mWR: 4}, {dU: 4}, { mWP: 1 }, { mWP: 2 }, { mWP: 3 } ]) }) it("should unmount multiple nodes in a specified order during willAdd", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot1 = diff(h(1), null, r1); const managedRoot2 = diff(h(2), null, r1, managedRoot1); const managedRoot3 = diff(h(3), null, r1, managedRoot2); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, [h(1), h(2), h(3)]))) let called = 0; const temp = h(4, hooks("willAdd", f => { expect(diff(null, managedRoot3)).to.be.true; expect(diff(null, managedRoot2)).to.be.true; expect(diff(null, managedRoot1)).to.be.true; called++; })); diff(temp, null, tracker); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ { wA: 0 }, { mWA: 0 }, {wA: 1}, { mWA: 1 }, {wA: 2}, {mWA: 2}, {wA: 3}, { mWA: 3}, { wA: 4}, {mWP: 3}, {mWP: 2}, {mWP: 1}, {mWA: 4} ]) }) it("should unmount multiple nodes in a specified order during didAdd", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot1 = diff(h(1), null, r1); const managedRoot2 = diff(h(2), null, r1, managedRoot1); const managedRoot3 = diff(h(3), null, r1, managedRoot2); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, [h(1), h(2), h(3)]))) let called = 0; const temp = h(4, hooks("didAdd", f => { expect(diff(null, managedRoot3)).to.be.true; expect(diff(null, managedRoot2)).to.be.true; expect(diff(null, managedRoot1)).to.be.true; called++; })); diff(temp, null, tracker); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ { wA: 0 }, { mWA: 0 }, {wA: 1}, { mWA: 1 }, {wA: 2}, {mWA: 2}, {wA: 3}, { mWA: 3}, { wA: 4}, {mWA: 4}, {dA: 4}, {mWP: 3}, {mWP: 2}, {mWP: 1} ]) }) it("should unmount multiple nodes in a specified order during willUpdate", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot1 = diff(h(1), null, r1); const managedRoot2 = diff(h(2), null, r1, managedRoot1); const managedRoot3 = diff(h(3), null, r1, managedRoot2); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, [h(1), h(2), h(3)]))) let called = 0; const temp = h(4, hooks("willUpdate", f => { expect(diff(null, managedRoot3)).to.be.true; expect(diff(null, managedRoot2)).to.be.true; expect(diff(null, managedRoot1)).to.be.true; called++; })); const r2 = diff(temp, null, tracker); events.length = 0; diff(copy(temp), r2); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ {wU: 4}, { mWP: 3 }, { mWP: 2 }, { mWP: 1 }, {mWR: 4} ]) }) it("should unmount multiple nodes in a specified order during didUpdate", function(){ const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot1 = diff(h(1), null, r1); const managedRoot2 = diff(h(2), null, r1, managedRoot1); const managedRoot3 = diff(h(3), null, r1, managedRoot2); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, [h(1), h(2), h(3)]))) let called = 0; const temp = h(4, hooks("didUpdate", f => { expect(diff(null, managedRoot3)).to.be.true; expect(diff(null, managedRoot2)).to.be.true; expect(diff(null, managedRoot1)).to.be.true; called++; })); const r2 = diff(temp, null, tracker); events.length = 0; diff(copy(temp), r2); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(events).to.eql([ {wU: 4}, {mWR: 4}, {dU: 4}, { mWP: 3 }, { mWP: 2 }, { mWP: 1 } ]) }) it("should rebase all entangled affects during willAdd", function(){ const events = [], tracker = new Tracker(events); const renderer = new LCRSRenderer, renderer2 = new LCRSRenderer, renderer3 = new LCRSRenderer; const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot = diff(h(1, null, h(2)), null, r1); const affectedRoot1 = diff(h(3, null, h(4)), null, [renderer2, tracker]); const affectedRoot2 = diff(h(5, null, [h(6), h(7)]), null, [renderer3, tracker]); affectedRoot1.sub(managedRoot), affectedRoot2.sub(managedRoot.next); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1, null, h(2))))) let called = 0; const temp = h(8, hooks("willAdd", f => { const res = diff(null, managedRoot); expect(res).to.be.true; called++; })); diff(temp, null, tracker); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(renderer2.tree).to.eql(renderer2.renderStatic(h(3, null, h(4)))) expect(renderer3.tree).to.eql(renderer3.renderStatic(h(5, null, [h(6), h(7)]))) expect(events).to.eql([ { wA: 0 }, { mWA: 0 }, {wA: 1}, {wA: 2}, {mWA: 1}, { mWA: 2 }, {wA: 3}, {wA: 4}, {mWA: 3}, {mWA: 4}, {wA: 5}, {wA: 6}, {wA: 7}, {mWA: 5}, {mWA:6}, {mWA: 7}, {wA: 8}, {wU: 3}, {wU: 4}, {wU: 5}, {wU: 6}, {wU: 7}, {mWP: 1}, {mWP: 2}, {mWA: 8}, {mWR: 4}, {mWR: 6}, {mWR: 7} ]) }) it("should not rebase laggard entangled affects during willAdd", function(){ const events = [], tracker = new Tracker(events); const renderer = new LCRSRenderer, renderer2 = new LCRSRenderer, renderer3 = new LCRSRenderer; const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot = diff(h(1, null, h(2)), null, r1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1, null, h(2))))) let called = 0; const temp = h(8, hooks("willAdd", f => { const affectedRoot1 = diff(h(3, null, h(4)), null, [renderer2, tracker]); const affectedRoot2 = diff(h(5, null, [h(6), h(7)]), null, [renderer3, tracker]); affectedRoot1.sub(managedRoot), affectedRoot2.sub(managedRoot.next); const res = diff(null, managedRoot); expect(res).to.be.true; called++; })); diff(temp, null, tracker); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(renderer2.tree).to.eql(renderer2.renderStatic(h(3, null, h(4)))) expect(renderer3.tree).to.eql(renderer3.renderStatic(h(5, null, [h(6), h(7)]))) expect(events).to.eql([ { wA: 0 }, { mWA: 0 }, {wA: 1}, {wA: 2}, {mWA: 1}, { mWA: 2 }, {wA: 8}, {wA: 5}, {wA: 6}, {wA: 7}, {wA: 3}, {wA: 4}, {mWP: 1}, {mWP: 2}, {mWA: 8}, {mWA: 3}, {mWA: 4}, {mWA: 5}, {mWA:6}, {mWA: 7} ]) }) it("should rebase all entangled affects during didAdd", function(){ const events = [], tracker = new Tracker(events); const renderer = new LCRSRenderer, renderer2 = new LCRSRenderer, renderer3 = new LCRSRenderer; const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot = diff(h(1, null, h(2)), null, r1); const affectedRoot1 = diff(h(3, null, h(4)), null, [renderer2, tracker]); const affectedRoot2 = diff(h(5, null, [h(6), h(7)]), null, [renderer3, tracker]); affectedRoot1.sub(managedRoot), affectedRoot2.sub(managedRoot.next); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1, null, h(2))))) let called = 0; const temp = h(8, hooks("didAdd", f => { const res = diff(null, managedRoot); expect(res).to.be.true; called++; })); diff(temp, null, tracker); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(renderer2.tree).to.eql(renderer2.renderStatic(h(3, null, h(4)))) expect(renderer3.tree).to.eql(renderer3.renderStatic(h(5, null, [h(6), h(7)]))) expect(events).to.eql([ { wA: 0 }, { mWA: 0 }, {wA: 1}, {wA: 2}, {mWA: 1}, { mWA: 2 }, {wA: 3}, {wA: 4}, {mWA: 3}, {mWA: 4}, {wA: 5}, {wA: 6}, {wA: 7}, {mWA: 5}, {mWA:6}, {mWA: 7}, {wA: 8}, {mWA: 8}, {dA: 8}, {wU: 3}, {wU: 4}, {wU: 5}, {wU: 6}, {wU: 7}, {mWP: 1}, {mWP: 2}, {mWR: 4}, {mWR: 6}, {mWR: 7} ]) }) it("should rebase all entangled affects during willUpdate", function(){ const events = [], tracker = new Tracker(events); const renderer = new LCRSRenderer, renderer2 = new LCRSRenderer, renderer3 = new LCRSRenderer; const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot = diff(h(1, null, h(2)), null, r1); const affectedRoot1 = diff(h(3, null, h(4)), null, [renderer2, tracker]); const affectedRoot2 = diff(h(5, null, [h(6), h(7)]), null, [renderer3, tracker]); affectedRoot1.sub(managedRoot), affectedRoot2.sub(managedRoot.next); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1, null, h(2))))) let called = 0; const temp = h(8, hooks("willUpdate", f => { const res = diff(null, managedRoot); expect(res).to.be.true; called++; })); const r2 = diff(temp, null, tracker); events.length = 0; diff(copy(temp), r2); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(renderer2.tree).to.eql(renderer2.renderStatic(h(3, null, h(4)))) expect(renderer3.tree).to.eql(renderer3.renderStatic(h(5, null, [h(6), h(7)]))) expect(events).to.eql([ {wU: 8}, {wU: 3}, {wU: 4}, {wU: 5}, {wU: 6}, {wU: 7}, {mWP: 1}, {mWP: 2}, {mWR: 8}, {mWR: 4}, {mWR: 6}, {mWR: 7} ]) }) it("should not rebase laggard entangled affects during willUpdate", function(){ const events = [], tracker = new Tracker(events); const renderer = new LCRSRenderer, renderer2 = new LCRSRenderer, renderer3 = new LCRSRenderer; const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot = diff(h(1, null, h(2)), null, r1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1, null, h(2))))) let called = 0; const temp = h(8, hooks("willUpdate", f => { const affectedRoot1 = diff(h(3, null, h(4)), null, [renderer2, tracker]); const affectedRoot2 = diff(h(5, null, [h(6), h(7)]), null, [renderer3, tracker]); affectedRoot1.sub(managedRoot), affectedRoot2.sub(managedRoot.next); const res = diff(null, managedRoot); expect(res).to.be.true; called++; })); const r2 = diff(temp, null, tracker); events.length = 0; diff(copy(temp), r2); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(renderer2.tree).to.eql(renderer2.renderStatic(h(3, null, h(4)))) expect(renderer3.tree).to.eql(renderer3.renderStatic(h(5, null, [h(6), h(7)]))) expect(events).to.eql([ {wU: 8}, {wA: 5}, {wA: 6}, {wA: 7}, {wA: 3}, {wA: 4}, {mWP: 1}, {mWP: 2}, {mWR: 8}, {mWA: 3}, {mWA: 4}, {mWA: 5}, {mWA: 6}, {mWA: 7} ]) }) it("should rebase all entangled affects during didUpdate", function(){ const events = [], tracker = new Tracker(events); const renderer = new LCRSRenderer, renderer2 = new LCRSRenderer, renderer3 = new LCRSRenderer; const r1 = diff(h(0, null), null, [renderer, tracker]); const managedRoot = diff(h(1, null, h(2)), null, r1); const affectedRoot1 = diff(h(3, null, h(4)), null, [renderer2, tracker]); const affectedRoot2 = diff(h(5, null, [h(6), h(7)]), null, [renderer3, tracker]); affectedRoot1.sub(managedRoot), affectedRoot2.sub(managedRoot.next); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null, h(1, null, h(2))))) let called = 0; const temp = h(8, hooks("didUpdate", f => { const res = diff(null, managedRoot); expect(res).to.be.true; called++; })); const r2 = diff(temp, null, tracker); events.length = 0; diff(copy(temp), r2); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0, null))) expect(renderer2.tree).to.eql(renderer2.renderStatic(h(3, null, h(4)))) expect(renderer3.tree).to.eql(renderer3.renderStatic(h(5, null, [h(6), h(7)]))) expect(events).to.eql([ {wU: 8}, {mWR: 8}, {dU: 8}, {wU: 3}, {wU: 4}, {wU: 5}, {wU: 6}, {wU: 7}, {mWP: 1}, {mWP: 2}, {mWR: 4}, {mWR: 6}, {mWR: 7} ]) }) }) describe("free (unmanaged) nodes", function(){ it("should not unmount nodes during a constructor", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0), null, [renderer, tracker]); diff(h(1, hooks("ctor", f => { const res = diff(null, r); expect(res).to.be.false; called++ }))) expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0))); expect(events).to.eql([{wA: 0}, {mWA: 0}]) }) it("should not unmount nodes during cleanup", function(){ let called = 0; const events = [], renderer = new LCRSRenderer, tracker = new Tracker(events); const r = diff(h(0), null, [renderer, tracker]); const f = diff(h(1, hooks("cleanup", f => { const res = diff(null, r); expect(res).to.be.false; called++ }))) diff(null, f); expect(called).to.equal(1); expect(renderer.tree).to.eql(renderer.renderStatic(h(0))); expect(events).to.eql([{wA: 0}, {mWA: 0}]) }) it("should not unmount nodes during add mutation event", function(){ let called = 0; const events = [], rend