UNPKG

mithril

Version:

A framework for building brilliant applications

579 lines (460 loc) 13.6 kB
"use strict" // Low-priority TODO: remove the dependency on the renderer here. var o = require("ospec") var components = require("../../test-utils/components") var domMock = require("../../test-utils/domMock") var throttleMocker = require("../../test-utils/throttleMock") var mountRedraw = require("../../api/mount-redraw") var coreRenderer = require("../../render/render") var h = require("../../render/hyperscript") o.spec("mount/redraw", function() { var root, m, throttleMock, consoleMock, $document, errors o.beforeEach(function() { var $window = domMock() consoleMock = {error: o.spy()} throttleMock = throttleMocker() root = $window.document.body m = mountRedraw(coreRenderer($window), throttleMock.schedule, consoleMock) $document = $window.document errors = [] }) o.afterEach(function() { o(consoleMock.error.calls.map(function(c) { return c.args[0] })).deepEquals(errors) o(throttleMock.queueLength()).equals(0) }) o("shouldn't error if there are no renderers", function() { m.redraw() throttleMock.fire() }) o("schedules correctly", function() { var spy = o.spy() m.mount(root, {view: spy}) o(spy.callCount).equals(1) m.redraw() o(spy.callCount).equals(1) throttleMock.fire() o(spy.callCount).equals(2) }) o("should run a single renderer entry", function() { var spy = o.spy() m.mount(root, {view: spy}) o(spy.callCount).equals(1) m.redraw() m.redraw() m.redraw() o(spy.callCount).equals(1) throttleMock.fire() o(spy.callCount).equals(2) }) o("should run all renderer entries", function() { var el1 = $document.createElement("div") var el2 = $document.createElement("div") var el3 = $document.createElement("div") var spy1 = o.spy() var spy2 = o.spy() var spy3 = o.spy() m.mount(el1, {view: spy1}) m.mount(el2, {view: spy2}) m.mount(el3, {view: spy3}) m.redraw() o(spy1.callCount).equals(1) o(spy2.callCount).equals(1) o(spy3.callCount).equals(1) m.redraw() o(spy1.callCount).equals(1) o(spy2.callCount).equals(1) o(spy3.callCount).equals(1) throttleMock.fire() o(spy1.callCount).equals(2) o(spy2.callCount).equals(2) o(spy3.callCount).equals(2) }) o("should not redraw when mounting another root", function() { var el1 = $document.createElement("div") var el2 = $document.createElement("div") var el3 = $document.createElement("div") var spy1 = o.spy() var spy2 = o.spy() var spy3 = o.spy() m.mount(el1, {view: spy1}) o(spy1.callCount).equals(1) o(spy2.callCount).equals(0) o(spy3.callCount).equals(0) m.mount(el2, {view: spy2}) o(spy1.callCount).equals(1) o(spy2.callCount).equals(1) o(spy3.callCount).equals(0) m.mount(el3, {view: spy3}) o(spy1.callCount).equals(1) o(spy2.callCount).equals(1) o(spy3.callCount).equals(1) }) o("should stop running after mount null", function() { var spy = o.spy() m.mount(root, {view: spy}) o(spy.callCount).equals(1) m.mount(root, null) m.redraw() o(spy.callCount).equals(1) throttleMock.fire() o(spy.callCount).equals(1) }) o("should stop running after mount undefined", function() { var spy = o.spy() m.mount(root, {view: spy}) o(spy.callCount).equals(1) m.mount(root, undefined) m.redraw() o(spy.callCount).equals(1) throttleMock.fire() o(spy.callCount).equals(1) }) o("should stop running after mount no arg", function() { var spy = o.spy() m.mount(root, {view: spy}) o(spy.callCount).equals(1) m.mount(root) m.redraw() o(spy.callCount).equals(1) throttleMock.fire() o(spy.callCount).equals(1) }) o("should invoke remove callback on unmount", function() { var spy = o.spy() var onremove = o.spy() m.mount(root, {view: spy, onremove: onremove}) o(spy.callCount).equals(1) m.mount(root) o(spy.callCount).equals(1) o(onremove.callCount).equals(1) }) o("should stop running after unsubscribe, even if it occurs after redraw is requested", function() { var spy = o.spy() m.mount(root, {view: spy}) o(spy.callCount).equals(1) m.redraw() m.mount(root) o(spy.callCount).equals(1) throttleMock.fire() o(spy.callCount).equals(1) }) o("does nothing on invalid unmount", function() { var spy = o.spy() m.mount(root, {view: spy}) o(spy.callCount).equals(1) m.mount(null) m.redraw() throttleMock.fire() o(spy.callCount).equals(2) }) o("redraw.sync() redraws all roots synchronously", function() { var el1 = $document.createElement("div") var el2 = $document.createElement("div") var el3 = $document.createElement("div") var spy1 = o.spy() var spy2 = o.spy() var spy3 = o.spy() m.mount(el1, {view: spy1}) m.mount(el2, {view: spy2}) m.mount(el3, {view: spy3}) o(spy1.callCount).equals(1) o(spy2.callCount).equals(1) o(spy3.callCount).equals(1) m.redraw.sync() o(spy1.callCount).equals(2) o(spy2.callCount).equals(2) o(spy3.callCount).equals(2) m.redraw.sync() o(spy1.callCount).equals(3) o(spy2.callCount).equals(3) o(spy3.callCount).equals(3) }) o("throws on invalid component", function() { o(function() { m.mount(root, {}) }).throws(TypeError) }) o("skips roots that were synchronously unsubscribed before they were visited", function() { var calls = [] var root1 = $document.createElement("div") var root2 = $document.createElement("div") var root3 = $document.createElement("div") m.mount(root1, { onbeforeupdate: function() { m.mount(root2, null) }, view: function() { calls.push("root1") }, }) m.mount(root2, {view: function() { calls.push("root2") }}) m.mount(root3, {view: function() { calls.push("root3") }}) o(calls).deepEquals([ "root1", "root2", "root3", ]) m.redraw.sync() o(calls).deepEquals([ "root1", "root2", "root3", "root1", "root3", ]) }) o("keeps its place when synchronously unsubscribing previously visited roots", function() { var calls = [] var root1 = $document.createElement("div") var root2 = $document.createElement("div") var root3 = $document.createElement("div") m.mount(root1, {view: function() { calls.push("root1") }}) m.mount(root2, { onbeforeupdate: function() { m.mount(root1, null) }, view: function() { calls.push("root2") }, }) m.mount(root3, {view: function() { calls.push("root3") }}) o(calls).deepEquals([ "root1", "root2", "root3", ]) m.redraw.sync() o(calls).deepEquals([ "root1", "root2", "root3", "root1", "root2", "root3", ]) }) o("keeps its place when synchronously unsubscribing previously visited roots in the face of errors", function() { errors = ["fail"] var calls = [] var root1 = $document.createElement("div") var root2 = $document.createElement("div") var root3 = $document.createElement("div") m.mount(root1, {view: function() { calls.push("root1") }}) m.mount(root2, { onbeforeupdate: function() { m.mount(root1, null) throw "fail" }, view: function() { calls.push("root2") }, }) m.mount(root3, {view: function() { calls.push("root3") }}) o(calls).deepEquals([ "root1", "root2", "root3", ]) m.redraw.sync() o(calls).deepEquals([ "root1", "root2", "root3", "root1", "root3", ]) }) o("keeps its place when synchronously unsubscribing the current root", function() { var calls = [] var root1 = $document.createElement("div") var root2 = $document.createElement("div") var root3 = $document.createElement("div") m.mount(root1, {view: function() { calls.push("root1") }}) m.mount(root2, { onbeforeupdate: function() { try { m.mount(root2, null) } catch (e) { calls.push([e.constructor, e.message]) } }, view: function() { calls.push("root2") }, }) m.mount(root3, {view: function() { calls.push("root3") }}) o(calls).deepEquals([ "root1", "root2", "root3", ]) m.redraw.sync() o(calls).deepEquals([ "root1", "root2", "root3", "root1", [TypeError, "Node is currently being rendered to and thus is locked."], "root2", "root3", ]) }) o("keeps its place when synchronously unsubscribing the current root in the face of an error", function() { errors = [ [TypeError, "Node is currently being rendered to and thus is locked."], ] var calls = [] var root1 = $document.createElement("div") var root2 = $document.createElement("div") var root3 = $document.createElement("div") m.mount(root1, {view: function() { calls.push("root1") }}) m.mount(root2, { onbeforeupdate: function() { try { m.mount(root2, null) } catch (e) { throw [e.constructor, e.message] } }, view: function() { calls.push("root2") }, }) m.mount(root3, {view: function() { calls.push("root3") }}) o(calls).deepEquals([ "root1", "root2", "root3", ]) m.redraw.sync() o(calls).deepEquals([ "root1", "root2", "root3", "root1", "root3", ]) }) components.forEach(function(cmp){ o.spec(cmp.kind, function(){ var createComponent = cmp.create o("throws on invalid `root` DOM node", function() { o(function() { m.mount(null, createComponent({view: function() {}})) }).throws(TypeError) }) o("renders into `root` synchronously", function() { m.mount(root, createComponent({ view: function() { return h("div") } })) o(root.firstChild.nodeName).equals("DIV") }) o("mounting null unmounts", function() { m.mount(root, createComponent({ view: function() { return h("div") } })) m.mount(root, null) o(root.childNodes.length).equals(0) }) o("Mounting a second root doesn't cause the first one to redraw", function() { var root1 = $document.createElement("div") var root2 = $document.createElement("div") var view = o.spy() m.mount(root1, createComponent({view: view})) o(view.callCount).equals(1) m.mount(root2, createComponent({view: function() {}})) o(view.callCount).equals(1) throttleMock.fire() o(view.callCount).equals(1) }) o("redraws on events", function() { var onupdate = o.spy() var oninit = o.spy() var onclick = o.spy() var e = $document.createEvent("MouseEvents") e.initEvent("click", true, true) m.mount(root, createComponent({ view: function() { return h("div", { oninit: oninit, onupdate: onupdate, onclick: onclick, }) } })) root.firstChild.dispatchEvent(e) o(oninit.callCount).equals(1) o(onupdate.callCount).equals(0) o(onclick.callCount).equals(1) o(onclick.this).equals(root.firstChild) o(onclick.args[0].type).equals("click") o(onclick.args[0].target).equals(root.firstChild) throttleMock.fire() o(onupdate.callCount).equals(1) }) o("redraws several mount points on events", function() { var onupdate0 = o.spy() var oninit0 = o.spy() var onclick0 = o.spy() var onupdate1 = o.spy() var oninit1 = o.spy() var onclick1 = o.spy() var root1 = $document.createElement("div") var root2 = $document.createElement("div") var e = $document.createEvent("MouseEvents") e.initEvent("click", true, true) m.mount(root1, createComponent({ view: function() { return h("div", { oninit: oninit0, onupdate: onupdate0, onclick: onclick0, }) } })) o(oninit0.callCount).equals(1) o(onupdate0.callCount).equals(0) m.mount(root2, createComponent({ view: function() { return h("div", { oninit: oninit1, onupdate: onupdate1, onclick: onclick1, }) } })) o(oninit1.callCount).equals(1) o(onupdate1.callCount).equals(0) root1.firstChild.dispatchEvent(e) o(onclick0.callCount).equals(1) o(onclick0.this).equals(root1.firstChild) throttleMock.fire() o(onupdate0.callCount).equals(1) o(onupdate1.callCount).equals(1) root2.firstChild.dispatchEvent(e) o(onclick1.callCount).equals(1) o(onclick1.this).equals(root2.firstChild) throttleMock.fire() o(onupdate0.callCount).equals(2) o(onupdate1.callCount).equals(2) }) o("event handlers can skip redraw", function() { var onupdate = o.spy(function(){ throw new Error("This shouldn't have been called") }) var oninit = o.spy() var e = $document.createEvent("MouseEvents") e.initEvent("click", true, true) m.mount(root, createComponent({ view: function() { return h("div", { oninit: oninit, onupdate: onupdate, onclick: function(e) { e.redraw = false } }) } })) root.firstChild.dispatchEvent(e) o(oninit.callCount).equals(1) o(e.redraw).equals(false) throttleMock.fire() o(onupdate.callCount).equals(0) o(e.redraw).equals(false) }) o("redraws when the render function is run", function() { var onupdate = o.spy() var oninit = o.spy() m.mount(root, createComponent({ view: function() { return h("div", { oninit: oninit, onupdate: onupdate }) } })) o(oninit.callCount).equals(1) o(onupdate.callCount).equals(0) m.redraw() throttleMock.fire() o(onupdate.callCount).equals(1) }) o("emits errors correctly", function() { errors = ["foo", "bar", "baz"] var counter = -1 m.mount(root, createComponent({ view: function() { var value = errors[counter++] if (value != null) throw value return null } })) m.redraw() throttleMock.fire() m.redraw() throttleMock.fire() m.redraw() throttleMock.fire() }) }) }) })