pax_global_header00006660000000000000000000000064151240620650014513gustar00rootroot0000000000000052 comment=2f0b60b42ccb2f357edba31983473444df16d117 mithril-1.1.7+dfsg/000077500000000000000000000000001512406206500141105ustar00rootroot00000000000000mithril-1.1.7+dfsg/.deploy.enc000066400000000000000000000062601512406206500161550ustar00rootroot0000000000000016fd{DlgdZ5BRہreoB1ctZȕIŤt)Ҳw-NPah@( `8#t`7ֈl,(}lDs;0`UÉ>`E68С|`;8WuwaXқX ^1>dSfWnA:s.:W@~.#lU U˨~g.|\_jr`w2i$OCrN{wɮۨ姈D3f;G u"t5-PJ4ࡻ"H͞ ǚF̰>9~?sa" (cmiA{-N![<ݯP2 գ0 G#JҨ?.j|( 5ճ'9]FקKqܠvv,zUۍEA9@'n;^ C7TxKh+ fd⋵DΨQ/p`/|rCJzqwbTD*^F <3M@*sS櫼$KLAӧtIm./cTtI<jbmj0u^+ԇ^5qj=q,##褛V'V ܎pjJz `msּW%+ p$[1"u }цc{5G4fΤE5Ļ/!+찚*{-@u4b{̲DBv6';Dp=&c?eRr3?i:>ŀ. :/ǵX܄:Z;U.B{1ki+[o$yB[?mXNn3XDtHO*k]B Fk\@J,#P 0Xsq[V|{/NP]Cq[]$IˆeGLMv*xmY9rj)cN8P߃e"Ձ^Zb;y&3ʀ9aͲ:pi_nwjF%) 71c$/,$r"SWya<ž ٓaF Sֈee ŀ HzٙTMfzC5XJc1X$,ob.UO7 CP$uc|t qItK%qvlDb7YS lWidf5 Φ`"[c?߸h;8XgH?:D:̮_' .`.)زnV#3fj;!J@]Ӥk@=_(]OWX)rPS27VۮT(L ;3iU*މTuw?qU+$SL[Fm,k俵ijY$!82zOq\]c)/uY1B{~Gڋ+s Qp൹`$Zh$P_6䴃8P+-jO~nV\m 4ȿ JU@1]qbH(5h\m۩v u fpE3 ?n"ޣAF8,pZe^9BOsn*mC]"J.!C7XI#UYXEDztM݆tP]myO&~,?dz@W·=aBY$B IRLobeuCI}B_^n PF=3kA!k,u| <;;#L][:DݟĠ&avYm)%$C }$?3O'C=# :{`.#1K# "z|!'W>&ªgÚ?،+;?]p~s#m]f'瀾rAeoYd9ra´EYxtWGYj喉ЀA,ʤ?[Ҵ(hx08nut~Om\+p>vK`j|_{;HuIVd敏{64YQ[п1h#dǎW8DQKN7ESKvL-J;3|2Cۡ Pš>kЍ7_Z@(F#}%}}p*. :l.iY +_{|aB:z)DxZ)wk_WQI=/D@ J͂zI|D@_]ι%'LgmcX #m
Download size
Mithril (8kb)
Vue + Vue-Router + Vuex + fetch (40kb)
React + React-Router + Redux + fetch (64kb)
Angular (135kb)
Performance
Mithril (6.4ms)
Vue (9.8ms)
React (12.1ms)
Angular (11.5ms)
Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess. If you are an experienced developer and want to know how Mithril compares to other frameworks, see the [framework comparison](http://mithril.js.org/framework-comparison.html) page. Mithril supports browsers all the way back to IE9, no polyfills required. --- ### Getting started The easiest way to try out Mithril is to include it from a CDN, and follow this tutorial. It'll cover the majority of the API surface (including routing and XHR) but it'll only take 10 minutes. Let's create an HTML file to follow along: ```markup ``` --- ### Hello world Let's start as small as we can: render some text on screen. Copy the code below into your file (and by copy, I mean type it out - you'll learn better) ```javascript var root = document.body m.render(root, "Hello world") ``` Now, let's change the text to something else. Add this line of code under the previous one: ```javascript m.render(root, "My first app") ``` As you can see, you use the same code to both create and update HTML. Mithril automatically figures out the most efficient way of updating the text, rather than blindly recreating it from scratch. --- ### DOM elements Let's wrap our text in an `

` tag. ```javascript m.render(root, m("h1", "My first app")) ``` The `m()` function can be used to describe any HTML structure you want. So if you need to add a class to the `

`: ```javascript m("h1", {class: "title"}, "My first app") ``` If you want to have multiple elements: ```javascript [ m("h1", {class: "title"}, "My first app"), m("button", "A button"), ] ``` And so on: ```javascript m("main", [ m("h1", {class: "title"}, "My first app"), m("button", "A button"), ]) ``` Note: If you prefer `` syntax, [it's possible to use it via a Babel plugin](http://mithril.js.org/jsx.html). ```jsx // HTML syntax via Babel's JSX plugin

My first app

``` --- ### Components A Mithril component is just an object with a `view` function. Here's the code above as a component: ```javascript var Hello = { view: function() { return m("main", [ m("h1", {class: "title"}, "My first app"), m("button", "A button"), ]) } } ``` To activate the component, we use `m.mount`. ```javascript m.mount(root, Hello) ``` As you would expect, doing so creates this markup: ```markup

My first app

``` The `m.mount` function is similar to `m.render`, but instead of rendering some HTML only once, it activates Mithril's auto-redrawing system. To understand what that means, let's add some events: ```javascript var count = 0 // added a variable var Hello = { view: function() { return m("main", [ m("h1", {class: "title"}, "My first app"), // changed the next line m("button", {onclick: function() {count++}}, count + " clicks"), ]) } } m.mount(root, Hello) ``` We defined an `onclick` event on the button, which increments a variable `count` (which was declared at the top). We are now also rendering the value of that variable in the button label. You can now update the label of the button by clicking the button. Since we used `m.mount`, you don't need to manually call `m.render` to apply the changes in the `count` variable to the HTML; Mithril does it for you. If you're wondering about performance, it turns out Mithril is very fast at rendering updates, because it only touches the parts of the DOM it absolutely needs to. So in our example above, when you click the button, the text in it is the only part of the DOM Mithril actually updates. --- ### Routing Routing just means going from one screen to another in an application with several screens. Let's add a splash page that appears before our click counter. First we create a component for it: ```javascript var Splash = { view: function() { return m("a", {href: "#!/hello"}, "Enter!") } } ``` As you can see, this component simply renders a link to `#!/hello`. The `#!` part is known as a hashbang, and it's a common convention used in Single Page Applications to indicate that the stuff after it (the `/hello` part) is a route path. Now that we going to have more than one screen, we use `m.route` instead of `m.mount`. ```javascript m.route(root, "/splash", { "/splash": Splash, "/hello": Hello, }) ``` The `m.route` function still has the same auto-redrawing functionality that `m.mount` does, and it also enables URL awareness; in other words, it lets Mithril know what to do when it sees a `#!` in the URL. The `"/splash"` right after `root` means that's the default route, i.e. if the hashbang in the URL doesn't point to one of the defined routes (`/splash` and `/hello`, in our case), then Mithril redirects to the default route. So if you open the page in a browser and your URL is `http://localhost`, then you get redirected to `http://localhost/#!/splash`. Also, as you would expect, clicking on the link on the splash page takes you to the click counter screen we created earlier. Notice that now your URL will point to `http://localhost/#!/hello`. You can navigate back and forth to the splash page using the browser's back and next button. --- ### XHR Basically, XHR is just a way to talk to a server. Let's change our click counter to make it save data on a server. For the server, we'll use [REM](http://rem-rest-api.herokuapp.com), a mock REST API designed for toy apps like this tutorial. First we create a function that calls `m.request`. The `url` specifies an endpoint that represents a resource, the `method` specifies the type of action we're taking (typically the `PUT` method [upserts](https://en.wiktionary.org/wiki/upsert)), `data` is the payload that we're sending to the endpoint and `withCredentials` means to enable cookies (a requirement for the REM API to work) ```javascript var count = 0 var increment = function() { m.request({ method: "PUT", url: "//rem-rest-api.herokuapp.com/api/tutorial/1", data: {count: count + 1}, withCredentials: true, }) .then(function(data) { count = parseInt(data.count) }) } ``` Calling the increment function [upserts](https://en.wiktionary.org/wiki/upsert) an object `{count: 1}` to the `/api/tutorial/1` endpoint. This endpoint returns an object with the same `count` value that was sent to it. Notice that the `count` variable is only updated after the request completes, and it's updated with the response value from the server now. Let's replace the event handler in the component to call the `increment` function instead of incrementing the `count` variable directly: ```javascript var Hello = { view: function() { return m("main", [ m("h1", {class: "title"}, "My first app"), m("button", {onclick: increment}, count + " clicks"), ]) } } ``` Clicking the button should now update the count. --- We covered how to create and update HTML, how to create components, routes for a Single Page Application, and interacted with a server via XHR. This should be enough to get you started writing the frontend for a real application. Now that you are comfortable with the basics of the Mithril API, [be sure to check out the simple application tutorial](http://mithril.js.org/simple-application.html), which walks you through building a realistic application. mithril-1.1.7+dfsg/api/000077500000000000000000000000001512406206500146615ustar00rootroot00000000000000mithril-1.1.7+dfsg/api/mount.js000066400000000000000000000010331512406206500163560ustar00rootroot00000000000000"use strict" var Vnode = require("../render/vnode") module.exports = function(redrawService) { return function(root, component) { if (component === null) { redrawService.render(root, []) redrawService.unsubscribe(root) return } if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode") var run = function() { redrawService.render(root, Vnode(component)) } redrawService.subscribe(root, run) redrawService.redraw() } } mithril-1.1.7+dfsg/api/redraw.js000066400000000000000000000022761512406206500165120ustar00rootroot00000000000000"use strict" var coreRenderer = require("../render/render") function throttle(callback) { //60fps translates to 16.6ms, round it down since setTimeout requires int var time = 16 var last = 0, pending = null var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout return function() { var now = Date.now() if (last === 0 || now - last >= time) { last = now callback() } else if (pending === null) { pending = timeout(function() { pending = null callback() last = Date.now() }, time - (now - last)) } } } module.exports = function($window) { var renderService = coreRenderer($window) renderService.setEventCallback(function(e) { if (e.redraw === false) e.redraw = undefined else redraw() }) var callbacks = [] function subscribe(key, callback) { unsubscribe(key) callbacks.push(key, throttle(callback)) } function unsubscribe(key) { var index = callbacks.indexOf(key) if (index > -1) callbacks.splice(index, 2) } function redraw() { for (var i = 1; i < callbacks.length; i += 2) { callbacks[i]() } } return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render} } mithril-1.1.7+dfsg/api/router.js000066400000000000000000000046201512406206500165410ustar00rootroot00000000000000"use strict" var Vnode = require("../render/vnode") var Promise = require("../promise/promise") var coreRouter = require("../router/router") module.exports = function($window, redrawService) { var routeService = coreRouter($window) var identity = function(v) {return v} var render, component, attrs, currentPath, lastUpdate var route = function(root, defaultRoute, routes) { if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined") var run = function() { if (render != null) redrawService.render(root, render(Vnode(component, attrs.key, attrs))) } var bail = function(path) { if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true}) else throw new Error("Could not resolve default route " + defaultRoute) } routeService.defineRoutes(routes, function(payload, params, path) { var update = lastUpdate = function(routeResolver, comp) { if (update !== lastUpdate) return component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div" attrs = params, currentPath = path, lastUpdate = null render = (routeResolver.render || identity).bind(routeResolver) run() } if (payload.view || typeof payload === "function") update({}, payload) else { if (payload.onmatch) { Promise.resolve(payload.onmatch(params, path)).then(function(resolved) { update(payload, resolved) }, bail) } else update(payload, "div") } }, bail) redrawService.subscribe(root, run) } route.set = function(path, data, options) { if (lastUpdate != null) { options = options || {} options.replace = true } lastUpdate = null routeService.setPath(path, data, options) } route.get = function() {return currentPath} route.prefix = function(prefix) {routeService.prefix = prefix} route.link = function(vnode) { vnode.dom.setAttribute("href", routeService.prefix + vnode.attrs.href) vnode.dom.onclick = function(e) { if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return e.preventDefault() e.redraw = false var href = this.getAttribute("href") if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length) route.set(href, undefined, undefined) } } route.param = function(key) { if(typeof attrs !== "undefined" && typeof key !== "undefined") return attrs[key] return attrs } return route } mithril-1.1.7+dfsg/api/tests/000077500000000000000000000000001512406206500160235ustar00rootroot00000000000000mithril-1.1.7+dfsg/api/tests/index.html000066400000000000000000000026651512406206500200310ustar00rootroot00000000000000 mithril-1.1.7+dfsg/api/tests/test-mount.js000066400000000000000000000126461512406206500205110ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var components = require("../../test-utils/components") var domMock = require("../../test-utils/domMock") var m = require("../../render/hyperscript") var apiRedraw = require("../../api/redraw") var apiMounter = require("../../api/mount") o.spec("mount", function() { var FRAME_BUDGET = Math.floor(1000 / 60) var $window, root, redrawService, mount, render o.beforeEach(function() { $window = domMock() root = $window.document.body redrawService = apiRedraw($window) mount = apiMounter(redrawService) render = redrawService.render }) o("throws on invalid component", function() { var threw = false try { mount(root, {}) } catch (e) { threw = true } o(threw).equals(true) }) components.forEach(function(cmp){ o.spec(cmp.kind, function(){ var createComponent = cmp.create o("throws on invalid `root` DOM node", function() { var threw = false try { mount(null, createComponent({view: function() {}})) } catch (e) { threw = true } o(threw).equals(true) }) o("renders into `root`", function() { mount(root, createComponent({ view : function() { return m("div") } })) o(root.firstChild.nodeName).equals("DIV") }) o("mounting null unmounts", function() { mount(root, createComponent({ view : function() { return m("div") } })) mount(root, null) o(root.childNodes.length).equals(0) }) o("redraws on events", function(done) { var onupdate = o.spy() var oninit = o.spy() var onclick = o.spy() var e = $window.document.createEvent("MouseEvents") e.initEvent("click", true, true) mount(root, createComponent({ view : function() { return m("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) // Wrapped to give time for the rate-limited redraw to fire setTimeout(function() { o(onupdate.callCount).equals(1) done() }, FRAME_BUDGET) }) o("redraws several mount points on events", function(done, timeout) { timeout(60) 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 e = $window.document.createEvent("MouseEvents") e.initEvent("click", true, true) render(root, [ m("#child0"), m("#child1") ]) mount(root.childNodes[0], createComponent({ view : function() { return m("div", { oninit : oninit0, onupdate : onupdate0, onclick : onclick0, }) } })) o(oninit0.callCount).equals(1) o(onupdate0.callCount).equals(0) mount(root.childNodes[1], createComponent({ view : function() { return m("div", { oninit : oninit1, onupdate : onupdate1, onclick : onclick1, }) } })) o(oninit1.callCount).equals(1) o(onupdate1.callCount).equals(0) root.childNodes[0].firstChild.dispatchEvent(e) o(onclick0.callCount).equals(1) o(onclick0.this).equals(root.childNodes[0].firstChild) setTimeout(function() { o(onupdate0.callCount).equals(1) o(onupdate1.callCount).equals(1) root.childNodes[1].firstChild.dispatchEvent(e) o(onclick1.callCount).equals(1) o(onclick1.this).equals(root.childNodes[1].firstChild) setTimeout(function() { o(onupdate0.callCount).equals(2) o(onupdate1.callCount).equals(2) done() }, FRAME_BUDGET) }, FRAME_BUDGET) }) o("event handlers can skip redraw", function(done) { var onupdate = o.spy() var oninit = o.spy() var e = $window.document.createEvent("MouseEvents") e.initEvent("click", true, true) mount(root, createComponent({ view: function() { return m("div", { oninit: oninit, onupdate: onupdate, onclick: function(e) { e.redraw = false } }) } })) root.firstChild.dispatchEvent(e) o(oninit.callCount).equals(1) // Wrapped to ensure no redraw fired setTimeout(function() { o(onupdate.callCount).equals(0) done() }, FRAME_BUDGET) }) o("redraws when the render function is run", function(done) { var onupdate = o.spy() var oninit = o.spy() mount(root, createComponent({ view : function() { return m("div", { oninit: oninit, onupdate: onupdate }) } })) o(oninit.callCount).equals(1) o(onupdate.callCount).equals(0) redrawService.redraw() // Wrapped to give time for the rate-limited redraw to fire setTimeout(function() { o(onupdate.callCount).equals(1) done() }, FRAME_BUDGET) }) o("throttles", function(done, timeout) { timeout(200) var i = 0 mount(root, createComponent({view: function() {i++}})) var before = i redrawService.redraw() redrawService.redraw() redrawService.redraw() redrawService.redraw() var after = i setTimeout(function(){ o(before).equals(1) // mounts synchronously o(after).equals(1) // throttles rest o(i).equals(2) done() },40) }) }) }) }) mithril-1.1.7+dfsg/api/tests/test-redraw.js000066400000000000000000000037531512406206500206320ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var apiRedraw = require("../../api/redraw") o.spec("redrawService", function() { var root, redrawService, $document o.beforeEach(function() { var $window = domMock() root = $window.document.body redrawService = apiRedraw($window) $document = $window.document }) o("shouldn't error if there are no renderers", function() { redrawService.redraw() }) o("should run a single renderer entry", function(done) { var spy = o.spy() redrawService.subscribe(root, spy) o(spy.callCount).equals(0) redrawService.redraw() o(spy.callCount).equals(1) redrawService.redraw() redrawService.redraw() redrawService.redraw() o(spy.callCount).equals(1) setTimeout(function() { o(spy.callCount).equals(2) done() }, 20) }) o("should run all renderer entries", function(done) { 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() redrawService.subscribe(el1, spy1) redrawService.subscribe(el2, spy2) redrawService.subscribe(el3, spy3) redrawService.redraw() o(spy1.callCount).equals(1) o(spy2.callCount).equals(1) o(spy3.callCount).equals(1) redrawService.redraw() o(spy1.callCount).equals(1) o(spy2.callCount).equals(1) o(spy3.callCount).equals(1) setTimeout(function() { o(spy1.callCount).equals(2) o(spy2.callCount).equals(2) o(spy3.callCount).equals(2) done() }, 20) }) o("should stop running after unsubscribe", function() { var spy = o.spy() redrawService.subscribe(root, spy) redrawService.unsubscribe(root, spy) redrawService.redraw() o(spy.callCount).equals(0) }) o("does nothing on invalid unsubscribe", function() { var spy = o.spy() redrawService.subscribe(root, spy) redrawService.unsubscribe(null) redrawService.redraw() o(spy.callCount).equals(1) }) }) mithril-1.1.7+dfsg/api/tests/test-router.js000066400000000000000000000647761512406206500207020ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var callAsync = require("../../test-utils/callAsync") var browserMock = require("../../test-utils/browserMock") var m = require("../../render/hyperscript") var callAsync = require("../../test-utils/callAsync") var apiRedraw = require("../../api/redraw") var apiRouter = require("../../api/router") var Promise = require("../../promise/promise") o.spec("route", function() { void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) { void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) { o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() { var FRAME_BUDGET = Math.floor(1000 / 60) var $window, root, redrawService, route o.beforeEach(function() { $window = browserMock(env) root = $window.document.body redrawService = apiRedraw($window) route = apiRouter($window, redrawService) route.prefix(prefix) }) o("throws on invalid `root` DOM node", function() { var threw = false try { route(null, "/", {"/":{view: function() {}}}) } catch (e) { threw = true } o(threw).equals(true) }) o("renders into `root`", function() { $window.location.href = prefix + "/" route(root, "/", { "/" : { view: function() { return m("div") } } }) o(root.firstChild.nodeName).equals("DIV") }) o("routed mount points can redraw synchronously (POJO component)", function() { var view = o.spy() $window.location.href = prefix + "/" route(root, "/", {"/":{view:view}}) o(view.callCount).equals(1) redrawService.redraw() o(view.callCount).equals(2) }) o("routed mount points can redraw synchronously (constructible component)", function() { var view = o.spy() var Cmp = function(){} Cmp.prototype.view = view $window.location.href = prefix + "/" route(root, "/", {"/":Cmp}) o(view.callCount).equals(1) redrawService.redraw() o(view.callCount).equals(2) }) o("routed mount points can redraw synchronously (closure component)", function() { var view = o.spy() function Cmp() {return {view: view}} $window.location.href = prefix + "/" route(root, "/", {"/":Cmp}) o(view.callCount).equals(1) redrawService.redraw() o(view.callCount).equals(2) }) o("default route doesn't break back button", function(done) { $window.location.href = "http://old.com" $window.location.href = "http://new.com" route(root, "/a", { "/a" : { view: function() { return m("div") } } }) callAsync(function() { o(root.firstChild.nodeName).equals("DIV") o(route.get()).equals("/a") $window.history.back() o($window.location.pathname).equals("/") o($window.location.hostname).equals("old.com") done() }) }) o("default route does not inherit params", function(done) { $window.location.href = "/invalid?foo=bar" route(root, "/a", { "/a" : { oninit: init, view: function() { return m("div") } } }) function init(vnode) { o(vnode.attrs.foo).equals(undefined) done() } }) o("redraws when render function is executed", function() { var onupdate = o.spy() var oninit = o.spy() $window.location.href = prefix + "/" route(root, "/", { "/" : { view: function() { return m("div", { oninit: oninit, onupdate: onupdate }) } } }) o(oninit.callCount).equals(1) redrawService.redraw() o(onupdate.callCount).equals(1) }) o("redraws on events", function(done) { var onupdate = o.spy() var oninit = o.spy() var onclick = o.spy() var e = $window.document.createEvent("MouseEvents") e.initEvent("click", true, true) $window.location.href = prefix + "/" route(root, "/", { "/" : { view: function() { return m("div", { oninit: oninit, onupdate: onupdate, onclick: onclick, }) } } }) root.firstChild.dispatchEvent(e) o(oninit.callCount).equals(1) 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) // Wrapped to give time for the rate-limited redraw to fire callAsync(function() { o(onupdate.callCount).equals(1) done() }) }) o("event handlers can skip redraw", function(done) { var onupdate = o.spy() var oninit = o.spy() var e = $window.document.createEvent("MouseEvents") e.initEvent("click", true, true) $window.location.href = prefix + "/" route(root, "/", { "/" : { view: function() { return m("div", { oninit: oninit, onupdate: onupdate, onclick: function(e) { e.redraw = false }, }) } } }) o(oninit.callCount).equals(1) root.firstChild.dispatchEvent(e) o(e.redraw).notEquals(false) // Wrapped to ensure no redraw fired callAsync(function() { o(onupdate.callCount).equals(0) done() }) }) o("changes location on route.link", function() { var e = $window.document.createEvent("MouseEvents") e.initEvent("click", true, true) $window.location.href = prefix + "/" route(root, "/", { "/" : { view: function() { return m("a", { href: "/test", oncreate: route.link }) } }, "/test" : { view : function() { return m("div") } } }) var slash = prefix[0] === "/" ? "" : "/" o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "")) root.firstChild.dispatchEvent(e) o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test") }) o("accepts RouteResolver with onmatch that returns Component", function(done) { var matchCount = 0 var renderCount = 0 var Component = { view: function() { return m("span") } } var resolver = { onmatch: function(args, requestedPath) { matchCount++ o(args.id).equals("abc") o(requestedPath).equals("/abc") o(this).equals(resolver) return Component }, render: function(vnode) { renderCount++ o(vnode.attrs.id).equals("abc") o(this).equals(resolver) return vnode }, } $window.location.href = prefix + "/abc" route(root, "/abc", { "/:id" : resolver }) callAsync(function() { o(matchCount).equals(1) o(renderCount).equals(1) o(root.firstChild.nodeName).equals("SPAN") done() }) }) o("accepts RouteResolver with onmatch that returns Promise", function(done) { var matchCount = 0 var renderCount = 0 var Component = { view: function() { return m("span") } } var resolver = { onmatch: function(args, requestedPath) { matchCount++ o(args.id).equals("abc") o(requestedPath).equals("/abc") o(this).equals(resolver) return Promise.resolve(Component) }, render: function(vnode) { renderCount++ o(vnode.attrs.id).equals("abc") o(this).equals(resolver) return vnode }, } $window.location.href = prefix + "/abc" route(root, "/abc", { "/:id" : resolver }) callAsync(function() { o(matchCount).equals(1) o(renderCount).equals(1) o(root.firstChild.nodeName).equals("SPAN") done() }) }) o("accepts RouteResolver with onmatch that returns Promise", function(done) { var matchCount = 0 var renderCount = 0 var resolver = { onmatch: function(args, requestedPath) { matchCount++ o(args.id).equals("abc") o(requestedPath).equals("/abc") o(this).equals(resolver) return Promise.resolve() }, render: function(vnode) { renderCount++ o(vnode.attrs.id).equals("abc") o(this).equals(resolver) return vnode }, } $window.location.href = prefix + "/abc" route(root, "/abc", { "/:id" : resolver }) callAsync(function() { o(matchCount).equals(1) o(renderCount).equals(1) o(root.firstChild.nodeName).equals("DIV") done() }) }) o("accepts RouteResolver with onmatch that returns Promise", function(done) { var matchCount = 0 var renderCount = 0 var resolver = { onmatch: function(args, requestedPath) { matchCount++ o(args.id).equals("abc") o(requestedPath).equals("/abc") o(this).equals(resolver) return Promise.resolve([]) }, render: function(vnode) { renderCount++ o(vnode.attrs.id).equals("abc") o(this).equals(resolver) return vnode }, } $window.location.href = prefix + "/abc" route(root, "/abc", { "/:id" : resolver }) callAsync(function() { o(matchCount).equals(1) o(renderCount).equals(1) o(root.firstChild.nodeName).equals("DIV") done() }) }) o("accepts RouteResolver with onmatch that returns rejected Promise", function(done) { var matchCount = 0 var renderCount = 0 var spy = o.spy() var resolver = { onmatch: function() { matchCount++ return Promise.reject(new Error("error")) }, render: function(vnode) { renderCount++ return vnode }, } $window.location.href = prefix + "/test/1" route(root, "/default", { "/default" : {view: spy}, "/test/:id" : resolver }) callAsync(function() { callAsync(function() { o(matchCount).equals(1) o(renderCount).equals(0) o(spy.callCount).equals(1) done() }) }) }) o("accepts RouteResolver without `render` method as payload", function(done) { var matchCount = 0 var Component = { view: function() { return m("div") } } $window.location.href = prefix + "/abc" route(root, "/abc", { "/:id" : { onmatch: function(args, requestedPath) { matchCount++ o(args.id).equals("abc") o(requestedPath).equals("/abc") return Component }, }, }) callAsync(function() { o(matchCount).equals(1) o(root.firstChild.nodeName).equals("DIV") done() }) }) o("changing `vnode.key` in `render` resets the component", function(done){ var oninit = o.spy() var Component = { oninit: oninit, view: function() { return m("div") } } $window.location.href = prefix + "/abc" route(root, "/abc", { "/:id": {render: function(vnode) { return m(Component, {key: vnode.attrs.id}) }} }) callAsync(function() { o(oninit.callCount).equals(1) route.set("/def") callAsync(function() { o(oninit.callCount).equals(2) done() }) }) }) o("accepts RouteResolver without `onmatch` method as payload", function() { var renderCount = 0 var Component = { view: function() { return m("div") } } $window.location.href = prefix + "/abc" route(root, "/abc", { "/:id" : { render: function(vnode) { renderCount++ o(vnode.attrs.id).equals("abc") return m(Component) }, }, }) o(root.firstChild.nodeName).equals("DIV") o(renderCount).equals(1) }) o("RouteResolver `render` does not have component semantics", function(done) { $window.location.href = prefix + "/a" route(root, "/a", { "/a" : { render: function() { return m("div") }, }, "/b" : { render: function() { return m("div") }, }, }) var dom = root.firstChild o(root.firstChild.nodeName).equals("DIV") route.set("/b") callAsync(function() { o(root.firstChild).equals(dom) done() }) }) o("calls onmatch and view correct number of times", function(done) { var matchCount = 0 var renderCount = 0 var Component = { view: function() { return m("div") } } $window.location.href = prefix + "/" route(root, "/", { "/" : { onmatch: function() { matchCount++ return Component }, render: function(vnode) { renderCount++ return vnode }, }, }) callAsync(function() { o(matchCount).equals(1) o(renderCount).equals(1) redrawService.redraw() o(matchCount).equals(1) o(renderCount).equals(2) done() }) }) o("calls onmatch and view correct number of times when not onmatch returns undefined", function(done) { var matchCount = 0 var renderCount = 0 var Component = { view: function() { return m("div") } } $window.location.href = prefix + "/" route(root, "/", { "/" : { onmatch: function() { matchCount++ }, render: function() { renderCount++ return {tag: Component} }, }, }) callAsync(function() { o(matchCount).equals(1) o(renderCount).equals(1) redrawService.redraw() o(matchCount).equals(1) o(renderCount).equals(2) done() }) }) o("onmatch can redirect to another route", function(done) { var redirected = false var render = o.spy() $window.location.href = prefix + "/a" route(root, "/a", { "/a" : { onmatch: function() { route.set("/b") }, render: render }, "/b" : { view: function() { redirected = true } } }) callAsync(function() { o(render.callCount).equals(0) o(redirected).equals(true) done() }) }) o("onmatch can redirect to another route that has RouteResolver w/ only onmatch", function(done) { var redirected = false var render = o.spy() var view = o.spy(function() {return m("div")}) $window.location.href = prefix + "/a" route(root, "/a", { "/a" : { onmatch: function() { route.set("/b", {}, {state: {a: 5}}) }, render: render }, "/b" : { onmatch: function() { redirected = true return {view: view} } } }) callAsync(function() { callAsync(function() { o(render.callCount).equals(0) o(redirected).equals(true) o(view.callCount).equals(1) o(root.childNodes.length).equals(1) o(root.firstChild.nodeName).equals("DIV") o($window.history.state).deepEquals({a: 5}) done() }) }) }) o("onmatch can redirect to another route that has RouteResolver w/ only render", function(done) { var redirected = false var render = o.spy() $window.location.href = prefix + "/a" route(root, "/a", { "/a" : { onmatch: function() { route.set("/b") }, render: render }, "/b" : { render: function(){ redirected = true } } }) callAsync(function() { o(render.callCount).equals(0) o(redirected).equals(true) done() }) }) o("onmatch can redirect to another route that has RouteResolver whose onmatch resolves asynchronously", function(done) { var redirected = false var render = o.spy() var view = o.spy() $window.location.href = prefix + "/a" route(root, "/a", { "/a" : { onmatch: function() { route.set("/b") }, render: render }, "/b" : { onmatch: function() { redirected = true return new Promise(function(fulfill){ callAsync(function(){ fulfill({view: view}) }) }) } } }) callAsync(function() { callAsync(function() { callAsync(function() { o(render.callCount).equals(0) o(redirected).equals(true) o(view.callCount).equals(1) done() }) }) }) }) o("onmatch can redirect to another route asynchronously", function(done) { var redirected = false var render = o.spy() var view = o.spy() $window.location.href = prefix + "/a" route(root, "/a", { "/a" : { onmatch: function() { callAsync(function() {route.set("/b")}) return new Promise(function() {}) }, render: render }, "/b" : { onmatch: function() { redirected = true return {view: view} } } }) callAsync(function() { callAsync(function() { callAsync(function() { o(render.callCount).equals(0) o(redirected).equals(true) o(view.callCount).equals(1) done() }) }) }) }) o("onmatch can redirect w/ window.history.back()", function(done) { var render = o.spy() var component = {view: o.spy()} $window.location.href = prefix + "/a" route(root, "/a", { "/a" : { onmatch: function() { return component }, render: function(vnode) { return vnode } }, "/b" : { onmatch: function() { $window.history.back() return new Promise(function() {}) }, render: render } }) callAsync(function() { route.set("/b") callAsync(function() { callAsync(function() { callAsync(function() { o(render.callCount).equals(0) o(component.view.callCount).equals(2) done() }) }) }) }) }) o("onmatch can redirect to a non-existent route that defaults to a RouteResolver w/ onmatch", function(done) { var redirected = false var render = o.spy() $window.location.href = prefix + "/a" route(root, "/b", { "/a" : { onmatch: function() { route.set("/c") }, render: render }, "/b" : { onmatch: function(){ redirected = true return {view: function() {}} } } }) callAsync(function() { callAsync(function() { o(render.callCount).equals(0) o(redirected).equals(true) done() }) }) }) o("onmatch can redirect to a non-existent route that defaults to a RouteResolver w/ render", function(done) { var redirected = false var render = o.spy() $window.location.href = prefix + "/a" route(root, "/b", { "/a" : { onmatch: function() { route.set("/c") }, render: render }, "/b" : { render: function(){ redirected = true } } }) callAsync(function() { callAsync(function() { o(render.callCount).equals(0) o(redirected).equals(true) done() }) }) }) o("onmatch can redirect to a non-existent route that defaults to a component", function(done) { var redirected = false var render = o.spy() $window.location.href = prefix + "/a" route(root, "/b", { "/a" : { onmatch: function() { route.set("/c") }, render: render }, "/b" : { view: function(){ redirected = true } } }) callAsync(function() { callAsync(function() { o(render.callCount).equals(0) o(redirected).equals(true) done() }) }) }) o("the previous view redraws while onmatch resolution is pending (#1268)", function(done) { var view = o.spy() var onmatch = o.spy(function() { return new Promise(function() {}) }) $window.location.href = prefix + "/a" route(root, "/", { "/a": {view: view}, "/b": {onmatch: onmatch} }) o(view.callCount).equals(1) o(onmatch.callCount).equals(0) route.set("/b") callAsync(function() { o(view.callCount).equals(1) o(onmatch.callCount).equals(1) redrawService.redraw() o(view.callCount).equals(2) o(onmatch.callCount).equals(1) done() }) }) o("when two async routes are racing, the last one set cancels the finalization of the first", function(done) { var renderA = o.spy() var renderB = o.spy() var onmatchA = o.spy(function(){ return new Promise(function(fulfill) { setTimeout(function(){ fulfill() }, 10) }) }) $window.location.href = prefix + "/a" route(root, "/a", { "/a": { onmatch: onmatchA, render: renderA }, "/b": { onmatch: function(){ var p = new Promise(function(fulfill) { o(onmatchA.callCount).equals(1) o(renderA.callCount).equals(0) o(renderB.callCount).equals(0) setTimeout(function(){ o(onmatchA.callCount).equals(1) o(renderA.callCount).equals(0) o(renderB.callCount).equals(0) fulfill() p.then(function(){ o(onmatchA.callCount).equals(1) o(renderA.callCount).equals(0) o(renderB.callCount).equals(1) done() }) }, 20) }) return p }, render: renderB } }) callAsync(function() { o(onmatchA.callCount).equals(1) o(renderA.callCount).equals(0) o(renderB.callCount).equals(0) route.set("/b") o(onmatchA.callCount).equals(1) o(renderA.callCount).equals(0) o(renderB.callCount).equals(0) }) }) o("m.route.set(m.route.get()) re-runs the resolution logic (#1180)", function(done){ var onmatch = o.spy() var render = o.spy(function() {return m("div")}) $window.location.href = prefix + "/" route(root, "/", { "/": { onmatch: onmatch, render: render } }) callAsync(function() { o(onmatch.callCount).equals(1) o(render.callCount).equals(1) route.set(route.get()) callAsync(function() { callAsync(function() { o(onmatch.callCount).equals(2) o(render.callCount).equals(2) done() }) }) }) }) o("m.route.get() returns the last fully resolved route (#1276)", function(done){ $window.location.href = prefix + "/" route(root, "/", { "/": {view: function() {}}, "/2": { onmatch: function() { return new Promise(function() {}) } } }) o(route.get()).equals("/") route.set("/2") callAsync(function() { o(route.get()).equals("/") done() }) }) o("routing with RouteResolver works more than once", function(done) { $window.location.href = prefix + "/a" route(root, "/a", { "/a": { render: function() { return m("a", "a") } }, "/b": { render: function() { return m("b", "b") } } }) route.set("/b") callAsync(function() { route.set("/a") callAsync(function() { o(root.firstChild.nodeName).equals("A") done() }) }) }) o("calling route.set invalidates pending onmatch resolution", function(done) { var rendered = false var resolved $window.location.href = prefix + "/a" route(root, "/a", { "/a": { onmatch: function() { return new Promise(function(resolve) { callAsync(function() { callAsync(function() { resolve({view: function() {rendered = true}}) }) }) }) }, render: function() { rendered = true resolved = "a" } }, "/b": { view: function() { resolved = "b" } } }) route.set("/b") callAsync(function() { o(rendered).equals(false) o(resolved).equals("b") callAsync(function() { o(rendered).equals(false) o(resolved).equals("b") done() }) }) }) o("route changes activate onbeforeremove", function(done) { var spy = o.spy() $window.location.href = prefix + "/a" route(root, "/a", { "/a": { onbeforeremove: spy, view: function() {} }, "/b": { view: function() {} } }) route.set("/b") callAsync(function() { o(spy.callCount).equals(1) done() }) }) o("asynchronous route.set in onmatch works", function(done) { var rendered = false, resolved route(root, "/a", { "/a": { onmatch: function() { return Promise.resolve().then(function() { route.set("/b") }) }, render: function() { rendered = true resolved = "a" } }, "/b": { view: function() { resolved = "b" } }, }) callAsync(function() { // tick for popstate for /a callAsync(function() { // tick for promise in onmatch callAsync(function() { // tick for onpopstate for /b o(rendered).equals(false) o(resolved).equals("b") done() }) }) }) }) o("throttles", function(done, timeout) { timeout(200) var i = 0 $window.location.href = prefix + "/" route(root, "/", { "/": {view: function() {i++}} }) var before = i redrawService.redraw() redrawService.redraw() redrawService.redraw() redrawService.redraw() var after = i setTimeout(function() { o(before).equals(1) // routes synchronously o(after).equals(2) // redraws synchronously o(i).equals(3) // throttles rest done() }, FRAME_BUDGET * 2) }) o("m.route.param is available outside of route handlers", function(done) { $window.location.href = prefix + "/" route(root, "/1", { "/:id" : { view : function() { o(route.param("id")).equals("1") return m("div") } } }) o(route.param("id")).equals(undefined); o(route.param()).deepEquals(undefined); callAsync(function() { o(route.param("id")).equals("1") o(route.param()).deepEquals({id:"1"}) done() }) }) }) }) }) }) mithril-1.1.7+dfsg/browser.js000066400000000000000000000001641512406206500161320ustar00rootroot00000000000000"use strict" var m = require("./index") if (typeof module !== "undefined") module["exports"] = m else window.m = m mithril-1.1.7+dfsg/bundler/000077500000000000000000000000001512406206500155435ustar00rootroot00000000000000mithril-1.1.7+dfsg/bundler/README.md000066400000000000000000000045561512406206500170340ustar00rootroot00000000000000# bundler.js Simplistic CommonJS module bundler Version: 0.1 License: MIT ## About This bundler attempts to aggressively bundle CommonJS modules by assuming the dependency tree is static, similar to what Rollup does for ES6 modules. Most browsers don't support ES6 `import/export` syntax, but we can achieve modularity by using CommonJS module syntax and employing [`module.js`](../module/README.md) in browser environments. Webpack is conservative and treats CommonJS modules as non-statically-analyzable since `require` and `module.exports` are legally allowed everywhere. Therefore, it must generate extra code to resolve dependencies at runtime (i.e. `__webpack_require()`). Rollup only works with ES6 modules. ES6 modules can be bundled more efficiently because they are statically analyzable, but some use cases are difficult to handle due to ES6's support for cyclic dependencies and hosting rules. This bundler assumes code is written in CommonJS style but follows a strict set of rules that emulate statically analyzable code and favors the usage of the factory pattern instead of relying on obscure corners of the Javascript language (hoisting rules and binding semantics). ### Caveats - Only supports modules that have the `require` and `module.exports` statement declared at the top-level scope before all other code, i.e. it does not support CommonJS modules that rely on dynamic importing/exporting. This means modules should only export a pure function or export a factory function if there are multiple statements and/or internal module state. The factory function pattern allows easier dependency injection in stateful modules, thus making modules testable. - Changes the semantics of value/binding exporting between unbundled and bundled code, and therefore relying on those semantics is discouraged. Instead, it is recommended that module consumers inject dependencies via the factory function pattern - Top level strictness is infectious (i.e. if entry file is in `"use strict"` mode, all modules inherit strict mode, and conversely, if the entry file is not in strict mode, all modules are pulled out of strict mode) - Currently only supports assignments to `module.exports` (i.e. `module.exports.foo = bar` will not work) - It is tiny and dependency-free because it uses regular expressions, and it only supports the narrow range of import/export declaration patterns outlined above. mithril-1.1.7+dfsg/bundler/bin/000077500000000000000000000000001512406206500163135ustar00rootroot00000000000000mithril-1.1.7+dfsg/bundler/bin/bundle000066400000000000000000000000641512406206500175070ustar00rootroot00000000000000#!/usr/bin/env node "use strict" require("../cli") mithril-1.1.7+dfsg/bundler/bin/bundle.cmd000066400000000000000000000000141512406206500202440ustar00rootroot00000000000000node bundle mithril-1.1.7+dfsg/bundler/bundle.js000066400000000000000000000127401512406206500173560ustar00rootroot00000000000000"use strict" var fs = require("fs") var path = require("path") var proc = require("child_process") function read(filepath) { try {return fs.readFileSync(filepath, "utf8")} catch (e) {throw new Error("File does not exist: " + filepath)} } function isFile(filepath) { try {return fs.statSync(filepath).isFile()} catch (e) {return false} } function parse(file) { var json = read(file) try {return JSON.parse(json)} catch (e) {throw new Error("invalid JSON: " + json)} } var error function run(input, output) { try { var modules = {} var bindings = {} var declaration = /^\s*(?:var|let|const|function)[\t ]+([\w_$]+)/gm var include = /(?:((?:var|let|const|,|)[\t ]*)([\w_$\.\[\]"'`]+)(\s*=\s*))?require\(([^\)]+)\)(\s*[`\.\(\[])?/gm var uuid = 0 var process = function(filepath, data) { data.replace(declaration, function(match, binding) {bindings[binding] = 0}) return data.replace(include, function(match, def, variable, eq, dep, rest) { var filename = new Function("return " + dep).call(), pre = "" def = def || "", variable = variable || "", eq = eq || "", rest = rest || "" if (def[0] === ",") def = "\nvar ", pre = "\n" var dependency = resolve(filepath, filename) var localUUID = uuid // global uuid can update from nested `process` call, ensure same id is used on declaration and consumption var code = process(dependency, pre + (modules[dependency] == null ? exportCode(filename, dependency, def, variable, eq, rest, localUUID) : def + variable + eq + modules[dependency])) modules[dependency] = rest ? "_" + localUUID : variable uuid++ return code + rest }) } var resolve = function(filepath, filename) { if (filename[0] !== ".") { // resolve as npm dependency var packagePath = "./node_modules/" + filename + "/package.json" var meta = isFile(packagePath) ? parse(packagePath) : {} var main = "./node_modules/" + filename + "/" + (meta.main || filename + ".js") return path.resolve(isFile(main) ? main : "./node_modules/" + filename + "/index.js") } else { // resolve as local dependency return path.resolve(path.dirname(filepath), filename + ".js") } } var exportCode = function(filename, filepath, def, variable, eq, rest, uuid) { var code = read(filepath) // if there's a syntax error, report w/ proper stack trace try {new Function(code)} catch (e) { proc.exec("node " + filepath, function(e) { if (e !== null && e.message !== error) { error = e.message console.log("\x1b[31m" + e.message + "\x1b[0m") } }) } // disambiguate collisions var ignored = {} code.replace(include, function(match, def, variable, eq, dep) { var filename = new Function("return " + dep).call() var binding = modules[resolve(filepath, filename)] if (binding != null) ignored[binding] = true }) if (code.match(new RegExp("module\\.exports\\s*=\\s*" + variable + "\s*$", "m"))) ignored[variable] = true for (var binding in bindings) { if (!ignored[binding]) { var before = code code = code.replace(new RegExp("(\\b)" + binding + "\\b", "g"), binding + bindings[binding]) if (before !== code) bindings[binding]++ } } // fix strings that got mangled by collision disambiguation var string = /(["'])((?:\\\1|.)*?)(\1)/g var candidates = Object.keys(bindings).map(function(binding) {return binding + (bindings[binding] - 1)}).join("|") code = code.replace(string, function(match, open, data, close) { var variables = new RegExp(Object.keys(bindings).map(function(binding) {return binding + (bindings[binding] - 1)}).join("|"), "g") var fixed = data.replace(variables, function(match) { return match.replace(/\d+$/, "") }) return open + fixed + close }) //fix props var props = new RegExp("(\\.\\s*)(" + candidates + ")|([\\{,]\\s*)(" + candidates + ")(\\s*:)", "gm") code = code.replace(props, function(match, dot, a, pre, b, post) { if (dot) return dot + a.replace(/\d+$/, "") else return pre + b.replace(/\d+$/, "") + post }) return code .replace(/("|')use strict\1;?/gm, "") // remove extraneous "use strict" .replace(/module\.exports\s*=\s*/gm, rest ? "var _" + uuid + eq : def + (rest ? "_" : "") + variable + eq) // export + (rest ? "\n" + def + variable + eq + "_" + uuid : "") // if `rest` is truthy, it means the expression is fluent or higher-order (e.g. require(path).foo or require(path)(foo) } var versionTag = "bleeding-edge" var packageFile = __dirname + "/../package.json" var code = process(path.resolve(input), read(input)) .replace(/^\s*((?:var|let|const|)[\t ]*)([\w_$\.]+)(\s*=\s*)(\2)(?=[\s]+(\w)|;|$)/gm, "") // remove assignments to self .replace(/;+(\r|\n|$)/g, ";$1") // remove redundant semicolons .replace(/(\r|\n)+/g, "\n").replace(/(\r|\n)$/, "") // remove multiline breaks .replace(versionTag, isFile(packageFile) ? parse(packageFile).version : versionTag) // set version code = ";(function() {\n" + code + "\n}());" if (!isFile(output) || code !== read(output)) { //try {new Function(code); console.log("build completed at " + new Date())} catch (e) {} error = null fs.writeFileSync(output, code, "utf8") } } catch (e) { console.error(e.message) } } module.exports = function(input, output, options) { run(input, output) if (options && options.watch) { fs.watch(process.cwd(), {recursive: true}, function(file) { if (typeof file === "string" && path.resolve(output) !== path.resolve(file)) run(input, output) }) } } mithril-1.1.7+dfsg/bundler/cli.js000066400000000000000000000026761512406206500166630ustar00rootroot00000000000000"use strict" var fs = require("fs"); var bundle = require("./bundle") var minify = require("./minify") var aliases = {o: "output", m: "minify", w: "watch", a: "aggressive"} var params = {} var args = process.argv.slice(2), command = null for (var i = 0; i < args.length; i++) { if (args[i][0] === '"') args[i] = JSON.parse(args[i]) if (args[i][0] === "-") { if (command != null) add(true) command = args[i].replace(/\-+/g, "") } else if (command != null) add(args[i]) else params.input = args[i] } if (command != null) add(true) function add(value) { params[aliases[command] || command] = value command = null } bundle(params.input, params.output, {watch: params.watch}) if (params.minify) { minify(params.output, params.output, {watch: params.watch, advanced: params.aggressive}, function(stats) { var readme, kb; function format(n) { return n.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,") } console.log("Original size: " + format(stats.originalGzipSize) + " bytes gzipped (" + format(stats.originalSize) + " bytes uncompressed)") console.log("Compiled size: " + format(stats.compressedGzipSize) + " bytes gzipped (" + format(stats.compressedSize) + " bytes uncompressed)") readme = fs.readFileSync("./README.md", "utf8") kb = stats.compressedGzipSize / 1024 fs.writeFileSync("./README.md", readme.replace( /()(.+?)()/, "$1" + (kb % 1 ? kb.toFixed(2) : kb) + " KB$3" ) ) }) } mithril-1.1.7+dfsg/bundler/minify.js000066400000000000000000000030131512406206500173710ustar00rootroot00000000000000"use strict" var http = require("https") var querystring = require("querystring") var fs = require("fs") module.exports = function(input, output, options, done) { function minify(input, output) { var code = fs.readFileSync(input, "utf8") var data = { output_format: "json", output_info: ["compiled_code", "warnings", "errors", "statistics"], compilation_level: options.advanced ? "ADVANCED_OPTIMIZATIONS" : "SIMPLE_OPTIMIZATIONS", warning_level: "default", output_file_name: "default.js", js_code: code, } var body = querystring.stringify(data) var response = "" var req = http.request({ method: "POST", hostname: "closure-compiler.appspot.com", path: "/compile", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", "Content-Length": body.length } }, function(res) { res.on("data", function(chunk) { response += chunk.toString() }) res.on("end", function() { try { var results = JSON.parse(response) } catch(e) { console.error(response); throw e; } if (results.errors) { for (var i = 0; i < results.errors.length; i++) console.log(results.errors[i]) } else { fs.writeFileSync(output, results.compiledCode, "utf8") console.log("done") if(typeof done === "function") done(results.statistics) } }) }) req.write(body) req.end() console.log("minifying...") } function run() { minify(input, output) } run() if (options && options.watch) fs.watchFile(input, run) } mithril-1.1.7+dfsg/bundler/tests/000077500000000000000000000000001512406206500167055ustar00rootroot00000000000000mithril-1.1.7+dfsg/bundler/tests/test-bundler.js000066400000000000000000000302571512406206500216620ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var bundle = require("../bundle") var fs = require("fs") var ns = "bundler/tests/" function read(filepath) { try {return fs.readFileSync(ns + filepath, "utf8")} catch (e) {/* ignore */} } function write(filepath, data) { try {var exists = fs.statSync(ns + filepath).isFile()} catch (e) {/* ignore */} if (exists) throw new Error("Don't call `write('" + filepath + "')`. Cannot overwrite file") fs.writeFileSync(ns + filepath, data, "utf8") } function remove(filepath) { fs.unlinkSync(ns + filepath) } o.spec("bundler", function() { o("relative imports works", function() { write("a.js", 'var b = require("./b")') write("b.js", "module.exports = 1") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar b = 1\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("relative imports works with semicolons", function() { write("a.js", 'var b = require("./b");') write("b.js", "module.exports = 1;") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar b = 1;\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("relative imports works with let", function() { write("a.js", 'let b = require("./b")') write("b.js", "module.exports = 1") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nlet b = 1\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("relative imports works with const", function() { write("a.js", 'const b = require("./b")') write("b.js", "module.exports = 1") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nconst b = 1\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("relative imports works with assignment", function() { write("a.js", 'var a = {}\na.b = require("./b")') write("b.js", "module.exports = 1") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar a = {}\na.b = 1\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("relative imports works with reassignment", function() { write("a.js", 'var b = {}\nb = require("./b")') write("b.js", "module.exports = 1") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar b = {}\nb = 1\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("relative imports removes extra use strict", function() { write("a.js", '"use strict"\nvar b = require("./b")') write("b.js", '"use strict"\nmodule.exports = 1') bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(';(function() {\n"use strict"\nvar b = 1\n}());') remove("a.js") remove("b.js") remove("out.js") }) o("relative imports removes extra use strict using single quotes", function() { write("a.js", "'use strict'\nvar b = require(\"./b\")") write("b.js", "'use strict'\nmodule.exports = 1") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\n'use strict'\nvar b = 1\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("relative imports removes extra use strict using mixed quotes", function() { write("a.js", '"use strict"\nvar b = require("./b")') write("b.js", "'use strict'\nmodule.exports = 1") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(';(function() {\n"use strict"\nvar b = 1\n}());') remove("a.js") remove("b.js") remove("out.js") }) o("works w/ window", function() { write("a.js", 'window.a = 1\nvar b = require("./b")') write("b.js", "module.exports = function() {return a}") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nwindow.a = 1\nvar b = function() {return a}\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("works without assignment", function() { write("a.js", 'require("./b")') write("b.js", "1 + 1") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\n1 + 1\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("works if used fluently", function() { write("a.js", 'var b = require("./b").toString()') write("b.js", "module.exports = []") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar _0 = []\nvar b = _0.toString()\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("works if used fluently w/ multiline", function() { write("a.js", 'var b = require("./b")\n\t.toString()') write("b.js", "module.exports = []") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar _0 = []\nvar b = _0\n\t.toString()\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("works if used w/ curry", function() { write("a.js", 'var b = require("./b")()') write("b.js", "module.exports = function() {}") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar _0 = function() {}\nvar b = _0()\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("works if used w/ curry w/ multiline", function() { write("a.js", 'var b = require("./b")\n()') write("b.js", "module.exports = function() {}") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar _0 = function() {}\nvar b = _0\n()\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("works if used fluently in one place and not in another", function() { write("a.js", 'var b = require("./b").toString()\nvar c = require("./c")') write("b.js", "module.exports = []") write("c.js", 'var b = require("./b")\nmodule.exports = function() {return b}') bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar _0 = []\nvar b = _0.toString()\nvar b0 = _0\nvar c = function() {return b0}\n}());") remove("a.js") remove("b.js") remove("c.js") remove("out.js") }) o("works if used in sequence", function() { write("a.js", 'var b = require("./b"), c = require("./c")') write("b.js", "module.exports = 1") write("c.js", "var x\nmodule.exports = 2") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar b = 1\nvar x\nvar c = 2\n}());") remove("a.js") remove("b.js") remove("c.js") remove("out.js") }) o("works if assigned to property", function() { write("a.js", 'var x = {}\nx.b = require("./b")\nx.c = require("./c")') write("b.js", "var bb = 1\nmodule.exports = bb") write("c.js", "var cc = 2\nmodule.exports = cc") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar x = {}\nvar bb = 1\nx.b = bb\nvar cc = 2\nx.c = cc\n}());") remove("a.js") remove("b.js") remove("c.js") remove("out.js") }) o("works if assigned to property using bracket notation", function() { write("a.js", 'var x = {}\nx["b"] = require("./b")\nx["c"] = require("./c")') write("b.js", "var bb = 1\nmodule.exports = bb") write("c.js", "var cc = 2\nmodule.exports = cc") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(';(function() {\nvar x = {}\nvar bb = 1\nx["b"] = bb\nvar cc = 2\nx["c"] = cc\n}());') remove("a.js") remove("b.js") remove("c.js") remove("out.js") }) o("works if collision", function() { write("a.js", 'var b = require("./b")') write("b.js", "var b = 1\nmodule.exports = 2") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar b0 = 1\nvar b = 2\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("works if multiple aliases", function() { write("a.js", 'var b = require("./b")\n') write("b.js", 'var b = require("./c")\nb.x = 1\nmodule.exports = b') write("c.js", "var b = {}\nmodule.exports = b") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar b = {}\nb.x = 1\n}());") remove("a.js") remove("b.js") remove("c.js") remove("out.js") }) o("works if multiple collision", function() { write("a.js", 'var b = require("./b")\nvar c = require("./c")\nvar d = require("./d")') write("b.js", "var a = 1\nmodule.exports = a") write("c.js", "var a = 2\nmodule.exports = a") write("d.js", "var a = 3\nmodule.exports = a") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar a = 1\nvar b = a\nvar a0 = 2\nvar c = a0\nvar a1 = 3\nvar d = a1\n}());") remove("a.js") remove("b.js") remove("c.js") remove("d.js") remove("out.js") }) o("works if included multiple times", function() { write("a.js", "module.exports = 123") write("b.js", 'var a = require("./a").toString()\nmodule.exports = a') write("c.js", 'var a = require("./a").toString()\nvar b = require("./b")') bundle(ns + "c.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar _0 = 123\nvar a = _0.toString()\nvar a0 = _0.toString()\nvar b = a0\n}());") remove("a.js") remove("b.js") remove("c.js") }) o("works if included multiple times reverse", function() { write("a.js", "module.exports = 123") write("b.js", 'var a = require("./a").toString()\nmodule.exports = a') write("c.js", 'var b = require("./b")\nvar a = require("./a").toString()') bundle(ns + "c.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar _0 = 123\nvar a0 = _0.toString()\nvar b = a0\nvar a = _0.toString()\n}());") remove("a.js") remove("b.js") remove("c.js") }) o("reuses binding if possible", function() { write("a.js", 'var b = require("./b")\nvar c = require("./c")') write("b.js", 'var d = require("./d")\nmodule.exports = function() {return d + 1}') write("c.js", 'var d = require("./d")\nmodule.exports = function() {return d + 2}') write("d.js", "module.exports = 1") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar d = 1\nvar b = function() {return d + 1}\nvar c = function() {return d + 2}\n}());") remove("a.js") remove("b.js") remove("c.js") remove("d.js") remove("out.js") }) o("disambiguates conflicts if imported collides with itself", function() { write("a.js", 'var b = require("./b")') write("b.js", "var b = 1\nmodule.exports = function() {return b}") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar b0 = 1\nvar b = function() {return b0}\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("disambiguates conflicts if imported collides with something else", function() { write("a.js", 'var a = 1\nvar b = require("./b")') write("b.js", "var a = 2\nmodule.exports = function() {return a}") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar a = 1\nvar a0 = 2\nvar b = function() {return a0}\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("disambiguates conflicts if imported collides with function declaration", function() { write("a.js", 'function a() {}\nvar b = require("./b")') write("b.js", "var a = 2\nmodule.exports = function() {return a}") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nfunction a() {}\nvar a0 = 2\nvar b = function() {return a0}\n}());") remove("a.js") remove("b.js") remove("out.js") }) o("disambiguates conflicts if imported collides with another module's private", function() { write("a.js", 'var b = require("./b")\nvar c = require("./c")') write("b.js", "var a = 1\nmodule.exports = function() {return a}") write("c.js", "var a = 2\nmodule.exports = function() {return a}") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar a = 1\nvar b = function() {return a}\nvar a0 = 2\nvar c = function() {return a0}\n}());") remove("a.js") remove("b.js") remove("c.js") remove("out.js") }) o("does not mess up strings", function() { write("a.js", 'var b = require("./b")') write("b.js", 'var b = "b b b \\" b"\nmodule.exports = function() {return b}') bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(';(function() {\nvar b0 = "b b b \\\" b"\nvar b = function() {return b0}\n}());') remove("a.js") remove("b.js") remove("out.js") }) o("does not mess up properties", function() { write("a.js", 'var b = require("./b")') write("b.js", "var b = {b: 1}\nmodule.exports = function() {return b.b}") bundle(ns + "a.js", ns + "out.js") o(read("out.js")).equals(";(function() {\nvar b0 = {b: 1}\nvar b = function() {return b0.b}\n}());") remove("a.js") remove("b.js") remove("out.js") }) }) mithril-1.1.7+dfsg/docs/000077500000000000000000000000001512406206500150405ustar00rootroot00000000000000mithril-1.1.7+dfsg/docs/CNAME000066400000000000000000000000171512406206500156040ustar00rootroot00000000000000mithril.js.org mithril-1.1.7+dfsg/docs/animation.md000066400000000000000000000105511512406206500173430ustar00rootroot00000000000000# Animations - [Technology choices](#technology-choices) - [Animation on element creation](#animation-on-element-creation) - [Animation on element removal](#animation-on-element-removal) - [Performance](#performance) --- ### Technology choices Animations are often used to make applications come alive. Nowadays, browsers have good support for CSS animations, and there are [various](https://greensock.com/gsap) [libraries](http://velocityjs.org/) that provide fast Javascript-based animations. There's also an upcoming [Web API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) and a [polyfill](https://github.com/web-animations/web-animations-js) if you like living on the bleeding edge. Mithril does not provide any animation APIs per se, since these other options are more than sufficient to achieve rich, complex animations. Mithril does, however, offer hooks to make life easier in some specific cases where it's traditionally difficult to make animations work. --- ### Animation on element creation Animating an element via CSS when the element created couldn't be simpler. Just add an animation to a CSS class normally: ```css .fancy {animation:fade-in 0.5s;} @keyframes fade-in { from {opacity:0;} to {opacity:1;} } ``` ```javascript var FancyComponent = { view: function() { return m(".fancy", "Hello world") } } m.mount(document.body, FancyComponent) ``` --- ### Animation on element removal The problem with animating before removing an element is that we must wait until the animation is complete before we can actually remove the element. Fortunately, Mithril offers a [`onbeforeremove`](lifecycle-methods.md#onbeforeremove) hook that allows us to defer the removal of an element. Let's create an `exit` animation that fades `opacity` from 1 to 0. ```css .exit {animation:fade-out 0.5s;} @keyframes fade-out { from {opacity:1;} to {opacity:0;} } ``` Now let's create a contrived component that shows and hides the `FancyComponent` we created in the previous section: ```javascript var on = true var Toggler = { view: function() { return [ m("button", {onclick: function() {on = !on}}, "Toggle"), on ? m(FancyComponent) : null, ] } } ``` Next, let's modify `FancyComponent` so that it fades out when removed: ```javascript var FancyComponent = { onbeforeremove: function(vnode) { vnode.dom.classList.add("exit") return new Promise(function(resolve) { setTimeout(resolve, 500) }) }, view: function() { return m(".fancy", "Hello world") } } ``` `vnode.dom` points to the root DOM element of the component (`
`). We use the classList API here to add an `exit` class to `
`. Then we return a [Promise](promise.md) that resolves after half a second. When we return a promise from `onbeforeremove`, Mithril waits until the promise is resolved and only then it removes the element. In this case, it waits half a second, giving the exit animation the exact time it needs to complete. We can verify that both the enter and exit animations work by mounting the `Toggler` component: ```javascript m.mount(document.body, Toggler) ``` Note that the `onbeforeremove` hook only fires on the element that loses its `parentNode` when an element gets detached from the DOM. This behavior is by design and exists to prevent a potential jarring user experience where every conceivable exit animation on the page would run on a route change. If your exit animation is not running, make sure to attach the `onbeforeremove` handler as high up the tree as it makes sense to ensure that your animation code is called. --- ### Performance When creating animations, it's recommended that you only use the `opacity` and `transform` CSS rules, since these can be hardware-accelerated by modern browsers and yield better performance than animating `top`, `left`, `width`, and `height`. It's also recommended that you avoid the `box-shadow` rule and selectors like `:nth-child`, since these are also resource intensive options. If you want to animate a `box-shadow`, consider [putting the `box-shadow` rule on a pseudo element, and animate that element's opacity instead](http://tobiasahlin.com/blog/how-to-animate-box-shadow/). Other things that can be expensive include large or dynamically scaled images and overlapping elements with different `position` values (e.g. an absolute positioned element over a fixed element). mithril-1.1.7+dfsg/docs/api.md000066400000000000000000000051441512406206500161370ustar00rootroot00000000000000# API ### Cheatsheet Here are examples for the most commonly used methods. If a method is not listed below, it's meant for advanced usage. #### m(selector, attrs, children) - [docs](hyperscript.md) ```javascript m("div.class#id", {title: "title"}, ["children"]) ``` --- #### m.mount(element, component) - [docs](mount.md) ```javascript var state = { count: 0, inc: function() {state.count++} } var Counter = { view: function() { return m("div", {onclick: state.inc}, state.count) } } m.mount(document.body, Counter) ``` --- #### m.route(root, defaultRoute, routes) - [docs](route.md) ```javascript var Home = { view: function() { return "Welcome" } } m.route(document.body, "/home", { "/home": Home, // defines `http://localhost/#!/home` }) ``` #### m.route.set(path) - [docs](route.md#mrouteset) ```javascript m.route.set("/home") ``` #### m.route.get() - [docs](route.md#mrouteget) ```javascript var currentRoute = m.route.get() ``` #### m.route.prefix(prefix) - [docs](route.md#mrouteprefix) Call this before `m.route()` ```javascript m.route.prefix("#!") ``` #### m.route.link() - [docs](route.md#mroutelink) ```javascript m("a[href='/Home']", {oncreate: m.route.link}, "Go to home page") ``` --- #### m.request(options) - [docs](request.md) ```javascript m.request({ method: "PUT", url: "/api/v1/users/:id", data: {id: 1, name: "test"} }) .then(function(result) { console.log(result) }) ``` --- #### m.jsonp(options) - [docs](jsonp.md) ```javascript m.jsonp({ url: "/api/v1/users/:id", data: {id: 1}, callbackKey: "callback", }) .then(function(result) { console.log(result) }) ``` --- #### m.parseQueryString(querystring) - [docs](parseQueryString.md) ```javascript var object = m.parseQueryString("a=1&b=2") // {a: "1", b: "2"} ``` --- #### m.buildQueryString(object) - [docs](buildQueryString.md) ```javascript var querystring = m.buildQueryString({a: "1", b: "2"}) // "a=1&b=2" ``` --- #### m.withAttr(attrName, callback) - [docs](withAttr.md) ```javascript var state = { value: "", setValue: function(v) {state.value = v} } var Component = { view: function() { return m("input", { oninput: m.withAttr("value", state.setValue), value: state.value, }) } } m.mount(document.body, Component) ``` --- #### m.trust(htmlString) - [docs](trust.md) ```javascript m.render(document.body, m.trust("

Hello

")) ``` --- #### m.redraw() - [docs](redraw.md) ```javascript var count = 0 function inc() { setInterval(function() { count++ m.redraw() }, 1000) } var Counter = { oninit: inc, view: function() { return m("div", count) } } m.mount(document.body, Counter) ``` mithril-1.1.7+dfsg/docs/autoredraw.md000066400000000000000000000100441512406206500175360ustar00rootroot00000000000000# The auto-redraw system Mithril implements a virtual DOM diffing system for fast rendering, and in addition, it offers various mechanisms to gain granular control over the rendering of an application. When used idiomatically, Mithril employs an auto-redraw system that synchronizes the DOM whenever changes are made in the data layer. The auto-redraw system becomes enabled when you call `m.mount` or `m.route` (but it stays disabled if your app is bootstrapped solely via `m.render` calls). The auto-redraw system simply consists of triggering a re-render function behind the scenes after certain functions complete. ### After event handlers Mithril automatically redraws after DOM event handlers that are defined in a Mithril view: ```javascript var MyComponent = { view: function() { return m("div", {onclick: doSomething}) } } function doSomething() { // a redraw happens synchronously after this function runs } m.mount(document.body, MyComponent) ``` You can disable an auto-redraw for specific events by setting `e.redraw` to `false`. ```javascript var MyComponent = { view: function() { return m("div", {onclick: doSomething}) } } function doSomething(e) { e.redraw = false // no longer triggers a redraw when the div is clicked } m.mount(document.body, MyComponent) ``` ### After m.request Mithril automatically redraws after [`m.request`](request.md) completes: ```javascript m.request("/api/v1/users").then(function() { // a redraw happens after this function runs }) ``` You can disable an auto-redraw for a specific request by setting the `background` option to true: ```javascript m.request("/api/v1/users", {background: true}).then(function() { // does not trigger a redraw }) ``` ### After route changes Mithril automatically redraws after [`m.route.set()`](route.md#mrouteset) calls (or route changes via links that use [`m.route.link`](route.md#mroutelink) ```javascript var RoutedComponent = { view: function() { return [ // a redraw happens asynchronously after the route changes m("a", {href: "/", oncreate: m.route.link}), m("div", { onclick: function() { m.route.set("/") } }), ] } } m.route(document.body, "/", { "/": RoutedComponent, }) ``` --- ### When Mithril does not redraw Mithril does not redraw after `setTimeout`, `setInterval`, `requestAnimationFrame`, raw `Promise` resolutions and 3rd party library event handlers (e.g. Socket.io callbacks). In those cases, you must manually call [`m.redraw()`](redraw.md). Mithril also does not redraw after lifecycle methods. Parts of the UI may be redrawn after an `oninit` handler, but other parts of the UI may already have been redrawn when a given `oninit` handler fires. Handlers like `oncreate` and `onupdate` fire after the UI has been redrawn. If you need to explicitly trigger a redraw within a lifecycle method, you should call `m.redraw()`, which will trigger an asynchronous redraw. ```javascript var StableComponent = { oncreate: function(vnode) { vnode.state.height = vnode.dom.offsetHeight m.redraw() }, view: function() { return m("div", "This component is " + vnode.state.height + "px tall") } } ``` Mithril does not auto-redraw vnode trees that are rendered via `m.render`. This means redraws do not occur after event changes and `m.request` calls for templates that were rendered via `m.render`. Thus, if your architecture requires manual control over when rendering occurs (as can sometimes be the case when using libraries like Redux), you should use `m.render` instead of `m.mount`. Remember that `m.render` expects a vnode tree, and `m.mount` expects a component: ```javascript // wrap the component in a m() call for m.render m.render(document.body, m(MyComponent)) // don't wrap the component for m.mount m.mount(document.body, MyComponent) ``` Mithril may also avoid auto-redrawing if the frequency of requested redraws is higher than one animation frame (typically around 16ms). This means, for example, that when using fast-firing events like `onresize` or `onscroll`, Mithril will automatically throttle the number of redraws to avoid lag. mithril-1.1.7+dfsg/docs/buildQueryString.md000066400000000000000000000023661512406206500207050ustar00rootroot00000000000000# buildQueryString(object) - [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) --- ### Description Turns an object into a string of form `a=1&b=2` ```javascript var querystring = m.buildQueryString({a: "1", b: "2"}) // "a=1&b=2" ``` --- ### Signature `querystring = m.buildQueryString(object)` Argument | Type | Required | Description ------------ | ------------------------------------------ | -------- | --- `object` | `Object` | Yes | A key-value map to be converted into a string **returns** | `String` | | A string representing the input object [How to read signatures](signatures.md) --- ### How it works The `m.buildQueryString` creates a querystring from an object. It's useful for manipulating URLs ```javascript var querystring = m.buildQueryString({a: 1, b: 2}) // querystring is "a=1&b=2" ``` #### Deep data structures Deep data structures are serialized in a way that is understood by popular web application servers such as PHP, Rails and ExpressJS ```javascript var querystring = m.buildQueryString({a: ["hello", "world"]}) // querystring is "a[0]=hello&a[1]=world" ``` mithril-1.1.7+dfsg/docs/change-log.md000066400000000000000000000604761512406206500174030ustar00rootroot00000000000000# Change log - [v1.1.7](#v117) - [v1.1.6](#v116) - [v1.1.5](#v115) - [v1.1.4](#v114) - [v1.1.3](#v113) - [v1.1.2](#v112) - [v1.1.1](#v111) - [v1.1.0](#v110) - [v1.0.1](#v101) - [Migrating from v0.2.x](#migrating-from-v02x) - [Older docs](http://mithril.js.org/archive/v0.2.5/index.html) --- ### v1.1.7 #### Bug fixes - core: Workaround for [Internet Explorer bug](https://www.tjvantoll.com/2013/08/30/bugs-with-document-activeelement-in-internet-explorer/) when running in an iframe - Fix prototype pollution vulnerability in `m.parseQueryString` ([#2523](https://github.com/MithrilJS/mithril.js/pull/2523) [@isiahmeadows](https://github.com/isiahmeadows) [@Hunter-Dolan](https://github.com/Hunter-Dolan)) ### v1.1.6 #### Bug fixes - core: render() function can no longer prevent from changing `document.activeElement` in lifecycle hooks ([#1988](https://github.com/MithrilJS/mithril.js/pull/1988), [@purplecode](https://github.com/purplecode)) - core: don't call `onremove` on the children of components that return null from the view [#1921](https://github.com/MithrilJS/mithril.js/issues/1921) [@octavore](https://github.com/octavore) ([#1922](https://github.com/MithrilJS/mithril.js/pull/1922)) - hypertext: correct handling of shared attributes object passed to `m()`. Will copy attributes when it's necessary [#1941](https://github.com/MithrilJS/mithril.js/issues/1941) [@s-ilya](https://github.com/s-ilya) ([#1942](https://github.com/MithrilJS/mithril.js/pull/1942)) #### Ospec improvements - ospec v1.4.0 - Added support for async functions and promises in tests ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928), [@StephanHoyer](https://github.com/StephanHoyer)) - Error handling for async tests with `done` callbacks supports error as first argument ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928)) - Error messages which include newline characters do not swallow the stack trace [#1495](https://github.com/MithrilJS/mithril.js/issues/1495) ([#1984](https://github.com/MithrilJS/mithril.js/pull/1984), [@RodericDay](https://github.com/RodericDay)) - ospec v2.0.0 (to be released) - Added support for custom reporters ([#2009](https://github.com/MithrilJS/mithril.js/pull/2020)) - Make Ospec more [Flems](https://flems.io)-friendly ([#2034](https://github.com/MithrilJS/mithril.js/pull/2034)) - Works either as a global or in CommonJS environments - the o.run() report is always printed asynchronously (it could be synchronous before if none of the tests were async). - Properly point to the assertion location of async errors [#2036](https://github.com/MithrilJS/mithril.js/issues/2036) - expose the default reporter as `o.report(results)` - Don't try to access the stack traces in IE9 --- ### v1.1.5 #### Bug fixes - API: If a user sets the Content-Type header within a request's options, that value will be the entire header value rather than being appended to the default value [#1919](https://github.com/MithrilJS/mithril.js/issues/1919) ([#1924](https://github.com/MithrilJS/mithril.js/pull/1924), [@tskillian](https://github.com/tskillian)) --- ### v1.1.4 #### Bug fixes - Fix IE bug where active element is null causing render function to throw error ([#1943](https://github.com/MithrilJS/mithril.js/pull/1943), [@JacksonJN](https://github.com/JacksonJN)) --- ### v1.1.3 #### Bug fixes - move out npm dependencies added by mistake --- ### v1.1.2 #### Bug fixes - core: Namespace fixes [#1819](https://github.com/MithrilJS/mithril.js/issues/1819), ([#1825](https://github.com/MithrilJS/mithril.js/pull/1825) [@SamuelTilly](https://github.com/SamuelTilly)), [#1820](https://github.com/MithrilJS/mithril.js/issues/1820) ([#1864](https://github.com/MithrilJS/mithril.js/pull/1864)), [#1872](https://github.com/MithrilJS/mithril.js/issues/1872) ([#1873](https://github.com/MithrilJS/mithril.js/pull/1873)) - core: Fix select option to allow empty string value [#1814](https://github.com/MithrilJS/mithril.js/issues/1814) ([#1828](https://github.com/MithrilJS/mithril.js/pull/1828) [@spacejack](https://github.com/spacejack)) - core: Reset e.redraw when it was set to `false` [#1850](https://github.com/MithrilJS/mithril.js/issues/1850) ([#1890](https://github.com/MithrilJS/mithril.js/pull/1890)) - core: differentiate between `{ value: "" }` and `{ value: 0 }` for form elements [#1595 comment](https://github.com/MithrilJS/mithril.js/pull/1595#issuecomment-304071453) ([#1862](https://github.com/MithrilJS/mithril.js/pull/1862)) - core: Don't reset the cursor of textareas in IE10 when setting an identical `value` [#1870](https://github.com/MithrilJS/mithril.js/issues/1870) ([#1871](https://github.com/MithrilJS/mithril.js/pull/1871)) - hypertext: Correct handling of `[value=""]` ([#1843](https://github.com/MithrilJS/mithril.js/issues/1843), [@CreaturesInUnitards](https://github.com/CreaturesInUnitards)) - router: Don't overwrite the options object when redirecting from `onmatch with m.route.set()` [#1857](https://github.com/MithrilJS/mithril.js/issues/1857) ([#1889](https://github.com/MithrilJS/mithril.js/pull/1889)) - stream: Move the "use strict" directive inside the IIFE [#1831](https://github.com/MithrilJS/mithril.js/issues/1831) ([#1893](https://github.com/MithrilJS/mithril.js/pull/1893)) #### Ospec improvements - Shell command: Ignore hidden directories and files ([#1855](https://github.com/MithrilJS/mithril.js/pull/1855) [@pdfernhout)](https://github.com/pdfernhout)) - Library: Add the possibility to name new test suites ([#1529](https://github.com/MithrilJS/mithril.js/pull/1529)) #### Docs / Repo maintenance Our thanks to [@0joshuaolson1](https://github.com/0joshuaolson1), [@ACXgit](https://github.com/ACXgit), [@cavemansspa](https://github.com/cavemansspa), [@CreaturesInUnitards](https://github.com/CreaturesInUnitards), [@dlepaux](https://github.com/dlepaux), [@isaaclyman](https://github.com/isaaclyman), [@kevinkace](https://github.com/kevinkace), [@micellius](https://github.com/micellius), [@spacejack](https://github.com/spacejack) and [@yurivish](https://github.com/yurivish) #### Other - Addition of a performance regression test suite ([#1789](https://github.com/MithrilJS/mithril.js/issues/1789)) --- ### v1.1.1 #### Bug fixes - hyperscript: Allow `0` as the second argument to `m()` - [#1752](https://github.com/MithrilJS/mithril.js/issues/1752) / [#1753](https://github.com/MithrilJS/mithril.js/pull/1753) ([@StephanHoyer](https://github.com/StephanHoyer)) - hyperscript: restore `attrs.class` handling to what it was in v1.0.1 - [#1764](https://github.com/MithrilJS/mithril.js/issues/1764) / [#1769](https://github.com/MithrilJS/mithril.js/pull/1769) - documentation improvements ([@JAForbes](https://github.com/JAForbes), [@smuemd](https://github.com/smuemd), [@hankeypancake](https://github.com/hankeypancake)) --- ### v1.1.0 #### News - support for ES6 class components - support for closure components - improvements in build and release automation #### Bug fixes - fix IE11 input[type] error - [#1610](https://github.com/MithrilJS/mithril.js/issues/1610) - apply [#1609](https://github.com/MithrilJS/mithril.js/issues/1609) to unkeyed children case - fix abort detection [#1612](https://github.com/MithrilJS/mithril.js/issues/1612) - fix input value focus issue when value is loosely equal to old value [#1593](https://github.com/MithrilJS/mithril.js/issues/1593) --- ### v1.0.1 #### News - performance improvements in IE [#1598](https://github.com/MithrilJS/mithril.js/pull/1598) #### Bug fixes - prevent infinite loop in non-existent default route - [#1579](https://github.com/MithrilJS/mithril.js/issues/1579) - call correct lifecycle methods on children of recycled keyed vnodes - [#1609](https://github.com/MithrilJS/mithril.js/issues/1609) --- ### Migrating from `v0.2.x` `v1.x` is largely API-compatible with `v0.2.x`, but there are some breaking changes. If you are migrating, consider using the [mithril-codemods](https://www.npmjs.com/package/mithril-codemods) tool to help automate the most straightforward migrations. - [`m.prop` removed](#mprop-removed) - [`m.component` removed](#mcomponent-removed) - [`config` function](#config-function) - [Changes in redraw behaviour](#changes-in-redraw-behaviour) - [No more redraw locks](#no-more-redraw-locks) - [Cancelling redraw from event handlers](#cancelling-redraw-from-event-handlers) - [Synchronous redraw removed](#synchronous-redraw-removed) - [`m.startComputation`/`m.endComputation` removed](#mstartcomputationmendcomputation-removed) - [Component `controller` function](#component-controller-function) - [Component arguments](#component-arguments) - [`view()` parameters](#view-parameters) - [Passing components to `m()`](#passing-components-to-m) - [Passing vnodes to `m.mount()` and `m.route()`](#passing-vnodes-to-mmount-and-mroute) - [`m.route.mode`](#mroutemode) - [`m.route` and anchor tags](#mroute-and-anchor-tags) - [Reading/writing the current route](#readingwriting-the-current-route) - [Accessing route params](#accessing-route-params) - [Building/Parsing query strings](#buildingparsing-query-strings) - [Preventing unmounting](#preventing-unmounting) - [Run code on component removal](#run-code-on-component-removal) - [`m.request`](#mrequest) - [`m.deferred` removed](#mdeferred-removed) - [`m.sync` removed](#msync-removed) - [`xlink` namespace required](#xlink-namespace-required) - [Nested arrays in views](#nested-arrays-in-views) - [`vnode` equality checks](#vnode-equality-checks) --- ## `m.prop` removed In `v1.x`, `m.prop()` is now a more powerful stream micro-library, but it's no longer part of core. You can read about how to use the optional Streams module in [the documentation](stream.md). ### `v0.2.x` ```javascript var m = require("mithril") var num = m.prop(1) ``` ### `v1.x` ```javascript var m = require("mithril") var prop = require("mithril/stream") var num = prop(1) var doubled = num.map(function(n) {return n * 2}) ``` --- ## `m.component` removed In `v0.2.x` components could be created using either `m(component)` or `m.component(component)`. `v1.x` only supports `m(component)`. ### `v0.2.x` ```javascript // These are equivalent m.component(component) m(component) ``` ### `v1.x` ```javascript m(component) ``` --- ## `config` function In `v0.2.x` mithril provided a single lifecycle method, `config`. `v1.x` provides much more fine-grained control over the lifecycle of a vnode. ### `v0.2.x` ```javascript m("div", { config : function(element, isInitialized) { // runs on each redraw // isInitialized is a boolean representing if the node has been added to the DOM } }) ``` ### `v1.x` More documentation on these new methods is available in [lifecycle-methods.md](lifecycle-methods.md). ```javascript m("div", { // Called before the DOM node is created oninit : function(vnode) { /*...*/ }, // Called after the DOM node is created oncreate : function(vnode) { /*...*/ }, // Called before the node is updated, return false to cancel onbeforeupdate : function(vnode, old) { /*...*/ }, // Called after the node is updated onupdate : function(vnode) { /*...*/ }, // Called before the node is removed, return a Promise that resolves when // ready for the node to be removed from the DOM onbeforeremove : function(vnode) { /*...*/ }, // Called before the node is removed, but after onbeforeremove calls done() onremove : function(vnode) { /*...*/ } }) ``` If available the DOM-Element of the vnode can be accessed at `vnode.dom`. --- ## Changes in redraw behaviour Mithril's rendering engine still operates on the basis of semi-automated global redraws, but some APIs and behaviours differ: ### No more redraw locks In v0.2.x, Mithril allowed 'redraw locks' which temporarily prevented blocked draw logic: by default, `m.request` would lock the draw loop on execution and unlock when all pending requests had resolved - the same behaviour could be invoked manually using `m.startComputation()` and `m.endComputation()`. The latter APIs and the associated behaviour has been removed in v1.x. Redraw locking can lead to buggy UIs: the concerns of one part of the application should not be allowed to prevent other parts of the view from updating to reflect change. ### Cancelling redraw from event handlers `m.mount()` and `m.route()` still automatically redraw after a DOM event handler runs. Cancelling these redraws from within your event handlers is now done by setting the `redraw` property on the passed-in event object to `false`. #### `v0.2.x` ```javascript m("div", { onclick : function(e) { m.redraw.strategy("none") } }) ``` #### `v1.x` ```javascript m("div", { onclick : function(e) { e.redraw = false } }) ``` ### Synchronous redraw removed In v0.2.x it was possible to force mithril to redraw immediately by passing a truthy value to `m.redraw()`. This behavior complicated usage of `m.redraw()` and caused some hard-to-reason about issues and has been removed. #### `v0.2.x` ```javascript m.redraw(true) // redraws immediately & synchronously ``` #### `v1.x` ```javascript m.redraw() // schedules a redraw on the next requestAnimationFrame tick ``` ### `m.startComputation`/`m.endComputation` removed They are considered anti-patterns and have a number of problematic edge cases, so they no longer exist in v1.x. --- ## Component `controller` function In `v1.x` there is no more `controller` property in components, use `oninit` instead. ### `v0.2.x` ```javascript m.mount(document.body, { controller : function() { var ctrl = this ctrl.fooga = 1 }, view : function(ctrl) { return m("p", ctrl.fooga) } }) ``` ### `v1.x` ```javascript m.mount(document.body, { oninit : function(vnode) { vnode.state.fooga = 1 }, view : function(vnode) { return m("p", vnode.state.fooga) } }) // OR m.mount(document.body, { oninit : function(vnode) { var state = this // this is bound to vnode.state by default state.fooga = 1 }, view : function(vnode) { var state = this // this is bound to vnode.state by default return m("p", state.fooga) } }) ``` --- ## Component arguments Arguments to a component in `v1.x` must be an object, simple values like `String`/`Number`/`Boolean` will be treated as text children. Arguments are accessed within the component by reading them from the `vnode.attrs` object. ### `v0.2.x` ```javascript var component = { controller : function(options) { // options.fooga === 1 }, view : function(ctrl, options) { // options.fooga == 1 } } m("div", m.component(component, { fooga : 1 })) ``` ### `v1.x` ```javascript var component = { oninit : function(vnode) { // vnode.attrs.fooga === 1 }, view : function(vnode) { // vnode.attrs.fooga == 1 } } m("div", m(component, { fooga : 1 })) ``` --- ## `view()` parameters In `v0.2.x` view functions are passed a reference to the `controller` instance and (optionally) any options passed to the component. In `v1.x` they are passed **only** the `vnode`, exactly like the `controller` function. ### `v0.2.x` ```javascript m.mount(document.body, { controller : function() {}, view : function(ctrl, options) { // ... } }) ``` ### `v1.x` ```javascript m.mount(document.body, { oninit : function(vnode) { // ... }, view : function(vnode) { // Use vnode.state instead of ctrl // Use vnode.attrs instead of options } }) ``` --- ## Passing components to `m()` In `v0.2.x` you could pass components as the second argument of `m()` w/o any wrapping required. To help with consistency in `v1.x` they must always be wrapped with a `m()` invocation. ### `v0.2.x` ```javascript m("div", component) ``` ### `v1.x` ```javascript m("div", m(component)) ``` --- ## Passing vnodes to `m.mount()` and `m.route()` In `v0.2.x`, `m.mount(element, component)` tolerated [vnodes](vnodes.md) as second arguments instead of [components](components.md) (even though it wasn't documented). Likewise, `m.route(element, defaultRoute, routes)` accepted vnodes as values in the `routes` object. In `v1.x`, components are required instead in both cases. ### `v0.2.x` ```javascript m.mount(element, m('i', 'hello')) m.mount(element, m(Component, attrs)) m.route(element, '/', { '/': m('b', 'bye') }) ``` ### `v1.x` ```javascript m.mount(element, {view: function () {return m('i', 'hello')}}) m.mount(element, {view: function () {return m(Component, attrs)}}) m.route(element, '/', { '/': {view: function () {return m('b', 'bye')}} }) ``` --- ## `m.route.mode` In `v0.2.x` the routing mode could be set by assigning a string of `"pathname"`, `"hash"`, or `"search"` to `m.route.mode`. In `v.1.x` it is replaced by `m.route.prefix(prefix)` where `prefix` can be `#`, `?`, or an empty string (for "pathname" mode). The new API also supports hashbang (`#!`), which is the default, and it supports non-root pathnames and arbitrary mode variations such as querybang (`?!`) ### `v0.2.x` ```javascript m.route.mode = "pathname" m.route.mode = "search" ``` ### `v1.x` ```javascript m.route.prefix("") m.route.prefix("?") ``` --- ## `m.route()` and anchor tags Handling clicks on anchor tags via the mithril router is similar to `v0.2.x` but uses a new lifecycle method and API. ### `v0.2.x` ```javascript // When clicked this link will load the "/path" route instead of navigating m("a", { href : "/path", config : m.route }) ``` ### `v1.x` ```javascript // When clicked this link will load the "/path" route instead of navigating m("a", { href : "/path", oncreate : m.route.link }) ``` --- ## Reading/writing the current route In `v0.2.x` all interaction w/ the current route happened via `m.route()`. In `v1.x` this has been broken out into two functions. ### `v0.2.x` ```javascript // Getting the current route m.route() // Setting a new route m.route("/other/route") ``` ### `v1.x` ```javascript // Getting the current route m.route.get() // Setting a new route m.route.set("/other/route") ``` --- ## Accessing route params In `v0.2.x` reading route params was entirely handled through `m.route.param()`. This API is still available in `v1.x`, and additionally any route params are passed as properties in the `attrs` object on the vnode. ### `v0.2.x` ```javascript m.route(document.body, "/booga", { "/:attr" : { controller : function() { m.route.param("attr") // "booga" }, view : function() { m.route.param("attr") // "booga" } } }) ``` ### `v1.x` ```javascript m.route(document.body, "/booga", { "/:attr" : { oninit : function(vnode) { vnode.attrs.attr // "booga" m.route.param("attr") // "booga" }, view : function(vnode) { vnode.attrs.attr // "booga" m.route.param("attr") // "booga" } } }) ``` --- ## Building/Parsing query strings `v0.2.x` used methods hanging off of `m.route`, `m.route.buildQueryString()` and `m.route.parseQueryString()`. In `v1.x` these have been broken out and attached to the root `m`. ### `v0.2.x` ```javascript var qs = m.route.buildQueryString({ a : 1 }); var obj = m.route.parseQueryString("a=1"); ``` ### `v1.x` ```javascript var qs = m.buildQueryString({ a : 1 }); var obj = m.parseQueryString("a=1"); ``` --- ## Preventing unmounting It is no longer possible to prevent unmounting via `onunload`'s `e.preventDefault()`. Instead you should explicitly call `m.route.set` when the expected conditions are met. ### `v0.2.x` ```javascript var Component = { controller: function() { this.onunload = function(e) { if (condition) e.preventDefault() } }, view: function() { return m("a[href=/]", {config: m.route}) } } ``` ### `v1.x` ```javascript var Component = { view: function() { return m("a", {onclick: function() {if (!condition) m.route.set("/")}}) } } ``` --- ## Run code on component removal Components no longer call `this.onunload` when they are being removed. They now use the standardized lifecycle hook `onremove`. ### `v0.2.x` ```javascript var Component = { controller: function() { this.onunload = function(e) { // ... } }, view: function() { // ... } } ``` ### `v1.x` ```javascript var Component = { onremove : function() { // ... } view: function() { // ... } } ``` --- ## m.request Promises returned by [m.request](request.md) are no longer `m.prop` getter-setters. In addition, `initialValue`, `unwrapSuccess` and `unwrapError` are no longer supported options. In addition, requests no longer have `m.startComputation`/`m.endComputation` semantics. Instead, redraws are always triggered when a request promise chain completes (unless `background:true` is set). ### `v0.2.x` ```javascript var data = m.request({ method: "GET", url: "https://api.github.com/", initialValue: [], }) setTimeout(function() { console.log(data()) }, 1000) ``` ### `v1.x` ```javascript var data = [] m.request({ method: "GET", url: "https://api.github.com/", }) .then(function (responseBody) { data = responseBody }) setTimeout(function() { console.log(data) // note: not a getter-setter }, 1000) ``` Additionally, if the `extract` option is passed to `m.request` the return value of the provided function will be used directly to resolve the request promise, and the `deserialize` callback is ignored. --- ## `m.deferred` removed `v0.2.x` used its own custom asynchronous contract object, exposed as `m.deferred`, which was used as the basis for `m.request`. `v1.x` uses Promises instead, and implements a [polyfill](promises.md) in non-supporting environments. In situations where you would have used `m.deferred`, you should use Promises instead. ### `v0.2.x` ```javascript var greetAsync = function() { var deferred = m.deferred() setTimeout(function() { deferred.resolve("hello") }, 1000) return deferred.promise } greetAsync() .then(function(value) {return value + " world"}) .then(function(value) {console.log(value)}) //logs "hello world" after 1 second ``` ### `v1.x` ```javascript var greetAsync = function() { return new Promise(function(resolve){ setTimeout(function() { resolve("hello") }, 1000) }) } greetAsync() .then(function(value) {return value + " world"}) .then(function(value) {console.log(value)}) //logs "hello world" after 1 second ``` --- ## `m.sync` removed Since `v1.x` uses standards-compliant Promises, `m.sync` is redundant. Use `Promise.all` instead. ### `v0.2.x` ```javascript m.sync([ m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }), m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }), ]) .then(function (users) { console.log("Contributors:", users[0].name, "and", users[1].name) }) ``` ### `v1.x` ```javascript Promise.all([ m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }), m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }), ]) .then(function (users) { console.log("Contributors:", users[0].name, "and", users[1].name) }) ``` --- ## `xlink` namespace required In `v0.2.x`, the `xlink` namespace was the only supported attribute namespace, and it was supported via special casing behavior. Now namespace parsing is fully supported, and namespaced attributes should explicitly declare their namespace. ### `v0.2.x` ```javascript m("svg", // the `href` attribute is namespaced automatically m("image[href='image.gif']") ) ``` ### `v1.x` ```javascript m("svg", // User-specified namespace on the `href` attribute m("image[xlink:href='image.gif']") ) ``` --- ## Nested arrays in views Arrays now represent [fragments](fragment.md), which are structurally significant in v1.x virtual DOM. Whereas nested arrays in v0.2.x would be flattened into one continuous list of virtual nodes for the purposes of diffing, v1.x preserves the array structure - the children of any given array are not considered siblings of those of adjacent arrays. --- ## `vnode` equality checks If a vnode is strictly equal to the vnode occupying its place in the last draw, v1.x will skip that part of the tree without checking for mutations or triggering any lifecycle methods in the subtree. The component documentation contains [more detail on this issue](components.md#avoid-creating-component-instances-outside-views). mithril-1.1.7+dfsg/docs/code-of-conduct.md000066400000000000000000000063371512406206500203440ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [github@patcavit.com](mailto:github@patcavit.com?subject=Mithril%20Code%20of%20Conduct). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ mithril-1.1.7+dfsg/docs/components.md000066400000000000000000000365561512406206500175660ustar00rootroot00000000000000# Components - [Structure](#structure) - [Lifecycle methods](#lifecycle-methods) - [Syntactic variants](#syntactic-variants) - [State](#state) - [Avoid anti-patterns](#avoid-anti-patterns) ### Structure Components are a mechanism to encapsulate parts of a view to make code easier to organize and/or reuse. Any Javascript object that has a view method is a Mithril component. Components can be consumed via the [`m()`](hyperscript.md) utility: ```javascript var Example = { view: function() { return m("div", "Hello") } } m(Example) // equivalent HTML //
Hello
``` --- ### Passing data to components Data can be passed to component instances by passing an `attrs` object as the second parameter in the hyperscript function: ```javascript m(Example, {name: "Floyd"}) ``` This data can be accessed in the component's view or lifecycle methods via the `vnode.attrs`: ```javascript var Example = { view: function (vnode) { return m("div", "Hello, " + vnode.attrs.name) } } ``` NOTE: Lifecycle methods can also be provided via the `attrs` object, so you should avoid using the lifecycle method names for your own callbacks as they would also be invoked by Mithril. Use lifecycle methods in `attrs` only when you specifically wish to create lifecycle hooks. --- ### Lifecycle methods Components can have the same [lifecycle methods](lifecycle-methods.md) as virtual DOM nodes: `oninit`, `oncreate`, `onupdate`, `onbeforeremove`, `onremove` and `onbeforeupdate`. ```javascript var ComponentWithHooks = { oninit: function(vnode) { console.log("initialized") }, oncreate: function(vnode) { console.log("DOM created") }, onbeforeupdate: function(vnode, old) { return true }, onupdate: function(vnode) { console.log("DOM updated") }, onbeforeremove: function(vnode) { console.log("exit animation can start") return new Promise(function(resolve) { // call after animation completes resolve() }) }, onremove: function(vnode) { console.log("removing DOM element") }, view: function(vnode) { return "hello" } } ``` Like other types of virtual DOM nodes, components may have additional lifecycle methods defined when consumed as vnode types. ```javascript function initialize() { console.log("initialized as vnode") } m(ComponentWithHooks, {oninit: initialize}) ``` Lifecycle methods in vnodes do not override component methods, nor vice versa. Component lifecycle methods are always run after the vnode's corresponding method. Take care not to use lifecycle method names for your own callback function names in vnodes. To learn more about lifecycle methods, [see the lifecycle methods page](lifecycle-methods.md). --- ### Syntactic variants #### ES6 classes Components can also be written using ES6 class syntax: ```javascript class ES6ClassComponent { constructor(vnode) { // vnode.state is undefined at this point this.kind = "ES6 class" } view() { return m("div", `Hello from an ${this.kind}`) } oncreate() { console.log(`A ${this.kind} component was created`) } } ``` Component classes must define a `view()` method, detected via `.prototype.view`, to get the tree to render. They can be consumed in the same way regular components can. ```javascript // EXAMPLE: via m.render m.render(document.body, m(ES6ClassComponent)) // EXAMPLE: via m.mount m.mount(document.body, ES6ClassComponent) // EXAMPLE: via m.route m.route(document.body, "/", { "/": ES6ClassComponent }) // EXAMPLE: component composition class AnotherES6ClassComponent { view() { return m("main", [ m(ES6ClassComponent) ]) } } ``` #### Closure components Functionally minded developers may prefer using the "closure component" syntax: ```javascript function closureComponent(vnode) { // vnode.state is undefined at this point var kind = "closure component" return { view: function() { return m("div", "Hello from a " + kind) }, oncreate: function() { console.log("We've created a " + kind) } } } ``` The returned object must hold a `view` function, used to get the tree to render. They can be consumed in the same way regular components can. ```javascript // EXAMPLE: via m.render m.render(document.body, m(closureComponent)) // EXAMPLE: via m.mount m.mount(document.body, closureComponent) // EXAMPLE: via m.route m.route(document.body, "/", { "/": closureComponent }) // EXAMPLE: component composition function anotherClosureComponent() { return { view: function() { return m("main", [ m(closureComponent) ]) } } } ``` #### Mixing component kinds Components can be freely mixed. A Class component can have closure or POJO components as children, etc... --- ### State Like all virtual DOM nodes, component vnodes can have state. Component state is useful for supporting object-oriented architectures, for encapsulation and for separation of concerns. The state of a component can be accessed three ways: as a blueprint at initialization, via `vnode.state` and via the `this` keyword in component methods. #### At initialization For POJO components, the component object is the prototype of each component instance, so any property defined on the component object will be accessible as a property of `vnode.state`. This allows simple state initialization. In the example below, `data` is a property of the `ComponentWithInitialState` component's state object. ```javascript var ComponentWithInitialState = { data: "Initial content", view: function(vnode) { return m("div", vnode.state.data) } } m(ComponentWithInitialState) // Equivalent HTML //
Initial content
``` For class components, the state is an instance of the class, set right after the constructor is called. For closure components, the state is the object returned by the closure, set right after the closure returns. The state object is mostly redundant for closure components (since variables defined in the closure scope can be used instead). #### Via vnode.state State can also be accessed via the `vnode.state` property, which is available to all lifecycle methods as well as the `view` method of a component. ```javascript var ComponentWithDynamicState = { oninit: function(vnode) { vnode.state.data = vnode.attrs.text }, view: function(vnode) { return m("div", vnode.state.data) } } m(ComponentWithDynamicState, {text: "Hello"}) // Equivalent HTML //
Hello
``` #### Via the this keyword State can also be accessed via the `this` keyword, which is available to all lifecycle methods as well as the `view` method of a component. ```javascript var ComponentUsingThis = { oninit: function(vnode) { this.data = vnode.attrs.text }, view: function(vnode) { return m("div", this.data) } } m(ComponentUsingThis, {text: "Hello"}) // Equivalent HTML //
Hello
``` Be aware that when using ES5 functions, the value of `this` in nested anonymous functions is not the component instance. There are two recommended ways to get around this Javascript limitation, use ES6 arrow functions, or if ES6 is not available, use `vnode.state`. --- ### Avoid anti-patterns Although Mithril is flexible, some code patterns are discouraged: #### Avoid fat components Generally speaking, a "fat" component is a component that has custom instance methods. In other words, you should avoid attaching functions to `vnode.state` or `this`. It's exceedingly rare to have logic that logically fits in a component instance method and that can't be reused by other components. It's relatively common that said logic might be needed by a different component down the road. It's easier to refactor code if that logic is placed in the data layer than if it's tied to a component state. Consider this fat component: ```javascript // views/Login.js // AVOID var Login = { username: "", password: "", setUsername: function(value) { this.username = value }, setPassword: function(value) { this.password = value }, canSubmit: function() { return this.username !== "" && this.password !== "" }, login: function() {/*...*/}, view: function() { return m(".login", [ m("input[type=text]", {oninput: m.withAttr("value", this.setUsername.bind(this)), value: this.username}), m("input[type=password]", {oninput: m.withAttr("value", this.setPassword.bind(this)), value: this.password}), m("button", {disabled: !this.canSubmit(), onclick: this.login}, "Login"), ]) } } ``` Normally, in the context of a larger application, a login component like the one above exists alongside components for user registration and password recovery. Imagine that we want to be able to prepopulate the email field when navigating from the login screen to the registration or password recovery screens (or vice versa), so that the user doesn't need to re-type their email if they happened to fill the wrong page (or maybe you want to bump the user to the registration form if a username is not found). Right away, we see that sharing the `username` and `password` fields from this component to another is difficult. This is because the fat component encapsulates its state, which by definition makes this state difficult to access from outside. It makes more sense to refactor this component and pull the state code out of the component and into the application's data layer. This can be as simple as creating a new module: ```javascript // models/Auth.js // PREFER var Auth = { username: "", password: "", setUsername: function(value) { Auth.username = value }, setPassword: function(value) { Auth.password = value }, canSubmit: function() { return Auth.username !== "" && Auth.password !== "" }, login: function() {/*...*/}, } module.exports = Auth ``` Then, we can clean up the component: ```javascript // views/Login.js // PREFER var Auth = require("../models/Auth") var Login = { view: function() { return m(".login", [ m("input[type=text]", {oninput: m.withAttr("value", Auth.setUsername), value: Auth.username}), m("input[type=password]", {oninput: m.withAttr("value", Auth.setPassword), value: Auth.password}), m("button", {disabled: !Auth.canSubmit(), onclick: Auth.login}, "Login"), ]) } } ``` This way, the `Auth` module is now the source of truth for auth-related state, and a `Register` component can easily access this data, and even reuse methods like `canSubmit`, if needed. In addition, if validation code is required (for example, for the email field), you only need to modify `setEmail`, and that change will do email validation for any component that modifies an email field. As a bonus, notice that we no longer need to use `.bind` to keep a reference to the state for the component's event handlers. #### Avoid restrictive interfaces Try to keep component interfaces generic - using `attrs` and `children` directly - unless the component requires special logic to operate on input. In the example below, the `button` configuration is severely limited: it does not support any events other than `onclick`, it's not styleable and it only accepts text as children (but not elements, fragments or trusted HTML). ```javascript // AVOID var RestrictiveComponent = { view: function(vnode) { return m("button", {onclick: vnode.attrs.onclick}, [ "Click to " + vnode.attrs.text ]) } } ``` If the required attributes are equivalent to generic DOM attributes, it's preferable to allow passing through parameters to a component's root node. ```javascript // PREFER var FlexibleComponent = { view: function(vnode) { return m("button", vnode.attrs, [ "Click to ", vnode.children ]) } } ``` #### Don't manipulate `children` If a component is opinionated in how it applies attributes or children, you should switch to using custom attributes. Often it's desirable to define multiple sets of children, for example, if a component has a configurable title and body. Avoid destructuring the `children` property for this purpose. ```javascript // AVOID var Header = { view: function(vnode) { return m(".section", [ m(".header", vnode.children[0]), m(".tagline", vnode.children[1]), ]) } } m(Header, [ m("h1", "My title"), m("h2", "Lorem ipsum"), ]) // awkward consumption use case m(Header, [ [ m("h1", "My title"), m("small", "A small note"), ], m("h2", "Lorem ipsum"), ]) ``` The component above breaks the assumption that children will be output in the same contiguous format as they are received. It's difficult to understand the component without reading its implementation. Instead, use attributes as named parameters and reserve `children` for uniform child content: ```javascript // PREFER var BetterHeader = { view: function(vnode) { return m(".section", [ m(".header", vnode.attrs.title), m(".tagline", vnode.attrs.tagline), ]) } } m(BetterHeader, { title: m("h1", "My title"), tagline: m("h2", "Lorem ipsum"), }) // clearer consumption use case m(BetterHeader, { title: [ m("h1", "My title"), m("small", "A small note"), ], tagline: m("h2", "Lorem ipsum"), }) ``` #### Define components statically, call them dynamically ##### Avoid creating component definitions inside views If you create a component from within a `view` method (either directly inline or by calling a function that does so), each redraw will have a different clone of the component. When diffing component vnodes, if the component referenced by the new vnode is not strictly equal to the one referenced by the old component, the two are assumed to be different components even if they ultimately run equivalent code. This means components created dynamically via a factory will always be re-created from scratch. For that reason you should avoid recreating components. Instead, consume components idiomatically. ```javascript // AVOID var ComponentFactory = function(greeting) { // creates a new component on every call return { view: function() { return m("div", greeting) } } } m.render(document.body, m(ComponentFactory("hello"))) // calling a second time recreates div from scratch rather than doing nothing m.render(document.body, m(ComponentFactory("hello"))) // PREFER var Component = { view: function(vnode) { return m("div", vnode.attrs.greeting) } } m.render(document.body, m(Component, {greeting: "hello"})) // calling a second time does not modify DOM m.render(document.body, m(Component, {greeting: "hello"})) ``` ##### Avoid creating component instances outside views Conversely, for similar reasons, if a component instance is created outside of a view, future redraws will perform an equality check on the node and skip it. Therefore component instances should always be created inside views: ```javascript // AVOID var Counter = { count: 0, view: function(vnode) { return m("div", m("p", "Count: " + vnode.state.count ), m("button", { onclick: function() { vnode.state.count++ } }, "Increase count") ) } } var counter = m(Counter) m.mount(document.body, { view: function(vnode) { return [ m("h1", "My app"), counter ] } }) ``` In the example above, clicking the counter component button will increase its state count, but its view will not be triggered because the vnode representing the component shares the same reference, and therefore the render process doesn't diff them. You should always call components in the view to ensure a new vnode is created: ```javascript // PREFER var Counter = { count: 0, view: function(vnode) { return m("div", m("p", "Count: " + vnode.state.count ), m("button", { onclick: function() { vnode.state.count++ } }, "Increase count") ) } } m.mount(document.body, { view: function(vnode) { return [ m("h1", "My app"), m(Counter) ] } }) ``` mithril-1.1.7+dfsg/docs/contributing.md000066400000000000000000000136771512406206500201070ustar00rootroot00000000000000# Contributing # FAQ ## How do I go about contributing ideas or new features? Create an [issue thread on GitHub](https://github.com/MithrilJS/mithril.js/issues/new) to suggest your idea so the community can discuss it. If the consensus is that it's a good idea, the fastest way to get it into a release is to send a pull request. Without a PR, the time to implement the feature will depend on the bandwidth of the development team and its list of priorities. ## How should I report bugs? Ideally, the best way to report bugs is to provide a small snippet of code where the issue can be reproduced (via jsfiddle, jsbin, a gist, etc). Even better would be to submit a pull request with a fix and tests. If you don't know how to test your fix, or lint or whatever, submit anyways, and we can help you. ## How do I send a pull request? To send a pull request: - fork the repo (button at the top right in GitHub) - clone the forked repo to your computer (green button in GitHub) - Switch to the `next` branch (run `git checkout next`) - create a feature branch (run `git checkout -b the-feature-branch-name`) - make your changes - run the tests (run `npm test`) - push your changes to your fork - submit a pull request (go to the pull requests tab in GitHub, click the green button and select your feature branch) ## I'm submitting a PR. How do I run tests? Assuming you have forked this repo, you can open the `index.html` file in a module's `tests` folder and look at console output to see only tests for that module, or you can run `ospec/bin/ospec` from the command line to run all tests. While testing, you can modify a test to use `o.only(description, test)` instead of `o(description, test)` if you wish to run only a specific test to speed up your debugging experience. Don't forget to remove the `.only` after you're done! There is no need to `npm install` anything in order to run the test suite, however NodeJS is required to run the test suite from the command line. You do need to `npm install` if you want to lint or get a code coverage report though. ## How do I build Mithril? If all you're trying to do is run examples in the codebase, you don't need to build Mithril, you can just open the various html files and things should just work. To generate the bundled file for testing, run `npm run dev` from the command line. To generate the minified file, run `npm run build`. There is no need to `npm install` anything, but NodeJS is required to run the build scripts. ## Is there a style guide? Yes, there's an `eslint` configuration, but it's not strict about formatting at all. If your contribution passes `npm run lint`, it's good enough for a PR (and it can still be accepted even if it doesn't pass). Spacing and formatting inconsistencies may be fixed after the fact, and we don't want that kind of stuff getting in the way of contributing. ## Why do tests mock the browser APIs? Most notoriously, because it's impossible to test the router and some side effects properly otherwise. Also, mocks allow the tests to run under Node.js without requiring heavy dependencies like PhantomJS/ChromeDriver/JSDOM. Another important reason is that it allows us to document browser API quirks via code, through the tests for the mocks. ## Why does Mithril use its own testing framework and not Mocha/Jasmine/Tape? Mainly to avoid requiring dependencies. `ospec` is customized to provide only essential information for common testing workflows (namely, no spamming ok's on pass, and accurate noiseless errors on failure) ## Why do tests use `module/module.js`? Why not use Browserify, Webpack or Rollup? Again, to avoid requiring dependencies. The Mithril codebase is written using a statically analyzable subset of CommonJS module definitions (as opposed to ES6 modules) because its syntax is backwards compatible with ES5, therefore making it possible to run source code unmodified in browsers without the need for a build tool or a file watcher. This simplifies the workflow for bug fixes, which means they can be fixed faster. ## Why doesn't the Mithril codebase use ES6 via Babel? Would a PR to upgrade be welcome? Being able to run Mithril raw source code in IE is a requirement for all browser-related modules in this repo. In addition, ES6 features are usually less performant than equivalent ES5 code, and transpiled code is bulkier. ## Why doesn't the Mithril codebase use trailing semi-colons? Would a PR to add them be welcome? I don't use them. Adding them means the semi-colon usage in the codebase will eventually become inconsistent. ## Why does the Mithril codebase use a mix of `instanceof` and `typeof` checks instead of `Object.prototype.toString.call`, `Array.isArray`, etc? Would a PR to refactor those checks be welcome? Mithril avoids peeking at objects' [[class]] string for performance considerations. Many type checks are seemingly inconsistent, weird or convoluted because those specific constructs demonstrated the best performance profile in benchmarks compared to alternatives. Type checks are generally already irreducible expressions and having micro-modules for type checking subroutines would add maintenance overhead. ## What should I know in advance when attempting a performance related contribution? You should be trying to reduce the number of DOM operations or reduce algorithmic complexity in a hot spot. Anything else is likely a waste of time. Specifically, micro-optimizations like caching array lengths, caching object property values and inlining functions won't have any positive impact in modern javascript engines. Keep object properties consistent (i.e. ensure the data objects always have the same properties and that properties are always in the same order) to allow the engine to keep using JIT'ed structs instead of hashmaps. Always place null checks first in compound type checking expressions to allow the Javascript engine to optimize to type-specific code paths. Prefer for loops over Array methods and try to pull conditionals out of loops if possible. mithril-1.1.7+dfsg/docs/credits.md000066400000000000000000000041401512406206500170160ustar00rootroot00000000000000# Credits Mithril was originally written by Leo Horie, but it is where it is today thanks to the hard work and great ideas of many people. Special thanks to: - Pat Cavit, who exposed most of the public API for Mithril 1.0, brought in test coverage and automated the publishing process - Isiah Meadows, who brought in linting, modernized the test suite and has been a strong voice in design discussions - Zoli Kahan, who replaced the original Promise implementation with one that actually worked properly - Alec Embke, who single-handedly wrote the JSON-P implementation - Barney Carroll, who suggested many great ideas and relentlessly pushed Mithril to the limit to uncover design issues prior to Mithril 1.0 - Dominic Gannaway, who offered insanely meticulous technical insight into rendering performance - Boris Letocha, whose search space reduction algorithm is the basis for Mithril's virtual DOM engine - Joel Richard, whose monomorphic virtual DOM structure is the basis for Mithril's vnode implementation - Simon Friis Vindum, whose open source work was an inspiration to many design decisions for Mithril 1.0 - Boris Kaul, for his awesome work on the benchmarking tools used to develop Mithril - Leon Sorokin, for writing a DOM instrumentation tool that helped improve performance in Mithril 1.0 - Jordan Walke, whose work on React was prior art to the implementation of keys in Mithril - Pierre-Yves Gérardy, who consistently makes high quality contributions - Gyandeep Singh, who contributed significant IE performance improvements Other people who also deserve recognition: - Arthur Clemens - creator of [Polythene](https://github.com/ArthurClemens/Polythene) and the [HTML-to-Mithril converter](http://arthurclemens.github.io/mithril-template-converter/index.html) - Stephan Hoyer - creator of [mithril-node-render](https://github.com/StephanHoyer/mithril-node-render), [mithril-query](https://github.com/StephanHoyer/mithril-query) and [mithril-source-hint](https://github.com/StephanHoyer/mithril-source-hint) - the countless people who have reported and fixed bugs, participated in discussions, and helped promote Mithril mithril-1.1.7+dfsg/docs/css.md000066400000000000000000000101261512406206500161520ustar00rootroot00000000000000# CSS - [Vanilla CSS](#vanilla-css) - [Tachyons](#tachyons) - [CSS-in-JS](#css-in-js) --- ### Vanilla CSS For various reasons, CSS has a bad reputation and often developers reach for complex tools in an attempt to make styling more manageable. In this section, we'll take a step back and cover some tips on writing plain CSS: - **Avoid using the space operator** - The vast majority of CSS maintainability issues are due to CSS specificity issues. The space operator defines a descendant (e.g. `.a .b`) and at the same time, it increases the level of specificity for the CSS rules that apply to that selector, sometimes overriding styles unexpectedly. Instead, it's preferable to share a namespace prefix in all class names that belong to a logical group of elements: ```css /* AVOID */ .chat.container {/*...*/} .chat .item {/*...*/} .chat .avatar {/*...*/} .chat .text {/*...*/} /* PREFER */ .chat-container {/*...*/} .chat-item {/*...*/} .chat-avatar {/*...*/} .chat-text {/*...*/} ``` - **Use only single-class selectors** - This convention goes hand-in-hand with the previous one: avoiding high specificity selectors such as `#foo` or `div.bar` help decrease the likelyhood of specificity conflicts. ```css /* AVOID */ #home {} input.highlighted {} /* PREFER */ .home {} .input-highlighted {} ``` - **Develop naming conventions** - You can reduce naming collisions by defining keywords for certain types of UI elements. This is particularly effective when brand names are involved: ```css /* AVOID */ .twitter {} /* icon link in footer */ .facebook {} /* icon link in footer */ /* later... */ .modal.twitter {} /* tweet modal */ .modal.facebook {} /* share modal */ /* PREFER */ .link-twitter {} .link-facebook {} /* later... */ .modal-twitter {} .modal-facebook {} ``` --- ### Tachyons [Tachyons](https://github.com/tachyons-css/tachyons) is a CSS framework, but the concept behind it can easily be used without the library itself. The basic idea is that every class name must declare one and only one CSS rule. For example, `bw1` stands for `border-width:1px;`. To create a complex style, one simply combines class names representing each of the required CSS rules. For example, `.black.bg-dark-blue.br2` styles an element with blue background, black text and a 4px border-radius. Since each class is small and atomic, it's essentially impossible to run into CSS conflicts. As it turns out, the Tachyons convention fits extremely well with Mithril and JSX: ```jsx var Hero = ".black.bg-dark-blue.br2.pa3" m.mount(document.body, Hello) // equivalent to `m(".black.bg-dark.br2.pa3", "Hello")` ``` --- ### CSS in JS In plain CSS, all selectors live in the global scope and are prone to name collisions and specificity conflicts. CSS-in-JS aims to solve the issue of scoping in CSS, i.e. it groups related styles into non-global modules that are invisible to each other. CSS-in-JS is suitable for extremely large dev teams working on a single codebase, but it's not a good choice for most teams. Nowadays there are [a lot of CSS-in-JS libraries with various degrees of robustness](https://github.com/MicheleBertoli/css-in-js). The main problem with many of these libraries is that even though they require a non-trivial amount of transpiler tooling and configuration, they also require sacrificing code readability in order to work, e.g. `` vs `` (or `m("a.button.danger")` if we're using hyperscript). Often sacrifices also need to be made at time of debugging, when mapping rendered CSS class names back to their source. Often all you get in browser developer tools is a class like `button_fvp6zc2gdj35evhsl73ffzq_0 danger_fgdl0s2a5fmle5g56rbuax71_0` with useless source maps (or worse, entirely cryptic class names). Another common issue is lack of support for less basic CSS features such as `@keyframes` and `@font-face`. If you are adamant about using a CSS-in-JS library, consider using [J2C](https://github.com/j2css/j2c), which works without configuration and implements `@keyframes` and `@font-face`. mithril-1.1.7+dfsg/docs/es6.md000066400000000000000000000062671512406206500160720ustar00rootroot00000000000000# ES6 - [Setup](#setup) - [Using Babel with Webpack](#using-babel-with-webpack) --- Mithril is written in ES5, and is fully compatible with ES6 as well. ES6 is a recent update to Javascript that introduces new syntax sugar for various common cases. It's not yet fully supported by all major browsers and it's not a requirement for writing an application, but it may be pleasing to use depending on your team's preferences. In some limited environments, it's possible to use a significant subset of ES6 directly without extra tooling (for example, in internal applications that do not support IE). However, for the vast majority of use cases, a compiler toolchain like [Babel](https://babeljs.io) is required to compile ES6 features down to ES5. ### Setup The simplest way to setup an ES6 compilation toolchain is via [Babel](https://babeljs.io/). Babel requires NPM, which is automatically installed when you install [Node.js](https://nodejs.org/en/). Once NPM is installed, create a project folder and run this command: ```bash npm init -y ``` If you want to use Webpack and Babel together, [skip to the section below](#using-babel-with-webpack). To install Babel as a standalone tool, use this command: ```bash npm install babel-cli babel-preset-es2015 babel-plugin-transform-react-jsx --save-dev ``` Create a `.babelrc` file: ```json { "presets": ["es2015"], "plugins": [ ["transform-react-jsx", { "pragma": "m" }] ] } ``` To run Babel as a standalone tool, run this from the command line: ```bash babel src --out-dir bin --source-maps ``` #### Using Babel with Webpack If you're already using Webpack as a bundler, you can integrate Babel to Webpack by following these steps. ```bash npm install babel-core babel-loader babel-preset-es2015 babel-plugin-transform-react-jsx --save-dev ``` Create a `.babelrc` file: ```json { "presets": ["es2015"], "plugins": [ ["transform-react-jsx", { "pragma": "m" }] ] } ``` Next, create a file called `webpack.config.js` ```javascript const path = require('path') module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, './bin'), filename: 'app.js', }, module: { loaders: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }] } } ``` This configuration assumes the source code file for the application entry point is in `src/index.js`, and this will output the bundle to `bin/app.js`. To run the bundler, setup an npm script. Open `package.json` and add this entry under `"scripts"`: ```json { "name": "my-project", "scripts": { "start": "webpack -d --watch" } } ``` You can now then run the bundler by running this from the command line: ```bash npm start ``` #### Production build To generate a minified file, open `package.json` and add a new npm script called `build`: ```json { "name": "my-project", "scripts": { "start": "webpack -d --watch", "build": "webpack -p" } } ``` You can use hooks in your production environment to run the production build script automatically. Here's an example for [Heroku](https://www.heroku.com/): ```json { "name": "my-project", "scripts": { "start": "webpack -d --watch", "build": "webpack -p", "heroku-postbuild": "webpack -p" } } ``` mithril-1.1.7+dfsg/docs/examples.md000066400000000000000000000014371512406206500172050ustar00rootroot00000000000000# Examples Here are some examples of Mithril in action - [Animation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/animation/mosaic.html) - [DBMonster](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html) - [Markdown Editor](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/editor/index.html) - SVG: [Clock](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/clock.html), [Ring](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/ring.html), [Tiger](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/tiger.html) - [ThreadItJS](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/threaditjs/index.html) - [TodoMVC](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/todomvc/index.html) mithril-1.1.7+dfsg/docs/favicon.ico000066400000000000000000000353561512406206500171750ustar00rootroot0000000000000000 %6  % h6(0` $Kc cK,,||[KK[).>@.)373 3{67{B$ Dgd-&%2Bsx}=e"$h!" !rr0/ Ry)3+"b7!TI#"RU*rjevZQTO7!=ZNFB0 jO&,+W:JJ:6#rr#6*TT]&&n##AZ}I.$~@Eu>} 5PnL2-/rBL|-\K???ÏǏǏÏ???( @ PE""EPq--q))q4))4qSrrSBssB983 kk 3j{{jHG FHfe CR 10} (,K,4 ::#S.l**q<9=@ $\]# :@^^oj L; +Zw,NK ^: XBIB??????'ycy1#????(  Gd!!dGtUUtklv::v,}vu}2$xx#k]_th*5TT 0'9DE7fW-4AA%(0R\_Ia^3=xsesmithril-1.1.7+dfsg/docs/favicon.png000066400000000000000000000032411512406206500171730ustar00rootroot00000000000000PNG  IHDR DgAMA a cHRMz&u0`:pQ<PLTEPtRNSIBXڦΝ NK ^:,wZ+;oj L辽=@ $\]#<9.l*q4 S}(ԯ10CRƠf̶eHGF{3k8sr)-PE")z&bKGDH pHYs чtIME 8 #WIDAT8uSiCRA)b[bek0sιw}D)COA(U @fVj(?<|ק NO$aTj\ɼ ()Me8s,gQLϡ2=)dI]Ӓ߶whDc#.X:8t.9==x4x}6?Q zC'귄:rzYyBp2B񄉥YնBcs8r{GXRDc,pG/nhvD̑WoQ b5U"^M 3__M.L;FZc?S&EUYy#LPI6`/3lWah"$V_Ќ9F)( X RR#|@bK i++K1%'`* bck5 ! wk3{` | Yes | A list of vnodes **returns** | `Vnode` | | A fragment [vnode](vnodes.md) [How to read signatures](signatures.md) --- ### How it works `m.fragment()` creates a [fragment vnode](vnodes.md) with attributes. It is meant for advanced use cases involving [keys](keys.md) or [lifecyle methods](lifecycle-methods.md). A fragment vnode represents a list of DOM elements. If you want a regular element vnode that represents only one DOM element, you should use [`m()`](hyperscript.md) instead. Normally you can use simple arrays instead to denote a list of nodes: ```javascript var groupVisible = true m("ul", [ m("li", "child 1"), m("li", "child 2"), groupVisible ? [ // a fragment containing two elements m("li", "child 3"), m("li", "child 4"), ] : null ]) ``` However, Javascript arrays cannot be keyed or hold lifecycle methods. One option would be to create a wrapper element to host the key or lifecycle method, but sometimes it is not desirable to have an extra element (for example in complex table structures). In those cases, a fragment vnode can be used instead. There are a few benefits that come from using `m.fragment` instead of handwriting a vnode object structure: m.fragment creates [monomorphic objects](vnodes.md#monomorphic-class), which have better performance characteristics than creating objects dynamically. In addition, using `m.fragment` makes your intentions clear to other developers, and it makes it less likely that you'll mistakenly set attributes on the vnode object itself rather than on its `attrs` map. mithril-1.1.7+dfsg/docs/framework-comparison.md000066400000000000000000000564351512406206500215440ustar00rootroot00000000000000# Framework comparison - [Why not X?](#why-not-insert-favorite-framework-here) - [Why use Mithril?](#why-use-mithril) - [React](#react) - [Angular](#angular) - [Vue](#vue) If you're reading this page, you probably have used other frameworks to build applications, and you want to know if Mithril would help you solve your problems more effectively. --- ## Why not [insert favorite framework here]? The reality is that most modern frameworks are fast, well-suited to build complex applications, and maintainable if you know how to use them effectively. There are examples of highly complex applications in the wild using just about every popular framework: Udemy uses Angular, AirBnB uses React, Gitlab uses Vue, Guild Wars 2 uses Mithril (yes, inside the game!). Clearly, these are all production-quality frameworks. As a rule of thumb, if your team is already heavily invested in another framework/library/stack, it makes more sense to stick with it, unless your team agrees that there's a very strong reason to justify a costly rewrite. However, if you're starting something new, do consider giving Mithril a try, if nothing else, to see how much value Mithril adopters have been getting out of 8kb (gzipped) of code. Mithril is used by many well-known companies (e.g. Vimeo, Nike, Fitbit), and it powers large open-sourced platforms too (e.g. Lichess, Flarum). --- ## Why use Mithril? In one sentence: because **Mithril is pragmatic**. This [10 minute guide](index.md) is a good example: that's how long it takes to learn components, XHR and routing - and that's just about the right amount of knowledge needed to build useful applications. Mithril is all about getting meaningful work done efficiently. Doing file uploads? [The docs show you how](request.md#file-uploads). Authentication? [Documented too](route.md#authentication). Exit animations? [You got it](animation.md). No extra libraries, no magic. --- ## Comparisons ### React React is a view library maintained by Facebook. React and Mithril share a lot of similarities. If you already learned React, you already know almost all you need to build apps with Mithril. - They both use virtual DOM, lifecycle methods and key-based reconciliation - They both organize views via components - They both use Javascript as a flow control mechanism within views The most obvious difference between React and Mithril is in their scope. React is a view library, so a typical React-based application relies on third-party libraries for routing, XHR and state management. Using a library oriented approach allows developers to customize their stack to precisely match their needs. The not-so-nice way of saying that is that React-based architectures can vary wildly from project to project, and that those projects are that much more likely to cross the 1MB size line. Mithril has built-in modules for common necessities such as routing and XHR, and the [guide](simple-application.md) demonstrates idiomatic usage. This approach is preferable for teams that value consistency and ease of onboarding. #### Performance Both React and Mithril care strongly about rendering performance, but go about it in different ways. In the past React had two DOM rendering implementations (one using the DOM API, and one using `innerHTML`). Its upcoming fiber architecture introduces scheduling and prioritization of units of work. React also has a sophisticated build system that disables various checks and error messages for production deployments, and various browser-specific optimizations. In addition, there are also several performance-oriented libraries that leverage React's `shouldComponentUpdate` hook and immutable data structure libraries' fast object equality checking properties to reduce virtual DOM reconciliation times. Generally speaking, React's approach to performance is to engineer relatively complex solutions. Mithril follows the less-is-more school of thought. It has a substantially smaller, aggressively optimized codebase. The rationale is that a small codebase is easier to audit and optimize, and ultimately results in less code being run. Here's a comparison of library load times, i.e. the time it takes to parse and run the Javascript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop: React | Mithril ------- | ------- 55.8 ms | 4.5 ms Library load times matter in applications that don't stay open for long periods of time (for example, anything in mobile) and cannot be improved via caching or other optimization techniques. Since this is a micro-benchmark, you are encouraged to replicate these tests yourself since hardware can heavily affect the numbers. Note that bundler frameworks like Webpack can move dependencies out before the timer calls to emulate static module resolution, so you should either copy the code from the compiled CDN files or open the output file from the bundler library, and manually add the high resolution timer calls `console.time` and `console.timeEnd` to the bundled script. Avoid using `new Date` and `performance.now`, as those mechanisms are not as statistically accurate. For your reading convenience, here's a version of that benchmark adapted to use CDNs on the web: the [benchmark for React is here](https://jsfiddle.net/0ovkv64u/), and the [benchmark for Mithril is here](https://jsfiddle.net/o7hxooqL/). Note that we're benchmarking all of Mithril rather than benchmarking only the rendering module (which would be equivalent in scope to React). Also note that this CDN-driven setup incurs some overheads due to fetching resources from disk cache (~2ms per resource). Due to those reasons, the numbers here are not entirely accurate, but they should be sufficient to observe that Mithril's initialization speed is noticeably better than React. Here's a slightly more meaningful benchmark: measuring the scripting time for creating 10,000 divs (and 10,000 text nodes). Again, here's the benchmark code for [React](https://jsfiddle.net/bfoeay4f/) and [Mithril](https://jsfiddle.net/fft0ht7n/). Their best results are shown below: React | Mithril ------- | ------- 99.7 ms | 42.8 ms What these numbers show is that not only does Mithril initializes significantly faster, it can process upwards of 20,000 virtual DOM nodes before React is ready to use. ##### Update performance Update performance can be even more important than first-render performance, since updates can happen many times while a Single Page Application is running. A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [React implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/react/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Sample results are shown below: React | Mithril ------- | ------- 12.1 ms | 6.4 ms ##### Development performance Another thing to keep in mind is that because React adds extra checks and helpful error messages in development mode, it is slower in development than the production version used for the benchmarks above. To illustrate, [here's the 10,000 node benchmark from above using the development version of React](https://jsfiddle.net/r1jfckrd/). ##### Drop-in replacements There are [several](https://preactjs.com/) [projects](https://github.com/Lucifier129/react-lite) [that](https://infernojs.org/) [claim](https://github.com/alibaba/rax) API parity with React (some via compatibility layer libraries), but they are not fully compatible (e.g. PropType support is usually stubbed out, synthetic events are sometimes not supported, and some APIs have different semantics). Note that these libraries typically also include features of their own that are not part of the official React API, which may become problematic down the road if one decides to switch back to React Fiber. Claims about small download size (compared to React) are accurate, but most of these libraries are slightly larger than Mithril's renderer module. Preact is the only exception. Be wary of aggressive performance claims, as benchmarks used by some of these projects are known to be out-of-date and flawed (in the sense that they can be - and are - exploited). Boris Kaul (author of some of the benchmarks) has [written in detail about how benchmarks are gamed](https://medium.com/@localvoid/how-to-win-in-web-framework-benchmarks-8bc31af76ce7). Another thing to keep in mind is that some benchmarks aggressively use advanced optimization features and thus demonstrate *potential* performance, i.e. performance that is possible given some caveats, but realistically unlikely unless you actively spend the time to go over your entire codebase identifying optimization candidates and evaluating the regression risks brought by the optimization caveats. In the spirit of demonstrating *typical* performance characteristics, the benchmarks presented in this comparison page are implemented in an apples-to-apples, naive, idiomatic way (i.e. the way you would normally write 99% of your code) and do not employ tricks or advanced optimizations to make one or other framework look artificially better. You are encouraged to contribute a PR if you feel any DbMonster implementation here could be written more idiomatically. #### Complexity Both React and Mithril have relatively small API surfaces compared to other frameworks, which help ease learning curve. However, whereas idiomatic Mithril can be written without loss of readability using plain ES5 and no other dependencies, idiomatic React relies heavily on complex tooling (e.g. Babel, JSX plugin, etc), and this level of complexity frequently extends to popular parts of its ecosystem, be it in the form of syntax extensions (e.g. non-standard object spread syntax in Redux), architectures (e.g. ones using immutable data libraries), or bells and whistles (e.g. hot module reloading). While complex toolchains are also possible with Mithril and other frameworks alike, it's *strongly* recommended that you follow the [KISS](https://en.wikipedia.org/wiki/KISS_principle) and [YAGNI](https://en.wikipedia.org/wiki/You_aren't_gonna_need_it) principles when using Mithril. #### Learning curve Both React and Mithril have relatively small learning curves. React's learning curve mostly involves understanding components and their lifecycle. The learning curve for Mithril components is nearly identical. There are obviously more APIs to learn in Mithril, since Mithril also includes routing and XHR, but the learning curve would be fairly similar to learning React, React Router and a XHR library like superagent or axios. Idiomatic React requires working knowledge of JSX and its caveats, and therefore there's also a small learning curve related to Babel. #### Documentation React documentation is clear and well written, and includes a good API reference, tutorials for getting started, as well as pages covering various advanced concepts. Unfortunately, since React is limited to being only a view library, its documentation does not explore how to use React idiomatically in the context of a real-life application. As a result, there are many popular state management libraries and thus architectures using React can differ drastically from company to company (or even between projects). Mithril documentation also includes [introductory](index.md) [tutorials](simple-application.md), pages about advanced concepts, and an extensive API reference section, which includes input/output type information, examples for various common use cases and advice against misuse and anti-patterns. It also includes a cheatsheet for quick reference. Mithril documentation also demonstrates simple, close-to-the-metal solutions to common use cases in real-life applications where it's appropriate to inform a developer that web standards may be now on par with larger established libraries. --- ### Angular Angular is a web application framework maintained by Google. Angular and Mithril are fairly different, but they share a few similarities: - Both support componentization - Both have an array of tools for various aspects of web applications (e.g. routing, XHR) The most obvious difference between Angular and Mithril is in their complexity. This can be seen most easily in how views are implemented. Mithril views are plain Javascript, and flow control is done with Javascript built-in mechanisms such as ternary operators or `Array.prototype.map`. Angular, on the other hand, implements a directive system to extend HTML views so that it's possible to evaluate Javascript-like expressions within HTML attributes and interpolations. Angular actually ships with a parser and a compiler written in Javascript to achieve that. If that doesn't seem complex enough, there's actually two compilation modes (a default mode that generates Javascript functions dynamically for performance, and [a slower mode](https://docs.angularjs.org/api/ng/directive/ngCsp) for dealing with Content Security Policy restrictions). #### Performance Angular has made a lot of progress in terms of performance over the years. Angular 1 used a mechanism known as dirty checking which tended to get slow due to the need to constantly diff large `$scope` structures. Angular 2 uses a template change detection mechanism that is much more performant. However, even despite Angular's improvements, Mithril is often faster than Angular, due to the ease of auditing that Mithril's small codebase size affords. It's difficult to make a comparison of load times between Angular and Mithril for a couple of reasons. The first is that Angular 1 and 2 are in fact completely different codebases, and both versions are officially supported and maintained (and the vast majority of Angular codebases in the wild currently still use version 1). The second reason is that both Angular and Mithril are modular. In both cases, it's possible to remove a significant part of the framework that is not used in a given application. With that being said, the smallest known Angular 2 bundle is a [29kb hello world](https://www.lucidchart.com/techblog/2016/09/26/improving-angular-2-load-times/) compressed w/ the Brotli algorithm (it's 35kb using standard gzip), and with most of Angular's useful functionality removed. By comparison, a Mithril hello world - including the entire Mithril core - would not be over 8kb gzipped (a more optimized bundle could easily be half of that). Also, remember that frameworks like Angular and Mithril are designed for non-trivial application, so an application that managed to use all of Angular's API surface would need to download several hundred kb of framework code, rather than merely 29kb. ##### Update performance A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/angular/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: Angular | Mithril ------- | ------- 11.5 ms | 6.4 ms #### Complexity Angular is superior to Mithril in the amount of tools it offers (in the form of various directives and services), but it is also far more complex. Compare [Angular's API surface](https://angular.io/docs/ts/latest/api/) with [Mithril's](api.md). You can make your own judgment on which API is more self-descriptive and more relevant to your needs. Angular 2 has a lot more concepts to understand: on the language level, Typescript is the recommended language, and on top of that there's also Angular-specific template syntax such as bindings, pipes, "safe navigator operator". You also need to learn about architectural concepts such as modules, components, services, directives, etc, and where it's appropriate to use what. #### Learning curve If we compare apples to apples, Angular 2 and Mithril have similar learning curves: in both, components are a central aspect of architecture, and both have reasonable routing and XHR tools. With that being said, Angular has a lot more concepts to learn than Mithril. It offers Angular-specific APIs for many things that often can be trivially implemented (e.g. pluralization is essentially a switch statement, "required" validation is simply an equality check, etc). Angular templates also have several layers of abstractions to emulate what Javascript does natively in Mithril - Angular's `ng-if`/`ngIf` is a *directive*, which uses a custom *parser* and *compiler* to evaluate an expression string and emulate lexical scoping... and so on. Mithril tends to be a lot more transparent, and therefore easier to reason about. #### Documentation Angular 2 documentation provides an extensive introductory tutorial, and another tutorial that implements an application. It also has various guides for advanced concepts, a cheatsheet and a style guide. Unfortunately, at the moment, the API reference leaves much to be desired. Several APIs are either undocumented or provide no context for what the API might be used for. Mithril documentation includes [introductory](index.md) [tutorials](simple-application.md), pages about advanced concepts, and an extensive API reference section, which includes input/output type information, examples for various common use cases and advice against misuse and anti-patterns. It also includes a cheatsheet for quick reference. Mithril documentation also demonstrates simple, close-to-the-metal solutions to common use cases in real-life applications where it's appropriate to inform a developer that web standards may be now on par with larger established libraries. --- ### Vue Vue is a view library similar to Angular. Vue and Mithril have a lot of differences but they also share some similarities: - They both use virtual DOM and lifecycle methods - Both organize views via components Vue 2 uses a fork of Snabbdom as its virtual DOM system. In addition, Vue also provides tools for routing and state management as separate modules. Vue looks very similar to Angular and provides a similar directive system, HTML-based templates and logic flow directives. It differs from Angular in that it implements a monkeypatching reactive system that overwrites native methods in a component's data tree (whereas Angular 1 uses dirty checking and digest/apply cycles to achieve similar results). Similar to Angular 2, Vue compiles HTML templates into functions, but the compiled functions look more like Mithril or React views, rather than Angular's compiled rendering functions. Vue is significantly smaller than Angular when comparing apples to apples, but not as small as Mithril (Vue core is around 23kb gzipped, whereas the equivalent rendering module in Mithril is around 4kb gzipped). Both have similar performance characteristics, but benchmarks usually suggest Mithril is slightly faster. #### Performance Here's a comparison of library load times, i.e. the time it takes to parse and run the Javascript code for each framework, by adding a `console.time()` call on the first line and a `console.timeEnd()` call on the last of a script that is composed solely of framework code. For your reading convenience, here are best-of-20 results with logging code manually added to bundled scripts, running from the filesystem, in Chrome on a modest 2010 PC desktop: Vue | Mithril ------- | ------- 21.8 ms | 4.5 ms Library load times matter in applications that don't stay open for long periods of time (for example, anything in mobile) and cannot be improved via caching or other optimization techniques. ##### Update performance A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [Vue implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/vue/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: Vue | Mithril ------ | ------- 9.8 ms | 6.4 ms #### Complexity Vue is heavily inspired by Angular and has many things that Angular does (e.g. directives, filters, bi-directional bindings, `v-cloak`), but also has things inspired by React (e.g. components). As of Vue 2.0, it's also possible to write templates using hyperscript/JSX syntax (in addition to single-file components and the various webpack-based language transpilation plugins). Vue provides both bi-directional data binding and an optional Redux-like state management library, but unlike Angular, it provides no style guide. The many-ways-of-doing-one-thing approach can cause architectural fragmentation in long-lived projects. Mithril has far less concepts and typically organizes applications in terms of components and a data layer. All component creation styles in Mithril output the same vnode structure using native Javascript features only. The direct consequence of leaning on the language is less tooling and a simpler project setup. #### Documentation Both Vue and Mithril have good documentation. Both include a good API reference with examples, tutorials for getting started, as well as pages covering various advanced concepts. However, due to Vue's many-ways-to-do-one-thing approach, some things may not be adequately documented. For example, there's no documentation on hyperscript syntax or usage. Mithril documentation typically errs on the side of being overly thorough if a topic involves things outside of the scope of Mithril. For example, when a topic involves a 3rd party library, Mithril documentation walks through the installation process for the 3rd party library. Mithril documentation also often demonstrates simple, close-to-the-metal solutions to common use cases in real-life applications where it's appropriate to inform a developer that web standards may be now on par with larger established libraries. Mithril's tutorials also cover a lot more ground than Vue's: the [Vue tutorial](https://vuejs.org/v2/guide/#Getting-Started) finishes with a static list of foodstuff. [Mithril's 10 minute guide](index.md) covers the majority of its API and goes over key aspects of real-life applications, such as fetching data from a server and routing (and there's a [longer, more thorough tutorial](simple-application.md) if that's not enough). mithril-1.1.7+dfsg/docs/generate.js000066400000000000000000000063601512406206500171750ustar00rootroot00000000000000"use strict" var fs = require("fs") var path = require("path") var marked = require("marked") var layout = fs.readFileSync("./docs/layout.html", "utf-8") var version = JSON.parse(fs.readFileSync("./package.json", "utf-8")).version try {fs.mkdirSync("./dist")} catch (e) {/* ignore */} try {fs.mkdirSync("./dist/archive")} catch (e) {/* ignore */} try {fs.mkdirSync("./dist/archive/v" + version)} catch (e) {/* ignore */} var guides = fs.readFileSync("docs/nav-guides.md", "utf-8") var methods = fs.readFileSync("docs/nav-methods.md", "utf-8") generate("docs") function generate(pathname) { if (fs.lstatSync(pathname).isDirectory()) { fs.readdirSync(pathname).forEach(function(filename) { generate(pathname + "/" + filename) }) } else if (!pathname.match(/tutorials|archive|nav-/)) { if (pathname.match(/\.md$/)) { var outputFilename = pathname.replace(/\.md$/, ".html") var markdown = fs.readFileSync(pathname, "utf-8") var anchors = {} var fixed = markdown .replace(/`((?:\S| -> |, )+)(\|)(\S+)`/gim, function(match, a, b, c) { // fix pipes in code tags return "" + (a + b + c).replace(/\|/g, "|") + "" }) .replace(/(^# .+?(?:\r?\n){2,}?)(?:(-(?:.|\r|\n)+?)((?:\r?\n){2,})|)/m, function(match, title, nav) { // inject menu var file = path.basename(pathname) var link = new RegExp("([ \t]*)(- )(\\[.+?\\]\\(" + file + "\\))") var replace = function(match, space, li, link) { return space + li + "**" + link + "**" + (nav ? "\n" + nav.replace(/(^|\n)/g, "$1\t" + space) : "") } var modified = guides.match(link) ? guides.replace(link, replace) : methods.replace(link, replace) return title + modified + "\n\n" }) .replace(/(\]\([^\)]+)(\.md)/gim, function(match, path, extension) { return path + (path.match(/http/) ? extension : ".html") }) // fix links var markedHtml = marked(fixed) .replace(/(\W)Array<([^/<]+?)>/gim, "$1Array<$2>") // Fix type signatures containing Array<...> var title = fixed.match(/^#([^\n\r]+)/i) || [] var html = layout .replace(/Mithril\.js<\/title>/, "<title>" + title[1] + " - Mithril.js") .replace(/\[version\]/g, version) // update version .replace(/\[body\]/, markedHtml) .replace(/(.+?)<\/h.>/gim, function(match, n, id, text) { // fix anchors var anchor = text.toLowerCase().replace(/<(\/?)code>/g, "").replace(/.+?<\/a>/g, "").replace(/\.|\[|\]|"|\/|\(|\)/g, "").replace(/\s/g, "-"); if(anchor in anchors) { anchor += ++anchors[anchor] } else { anchors[anchor] = 0; } return `${text}`; }) fs.writeFileSync("./dist/archive/v" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8") fs.writeFileSync("./dist/" + outputFilename.replace(/^docs\//, ""), html, "utf-8") } else if (!pathname.match(/lint|generate/)) { var encoding = (/\.(ico|png)$/i).test(path.extname(pathname)) ? "binary" : "utf-8"; fs.writeFileSync("./dist/archive/v" + version + "/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, encoding), encoding) fs.writeFileSync("./dist/" + pathname.replace(/^docs\//, ""), fs.readFileSync(pathname, encoding), encoding) } } } mithril-1.1.7+dfsg/docs/hyperscript.md000066400000000000000000000337001512406206500177410ustar00rootroot00000000000000# m(selector, attributes, children) - [Description](#description) - [Signature](#signature) - [How it works](#how-it-works) - [Flexibility](#flexibility) - [CSS selectors](#css-selectors) - [DOM attributes](#dom-attributes) - [Style attribute](#style-attribute) - [Events](#events) - [Properties](#properties) - [Components](#components) - [Lifecycle methods](#lifecycle-methods) - [Keys](#keys) - [SVG and MathML](#svg-and-mathml) - [Making templates dynamic](#making-templates-dynamic) - [Converting HTML](#converting-html) - [Avoid anti-patterns](#avoid-anti-patterns) --- ### Description Represents an HTML element in a Mithril view ```javascript m("div", {class: "foo"}, "hello") // represents
hello
``` You can also [use HTML syntax](https://babeljs.io/repl/#?code=%2F**%20%40jsx%20m%20*%2F%0A%3Ch1%3EMy%20first%20app%3C%2Fh1%3E) via a Babel plugin. ```markup /** jsx m */
hello
``` --- ### Signature `vnode = m(selector, attributes, children)` Argument | Type | Required | Description ------------ | ------------------------------------------ | -------- | --- `selector` | `String|Object` | Yes | A CSS selector or a [component](components.md) `attributes` | `Object` | No | HTML attributes or element properties `children` | `Array|String|Number|Boolean` | No | Child [vnodes](vnodes.md#structure). Can be written as [splat arguments](signatures.md#splats) **returns** | `Vnode` | | A [vnode](vnodes.md#structure) [How to read signatures](signatures.md) --- ### How it works Mithril provides a hyperscript function `m()`, which allows expressing any HTML structure using javascript syntax. It accepts a `selector` string (required), an `attributes` object (optional) and a `children` array (optional). ```javascript m("div", {id: "box"}, "hello") // equivalent HTML: //
hello
``` The `m()` function does not actually return a DOM element. Instead it returns a [virtual DOM node](vnodes.md), or *vnode*, which is a javascript object that represents the DOM element to be created. ```javascript // a vnode var vnode = {tag: "div", attrs: {id: "box"}, children: [ /*...*/ ]} ``` To transform a vnode into an actual DOM element, use the [`m.render()`](render.md) function: ```javascript m.render(document.body, m("br")) // puts a
in ``` Calling `m.render()` multiple times does **not** recreate the DOM tree from scratch each time. Instead, each call will only make a change to a DOM tree if it is absolutely necessary to reflect the virtual DOM tree passed into the call. This behavior is desirable because recreating the DOM from scratch is very expensive, and causes issues such as loss of input focus, among other things. By contrast, updating the DOM only where necessary is comparatively much faster and makes it easier to maintain complex UIs that handle multiple user stories. --- ### Flexibility The `m()` function is both *polymorphic* and *variadic*. In other words, it's very flexible in what it expects as input parameters: ```javascript // simple tag m("div") //
// attributes and children are optional m("a", {id: "b"}) // m("span", "hello") // hello // tag with child nodes m("ul", [ //
    m("li", "hello"), //
  • hello
  • m("li", "world"), //
  • world
  • ]) //
// array is optional m("ul", //
    m("li", "hello"), //
  • hello
  • m("li", "world") //
  • world
  • ) //
``` --- ### CSS selectors The first argument of `m()` can be any CSS selector that can describe an HTML element. It accepts any valid CSS combinations of `#` (id), `.` (class) and `[]` (attribute) syntax. ```javascript m("div#hello") //
m("section.container") //
m("input[type=text][placeholder=Name]") // m("a#exit.external[href='http://example.com']", "Leave") // Leave ``` If you omit the tag name, Mithril assumes a `div` tag. ```javascript m(".box.box-bordered") //
``` Typically, it's recommended that you use CSS selectors for static attributes (i.e. attributes whose value do not change), and pass an attributes object for dynamic attribute values. ```javascript var currentURL = "/" m("a.link[href=/]", { class: currentURL === "/" ? "selected" : "" }, "Home") // equivalent HTML: // Home ``` If there are class names in both first and second arguments of `m()`, they are merged together as you would expect. --- ### DOM attributes Mithril uses both the Javascript API and the DOM API (`setAttribute`) to resolve attributes. This means you can use both syntaxes to refer to attributes. For example, in the Javascript API, the `readonly` attribute is called `element.readOnly` (notice the uppercase). In Mithril, all of the following are supported: ```javascript m("input", {readonly: true}) // lowercase m("input", {readOnly: true}) // uppercase m("input[readonly]") m("input[readOnly]") ``` --- ### Style attribute Mithril supports both strings and objects as valid `style` values. In other words, all of the following are supported: ```javascript m("div", {style: "background:red;"}) m("div", {style: {background: "red"}}) m("div[style=background:red]") ``` Using a string as a `style` would overwrite all inline styles in the element if it is redrawn, and not only CSS rules whose values have changed. Mithril does not attempt to add units to number values. --- ### Events Mithril supports event handler binding for all DOM events, including events whose specs do not define an `on${event}` property, such as `touchstart` ```javascript function doSomething(e) { console.log(e) } m("div", {onclick: doSomething}) ``` --- ### Properties Mithril supports DOM functionality that is accessible via properties such as ``. ```javascript m.render(document.body, [ m("input[type=file]", {onchange: upload}) ]) function upload(e) { var file = e.target.files[0] } ``` The snippet above renders a file input. If a user picks a file, the `onchange` event is triggered, which calls the `upload` function. `e.target.files` is a list of `File` objects. Next, you need to create a [`FormData`](https://developer.mozilla.org/en/docs/Web/API/FormData) object to create a [multipart request](https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html), which is a specially formatted HTTP request that is able to send file data in the request body. ```javascript function upload(e) { var file = e.target.files[0] var data = new FormData() data.append("myfile", file) } ``` Next, you need to call `m.request` and set `options.method` to an HTTP method that uses body (e.g. `POST`, `PUT`, `PATCH`) and use the `FormData` object as `options.data`. ```javascript function upload(e) { var file = e.target.files[0] var data = new FormData() data.append("myfile", file) m.request({ method: "POST", url: "/api/v1/upload", data: data, }) } ``` Assuming the server is configured to accept multipart requests, the file information will be associated with the `myfile` key. #### Multiple file uploads It's possible to upload multiple files in one request. Doing so will make the batch upload atomic, i.e. no files will be processed if there's an error during the upload, so it's not possible to have only part of the files saved. If you want to save as many files as possible in the event of a network failure, you should consider uploading each file in a separate request instead. To upload multiple files, simply append them all to the `FormData` object. When using a file input, you can get a list of files by adding the `multiple` attribute to the input: ```javascript m.render(document.body, [ m("input[type=file][multiple]", {onchange: upload}) ]) function upload(e) { var files = e.target.files var data = new FormData() for (var i = 0; i < files.length; i++) { data.append("file" + i, files[i]) } m.request({ method: "POST", url: "/api/v1/upload", data: data, }) } ``` --- ### Monitoring progress Sometimes, if a request is inherently slow (e.g. a large file upload), it's desirable to display a progress indicator to the user to signal that the application is still working. `m.request()` exposes its underlying `XMLHttpRequest` object via the `options.config` parameter, which allows you to attach event listeners to the XMLHttpRequest object: ```javascript var progress = 0 m.mount(document.body, { view: function() { return [ m("input[type=file]", {onchange: upload}), progress + "% completed" ] } }) function upload(e) { var file = e.target.files[0] var data = new FormData() data.append("myfile", file) m.request({ method: "POST", url: "/api/v1/upload", data: data, config: function(xhr) { xhr.upload.addEventListener("progress", function(e) { progress = e.loaded / e.total m.redraw() // tell Mithril that data changed and a re-render is needed }) } }) } ``` In the example above, a file input is rendered. If the user picks a file, an upload is initiated, and in the `config` callback, a `progress` event handler is registered. This event handler is fired whenever there's a progress update in the XMLHttpRequest. Because the XMLHttpRequest's progress event is not directly handled by Mithril's virtual DOM engine, `m.redraw()` must be called to signal to Mithril that data has changed and a redraw is required. --- ### Casting response to a type Depending on the overall application architecture, it may be desirable to transform the response data of a request to a specific class or type (for example, to uniformly parse date fields or to have helper methods). You can pass a constructor as the `options.type` parameter and Mithril will instantiate it for each object in the HTTP response. ```javascript function User(data) { this.name = data.firstName + " " + data.lastName } m.request({ method: "GET", url: "/api/v1/users", type: User }) .then(function(users) { console.log(users[0].name) // logs a name }) ``` In the example above, assuming `/api/v1/users` returns an array of objects, the `User` constructor will be instantiated (i.e. called as `new User(data)`) for each object in the array. If the response returned a single object, that object would be used as the `data` argument. --- ### Non-JSON responses Sometimes a server endpoint does not return a JSON response: for example, you may be requesting an HTML file, an SVG file, or a CSV file. By default Mithril attempts to parse a response as if it was JSON. To override that behavior, define a custom `options.deserialize` function: ```javascript m.request({ method: "GET", url: "/files/icon.svg", deserialize: function(value) {return value} }) .then(function(svg) { m.render(document.body, m.trust(svg)) }) ``` In the example above, the request retrieves an SVG file, does nothing to parse it (because `deserialize` merely returns the value as-is), and then renders the SVG string as trusted HTML. Of course, a `deserialize` function may be more elaborate: ```javascript m.request({ method: "GET", url: "/files/data.csv", deserialize: parseCSV }) .then(function(data) { console.log(data) }) function parseCSV(data) { // naive implementation for the sake of keeping example simple return data.split("\n").map(function(row) { return row.split(",") }) } ``` Ignoring the fact that the parseCSV function above doesn't handle a lot of cases that a proper CSV parser would, the code above logs an array of arrays. Custom headers may also be helpful in this regard. For example, if you're requesting an SVG, you probably want to set the content type accordingly. To override the default JSON request type, set `options.headers` to an object of key-value pairs corresponding to request header names and values. ```javascript m.request({ method: "GET", url: "/files/image.svg", headers: { "Content-Type": "image/svg+xml; charset=utf-8", "Accept": "image/svg, text/*" }, deserialize: function(value) {return value} }) ``` --- ### Retrieving response details By default Mithril attempts to parse `xhr.responseText` as JSON and returns the parsed object. It may be useful to inspect a server response in more detail and process it manually. This can be accomplished by passing a custom `options.extract` function: ```javascript m.request({ method: "GET", url: "/api/v1/users", extract: function(xhr) {return {status: xhr.status, body: xhr.responseText}} }) .then(function(response) { console.log(response.status, response.body) }) ``` The parameter to `options.extract` is the XMLHttpRequest object once its operation is completed, but before it has been passed to the returned promise chain, so the promise may still end up in an rejected state if processing throws an exception. --- ### Why JSON instead of HTML Many server-side frameworks provide a view engine that interpolates database data into a template before serving HTML (on page load or via AJAX) and then employ jQuery to handle user interactions. By contrast, Mithril is framework designed for thick client applications, which typically download templates and data separately and combine them in the browser via Javascript. Doing the templating heavy-lifting in the browser can bring benefits like reducing operational costs by freeing server resources. Separating templates from data also allow template code to be cached more effectively and enables better code reusability across different types of clients (e.g. desktop, mobile). Another benefit is that Mithril enables a [retained mode](https://en.wikipedia.org/wiki/Retained_mode) UI development paradigm, which greatly simplifies development and maintenance of complex user interactions. By default, `m.request` expects response data to be in JSON format. In a typical Mithril application, that JSON data is then usually consumed by a view. You should avoid trying to render server-generated dynamic HTML with Mithril. If you have an existing application that does use a server-side templating system, and you wish to re-architecture it, first decide whether the effort is feasible at all to begin with. Migrating from a thick server architecture to a thick client architecture is typically a somewhat large effort, and involves refactoring logic out of templates into logical data services (and the testing that goes with it). Data services may be organized in many different ways depending on the nature of the application. [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) architectures are popular with API providers, and [service oriented architectures](https://en.wikipedia.org/wiki/Service-oriented_architecture) are often required where there are lots of highly transactional workflows. --- ### Why XHR instead of fetch [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is a newer Web API for fetching resources from servers, similar to `XMLHttpRequest`. Mithril's `m.request` uses `XMLHttpRequest` instead of `fetch()` for a number of reasons: - `fetch` is not fully standardized yet, and may be subject to specification changes. - `XMLHttpRequest` calls can be aborted before they resolve (e.g. to avoid race conditions in for instant search UIs). - `XMLHttpRequest` provides hooks for progress listeners for long running requests (e.g. file uploads). - `XMLHttpRequest` is supported by all browsers, whereas `fetch()` is not supported by Internet Explorer, Safari and Android (non-Chromium). Currently, due to lack of browser support, `fetch()` typically requires a [polyfill](https://github.com/github/fetch), which is over 11kb uncompressed - nearly three times larger than Mithril's XHR module. Despite being much smaller, Mithril's XHR module supports many important and not-so-trivial-to-implement features like [URL interpolation](#dynamic-urls), querystring serialization and [JSON-P requests](jsonp.md), in addition to its ability to integrate seamlessly to Mithril's autoredrawing subsystem. The `fetch` polyfill does not support any of those, and requires extra libraries and boilerplates to achieve the same level of functionality. In addition, Mithril's XHR module is optimized for JSON-based endpoints and makes that most common case appropriately terse - i.e. `m.request(url)` - whereas `fetch` requires an additional explicit step to parse the response data as JSON: `fetch(url).then(function(response) {return response.json()})` The `fetch()` API does have a few technical advantages over `XMLHttpRequest` in a few uncommon cases: - it provides a streaming API (in the "video streaming" sense, not in the reactive programming sense), which enables better latency and memory consumption for very large responses (at the cost of code complexity). - it integrates to the [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), which provides an extra layer of control over how and when network requests happen. This API also allows access to push notifications and background synchronization features. In typical scenarios, streaming won't provide noticeable performance benefits because it's generally not advisable to download megabytes of data to begin with. Also, the memory gains from repeatedly reusing small buffers may be offset or nullified if they result in excessive browser repaints. For those reasons, choosing `fetch()` streaming instead of `m.request` is only recommended for extremely resource intensive applications. --- ### Avoid anti-patterns #### Promises are not the response data The `m.request` method returns a [Promise](promise.md), not the response data itself. It cannot return that data directly because an HTTP request may take a long time to complete (due to network latency), and if Javascript waited for it, it would freeze the application until the data was available. ```javascript // AVOID var users = m.request("/api/v1/users") console.log("list of users:", users) // `users` is NOT a list of users, it's a promise // PREFER m.request("/api/v1/users").then(function(users) { console.log("list of users:", users) }) ``` mithril-1.1.7+dfsg/docs/route.md000066400000000000000000000672001512406206500165250ustar00rootroot00000000000000# route(root, defaultRoute, routes) - [Description](#description) - [Signature](#signature) - [Static members](#static-members) - [m.route.set](#mrouteset) - [m.route.get](#mrouteget) - [m.route.prefix](#mrouteprefix) - [m.route.link](#mroutelink) - [m.route.param](#mrouteparam) - [RouteResolver](#routeresolver) - [routeResolver.onmatch](#routeresolveronmatch) - [routeResolver.render](#routeresolverrender) - [How it works](#how-it-works) - [Typical usage](#typical-usage) - [Navigating to different routes](#navigating-to-different-routes) - [Routing parameters](#routing-parameters) - [Key parameter](#key-parameter) - [Variadic routes](#variadic-routes) - [History state](#history-state) - [Changing router prefix](#changing-router-prefix) - [Advanced component resolution](#advanced-component-resolution) - [Wrapping a layout component](#wrapping-a-layout-component) - [Authentication](#authentication) - [Preloading data](#preloading-data) - [Code splitting](#code-splitting) --- ### Description Navigate between "pages" within an application ```javascript var Home = { view: function() { return "Welcome" } } m.route(document.body, "/home", { "/home": Home, // defines `http://localhost/#!/home` }) ``` You can only have one `m.route` call per application. --- ### Signature `m.route(root, defaultRoute, routes)` Argument | Type | Required | Description ---------------------- | ---------------------------------------- | -------- | --- `root` | `Element` | Yes | A DOM element that will be the parent node to the subtree `defaultRoute` | `String` | Yes | The route to redirect to if the current URL does not match a route `routes` | `Object` | Yes | An object whose keys are route strings and values are either components or a [RouteResolver](#routeresolver) **returns** | | | Returns `undefined` [How to read signatures](signatures.md) #### Static members ##### m.route.set Redirects to a matching route, or to the default route if no matching routes can be found. Triggers an asynchronous redraw off all mount points. `m.route.set(path, data, options)` Argument | Type | Required | Description ----------------- | --------- | -------- | --- `path` | `String` | Yes | The path to route to, without a prefix. The path may include slots for routing parameters `data` | `Object` | No | Routing parameters. If `path` has routing parameter slots, the properties of this object are interpolated into the path string `options.replace` | `Boolean` | No | Whether to create a new history entry or to replace the current one. Defaults to false `options.state` | `Object` | No | The `state` object to pass to the underlying `history.pushState` / `history.replaceState` call. This state object becomes available in the `history.state` property, and is merged into the [routing parameters](#routing-parameters) object. Note that this option only works when using the pushState API, but is ignored if the router falls back to hashchange mode (i.e. if the pushState API is not available) `options.title` | `String` | No | The `title` string to pass to the underlying `history.pushState` / `history.replaceState` call. **returns** | | | Returns `undefined` ##### m.route.get Returns the last fully resolved routing path, without the prefix. It may differ from the path displayed in the location bar while an asynchronous route is [pending resolution](#code-splitting). `path = m.route.get()` Argument | Type | Required | Description ----------------- | --------- | -------- | --- **returns** | String | | Returns the last fully resolved path ##### m.route.prefix Defines a router prefix. The router prefix is a fragment of the URL that dictates the underlying [strategy](#routing-strategies) used by the router. `m.route.prefix(prefix)` Argument | Type | Required | Description ----------------- | --------- | -------- | --- `prefix` | `String` | Yes | The prefix that controls the underlying [routing strategy](#routing-strategies) used by Mithril. **returns** | | | Returns `undefined` ##### m.route.link This function can be used as the `oncreate` (and `onupdate`) hook in a `m("a")` vnode: ```JS m("a[href=/]", {oncreate: m.route.link}) ``` Using `m.route.link` as a `oncreate` hook causes the link to behave as a router link (i.e. it navigates to the route specified in `href`, instead of navigating away from the current page to the URL specified in `href`. If the `href` attribute is not static, the `onupdate` hook must also be set: ```JS m("a", {href: someVariable, oncreate: m.route.link, onupdate: m.route.link}) ``` `m.route.link` can also set the `options` passed to `m.route.set` when the link is clicked by calling the function in the lifecycle methods: ```JS m("a[href=/]", {oncreate: m.route.link({replace: true})}) ``` `m.route.link(args)` Argument | Type | Required | Description ----------------- | ---------------| -------- | --- `args` | `Vnode|Object` | Yes | This method is meant to be used as or in conjunction with an `` [vnode](vnodes.md)'s [`oncreate` and `onupdate` hooks](lifecycle-methods.md) **returns** | `function` | | Returns the onclick handler function for the component ##### m.route.param Retrieves a route parameter from the last fully resolved route. A route parameter is a key-value pair. Route parameters may come from a few different places: - route interpolations (e.g. if a route is `/users/:id`, and it resolves to `/users/1`, the route parameter has a key `id` and value `"1"`) - router querystrings (e.g. if the path is `/users?page=1`, the route parameter has a key `page` and value `"1"`) - `history.state` (e.g. if history.state is `{foo: "bar"}`, the route parameter has key `foo` and value `"bar"`) `value = m.route.param(key)` Argument | Type | Required | Description ----------------- | --------------- | -------- | --- `key` | `String` | No | A route parameter name (e.g. `id` in route `/users/:id`, or `page` in path `/users/1?page=3`, or a key in `history.state`) **returns** | `String|Object` | | Returns a value for the specified key. If a key is not specified, it returns an object that contains all the interpolation keys Note that in the `onmatch` function of a RouteResolver, the new route hasn't yet been fully resolved, and `m.route.params()` will return the parameters of the previous route, if any. `onmatch` receives the parameters of the new route as an argument. #### RouteResolver A RouteResolver is a non-component object that contains an `onmatch` method and/or a `render` method. Both methods are optional, but at least one must be present. If an object can be detected as a component (by the presence of a `view` method or by being a `function`/`class`), it will be treated as such even if it has `onmatch` or `render` methods. Since a RouteResolver is not a component, it does not have lifecycle methods. As a rule of thumb, RouteResolvers should be in the same file as the `m.route` call, whereas component definitions should be in their own modules. `routeResolver = {onmatch, render}` ##### routeResolver.onmatch The `onmatch` hook is called when the router needs to find a component to render. It is called once per router path changes, but not on subsequent redraws while on the same path. It can be used to run logic before a component initializes (for example authentication logic, data preloading, redirection analytics tracking, etc) This method also allows you to asynchronously define what component will be rendered, making it suitable for code splitting and asynchronous module loading. To render a component asynchronously return a promise that resolves to a component. For more information on `onmatch`, see the [advanced component resolution](#advanced-component-resolution) section `routeResolver.onmatch(args, requestedPath)` Argument | Type | Description --------------- | ---------------------------------------- | --- `args` | `Object` | The [routing parameters](#routing-parameters) `requestedPath` | `String` | The router path requested by the last routing action, including interpolated routing parameter values, but without the prefix. When `onmatch` is called, the resolution for this path is not complete and `m.route.get()` still returns the previous path. **returns** | `Component|Promise|undefined` | Returns a component or a promise that resolves to a component If `onmatch` returns a component or a promise that resolves to a component, this component is used as the `vnode.tag` for the first argument in the RouteResolver's `render` method. Otherwise, `vnode.tag` is set to `"div"`. Similarly, if the `onmatch` method is omitted, `vnode.tag` is also `"div"`. If `onmatch` returns a promise that gets rejected, the router redirects back to `defaultRoute`. You may override this behavior by calling `.catch` on the promise chain before returning it. ##### routeResolver.render The `render` method is called on every redraw for a matching route. It is similar to the `view` method in components and it exists to simplify [component composition](#wrapping-a-layout-component). `vnode = routeResolve.render(vnode)` Argument | Type | Description ------------------- | -------------------- | ----------- `vnode` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If onmatch does not return a component or a promise that resolves to a component, the vnode's `tag` field defaults to `"div"` `vnode.attrs` | `Object` | A map of URL parameter values **returns** | `Array|Vnode` | The [vnodes](vnodes.md) to be rendered --- #### How it works Routing is a system that allows creating Single-Page-Applications (SPA), i.e. applications that can go from a "page" to another without causing a full browser refresh. It enables seamless navigability while preserving the ability to bookmark each page individually, and the ability to navigate the application via the browser's history mechanism. Routing without page refreshes is made partially possible by the [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method) API. Using this API, it's possible to programmatically change the URL displayed by the browser after a page has loaded, but it's the application developer's responsibility to ensure that navigating to any given URL from a cold state (e.g. a new tab) will render the appropriate markup. #### Routing strategies The routing strategy dictates how a library might actually implement routing. There are three general strategies that can be used to implement a SPA routing system, and each has different caveats: - Using the [fragment identifier](https://en.wikipedia.org/wiki/Fragment_identifier) (aka the hash) portion of the URL. A URL using this strategy typically looks like `http://localhost/#!/page1` - Using the querystring. A URL using this strategy typically looks like `http://localhost/?/page1` - Using the pathname. A URL using this strategy typically looks like `http://localhost/page1` Using the hash strategy is guaranteed to work in browsers that don't support `history.pushState` (namely, Internet Explorer 9), because it can fall back to using `onhashchange`. Use this strategy if you want to support IE9. The querystring strategy also technically works in IE9, but it falls back to reloading the page. Use this strategy if you want to support anchored links and you are not able to make the server-side necessary to support the pathname strategy. The pathname strategy produces the cleanest looking URLs, but does not work in IE9 *and* requires setting up the server to serve the single page application code from every URL that the application can route to. Use this strategy if you want cleaner-looking URLs and do not need to support IE9. Single page applications that use the hash strategy often use the convention of having an exclamation mark after the hash to indicate that they're using the hash as a routing mechanism and not for the purposes of linking to anchors. The `#!` string is known as a *hashbang*. The default strategy uses the hashbang. --- ### Typical usage Normally, you need to create a few [components](components.md) to map routes to: ```javascript var Home = { view: function() { return [ m(Menu), m("h1", "Home") ] } } var Page1 = { view: function() { return [ m(Menu), m("h1", "Page 1") ] } } ``` In the example above, there are two components: `Home` and `Page1`. Each contains a menu and some text. The menu is itself being defined as a component to avoid repetition: ```javascript var Menu = { view: function() { return m("nav", [ m("a[href=/]", {oncreate: m.route.link}, "Home"), m("a[href=/page1]", {oncreate: m.route.link}, "Page 1"), ]) } } ``` Now we can define routes and map our components to them: ```javascript m.route(document.body, "/", { "/": Home, "/page1": Page1, }) ``` Here we specify two routes: `/` and `/page1`, which render their respective components when the user navigates to each URL. By default, the SPA router prefix is `#!` --- ### Navigating to different routes In the example above, the `Menu` component has two links. You can specify that their `href` attribute is a route URL (rather than being a regular link that navigates away from the current page), by adding the hook `{oncreate: m.route.link}` You can also navigate programmatically, via `m.route.set(route)`. For example, `m.route.set("/page1")`. When navigating to routes, there's no need to explicitly specify the router prefix. In other words, don't add the hashbang `#!` in front of the route path when linking via `m.route.link` or redirecting. --- ### Routing parameters Sometimes we want to have a variable id or similar data appear in a route, but we don't want to explicitly specify a separate route for every possible id. In order to achieve that, Mithril supports parameterized routes: ```javascript var Edit = { view: function(vnode) { return [ m(Menu), m("h1", "Editing " + vnode.attrs.id) ] } } m.route(document.body, "/edit/1", { "/edit/:id": Edit, }) ``` In the example above, we defined a route `/edit/:id`. This creates a dynamic route that matches any URL that starts with `/edit/` and is followed by some data (e.g. `/edit/1`, `edit/234`, etc). The `id` value is then mapped as an attribute of the component's [vnode](vnodes.md) (`vnode.attrs.id`) It's possible to have multiple arguments in a route, for example `/edit/:projectID/:userID` would yield the properties `projectID` and `userID` on the component's vnode attributes object. #### Key parameter When a user navigates from a parameterized route to the same route with a different parameter (e.g. going from `/page/1` to `/page/2` given a route `/page/:id`, the component would not be recreated from scratch since both routes resolve to the same component, and thus result in a virtual dom in-place diff. This has the side-effect of triggering the `onupdate` hook, rather than `oninit`/`oncreate`. However, it's relatively common for a developer to want to synchronize the recreation of the component to the route change event. To achieve that, it's possible to combine route parameterization with the virtual dom [key reconciliation](keys.md) feature: ```javascript m.route(document.body, "/edit/1", { "/edit/:key": Edit, }) ``` This means that the [vnode](vnodes.md) that is created for the root component of the route has a route parameter object `key`. Route parameters become `attrs` in the vnode. Thus, when jumping from one page to another, the `key` changes and causes the component to be recreated from scratch (since the key tells the virtual dom engine that old and new components are different entities). You can take that idea further to create components that recreate themselves when reloaded: `m.route.set(m.route.get(), {key: Date.now()})` Or even use the [`history state`](#history-state) feature to achieve reloadable components without polluting the URL: `m.route.set(m.route.get(), null, {state: {key: Date.now()}})` #### Variadic routes It's also possible to have variadic routes, i.e. a route with an argument that contains URL pathnames that contain slashes: ```javascript m.route(document.body, "/edit/pictures/image.jpg", { "/edit/:file...": Edit, }) ``` #### Handling 404s For isomorphic / universal javascript app, an url param and a variadic route combined is very useful to display custom 404 error page. In a case of 404 Not Found error, the server send back the custom page to client. When Mithril is loaded, it will redirect client to the default route because it can't know that route. ```javascript m.route(document.body, "/", { "/": homeComponent, // [...] "/:404...": errorPageComponent }); ``` #### History state It's possible to take full advantage of the underlying `history.pushState` API to improve user's navigation experience. For example, an application could "remember" the state of a large form when the user leaves a page by navigating away, such that if the user pressed the back button in the browser, they'd have the form filled rather than a blank form. For example, you could create a form like this: ```javascript var state = { term: "", search: function() { // save the state for this route // this is equivalent to `history.replaceState({term: state.term}, null, location.href)` m.route.set(m.route.get(), null, {replace: true, state: {term: state.term}}) // navigate away location.href = "https://google.com/?q=" + state.term } } var Form = { oninit: function(vnode) { state.term = vnode.attrs.term || "" // populated from the `history.state` property if the user presses the back button }, view: function() { return m("form", [ m("input[placeholder='Search']", {oninput: m.withAttr("value", function(v) {state.term = v}), value: state.term}), m("button", {onclick: state.search}, "Search") ]) } } m.route(document.body, "/", { "/": Form, }) ``` This way, if the user searches and presses the back button to return to the application, the input will still be populated with the search term. This technique can improve the user experience of large forms and other apps where non-persisted state is laborious for a user to produce. --- ### Changing router prefix The router prefix is a fragment of the URL that dictates the underlying [strategy](#routing-strategies) used by the router. ```javascript // set to pathname strategy m.route.prefix("") // set to querystring strategy m.route.prefix("?") // set to hash without bang m.route.prefix("#") // set to pathname strategy on a non-root URL // e.g. if the app lives under `http://localhost/my-app` and something else lives under `http://localhost` m.route.prefix("/my-app") ``` --- ### Advanced component resolution Instead of mapping a component to a route, you can specify a RouteResolver object. A RouteResolver object contains a `onmatch()` and/or a `render()` method. Both methods are optional but at least one of them must be present. ```javascript m.route(document.body, "/", { "/": { onmatch: function(args, requestedPath) { return Home }, render: function(vnode) { return vnode // equivalent to m(Home) }, } }) ``` RouteResolvers are useful for implementing a variety of advanced routing use cases. --- #### Wrapping a layout component It's often desirable to wrap all or most of the routed components in a reusable shell (often called a "layout"). In order to do that, you first need to create a component that contains the common markup that will wrap around the various different components: ```javascript var Layout = { view: function(vnode) { return m(".layout", vnode.children) } } ``` In the example above, the layout merely consists of a `
` that contains the children passed to the component, but in a real life scenario it could be as complex as needed. One way to wrap the layout is to define an anonymous component in the routes map: ```javascript // example 1 m.route(document.body, "/", { "/": { view: function() { return m(Layout, m(Home)) }, }, "/form": { view: function() { return m(Layout, m(Form)) }, } }) ``` However, note that because the top level component is an anonymous component, jumping from the `/` route to the `/form` route (or vice-versa) will tear down the anonymous component and recreate the DOM from scratch. If the Layout component had [lifecycle methods](lifecycle-methods.md) defined, the `oninit` and `oncreate` hooks would fire on every route change. Depending on the application, this may or may not be desirable. If you would prefer to have the Layout component be diffed and maintained intact rather than recreated from scratch, you should instead use a RouteResolver as the root object: ```javascript // example 2 m.route(document.body, "/", { "/": { render: function() { return m(Layout, m(Home)) }, }, "/form": { render: function() { return m(Layout, m(Form)) }, } }) ``` Note that in this case, if the Layout component has `oninit` and `oncreate` lifecycle methods, they would only fire on the first route change (assuming all routes use the same layout). To clarify the difference between the two examples, example 1 is equivalent to this code: ```javascript // functionally equivalent to example 1 var Anon1 = { view: function() { return m(Layout, m(Home)) }, } var Anon2 = { view: function() { return m(Layout, m(Form)) }, } m.route(document.body, "/", { "/": { render: function() { return m(Anon1) } }, "/form": { render: function() { return m(Anon2) } }, }) ``` Since `Anon1` and `Anon2` are different components, their subtrees (including `Layout`) are recreated from scratch. This is also what happens when components are used directly without a RouteResolver. In example 2, since `Layout` is the top-level component in both routes, the DOM for the `Layout` component is diffed (i.e. left intact if it has no changes), and only the change from `Home` to `Form` triggers a recreation of that subsection of the DOM. --- #### Authentication The RouteResolver's `onmatch` hook can be used to run logic before the top level component in a route is initialized. The example below shows how to implement a login wall that prevents users from seeing the `/secret` page unless they login. ```javascript var isLoggedIn = false var Login = { view: function() { return m("form", [ m("button[type=button]", { onclick: function() { isLoggedIn = true m.route.set("/secret") } }, "Login") ]) } } m.route(document.body, "/secret", { "/secret": { onmatch: function() { if (!isLoggedIn) m.route.set("/login") else return Home } }, "/login": Login }) ``` When the application loads, `onmatch` is called and since `isLoggedIn` is false, the application redirects to `/login`. Once the user pressed the login button, `isLoggedIn` would be set to true, and the application would redirect to `/secret`. The `onmatch` hook would run once again, and since `isLoggedIn` is true this time, the application would render the `Home` component. For the sake of simplicity, in the example above, the user's logged in status is kept in a global variable, and that flag is merely toggled when the user clicks the login button. In a real life application, a user would obviously have to supply proper login credentials, and clicking the login button would trigger a request to a server to authenticate the user: ```javascript var Auth = { username: "", password: "", setUsername: function(value) { Auth.username = value }, setPassword: function(value) { Auth.password = value }, login: function() { m.request({ url: "/api/v1/auth", data: {username: Auth.username, password: Auth.password} }).then(function(data) { localStorage.setItem("auth-token": data.token) m.route.set("/secret") }) } } var Login = { view: function() { return m("form", [ m("input[type=text]", {oninput: m.withAttr("value", Auth.setUsername), value: Auth.username}), m("input[type=password]", {oninput: m.withAttr("value", Auth.setPassword), value: Auth.password}), m("button[type=button]", {onclick: Auth.login}, "Login") ]) } } m.route(document.body, "/secret", { "/secret": { onmatch: function() { if (!localStorage.getItem("auth-token")) m.route.set("/login") else return Home } }, "/login": Login }) ``` --- #### Preloading data Typically, a component can load data upon initialization. Loading data this way renders the component twice. The first render pass occurs upon routing, and the second fires after the request completes. Take care to note that `loadUsers()` returns a Promise, but any Promise returned by `oninit` is currently ignored. The second render pass comes from the [`background` option for `m.request`](request.md). ```javascript var state = { users: [], loadUsers: function() { return m.request("/api/v1/users").then(function(users) { state.users = users }) } } m.route(document.body, "/user/list", { "/user/list": { oninit: state.loadUsers, view: function() { return state.users.length > 0 ? state.users.map(function(user) { return m("div", user.id) }) : "loading" } }, }) ``` In the example above, on the first render, the UI displays `"loading"` since `state.users` is an empty array before the request completes. Then, once data is available, the UI redraws and a list of user ids is shown. RouteResolvers can be used as a mechanism to preload data before rendering a component in order to avoid UI flickering and thus bypassing the need for a loading indicator: ```javascript var state = { users: [], loadUsers: function() { return m.request("/api/v1/users").then(function(users) { state.users = users }) } } m.route(document.body, "/user/list", { "/user/list": { onmatch: state.loadUsers, render: function() { return state.users.map(function(user) { return m("div", user.id) }) } }, }) ``` Above, `render` only runs after the request completes, making the ternary operator redundant. --- #### Code splitting In a large application, it may be desirable to download the code for each route on demand, rather than upfront. Dividing the codebase this way is known as code splitting or lazy loading. In Mithril, this can be accomplished by returning a promise from the `onmatch` hook: At its most basic form, one could do the following: ```javascript // Home.js module.export = { view: function() { return [ m(Menu), m("h1", "Home") ] } } ``` ```javascript // index.js function load(file) { return m.request({ method: "GET", url: file, extract: function(xhr) { return new Function("var module = {};" + xhr.responseText + ";return module.exports;") } }) } m.route(document.body, "/", { "/": { onmatch: function() { return load("Home.js") }, }, }) ``` However, realistically, in order for that to work on a production scale, it would be necessary to bundle all of the dependencies for the `Home.js` module into the file that is ultimately served by the server. Fortunately, there are a number of tools that facilitate the task of bundling modules for lazy loading. Here's an example using [webpack's code splitting system](https://webpack.github.io/docs/code-splitting.html): ```javascript m.route(document.body, "/", { "/": { onmatch: function() { // using Webpack async code splitting return new Promise(function(resolve) { require(['./Home.js'], resolve) }) }, }, }) ``` mithril-1.1.7+dfsg/docs/signatures.md000066400000000000000000000061771512406206500175610ustar00rootroot00000000000000# How to read signatures Signature sections typically look like this: `vnode = m(selector, attributes, children)` Argument | Type | Required | Description ------------ | ------------------------------------ | -------- | --- `selector` | `String|Object` | Yes | A CSS selector or a component `attributes` | `Object` | No | HTML attributes or element properties `children` | `Array|String|Number|Boolean` | No | Child [vnodes](vnodes.md). Can be written as [splat arguments](signatures.md#splats) **returns** | `Vnode` | | A [vnode](vnodes.md) The signature line above the table indicates the general syntax of the method, showing the name of the method, the order of its arguments and a suggested variable name for its return value. The **Argument** column in the table indicates which part of the signature is explained by the respective table row. The `returns` row displays information about the return value of the method. The **Type** column indicates the expected type for the argument. A pipe (`|`) indicates that an argument is valid if it has any of the listed types. For example, `String|Object` indicates that `selector` can be a string OR an object. Angled brackets (`< >`) after an `Array` indicate the expected type for array items. For exampe, `Array` indicates that the argument must be an array and that all items in that array must be strings. Angled brackets after an `Object` indicate a map. For example, `Object` indicates that the argument must be an object, whose keys are strings and values are [components](components.md) Sometimes non-native types may appear to indicate that a specific object signature is required. For example, `Vnode` is an object that has a [virtual DOM node](vnodes.md) structure. The **Required** column indicates whether an argument is required or optional. If an argument is optional, you may set it to `null` or `undefined`, or omit it altogether, such that the next argument appears in its place. --- ### Optional arguments Function arguments surrounded by square brackets `[ ]` are optional. In the example below, `url` is an optional argument: `m.request([url,] options)` --- ### Splats A splat argument means that if the argument is an array, you can omit the square brackets and have a variable number of arguments in the method instead. In the example at the top, this means that `m("div", {id: "foo"}, ["a", "b", "c"])` can also be written as `m("div", {id: "foo"}, "a", "b", "c")`. Splats are useful in some compile-to-js languages such as Coffeescript, and also allow helpful shorthands for some common use cases. --- ### Function signatures Functions are denoted with an arrow (`->`). The left side of the arrow indicates the types of the input arguments and the right side indicates the type for the return value. For example, `parseFloat` has the signature `String -> Number`, i.e. it takes a string as input and returns a number as output. Functions with multiple arguments are denoted with parenthesis: `(String, Array) -> Number` mithril-1.1.7+dfsg/docs/simple-application.md000066400000000000000000000656551512406206500211750ustar00rootroot00000000000000# Simple application Let's develop a simple application that covers some of the major aspects of Single Page Applications An interactive running example can be seen here [flems: Simple Application](https://flems.io/#0=N4IgzgpgNhDGAuEAmIBcICGAHLA6AVmCADQgBmAljEagNqgB2GAthGiAKqQBOAsgPZJoBIqVj8GiSewBuGbgAIuERQF4FwADoMFuhVAph4qBbQC6xbXv38MSADKHjCsgFcGCChIAUASg1W1nrcEPCu3DrMuCEAjq4QRt5aOkGprPAAFoImmiAA4gCiACq5limp1uFQOSAZ8PBYYKgA9M0hzAC0IUYd2BS4GSr8ANau2HjizM19za48YKWBFXoA7hSZAMIhQpIUGFBNCvDc8WXLAL6+S0G4mRAM3m4e8F4P3a5Q8P7Jy9bK3LgDEYFOp3p9cEgMPAMNdrJdrucytdYOEQpITMBEdcoLYkCYnp4fBQkN9YcFQuFItEIHEEvAkmS0qEsniFLlCiUSIyglUanUGk1Wu0unTelh+oNuCMxjhcJNpuLZvNmrkFABqBTEs6-XRrTbbe4vfaHY6nbnw8o3O4PAkvHxgr4BS3Lf5y1GGkEKB3mq6WrEMa5gDAyCD49yEh6k53ksIRBRRWLxRI-HXx5nZNkgAAKHE52p1vMz-MaLTaEE63XgYolQ1G4zl-CmMzmKjAKpA6qUPDd3DR8FwWu51kh0JMrpRvcN+d+eoyW2Qhr2BxMpog07hvrh2nOIERjBYbHQ-0cRhEJBA4kkhtk8i7KhP8E9Kd0EgoDHWY+7OLsD+nMgoEArGGzyvH4TrLCEsaRN4uS4C23AdEC8ClHeAJIbgzDYI84Z2g88FRqmXoUnGzAwZgcE8IhTgdOs5YocAGQhGQNTNMg6ztp28EDkgxAKBIsAhFCobxtE-CuIggJvsMiIKFxlDcEYAByB6dqqqoalxUAYEpB6bhUlx6bo5zbruxD7qw7D-AAYvw3BRIQ56XlI8A3oo1m2cwT7XK+77OLaoEyAwggQN8rrfkg3iBcFuBQscYDcb4-rWP+gHARGYHPkEkGUvGZFkB59FDhUEhgK4ABGzAfi4OGgSF4GERUEC4FgIQhpIAAiEBkBgHz0oZDV-N2QYhn4RWpMZ0bjbxtBjbluRaWVwgLdAKG5FZFAKY+TCsLkvjrhUpG5G+WDiQODAnfAtDwAAnlgECqIgAAe8BmLQWBabAEBZFAQjcKo62bQo20QGYhWTb8PkXSYUSzgAgvU3BkXIUDxCh-k+Mj8Shd2E59rg8k6awnqYxAlz7TqJOfioPZ4wT8DKTt4MbuTQSHSAy1QICGCLVAq0gPY2lbQeu0s9YbPHadEuXe9GCfd9v2qALwLA6DJD1QNkPidDuBwwjSP7Kjavow8JPY9TuOGlzhMQMTBuk3ts3JXbVMAhbkhW-TwtM3oZOzWzZXifAEi4AH9QSFdt33aVFXrKrvG5AAysGEAi9yZj9RNO57iAwPsAL11if2DliBIzmuQo+eF15lopUB1UgRjQVCARFTZSRZGYW+XMF+JKEzd7uhs0wMgYfcrh947ehszCauZQNjFdSYADkzRIUvosQx4gmINrUriU1BgMMMk9GfHnDzLts3pxvg9kZAEYoVFQhyhkVBIGi-XWOnCImdnufoPWYuF5S7XnQAmQuTUWpdQoI9bwS8ADES9fTgP3t4JA-AUSsHdmVQQ10z6rycGDawuQCFGFyBibkaJfppVwhlWabdoKV3ErxUix4nC+E-j7BE04SFsXgM0VAxJyHqyyvcah9d0pPzqnPVuxFGEYB7vAFh3h3J2V4lImKCMwAcPNNw7cvhTLmUPCAOUYBRDAKvNIdAOCkB4LOhdYgIdA4SA0PldEQU7L7AUAARgAGxYEegoAAaioSETAADcmFuAAHM3wmAAEwAAYAkTW0N3KuwAomxIYKgbxyTAk9SDpEjAj0OhrCQJkXJiTqkBPCRNUeDBXAaCyXExJCg2kAGZ8l1O0Gk+CVFgTACQh0Iw10YCoCCgwCAxSYmtPaT47pWA7BIDfNE1AiSekMAoioAZVZaKeWAGVWWwxol7wYHieB3UrkYHCTg7g1DvEBIUGAfgBgkAKHgUgL54TxA4m4KgeBHSgXhJWWAGW11UBlRxLAYYMzsnrPmY8x64SllfNWagAAHE87xABWWpT0qxCHENwKErwJkSGmfU-pwz9moCyCGRQwACUdCJbZUlEhUDuF+ofSlvStkcw0KC8FkLoWwpaTktpbS8XIvqVLDQdyHlPJeW8j5XykC3Nsr9LodgKBzFQB02pODSlgAoAAL3RQqnZRqQWGGFVCjBYr5DwslQs2pqKVkMDWXk7F0rwnlMqXkxJABSTZTiw46EOcc05YlzkAogPGjV9yVC5KVa84kqrvmWoQiSlZeqDXIt+bZAFQKOk2rBVpCFb4eUdHtTCuFcy2neuRe69FTafG+uZaykluFyVTNDaHIOOT6UqHlVGs5FyIAYsnZOupu4LDsykjQegOcDzsEqpkbgVBzxVHYMWQUsxzonIbFMddjEqAAAFvG4Cvb45op7N2cyATdO67AHLnDMOcIAA) First let's create an entry point for the application. Create a file `index.html`: ```markup My Application ``` The `` line indicates this is an HTML 5 document. The first `charset` meta tag indicates the encoding of the document and the `viewport` meta tag dictates how mobile browsers should scale the page. The `title` tag contains the text to be displayed on the browser tab for this application, and the `script` tag indicates what is the path to the Javascript file that controls the application. We could create the entire application in a single Javascript file, but doing so would make it difficult to navigate the codebase later on. Instead, let's split the code into *modules*, and assemble these modules into a *bundle* `bin/app.js`. There are many ways to setup a bundler tool, but most are distributed via NPM. In fact, most modern Javascript libraries and tools are distributed that way, including Mithril. NPM stands for Node.js Package Manager. To download NPM, [install Node.js](https://nodejs.org/en/); NPM is installed automatically with it. Once you have Node.js and NPM installed, open the command line and run this command: ```bash npm init -y ``` If NPM is installed correctly, a file `package.json` will be created. This file will contain a skeleton project meta-description file. Feel free to edit the project and author information in this file. --- To install Mithril, follow the instructions in the [installation](installation.md) page. Once you have a project skeleton with Mithril installed, we are ready to create the application. Let's start by creating a module to store our state. Let's create a file called `src/models/User.js` ```javascript // src/models/User.js var User = { list: [] } module.exports = User ``` Now let's add code to load some data from a server. To communicate with a server, we can use Mithril's XHR utility, `m.request`. First, we include Mithril in the module: ```javascript // src/models/User.js var m = require("mithril") var User = { list: [] } module.exports = User ``` Next we create a function that will trigger an XHR call. Let's call it `loadList` ```javascript // src/models/User.js var m = require("mithril") var User = { list: [], loadList: function() { // TODO: make XHR call } } module.exports = User ``` Then we can add an `m.request` call to make an XHR request. For this tutorial, we'll make XHR calls to the [REM](http://rem-rest-api.herokuapp.com/) API, a mock REST API designed for rapid prototyping. This API returns a list of users from the `GET https://rem-rest-api.herokuapp.com/api/users` endpoint. Let's use `m.request` to make an XHR request and populate our data with the response of that endpoint. ```javascript // src/models/User.js var m = require("mithril") var User = { list: [], loadList: function() { return m.request({ method: "GET", url: "https://rem-rest-api.herokuapp.com/api/users", withCredentials: true, }) .then(function(result) { User.list = result.data }) }, } module.exports = User ``` The `method` option is an [HTTP method](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods). To retrieve data from the server without causing side-effects on the server, we need to use the `GET` method. The `url` is the address for the API endpoint. The `withCredentials: true` line indicates that we're using cookies (which is a requirement for the REM API). The `m.request` call returns a Promise that resolves to the data from the endpoint. By default, Mithril assumes a HTTP response body are in JSON format and automatically parses it into a Javascript object or array. The `.then` callback runs when the XHR request completes. In this case, the callback assigns the `result.data` array to `User.list`. Notice we also have a `return` statement in `loadList`. This is a general good practice when working with Promises, which allows us to register more callbacks to run after the completion of the XHR request. This simple model exposes two members: `User.list` (an array of user objects), and `User.loadList` (a method that populates `User.list` with server data). --- Now, let's create a view module so that we can display data from our User model module. Create a file called `src/views/UserList.js`. First, let's include Mithril and our model, since we'll need to use both: ```javascript // src/views/UserList.js var m = require("mithril") var User = require("../models/User") ``` Next, let's create a Mithril component. A component is simply an object that has a `view` method: ```javascript // src/views/UserList.js var m = require("mithril") var User = require("../models/User") module.exports = { view: function() { // TODO add code here } } ``` By default, Mithril views are described using [hyperscript](hyperscript.md). Hyperscript offers a terse syntax that can be indented more naturally than HTML for complex tags, and in addition, since its syntax is simply Javascript, it's possible to leverage a lot of Javascript tooling ecosystem: for example [Babel](es6.md), [JSX](jsx.md) (inline-HTML syntax extension), [eslint](http://eslint.org/) (linting), [uglifyjs](https://github.com/mishoo/UglifyJS2) (minification), [istanbul](https://github.com/gotwarlost/istanbul) (code coverage), [flow](https://flowtype.org/) (static type analysis), etc. Let's use Mithril hyperscript to create a list of items. Hyperscript is the most idiomatic way of writing Mithril views, but [JSX is another popular alternative that you could explore](jsx.md) once you're more comfortable with the basics: ```javascript // src/views/UserList.js var m = require("mithril") var User = require("../models/User") module.exports = { view: function() { return m(".user-list") } } ``` The `".user-list"` string is a CSS selector, and as you would expect, `.user-list` represents a class. When a tag is not specified, `div` is the default. So this view is equivalent to `
`. Now, let's reference the list of users from the model we created earlier (`User.list`) to dynamically loop through data: ```javascript // src/views/UserList.js var m = require("mithril") var User = require("../models/User") module.exports = { view: function() { return m(".user-list", User.list.map(function(user) { return m(".user-list-item", user.firstName + " " + user.lastName) })) } } ``` Since `User.list` is a Javascript array, and since hyperscript views are just Javascript, we can loop through the array using the `.map` method. This creates an array of vnodes that represents a list of `div`s, each containing the name of a user. The problem, of course, is that we never called the `User.loadList` function. Therefore, `User.list` is still an empty array, and thus this view would render a blank page. Since we want `User.loadList` to be called when we render this component, we can take advantage of component [lifecycle methods](lifecycle-methods.md): ```javascript // src/views/UserList.js var m = require("mithril") var User = require("../models/User") module.exports = { oninit: User.loadList, view: function() { return m(".user-list", User.list.map(function(user) { return m(".user-list-item", user.firstName + " " + user.lastName) })) } } ``` Notice that we added an `oninit` method to the component, which references `User.loadList`. This means that when the component initializes, User.loadList will be called, triggering an XHR request. When the server returns a response, `User.list` gets populated. Also notice we **didn't** do `oninit: User.loadList()` (with parentheses at the end). The difference is that `oninit: User.loadList()` calls the function once and immediately, but `oninit: User.loadList` only calls that function when the component renders. This is an important difference and a common pitfall for developers new to javascript: calling the function immediately means that the XHR request will fire as soon as the source code is evaluated, even if the component never renders. Also, if the component is ever recreated (through navigating back and forth through the application), the function won't be called again as expected. --- Let's render the view from the entry point file `src/index.js` we created earlier: ```javascript // src/index.js var m = require("mithril") var UserList = require("./views/UserList") m.mount(document.body, UserList) ``` The `m.mount` call renders the specified component (`UserList`) into a DOM element (`document.body`), erasing any DOM that was there previously. Opening the HTML file in a browser should now display a list of person names. --- Right now, the list looks rather plain because we have not defined any styles. There are many similar conventions and libraries that help organize application styles nowadays. Some, like [Bootstrap](http://getbootstrap.com/) dictate a specific set of HTML structures and semantically meaningful class names, which has the upside of providing low cognitive dissonance, but the downside of making customization more difficult. Others, like [Tachyons](http://tachyons.io/) provide a large number of self-describing, atomic class names at the cost of making the class names themselves non-semantic. "CSS-in-JS" is another type of CSS system that is growing in popularity, which basically consists of scoping CSS via transpilation tooling. CSS-in-JS libraries achieve maintainability by reducing the size of the problem space, but come at the cost of having high complexity. Regardless of what CSS convention/library you choose, a good rule of thumb is to avoid the cascading aspect of CSS. To keep this tutorial simple, we'll just use plain CSS with overly explicit class names, so that the styles themselves provide the atomicity of Tachyons, and class name collisions are made unlikely through the verbosity of the class names. Plain CSS can be sufficient for low-complexity projects (e.g. 3 to 6 man-months of initial implementation time and few project phases). To add styles, let's first create a file called `styles.css` and include it in the `index.html` file: ```markup My Application ``` Now we can style the `UserList` component: ```css .user-list {list-style:none;margin:0 0 10px;padding:0;} .user-list-item {background:#fafafa;border:1px solid #ddd;color:#333;display:block;margin:0 0 1px;padding:8px 15px;text-decoration:none;} .user-list-item:hover {text-decoration:underline;} ``` The CSS above is written using a convention of keeping all styles for a rule in a single line, in alphabetical order. This convention is designed to take maximum advantage of screen real estate, and makes it easier to scan the CSS selectors (since they are always on the left side) and their logical grouping, and it enforces predictable and uniform placement of CSS rules for each selector. Obviously you can use whatever spacing/indentation convention you prefer. The example above is just an illustration of a not-so-widespread convention that has strong rationales behind it, but deviate from the more widespread cosmetic-oriented spacing conventions. Reloading the browser window now should display some styled elements. --- Let's add routing to our application. Routing means binding a screen to a unique URL, to create the ability to go from one "page" to another. Mithril is designed for Single Page Applications, so these "pages" aren't necessarily different HTML files in the traditional sense of the word. Instead, routing in Single Page Applications retains the same HTML file throughout its lifetime, but changes the state of the application via Javascript. Client side routing has the benefit of avoiding flashes of blank screen between page transitions, and can reduce the amount of data being sent down from the server when used in conjunction with an web service oriented architecture (i.e. an application that downloads data as JSON instead of downloading pre-rendered chunks of verbose HTML). We can add routing by changing the `m.mount` call to a `m.route` call: ```javascript // src/index.js var m = require("mithril") var UserList = require("./views/UserList") m.route(document.body, "/list", { "/list": UserList }) ``` The `m.route` call specifies that the application will be rendered into `document.body`. The `"/list"` argument is the default route. That means the user will be redirected to that route if they land in a route that does not exist. The `{"/list": UserList}` object declares a map of existing routes, and what components each route resolves to. Refreshing the page in the browser should now append `#!/list` to the URL to indicate that routing is working. Since that route render UserList, we should still see the list of people on screen as before. The `#!` snippet is known as a hashbang, and it's a commonly used string for implementing client-side routing. It's possible to configure this string it via [`m.route.prefix`](route.md#mrouteprefix). Some configurations require supporting server-side changes, so we'll just continue using the hashbang for the rest of this tutorial. --- Let's add another route to our application for editing users. First let's create a module called `views/UserForm.js` ```javascript // src/views/UserForm.js module.exports = { view: function() { // TODO implement view } } ``` Then we can `require` this new module from `src/index.js` ```javascript // src/index.js var m = require("mithril") var UserList = require("./views/UserList") var UserForm = require("./views/UserForm") m.route(document.body, "/list", { "/list": UserList }) ``` And finally, we can create a route that references it: ```javascript // src/index.js var m = require("mithril") var UserList = require("./views/UserList") var UserForm = require("./views/UserForm") m.route(document.body, "/list", { "/list": UserList, "/edit/:id": UserForm, }) ``` Notice that the new route has a `:id` in it. This is a route parameter; you can think of it as a wild card; the route `/edit/1` would resolve to `UserForm` with an `id` of `"1"`. `/edit/2` would also resolve to `UserForm`, but with an `id` of `"2"`. And so on. Let's implement the `UserForm` component so that it can respond to those route parameters: ```javascript // src/views/UserForm.js var m = require("mithril") module.exports = { view: function() { return m("form", [ m("label.label", "First name"), m("input.input[type=text][placeholder=First name]"), m("label.label", "Last name"), m("input.input[placeholder=Last name]"), m("button.button[type=button]", "Save"), ]) } } ``` And let's add some styles to `styles.css`: ```css /* styles.css */ body,.input,.button {font:normal 16px Verdana;margin:0;} .user-list {list-style:none;margin:0 0 10px;padding:0;} .user-list-item {background:#fafafa;border:1px solid #ddd;color:#333;display:block;margin:0 0 1px;padding:8px 15px;text-decoration:none;} .user-list-item:hover {text-decoration:underline;} .label {display:block;margin:0 0 5px;} .input {border:1px solid #ddd;border-radius:3px;box-sizing:border-box;display:block;margin:0 0 10px;padding:10px 15px;width:100%;} .button {background:#eee;border:1px solid #ddd;border-radius:3px;color:#333;display:inline-block;margin:0 0 10px;padding:10px 15px;text-decoration:none;} .button:hover {background:#e8e8e8;} ``` Right now, this component does nothing to respond to user events. Let's add some code to our `User` model in `src/models/User.js`. This is how the code is right now: ```javascript // src/models/User.js var m = require("mithril") var User = { list: [], loadList: function() { return m.request({ method: "GET", url: "https://rem-rest-api.herokuapp.com/api/users", withCredentials: true, }) .then(function(result) { User.list = result.data }) }, } module.exports = User ``` Let's add code to allow us to load a single user ```javascript // src/models/User.js var m = require("mithril") var User = { list: [], loadList: function() { return m.request({ method: "GET", url: "https://rem-rest-api.herokuapp.com/api/users", withCredentials: true, }) .then(function(result) { User.list = result.data }) }, current: {}, load: function(id) { return m.request({ method: "GET", url: "https://rem-rest-api.herokuapp.com/api/users/" + id, withCredentials: true, }) .then(function(result) { User.current = result }) } } module.exports = User ``` Notice we added a `User.current` property, and a `User.load(id)` method which populates that property. We can now populate the `UserForm` view using this new method: ```javascript // src/views/UserForm.js var m = require("mithril") var User = require("../models/User") module.exports = { oninit: function(vnode) {User.load(vnode.attrs.id)}, view: function() { return m("form", [ m("label.label", "First name"), m("input.input[type=text][placeholder=First name]", {value: User.current.firstName}), m("label.label", "Last name"), m("input.input[placeholder=Last name]", {value: User.current.lastName}), m("button.button[type=button]", "Save"), ]) } } ``` Similar to the `UserList` component, `oninit` calls `User.load()`. Remember we had a route parameter called `:id` on the `"/edit/:id": UserForm` route? The route parameter becomes an attribute of the `UserForm` component's vnode, so routing to `/edit/1` would make `vnode.attrs.id` have a value of `"1"`. Now, let's modify the `UserList` view so that we can navigate from there to a `UserForm`: ```javascript // src/views/UserList.js var m = require("mithril") var User = require("../models/User") module.exports = { oninit: User.loadList, view: function() { return m(".user-list", User.list.map(function(user) { return m("a.user-list-item", {href: "/edit/" + user.id, oncreate: m.route.link}, user.firstName + " " + user.lastName) })) } } ``` Here we changed `.user-list-item` to `a.user-list-item`. We added an `href` that references the route we want, and finally we added `oncreate: m.route.link`. This makes the link behave like a routed link (as opposed to merely behaving like a regular link). What this means is that clicking the link would change the part of URL that comes after the hashbang `#!` (thus changing the route without unloading the current HTML page) If you refresh the page in the browser, you should now be able to click on a person and be taken to a form. You should also be able to press the back button in the browser to go back from the form to the list of people. --- The form itself still doesn't save when you press "Save". Let's make this form work: ```javascript // src/views/UserForm.js var m = require("mithril") var User = require("../models/User") module.exports = { oninit: function(vnode) {User.load(vnode.attrs.id)}, view: function() { return m("form", { onsubmit: function(e) { e.preventDefault() User.save() } }, [ m("label.label", "First name"), m("input.input[type=text][placeholder=First name]", { oninput: m.withAttr("value", function(value) {User.current.firstName = value}), value: User.current.firstName }), m("label.label", "Last name"), m("input.input[placeholder=Last name]", { oninput: m.withAttr("value", function(value) {User.current.lastName = value}), value: User.current.lastName }), m("button.button[type=submit]", "Save"), ]) } } ``` We added `oninput` events to both inputs, that set the `User.current.firstName` and `User.current.lastName` properties when a user types. In addition, we declared that a `User.save` method should be called when the "Save" button is pressed. Let's implement that method: ```javascript // src/models/User.js var m = require("mithril") var User = { list: [], loadList: function() { return m.request({ method: "GET", url: "https://rem-rest-api.herokuapp.com/api/users", withCredentials: true, }) .then(function(result) { User.list = result.data }) }, current: {}, load: function(id) { return m.request({ method: "GET", url: "https://rem-rest-api.herokuapp.com/api/users/" + id, withCredentials: true, }) .then(function(result) { User.current = result }) }, save: function() { return m.request({ method: "PUT", url: "https://rem-rest-api.herokuapp.com/api/users/" + User.current.id, data: User.current, withCredentials: true, }) } } module.exports = User ``` In the `save` method at the bottom, we used the `PUT` HTTP method to indicate that we are upserting data to the server. Now try editing the name of a user in the application. Once you save a change, you should be able to see the change reflected in the list of users. --- Currently, we're only able to navigate back to the user list via the browser back button. Ideally, we would like to have a menu - or more generically, a layout where we can put global UI elements Let's create a file `src/views/Layout.js`: ```javascript // src/views/Layout.js var m = require("mithril") module.exports = { view: function(vnode) { return m("main.layout", [ m("nav.menu", [ m("a[href='/list']", {oncreate: m.route.link}, "Users") ]), m("section", vnode.children) ]) } } ``` This component is fairly straightforward, it has a `
mithril-1.1.7+dfsg/render/tests/test-attributes.js000066400000000000000000000357641512406206500222510ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("attributes", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.body render = vdom($window).render }) o.spec("basics", function() { o("works (create/update/remove)", function() { var a = {tag: "div", attrs: {}} var b = {tag: "div", attrs: {id: "test"}} var c = {tag: "div", attrs: {}} render(root, [a]); o(a.dom.hasAttribute("id")).equals(false) render(root, [b]); o(b.dom.getAttribute("id")).equals("test") render(root, [c]); o(c.dom.hasAttribute("id")).equals(false) }) o("undefined attr is equivalent to a lack of attr", function() { var a = {tag: "div", attrs: {id: undefined}} var b = {tag: "div", attrs: {id: "test"}} var c = {tag: "div", attrs: {id: undefined}} render(root, [a]); o(a.dom.hasAttribute("id")).equals(false) render(root, [b]); o(b.dom.hasAttribute("id")).equals(true) o(b.dom.getAttribute("id")).equals("test") render(root, [c]); // #1804 // TODO: uncomment // o(c.dom.hasAttribute("id")).equals(false) }) }) o.spec("customElements", function(){ o("when vnode is customElement, custom setAttribute called", function(){ var normal = [ {tag: "input", attrs: {value: "hello"}}, {tag: "input", attrs: {value: "hello"}}, {tag: "input", attrs: {value: "hello"}} ] var custom = [ {tag: "custom-element", attrs: {custom: "x"}}, {tag: "input", attrs: {is: "something-special", custom: "x"}}, {tag: "custom-element", attrs: {is: "something-special", custom: "x"}} ] var view = normal.concat(custom) var f = $window.document.createElement var spy $window.document.createElement = function(tag, is){ var el = f(tag, is) if(!spy){ spy = o.spy(el.setAttribute) } el.setAttribute = spy return el } render(root, view) o(spy.callCount).equals(custom.length) }) }) o.spec("input readonly", function() { o("when input readonly is true, attribute is present", function() { var a = {tag: "input", attrs: {readonly: true}} render(root, [a]) o(a.dom.attributes["readonly"].value).equals("") }) o("when input readonly is false, attribute is not present", function() { var a = {tag: "input", attrs: {readonly: false}} render(root, [a]) o(a.dom.attributes["readonly"]).equals(undefined) }) }) o.spec("input checked", function() { o("when input checked is true, attribute is not present", function() { var a = {tag: "input", attrs: {checked: true}} render(root, [a]) o(a.dom.checked).equals(true) o(a.dom.attributes["checked"]).equals(undefined) }) o("when input checked is false, attribute is not present", function() { var a = {tag: "input", attrs: {checked: false}} render(root, [a]) o(a.dom.checked).equals(false) o(a.dom.attributes["checked"]).equals(undefined) }) o("after input checked is changed by 3rd party, it can still be changed by render", function() { var a = {tag: "input", attrs: {checked: false}} var b = {tag: "input", attrs: {checked: true}} render(root, [a]) a.dom.checked = true //setting the javascript property makes the value no longer track the state of the attribute a.dom.checked = false render(root, [b]) o(a.dom.checked).equals(true) o(a.dom.attributes["checked"]).equals(undefined) }) }) o.spec("input.value", function() { o("can be set as text", function() { var a = {tag: "input", attrs: {value: "test"}} render(root, [a]); o(a.dom.value).equals("test") }) o("a lack of attribute removes `value`", function() { var a = {tag: "input", attrs: {}} var b = {tag: "input", attrs: {value: "test"}} // var c = {tag: "input", attrs: {}} render(root, [a]) o(a.dom.value).equals("") render(root, [b]) o(a.dom.value).equals("test") // https://github.com/MithrilJS/mithril.js/issues/1804#issuecomment-304521235 // TODO: Uncomment // render(root, [c]) // o(a.dom.value).equals("") }) o("can be set as number", function() { var a = {tag: "input", attrs: {value: 1}} render(root, [a]); o(a.dom.value).equals("1") }) o("null becomes the empty string", function() { var a = {tag: "input", attrs: {value: null}} var b = {tag: "input", attrs: {value: "test"}} var c = {tag: "input", attrs: {value: null}} render(root, [a]); o(a.dom.value).equals("") o(a.dom.getAttribute("value")).equals(null) render(root, [b]); o(b.dom.value).equals("test") o(b.dom.getAttribute("value")).equals(null) render(root, [c]); o(c.dom.value).equals("") o(c.dom.getAttribute("value")).equals(null) }) o("'' and 0 are different values", function() { var a = {tag: "input", attrs: {value: 0}, children:[{tag:"#", children:""}]} var b = {tag: "input", attrs: {value: ""}, children:[{tag:"#", children:""}]} var c = {tag: "input", attrs: {value: 0}, children:[{tag:"#", children:""}]} render(root, [a]); o(a.dom.value).equals("0") render(root, [b]); o(b.dom.value).equals("") // #1595 redux render(root, [c]); o(c.dom.value).equals("0") }) o("isn't set when equivalent to the previous value and focused", function() { var $window = domMock({spy: o.spy}) var root = $window.document.body var render = vdom($window).render var a = {tag: "input"} var b = {tag: "input", attrs: {value: "1"}} var c = {tag: "input", attrs: {value: "1"}} var d = {tag: "input", attrs: {value: 1}} var e = {tag: "input", attrs: {value: 2}} render(root, [a]) var spies = $window.__getSpies(a.dom) a.dom.focus() o(spies.valueSetter.callCount).equals(0) render(root, [b]) o(b.dom.value).equals("1") o(spies.valueSetter.callCount).equals(1) render(root, [c]) o(c.dom.value).equals("1") o(spies.valueSetter.callCount).equals(1) render(root, [d]) o(d.dom.value).equals("1") o(spies.valueSetter.callCount).equals(1) render(root, [e]) o(d.dom.value).equals("2") o(spies.valueSetter.callCount).equals(2) }) }) o.spec("input.type", function() { o("the input.type setter is never used", function() { var $window = domMock({spy: o.spy}) var root = $window.document.body var render = vdom($window).render var a = {tag: "input", attrs: {type: "radio"}} var b = {tag: "input", attrs: {type: "text"}} var c = {tag: "input", attrs: {}} render(root, [a]) var spies = $window.__getSpies(a.dom) o(spies.typeSetter.callCount).equals(0) o(a.dom.getAttribute("type")).equals("radio") render(root, [b]) o(spies.typeSetter.callCount).equals(0) o(b.dom.getAttribute("type")).equals("text") render(root, [c]) o(spies.typeSetter.callCount).equals(0) o(c.dom.hasAttribute("type")).equals(false) }) }) o.spec("textarea.value", function() { o("can be removed by not passing a value", function() { var a = {tag: "textarea", attrs: {value:"x"}} // var b = {tag: "textarea", attrs: {}} render(root, [a]) o(a.dom.value).equals("x") // https://github.com/MithrilJS/mithril.js/issues/1804#issuecomment-304521235 // TODO: Uncomment // render(root, [b]) // o(b.dom.value).equals("") }) o("isn't set when equivalent to the previous value and focused", function() { var $window = domMock({spy: o.spy}) var root = $window.document.body var render = vdom($window).render var a = {tag: "textarea"} var b = {tag: "textarea", attrs: {value: "1"}} var c = {tag: "textarea", attrs: {value: "1"}} var d = {tag: "textarea", attrs: {value: 1}} var e = {tag: "textarea", attrs: {value: 2}} render(root, [a]) var spies = $window.__getSpies(a.dom) a.dom.focus() o(spies.valueSetter.callCount).equals(0) render(root, [b]) o(b.dom.value).equals("1") o(spies.valueSetter.callCount).equals(1) render(root, [c]) o(c.dom.value).equals("1") o(spies.valueSetter.callCount).equals(1) render(root, [d]) o(d.dom.value).equals("1") o(spies.valueSetter.callCount).equals(1) render(root, [e]) o(d.dom.value).equals("2") o(spies.valueSetter.callCount).equals(2) }) }) o.spec("link href", function() { o("when link href is true, attribute is present", function() { var a = {tag: "a", attrs: {href: true}} render(root, [a]) o(a.dom.attributes["href"]).notEquals(undefined) }) o("when link href is false, attribute is not present", function() { var a = {tag: "a", attrs: {href: false}} render(root, [a]) o(a.dom.attributes["href"]).equals(undefined) }) }) o.spec("canvas width and height", function() { o("uses attribute API", function() { var canvas = {tag: "canvas", attrs: {width: "100%"}} render(root, canvas) o(canvas.dom.attributes["width"].value).equals("100%") o(canvas.dom.width).equals(100) }) }) o.spec("svg class", function() { o("when className is specified then it should be added as a class", function() { var a = {tag: "svg", attrs: {className: "test"}} render(root, [a]); o(a.dom.attributes["class"].value).equals("test") }) }) o.spec("option.value", function() { o("can be set as text", function() { var a = {tag: "option", attrs: {value: "test"}} render(root, [a]); o(a.dom.value).equals("test") }) o("can be set as number", function() { var a = {tag: "option", attrs: {value: 1}} render(root, [a]); o(a.dom.value).equals("1") }) o("null becomes the empty string", function() { var a = {tag: "option", attrs: {value: null}} var b = {tag: "option", attrs: {value: "test"}} var c = {tag: "option", attrs: {value: null}} render(root, [a]); o(a.dom.value).equals("") o(a.dom.getAttribute("value")).equals("") render(root, [b]); o(b.dom.value).equals("test") o(b.dom.getAttribute("value")).equals("test") render(root, [c]); o(c.dom.value).equals("") o(c.dom.getAttribute("value")).equals("") }) o("'' and 0 are different values", function() { var a = {tag: "option", attrs: {value: 0}, children:[{tag:"#", children:""}]} var b = {tag: "option", attrs: {value: ""}, children:[{tag:"#", children:""}]} var c = {tag: "option", attrs: {value: 0}, children:[{tag:"#", children:""}]} render(root, [a]); o(a.dom.value).equals("0") render(root, [b]); o(a.dom.value).equals("") // #1595 redux render(root, [c]); o(c.dom.value).equals("0") }) o("isn't set when equivalent to the previous value", function() { var $window = domMock({spy: o.spy}) var root = $window.document.body var render = vdom($window).render var a = {tag: "option"} var b = {tag: "option", attrs: {value: "1"}} var c = {tag: "option", attrs: {value: "1"}} var d = {tag: "option", attrs: {value: 1}} var e = {tag: "option", attrs: {value: 2}} render(root, [a]) var spies = $window.__getSpies(a.dom) o(spies.valueSetter.callCount).equals(0) render(root, [b]) o(b.dom.value).equals("1") o(spies.valueSetter.callCount).equals(1) render(root, [c]) o(c.dom.value).equals("1") o(spies.valueSetter.callCount).equals(1) render(root, [d]) o(d.dom.value).equals("1") o(spies.valueSetter.callCount).equals(1) render(root, [e]) o(d.dom.value).equals("2") o(spies.valueSetter.callCount).equals(2) }) }) o.spec("select.value", function() { function makeSelect(value) { var attrs = (arguments.length === 0) ? {} : {value: value} return {tag: "select", attrs: attrs, children: [ {tag:"option", attrs: {value: "1"}}, {tag:"option", attrs: {value: "2"}}, {tag:"option", attrs: {value: "a"}}, {tag:"option", attrs: {value: "0"}}, {tag:"option", attrs: {value: ""}} ]} } o("can be set as text", function() { var a = makeSelect() var b = makeSelect("2") var c = makeSelect("a") render(root, [a]) o(a.dom.value).equals("1") o(a.dom.selectedIndex).equals(0) render(root, [b]) o(b.dom.value).equals("2") o(b.dom.selectedIndex).equals(1) render(root, [c]) o(c.dom.value).equals("a") o(c.dom.selectedIndex).equals(2) }) o("setting null unsets the value", function() { var a = makeSelect(null) render(root, [a]) o(a.dom.value).equals("") o(a.dom.selectedIndex).equals(-1) }) o("values are type converted", function() { var a = makeSelect(1) var b = makeSelect(2) render(root, [a]) o(a.dom.value).equals("1") o(a.dom.selectedIndex).equals(0) render(root, [b]) o(b.dom.value).equals("2") o(b.dom.selectedIndex).equals(1) }) o("'' and 0 are different values when focused", function() { var a = makeSelect("") var b = makeSelect(0) render(root, [a]) a.dom.focus() o(a.dom.value).equals("") // #1595 redux render(root, [b]) o(b.dom.value).equals("0") }) o("'' and null are different values when focused", function() { var a = makeSelect("") var b = makeSelect(null) var c = makeSelect("") render(root, [a]) a.dom.focus() o(a.dom.value).equals("") o(a.dom.selectedIndex).equals(4) render(root, [b]) o(b.dom.value).equals("") o(b.dom.selectedIndex).equals(-1) render(root, [c]) o(c.dom.value).equals("") o(c.dom.selectedIndex).equals(4) }) o("updates with the same value do not re-set the attribute if the select has focus", function() { var $window = domMock({spy: o.spy}) var root = $window.document.body var render = vdom($window).render var a = makeSelect() var b = makeSelect("1") var c = makeSelect(1) var d = makeSelect("2") render(root, [a]) var spies = $window.__getSpies(a.dom) a.dom.focus() o(spies.valueSetter.callCount).equals(0) o(a.dom.value).equals("1") render(root, [b]) o(spies.valueSetter.callCount).equals(0) o(b.dom.value).equals("1") render(root, [c]) o(spies.valueSetter.callCount).equals(0) o(c.dom.value).equals("1") render(root, [d]) o(spies.valueSetter.callCount).equals(1) o(d.dom.value).equals("2") }) }) o.spec("contenteditable throws on untrusted children", function() { o("including text nodes", function() { var div = {tag: "div", attrs: {contenteditable: true}, text: ""} var succeeded = false try { render(root, div) succeeded = true } catch(e){/* ignore */} o(succeeded).equals(false) }) o("including elements", function() { var div = {tag: "div", attrs: {contenteditable: true}, children: [{tag: "script", attrs: {src: "http://evil.com"}}]} var succeeded = false try { render(root, div) succeeded = true } catch(e){/* ignore */} o(succeeded).equals(false) }) o("tolerating empty children", function() { var div = {tag: "div", attrs: {contenteditable: true}, children: []} var succeeded = false try { render(root, div) succeeded = true } catch(e){/* ignore */} o(succeeded).equals(true) }) o("tolerating trusted content", function() { var div = {tag: "div", attrs: {contenteditable: true}, children: [{tag: "<", children: "
"}]} var succeeded = false try { render(root, div) succeeded = true } catch(e){/* ignore */} o(succeeded).equals(true) }) }) }) mithril-1.1.7+dfsg/render/tests/test-component.js000066400000000000000000000671341512406206500220610ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var components = require("../../test-utils/components") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("component", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) components.forEach(function(cmp){ o.spec(cmp.kind, function(){ var createComponent = cmp.create o.spec("basics", function() { o("works", function() { var component = createComponent({ view: function() { return {tag: "div", attrs: {id: "a"}, text: "b"} } }) var node = {tag: component} render(root, [node]) o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("receives arguments", function() { var component = createComponent({ view: function(vnode) { return {tag: "div", attrs: vnode.attrs, text: vnode.text} } }) var node = {tag: component, attrs: {id: "a"}, text: "b"} render(root, [node]) o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("updates", function() { var component = createComponent({ view: function(vnode) { return {tag: "div", attrs: vnode.attrs, text: vnode.text} } }) render(root, [{tag: component, attrs: {id: "a"}, text: "b"}]) render(root, [{tag: component, attrs: {id: "c"}, text: "d"}]) o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.attributes["id"].value).equals("c") o(root.firstChild.firstChild.nodeValue).equals("d") }) o("updates root from null", function() { var visible = false var component = createComponent({ view: function() { return visible ? {tag: "div"} : null } }) render(root, [{tag: component}]) visible = true render(root, [{tag: component}]) o(root.firstChild.nodeName).equals("DIV") }) o("updates root from primitive", function() { var visible = false var component = createComponent({ view: function() { return visible ? {tag: "div"} : false } }) render(root, [{tag: component}]) visible = true render(root, [{tag: component}]) o(root.firstChild.nodeName).equals("DIV") }) o("updates root to null", function() { var visible = true var component = createComponent({ view: function() { return visible ? {tag: "div"} : null } }) render(root, [{tag: component}]) visible = false render(root, [{tag: component}]) o(root.childNodes.length).equals(0) }) o("updates root to primitive", function() { var visible = true var component = createComponent({ view: function() { return visible ? {tag: "div"} : false } }) render(root, [{tag: component}]) visible = false render(root, [{tag: component}]) o(root.firstChild.nodeValue).equals("") }) o("updates root from null to null", function() { var component = createComponent({ view: function() { return null } }) render(root, [{tag: component}]) render(root, [{tag: component}]) o(root.childNodes.length).equals(0) }) o("removes", function() { var component = createComponent({ view: function() { return {tag: "div"} } }) var div = {tag: "div", key: 2} render(root, [{tag: component, key: 1}, div]) render(root, [{tag: "div", key: 2}]) o(root.childNodes.length).equals(1) o(root.firstChild).equals(div.dom) }) o("svg works when creating across component boundary", function() { var component = createComponent({ view: function() { return {tag: "g"} } }) render(root, [{tag: "svg", children: [{tag: component}]}]) o(root.firstChild.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg") }) o("svg works when updating across component boundary", function() { var component = createComponent({ view: function() { return {tag: "g"} } }) render(root, [{tag: "svg", children: [{tag: component}]}]) render(root, [{tag: "svg", children: [{tag: component}]}]) o(root.firstChild.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg") }) }) o.spec("return value", function() { o("can return fragments", function() { var component = createComponent({ view: function() { return [ {tag: "label"}, {tag: "input"}, ] } }) render(root, [{tag: component}]) o(root.childNodes.length).equals(2) o(root.childNodes[0].nodeName).equals("LABEL") o(root.childNodes[1].nodeName).equals("INPUT") }) o("can return string", function() { var component = createComponent({ view: function() { return "a" } }) render(root, [{tag: component}]) o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeValue).equals("a") }) o("can return falsy string", function() { var component = createComponent({ view: function() { return "" } }) render(root, [{tag: component}]) o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeValue).equals("") }) o("can return number", function() { var component = createComponent({ view: function() { return 1 } }) render(root, [{tag: component}]) o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeValue).equals("1") }) o("can return falsy number", function() { var component = createComponent({ view: function() { return 0 } }) render(root, [{tag: component}]) o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeValue).equals("0") }) o("can return boolean", function() { var component = createComponent({ view: function() { return true } }) render(root, [{tag: component}]) o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeValue).equals("true") }) o("can return falsy boolean", function() { var component = createComponent({ view: function() { return false } }) render(root, [{tag: component}]) o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeValue).equals("") }) o("can return null", function() { var component = createComponent({ view: function() { return null } }) render(root, [{tag: component}]) o(root.childNodes.length).equals(0) }) o("can return undefined", function() { var component = createComponent({ view: function() { return undefined } }) render(root, [{tag: component}]) o(root.childNodes.length).equals(0) }) o("throws a custom error if it returns itself when created", function() { // A view that returns its vnode would otherwise trigger an infinite loop var threw = false var component = createComponent({ view: function(vnode) { return vnode } }) try { render(root, [{tag: component}]) } catch (e) { threw = true o(e instanceof Error).equals(true) // Call stack exception is a RangeError o(e instanceof RangeError).equals(false) } o(threw).equals(true) }) o("throws a custom error if it returns itself when updated", function() { // A view that returns its vnode would otherwise trigger an infinite loop var threw = false var init = true var oninit = o.spy() var component = createComponent({ oninit: oninit, view: function(vnode) { if (init) return init = false else return vnode } }) render(root, [{tag: component}]) o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeValue).equals("") try { render(root, [{tag: component}]) } catch (e) { threw = true o(e instanceof Error).equals(true) // Call stack exception is a RangeError o(e instanceof RangeError).equals(false) } o(threw).equals(true) o(oninit.callCount).equals(1) }) o("can update when returning fragments", function() { var component = createComponent({ view: function() { return [ {tag: "label"}, {tag: "input"}, ] } }) render(root, [{tag: component}]) render(root, [{tag: component}]) o(root.childNodes.length).equals(2) o(root.childNodes[0].nodeName).equals("LABEL") o(root.childNodes[1].nodeName).equals("INPUT") }) o("can update when returning primitive", function() { var component = createComponent({ view: function() { return "a" } }) render(root, [{tag: component}]) render(root, [{tag: component}]) o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeValue).equals("a") }) o("can update when returning null", function() { var component = createComponent({ view: function() { return null } }) render(root, [{tag: component}]) render(root, [{tag: component}]) o(root.childNodes.length).equals(0) }) o("can remove when returning fragments", function() { var component = createComponent({ view: function() { return [ {tag: "label"}, {tag: "input"}, ] } }) var div = {tag: "div", key: 2} render(root, [{tag: component, key: 1}, div]) render(root, [{tag: "div", key: 2}]) o(root.childNodes.length).equals(1) o(root.firstChild).equals(div.dom) }) o("can remove when returning primitive", function() { var component = createComponent({ view: function() { return "a" } }) var div = {tag: "div", key: 2} render(root, [{tag: component, key: 1}, div]) render(root, [{tag: "div", key: 2}]) o(root.childNodes.length).equals(1) o(root.firstChild).equals(div.dom) }) }) o.spec("lifecycle", function() { o("calls oninit", function() { var called = 0 var component = createComponent({ oninit: function(vnode) { called++ o(vnode.tag).equals(component) o(vnode.dom).equals(undefined) o(root.childNodes.length).equals(0) }, view: function() { return {tag: "div", attrs: {id: "a"}, text: "b"} } }) var node = {tag: component} render(root, [node]) o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls oninit when returning fragment", function() { var called = 0 var component = createComponent({ oninit: function(vnode) { called++ o(vnode.tag).equals(component) o(vnode.dom).equals(undefined) o(root.childNodes.length).equals(0) }, view: function() { return [{tag: "div", attrs: {id: "a"}, text: "b"}] } }) var node = {tag: component} render(root, [node]) o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls oninit before view", function() { var viewCalled = false render(root, createComponent({ tag: { view: function() { viewCalled = true return [{tag: "div", attrs: {id: "a"}, text: "b"}] }, oninit: function() { o(viewCalled).equals(false) }, } })) }) o("does not calls oninit on redraw", function() { var init = o.spy() var component = createComponent({ view: function() { return {tag: "div", attrs: {id: "a"}, text: "b"} }, oninit: init, }) function view() { return {tag: component} } render(root, view()) render(root, view()) o(init.callCount).equals(1) }) o("calls oncreate", function() { var called = 0 var component = createComponent({ oncreate: function(vnode) { called++ o(vnode.dom).notEquals(undefined) o(vnode.dom).equals(root.firstChild) o(root.childNodes.length).equals(1) }, view: function() { return {tag: "div", attrs: {id: "a"}, text: "b"} } }) var node = {tag: component} render(root, [node]) o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("does not calls oncreate on redraw", function() { var create = o.spy() var component = createComponent({ view: function() { return {tag: "div", attrs: {id: "a"}, text: "b"} }, oncreate: create, }) function view() { return {tag: component} } render(root, view()) render(root, view()) o(create.callCount).equals(1) }) o("calls oncreate when returning fragment", function() { var called = 0 var component = createComponent({ oncreate: function(vnode) { called++ o(vnode.dom).notEquals(undefined) o(vnode.dom).equals(root.firstChild) o(root.childNodes.length).equals(1) }, view: function() { return [{tag: "div", attrs: {id: "a"}, text: "b"}] } }) var node = {tag: component} render(root, [node]) o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls onupdate", function() { var called = 0 var component = createComponent({ onupdate: function(vnode) { called++ o(vnode.dom).notEquals(undefined) o(vnode.dom).equals(root.firstChild) o(root.childNodes.length).equals(1) }, view: function() { return {tag: "div", attrs: {id: "a"}, text: "b"} } }) render(root, [{tag: component}]) o(called).equals(0) render(root, [{tag: component}]) o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls onupdate when returning fragment", function() { var called = 0 var component = createComponent({ onupdate: function(vnode) { called++ o(vnode.dom).notEquals(undefined) o(vnode.dom).equals(root.firstChild) o(root.childNodes.length).equals(1) }, view: function() { return [{tag: "div", attrs: {id: "a"}, text: "b"}] } }) render(root, [{tag: component}]) o(called).equals(0) render(root, [{tag: component}]) o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls onremove", function() { var called = 0 var component = createComponent({ onremove: function(vnode) { called++ o(vnode.dom).notEquals(undefined) o(vnode.dom).equals(root.firstChild) o(root.childNodes.length).equals(1) }, view: function() { return {tag: "div", attrs: {id: "a"}, text: "b"} } }) render(root, [{tag: component}]) o(called).equals(0) render(root, []) o(called).equals(1) o(root.childNodes.length).equals(0) }) o("calls onremove when returning fragment", function() { var called = 0 var component = createComponent({ onremove: function(vnode) { called++ o(vnode.dom).notEquals(undefined) o(vnode.dom).equals(root.firstChild) o(root.childNodes.length).equals(1) }, view: function() { return [{tag: "div", attrs: {id: "a"}, text: "b"}] } }) render(root, [{tag: component}]) o(called).equals(0) render(root, []) o(called).equals(1) o(root.childNodes.length).equals(0) }) o("calls onbeforeremove", function() { var called = 0 var component = createComponent({ onbeforeremove: function(vnode) { called++ o(vnode.dom).notEquals(undefined) o(vnode.dom).equals(root.firstChild) o(root.childNodes.length).equals(1) }, view: function() { return {tag: "div", attrs: {id: "a"}, text: "b"} } }) render(root, [{tag: component}]) o(called).equals(0) render(root, []) o(called).equals(1) o(root.childNodes.length).equals(0) }) o("calls onbeforeremove when returning fragment", function() { var called = 0 var component = createComponent({ onbeforeremove: function(vnode) { called++ o(vnode.dom).notEquals(undefined) o(vnode.dom).equals(root.firstChild) o(root.childNodes.length).equals(1) }, view: function() { return [{tag: "div", attrs: {id: "a"}, text: "b"}] } }) render(root, [{tag: component}]) o(called).equals(0) render(root, []) o(called).equals(1) o(root.childNodes.length).equals(0) }) o("does not recycle when there's an onupdate", function() { var component = createComponent({ onupdate: function() {}, view: function() { return {tag: "div"} } }) var vnode = {tag: component, key: 1} var updated = {tag: component, key: 1} render(root, [vnode]) render(root, []) render(root, [updated]) o(vnode.dom).notEquals(updated.dom) }) o("lifecycle timing megatest (for a single component)", function() { var methods = { view: o.spy(function() { return "" }) } var attrs = {} var hooks = [ "oninit", "oncreate", "onbeforeupdate", "onupdate", "onbeforeremove", "onremove" ] hooks.forEach(function(hook) { // the `attrs` hooks are called before the component ones attrs[hook] = o.spy(function() { o(attrs[hook].callCount).equals(methods[hook].callCount + 1) }) methods[hook] = o.spy(function() { o(attrs[hook].callCount).equals(methods[hook].callCount) }) }) var component = createComponent(methods) o(methods.view.callCount).equals(0) o(methods.oninit.callCount).equals(0) o(methods.oncreate.callCount).equals(0) o(methods.onbeforeupdate.callCount).equals(0) o(methods.onupdate.callCount).equals(0) o(methods.onbeforeremove.callCount).equals(0) o(methods.onremove.callCount).equals(0) hooks.forEach(function(hook) { o(attrs[hook].callCount).equals(methods[hook].callCount)(hook) }) render(root, [{tag: component, attrs: attrs}]) o(methods.view.callCount).equals(1) o(methods.oninit.callCount).equals(1) o(methods.oncreate.callCount).equals(1) o(methods.onbeforeupdate.callCount).equals(0) o(methods.onupdate.callCount).equals(0) o(methods.onbeforeremove.callCount).equals(0) o(methods.onremove.callCount).equals(0) hooks.forEach(function(hook) { o(attrs[hook].callCount).equals(methods[hook].callCount)(hook) }) render(root, [{tag: component, attrs: attrs}]) o(methods.view.callCount).equals(2) o(methods.oninit.callCount).equals(1) o(methods.oncreate.callCount).equals(1) o(methods.onbeforeupdate.callCount).equals(1) o(methods.onupdate.callCount).equals(1) o(methods.onbeforeremove.callCount).equals(0) o(methods.onremove.callCount).equals(0) hooks.forEach(function(hook) { o(attrs[hook].callCount).equals(methods[hook].callCount)(hook) }) render(root, []) o(methods.view.callCount).equals(2) o(methods.oninit.callCount).equals(1) o(methods.oncreate.callCount).equals(1) o(methods.onbeforeupdate.callCount).equals(1) o(methods.onupdate.callCount).equals(1) o(methods.onbeforeremove.callCount).equals(1) o(methods.onremove.callCount).equals(1) hooks.forEach(function(hook) { o(attrs[hook].callCount).equals(methods[hook].callCount)(hook) }) }) o("lifecycle timing megatest (for a single component with the state overwritten)", function() { var methods = { view: o.spy(function(vnode) { o(vnode.state).equals(1) return "" }) } var attrs = {} var hooks = [ "oninit", "oncreate", "onbeforeupdate", "onupdate", "onbeforeremove", "onremove" ] hooks.forEach(function(hook) { // the `attrs` hooks are called before the component ones attrs[hook] = o.spy(function(vnode) { o(vnode.state).equals(1) o(attrs[hook].callCount).equals(methods[hook].callCount + 1) }) methods[hook] = o.spy(function(vnode) { o(vnode.state).equals(1) o(attrs[hook].callCount).equals(methods[hook].callCount) }) }) var attrsOninit = attrs.oninit var methodsOninit = methods.oninit attrs.oninit = o.spy(function(vnode){ vnode.state = 1 return attrsOninit.call(this, vnode) }) methods.oninit = o.spy(function(vnode){ vnode.state = 1 return methodsOninit.call(this, vnode) }) var component = createComponent(methods) o(methods.view.callCount).equals(0) o(methods.oninit.callCount).equals(0) o(methods.oncreate.callCount).equals(0) o(methods.onbeforeupdate.callCount).equals(0) o(methods.onupdate.callCount).equals(0) o(methods.onbeforeremove.callCount).equals(0) o(methods.onremove.callCount).equals(0) hooks.forEach(function(hook) { o(attrs[hook].callCount).equals(methods[hook].callCount)(hook) }) render(root, [{tag: component, attrs: attrs}]) o(methods.view.callCount).equals(1) o(methods.oninit.callCount).equals(1) o(methods.oncreate.callCount).equals(1) o(methods.onbeforeupdate.callCount).equals(0) o(methods.onupdate.callCount).equals(0) o(methods.onbeforeremove.callCount).equals(0) o(methods.onremove.callCount).equals(0) hooks.forEach(function(hook) { o(attrs[hook].callCount).equals(methods[hook].callCount)(hook) }) render(root, [{tag: component, attrs: attrs}]) o(methods.view.callCount).equals(2) o(methods.oninit.callCount).equals(1) o(methods.oncreate.callCount).equals(1) o(methods.onbeforeupdate.callCount).equals(1) o(methods.onupdate.callCount).equals(1) o(methods.onbeforeremove.callCount).equals(0) o(methods.onremove.callCount).equals(0) hooks.forEach(function(hook) { o(attrs[hook].callCount).equals(methods[hook].callCount)(hook) }) render(root, []) o(methods.view.callCount).equals(2) o(methods.oninit.callCount).equals(1) o(methods.oncreate.callCount).equals(1) o(methods.onbeforeupdate.callCount).equals(1) o(methods.onupdate.callCount).equals(1) o(methods.onbeforeremove.callCount).equals(1) o(methods.onremove.callCount).equals(1) hooks.forEach(function(hook) { o(attrs[hook].callCount).equals(methods[hook].callCount)(hook) }) }) o("hook state and arguments validation", function(){ var methods = { view: o.spy(function(vnode) { o(this).equals(vnode.state) return "" }) } var attrs = {} var hooks = [ "oninit", "oncreate", "onbeforeupdate", "onupdate", "onbeforeremove", "onremove" ] hooks.forEach(function(hook) { attrs[hook] = o.spy(function(vnode){ o(this).equals(vnode.state)(hook) }) methods[hook] = o.spy(function(vnode){ o(this).equals(vnode.state) }) }) var component = createComponent(methods) render(root, [{tag: component, attrs: attrs}]) render(root, [{tag: component, attrs: attrs}]) render(root, []) hooks.forEach(function(hook) { o(attrs[hook].this).equals(methods.view.this)(hook) o(methods[hook].this).equals(methods.view.this)(hook) }) o(methods.view.args.length).equals(1) o(methods.oninit.args.length).equals(1) o(methods.oncreate.args.length).equals(1) o(methods.onbeforeupdate.args.length).equals(2) o(methods.onupdate.args.length).equals(1) o(methods.onbeforeremove.args.length).equals(1) o(methods.onremove.args.length).equals(1) hooks.forEach(function(hook) { o(methods[hook].args.length).equals(attrs[hook].args.length)(hook) }) }) o("recycled components get a fresh state", function() { var step = 0 var firstState var view = o.spy(function(vnode) { if (step === 0) { firstState = vnode.state } else { o(vnode.state).notEquals(firstState) } return {tag: "div"} }) var component = createComponent({view: view}) render(root, [{tag: "div", children: [{tag: component, key: 1}]}]) var child = root.firstChild.firstChild render(root, []) step = 1 render(root, [{tag: "div", children: [{tag: component, key: 1}]}]) o(child).equals(root.firstChild.firstChild) o(view.callCount).equals(2) }) }) o.spec("state", function() { o("initializes state", function() { var data = {a: 1} var component = createComponent(createComponent({ data: data, oninit: init, view: function() { return "" } })) render(root, [{tag: component}]) function init(vnode) { o(vnode.state.data).equals(data) } }) o("state proxies to the component object/prototype", function() { var body = {a: 1} var data = [body] var component = createComponent(createComponent({ data: data, oninit: init, view: function() { return "" } })) render(root, [{tag: component}]) function init(vnode) { o(vnode.state.data).equals(data) o(vnode.state.data[0]).equals(body) } }) }) }) }) o.spec("Tests specific to certain component kinds", function() { o.spec("state", function() { o("POJO", function() { var data = {} var component = { data: data, oninit: init, view: function() { return "" } } render(root, [{tag: component}]) function init(vnode) { o(vnode.state.data).equals(data) //inherits state via prototype component.x = 1 o(vnode.state.x).equals(1) } }) o("Constructible", function() { var oninit = o.spy() var component = o.spy(function(vnode){ o(vnode.state).equals(undefined) o(oninit.callCount).equals(0) }) var view = o.spy(function(){ o(this instanceof component).equals(true) return "" }) component.prototype.view = view component.prototype.oninit = oninit render(root, [{tag: component, attrs: {oninit: oninit}}]) render(root, [{tag: component, attrs: {oninit: oninit}}]) render(root, []) o(component.callCount).equals(1) o(oninit.callCount).equals(2) o(view.callCount).equals(2) }) o("Closure", function() { var state var oninit = o.spy() var view = o.spy(function() { o(this).equals(state) return "" }) var component = o.spy(function(vnode) { o(vnode.state).equals(undefined) o(oninit.callCount).equals(0) return state = { view: view } }) render(root, [{tag: component, attrs: {oninit: oninit}}]) render(root, [{tag: component, attrs: {oninit: oninit}}]) render(root, []) o(component.callCount).equals(1) o(oninit.callCount).equals(1) o(view.callCount).equals(2) }) }) }) }) mithril-1.1.7+dfsg/render/tests/test-createElement.js000066400000000000000000000064631512406206500226320ustar00rootroot00000000000000/* eslint-disable no-script-url */ "use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("createElement", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("creates element", function() { var vnode = {tag: "div"} render(root, [vnode]) o(vnode.dom.nodeName).equals("DIV") }) o("creates attr", function() { var vnode = {tag: "div", attrs: {id: "a", title: "b"}} render(root, [vnode]) o(vnode.dom.nodeName).equals("DIV") o(vnode.dom.attributes["id"].value).equals("a") o(vnode.dom.attributes["title"].value).equals("b") }) o("creates style", function() { var vnode = {tag: "div", attrs: {style: {backgroundColor: "red"}}} render(root, [vnode]) o(vnode.dom.nodeName).equals("DIV") o(vnode.dom.style.backgroundColor).equals("red") }) o("creates children", function() { var vnode = {tag: "div", children: [{tag: "a"}, {tag: "b"}]} render(root, [vnode]) o(vnode.dom.nodeName).equals("DIV") o(vnode.dom.childNodes.length).equals(2) o(vnode.dom.childNodes[0].nodeName).equals("A") o(vnode.dom.childNodes[1].nodeName).equals("B") }) o("creates attrs and children", function() { var vnode = {tag: "div", attrs: {id: "a", title: "b"}, children: [{tag: "a"}, {tag: "b"}]} render(root, [vnode]) o(vnode.dom.nodeName).equals("DIV") o(vnode.dom.attributes["id"].value).equals("a") o(vnode.dom.attributes["title"].value).equals("b") o(vnode.dom.childNodes.length).equals(2) o(vnode.dom.childNodes[0].nodeName).equals("A") o(vnode.dom.childNodes[1].nodeName).equals("B") }) o("creates svg", function() { var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", children: [ {tag: "a", ns: "http://www.w3.org/2000/svg", attrs: {"xlink:href": "javascript:;"}}, {tag: "foreignObject", children: [{tag: "body", attrs: {xmlns: "http://www.w3.org/1999/xhtml"}}]} ]} render(root, [vnode]) o(vnode.dom.nodeName).equals("svg") o(vnode.dom.namespaceURI).equals("http://www.w3.org/2000/svg") o(vnode.dom.firstChild.nodeName).equals("a") o(vnode.dom.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg") o(vnode.dom.firstChild.attributes["href"].value).equals("javascript:;") o(vnode.dom.firstChild.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink") o(vnode.dom.childNodes[1].nodeName).equals("foreignObject") o(vnode.dom.childNodes[1].firstChild.nodeName).equals("body") o(vnode.dom.childNodes[1].firstChild.namespaceURI).equals("http://www.w3.org/1999/xhtml") }) o("sets attributes correctly for svg", function() { var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", attrs: {viewBox: "0 0 100 100"}} render(root, [vnode]) o(vnode.dom.attributes["viewBox"].value).equals("0 0 100 100") }) o("creates mathml", function() { var vnode = {tag: "math", ns: "http://www.w3.org/1998/Math/MathML", children: [{tag: "mrow", ns: "http://www.w3.org/1998/Math/MathML"}]} render(root, [vnode]) o(vnode.dom.nodeName).equals("math") o(vnode.dom.namespaceURI).equals("http://www.w3.org/1998/Math/MathML") o(vnode.dom.firstChild.nodeName).equals("mrow") o(vnode.dom.firstChild.namespaceURI).equals("http://www.w3.org/1998/Math/MathML") }) }) mithril-1.1.7+dfsg/render/tests/test-createFragment.js000066400000000000000000000023341512406206500227750ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("createFragment", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("creates fragment", function() { var vnode = {tag: "[", children: [{tag: "a"}]} render(root, [vnode]) o(vnode.dom.nodeName).equals("A") }) o("handles empty fragment", function() { var vnode = {tag: "[", children: []} render(root, [vnode]) o(vnode.dom).equals(null) o(vnode.domSize).equals(0) }) o("handles childless fragment", function() { var vnode = {tag: "["} render(root, [vnode]) o(vnode.dom).equals(null) o(vnode.domSize).equals(0) }) o("handles multiple children", function() { var vnode = {tag: "[", children: [{tag: "a"}, {tag: "b"}]} render(root, [vnode]) o(vnode.domSize).equals(2) o(vnode.dom.nodeName).equals("A") o(vnode.dom.nextSibling.nodeName).equals("B") }) o("handles td", function() { var vnode = {tag: "[", children: [{tag: "td"}]} render(root, [vnode]) o(vnode.dom).notEquals(null) o(vnode.dom.nodeName).equals("TD") }) }) mithril-1.1.7+dfsg/render/tests/test-createHTML.js000066400000000000000000000045331512406206500220010ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("createHTML", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("creates HTML", function() { var vnode = {tag: "<", children: ""} render(root, [vnode]) o(vnode.dom.nodeName).equals("A") }) o("creates text HTML", function() { var vnode = {tag: "<", children: "a"} render(root, [vnode]) o(vnode.dom.nodeValue).equals("a") }) o("handles empty HTML", function() { var vnode = {tag: "<", children: ""} render(root, [vnode]) o(vnode.dom).equals(null) o(vnode.domSize).equals(0) }) o("handles multiple children", function() { var vnode = {tag: "<", children: ""} render(root, [vnode]) o(vnode.domSize).equals(2) o(vnode.dom.nodeName).equals("A") o(vnode.dom.nextSibling.nodeName).equals("B") }) o("handles valid html tags", function() { //FIXME body,head,html,frame,frameset are not supported //FIXME keygen is broken in Firefox var tags = ["a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bdi", "bdo", "big", "blockquote", /*"body",*/ "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "datalist", "dd", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", /*"frame", "frameset",*/ "h1", "h2", "h3", "h4", "h5", "h6", /*"head",*/ "header", "hr", /*"html",*/ "i", "iframe", "img", "input", "ins", "kbd", /*"keygen", */"label", "legend", "li", "link", "main", "map", "mark", "menu", "menuitem", "meta", "meter", "nav", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"] tags.forEach(function(tag) { var vnode = {tag: "<", children: "<" + tag + " />"} render(root, [vnode]) o(vnode.dom.nodeName).equals(tag.toUpperCase()) }) }) }) mithril-1.1.7+dfsg/render/tests/test-createNodes.js000066400000000000000000000032001512406206500222730ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("createNodes", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("creates nodes", function() { var vnodes = [ {tag: "a"}, {tag: "#", children: "b"}, {tag: "<", children: "c"}, {tag: "[", children: [{tag: "#", children: "d"}]}, ] render(root, vnodes) o(root.childNodes.length).equals(4) o(root.childNodes[0].nodeName).equals("A") o(root.childNodes[1].nodeValue).equals("b") o(root.childNodes[2].nodeValue).equals("c") o(root.childNodes[3].nodeValue).equals("d") }) o("ignores null", function() { var vnodes = [ {tag: "a"}, {tag: "#", children: "b"}, null, {tag: "<", children: "c"}, {tag: "[", children: [{tag: "#", children: "d"}]}, ] render(root, vnodes) o(root.childNodes.length).equals(4) o(root.childNodes[0].nodeName).equals("A") o(root.childNodes[1].nodeValue).equals("b") o(root.childNodes[2].nodeValue).equals("c") o(root.childNodes[3].nodeValue).equals("d") }) o("ignores undefined", function() { var vnodes = [ {tag: "a"}, {tag: "#", children: "b"}, undefined, {tag: "<", children: "c"}, {tag: "[", children: [{tag: "#", children: "d"}]}, ] render(root, vnodes) o(root.childNodes.length).equals(4) o(root.childNodes[0].nodeName).equals("A") o(root.childNodes[1].nodeValue).equals("b") o(root.childNodes[2].nodeValue).equals("c") o(root.childNodes[3].nodeValue).equals("d") }) }) mithril-1.1.7+dfsg/render/tests/test-createText.js000066400000000000000000000034511512406206500221570ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("createText", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("creates string", function() { var vnode = {tag: "#", children: "a"} render(root, [vnode]) o(vnode.dom.nodeName).equals("#text") o(vnode.dom.nodeValue).equals("a") }) o("creates falsy string", function() { var vnode = {tag: "#", children: ""} render(root, [vnode]) o(vnode.dom.nodeName).equals("#text") o(vnode.dom.nodeValue).equals("") }) o("creates number", function() { var vnode = {tag: "#", children: 1} render(root, [vnode]) o(vnode.dom.nodeName).equals("#text") o(vnode.dom.nodeValue).equals("1") }) o("creates falsy number", function() { var vnode = {tag: "#", children: 0} render(root, [vnode]) o(vnode.dom.nodeName).equals("#text") o(vnode.dom.nodeValue).equals("0") }) o("creates boolean", function() { var vnode = {tag: "#", children: true} render(root, [vnode]) o(vnode.dom.nodeName).equals("#text") o(vnode.dom.nodeValue).equals("true") }) o("creates falsy boolean", function() { var vnode = {tag: "#", children: false} render(root, [vnode]) o(vnode.dom.nodeName).equals("#text") o(vnode.dom.nodeValue).equals("false") }) o("creates spaces", function() { var vnode = {tag: "#", children: " "} render(root, [vnode]) o(vnode.dom.nodeName).equals("#text") o(vnode.dom.nodeValue).equals(" ") }) o("ignores html", function() { var vnode = {tag: "#", children: "™"} render(root, [vnode]) o(vnode.dom.nodeName).equals("#text") o(vnode.dom.nodeValue).equals("™") }) }) mithril-1.1.7+dfsg/render/tests/test-event.js000066400000000000000000000050611512406206500211670ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("event", function() { var $window, root, onevent, render o.beforeEach(function() { $window = domMock() root = $window.document.body onevent = o.spy() var renderer = vdom($window) renderer.setEventCallback(onevent) render = renderer.render }) o("handles onclick", function() { var spy = o.spy() var div = {tag: "div", attrs: {onclick: spy}} var e = $window.document.createEvent("MouseEvents") e.initEvent("click", true, true) render(root, [div]) div.dom.dispatchEvent(e) o(spy.callCount).equals(1) o(spy.this).equals(div.dom) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) o(onevent.callCount).equals(1) o(onevent.this).equals(div.dom) o(onevent.args[0].type).equals("click") o(onevent.args[0].target).equals(div.dom) }) o("removes event", function() { var spy = o.spy() var vnode = {tag: "a", attrs: {onclick: spy}} var updated = {tag: "a", attrs: {}} render(root, [vnode]) render(root, [updated]) var e = $window.document.createEvent("MouseEvents") e.initEvent("click", true, true) vnode.dom.dispatchEvent(e) o(spy.callCount).equals(0) }) o("fires onclick only once after redraw", function() { var spy = o.spy() var div = {tag: "div", attrs: {id: "a", onclick: spy}} var updated = {tag: "div", attrs: {id: "b", onclick: spy}} var e = $window.document.createEvent("MouseEvents") e.initEvent("click", true, true) render(root, [div]) render(root, [updated]) div.dom.dispatchEvent(e) o(spy.callCount).equals(1) o(spy.this).equals(div.dom) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) o(onevent.callCount).equals(1) o(onevent.this).equals(div.dom) o(onevent.args[0].type).equals("click") o(onevent.args[0].target).equals(div.dom) o(div.dom).equals(updated.dom) o(div.dom.attributes["id"].value).equals("b") }) o("handles ontransitionend", function() { var spy = o.spy() var div = {tag: "div", attrs: {ontransitionend: spy}} var e = $window.document.createEvent("HTMLEvents") e.initEvent("transitionend", true, true) render(root, [div]) div.dom.dispatchEvent(e) o(spy.callCount).equals(1) o(spy.this).equals(div.dom) o(spy.args[0].type).equals("transitionend") o(spy.args[0].target).equals(div.dom) o(onevent.callCount).equals(1) o(onevent.this).equals(div.dom) o(onevent.args[0].type).equals("transitionend") o(onevent.args[0].target).equals(div.dom) }) }) mithril-1.1.7+dfsg/render/tests/test-fragment.js000066400000000000000000000013561512406206500216540ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var fragment = require("../../render/fragment") o.spec("fragment", function() { o("works", function() { var attrs = {foo: 5} var child = {tag: "p"} var frag = fragment(attrs, [child]) o(frag.tag).equals("[") o(Array.isArray(frag.children)).equals(true) o(frag.children.length).equals(1) o(frag.children[0]).equals(child) o(frag.attrs).equals(attrs) o(frag.key).equals(undefined) }) o("supports keys", function() { var attrs = {key: 7} var frag = fragment(attrs, []) o(frag.tag).equals("[") o(Array.isArray(frag.children)).equals(true) o(frag.children.length).equals(0) o(frag.attrs).equals(attrs) o(frag.attrs.key).equals(7) o(frag.key).equals(7) }) }) mithril-1.1.7+dfsg/render/tests/test-hyperscript.js000066400000000000000000000362601512406206500224270ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var m = require("../../render/hyperscript") o.spec("hyperscript", function() { o.spec("selector", function() { o("throws on null selector", function(done) { try {m(null)} catch(e) {done()} }) o("throws on non-string selector w/o a view property", function(done) { try {m({})} catch(e) {done()} }) o("handles tag in selector", function() { var vnode = m("a") o(vnode.tag).equals("a") }) o("v1.0.1 bug-for-bug regression suite", function(){ o(m("a", { class: null }).attrs).deepEquals({ class: undefined, className: null }) o(m("a", { class: undefined }).attrs).deepEquals({ class: undefined, }) o(m("a", { class: false }).attrs).deepEquals({ class: undefined, className: false }) o(m("a", { class: true }).attrs).deepEquals({ class: undefined, className: true }) o(m("a.x", { class: null }).attrs).deepEquals({ class: undefined, className: "x null" }) o(m("a.x", { class: undefined }).attrs).deepEquals({ class: undefined, className: "x" }) o(m("a.x", { class: false }).attrs).deepEquals({ class: undefined, className: "x false" }) o(m("a.x", { class: true }).attrs).deepEquals({ class: undefined, className: "x true" }) o(m("a", { className: null }).attrs).deepEquals({ className: null }) o(m("a", { className: undefined }).attrs).deepEquals({ className: undefined }) o(m("a", { className: false }).attrs).deepEquals({ className: false }) o(m("a", { className: true }).attrs).deepEquals({ className: true }) o(m("a.x", { className: null }).attrs).deepEquals({ className: "x" }) o(m("a.x", { className: undefined }).attrs).deepEquals({ className: "x" }) o(m("a.x", { className: false }).attrs).deepEquals({ className: "x" }) o(m("a.x", { className: true }).attrs).deepEquals({ className: "x true" }) }) o("handles class in selector", function() { var vnode = m(".a") o(vnode.tag).equals("div") o(vnode.attrs.className).equals("a") }) o("handles many classes in selector", function() { var vnode = m(".a.b.c") o(vnode.tag).equals("div") o(vnode.attrs.className).equals("a b c") }) o("handles id in selector", function() { var vnode = m("#a") o(vnode.tag).equals("div") o(vnode.attrs.id).equals("a") }) o("handles attr in selector", function() { var vnode = m("[a=b]") o(vnode.tag).equals("div") o(vnode.attrs.a).equals("b") }) o("handles many attrs in selector", function() { var vnode = m("[a=b][c=d]") o(vnode.tag).equals("div") o(vnode.attrs.a).equals("b") o(vnode.attrs.c).equals("d") }) o("handles attr w/ spaces in selector", function() { var vnode = m("[a = b]") o(vnode.tag).equals("div") o(vnode.attrs.a).equals("b") }) o("handles attr w/ quotes in selector", function() { var vnode = m("[a='b']") o(vnode.tag).equals("div") o(vnode.attrs.a).equals("b") }) o("handles attr w/ quoted square bracket", function() { var vnode = m("[x][a='[b]'].c") o(vnode.tag).equals("div") o(vnode.attrs.x).equals(true) o(vnode.attrs.a).equals("[b]") o(vnode.attrs.className).equals("c") }) o("handles attr w/ unmatched square bracket", function() { var vnode = m("[a=']'].c") o(vnode.tag).equals("div") o(vnode.attrs.a).equals("]") o(vnode.attrs.className).equals("c") }) o("handles attr w/ quoted square bracket and quote", function() { var vnode = m("[a='[b\"\\']'].c") // `[a='[b"\']']` o(vnode.tag).equals("div") o(vnode.attrs.a).equals("[b\"']") // `[b"']` o(vnode.attrs.className).equals("c") }) o("handles attr w/ quoted square containing escaped square bracket", function() { var vnode = m("[a='[\\]]'].c") // `[a='[\]]']` o(vnode.tag).equals("div") o(vnode.attrs.a).equals("[\\]]") // `[\]]` o(vnode.attrs.className).equals("c") }) o("handles attr w/ backslashes", function() { var vnode = m("[a='\\\\'].c") // `[a='\\']` o(vnode.tag).equals("div") o(vnode.attrs.a).equals("\\") o(vnode.attrs.className).equals("c") }) o("handles attr w/ quotes and spaces in selector", function() { var vnode = m("[a = 'b']") o(vnode.tag).equals("div") o(vnode.attrs.a).equals("b") }) o("handles many attr w/ quotes and spaces in selector", function() { var vnode = m("[a = 'b'][c = 'd']") o(vnode.tag).equals("div") o(vnode.attrs.a).equals("b") o(vnode.attrs.c).equals("d") }) o("handles tag, class, attrs in selector", function() { var vnode = m("a.b[c = 'd']") o(vnode.tag).equals("a") o(vnode.attrs.className).equals("b") o(vnode.attrs.c).equals("d") }) o("handles tag, mixed classes, attrs in selector", function() { var vnode = m("a.b[c = 'd'].e[f = 'g']") o(vnode.tag).equals("a") o(vnode.attrs.className).equals("b e") o(vnode.attrs.c).equals("d") o(vnode.attrs.f).equals("g") }) o("handles attr without value", function() { var vnode = m("[a]") o(vnode.tag).equals("div") o(vnode.attrs.a).equals(true) }) o("handles explicit empty string value for input", function() { var vnode = m('input[value=""]') o(vnode.tag).equals("input") o(vnode.attrs.value).equals("") }) o("handles explicit empty string value for option", function() { var vnode = m('option[value=""]') o(vnode.tag).equals("option") o(vnode.attrs.value).equals("") }) }) o.spec("attrs", function() { o("handles string attr", function() { var vnode = m("div", {a: "b"}) o(vnode.tag).equals("div") o(vnode.attrs.a).equals("b") }) o("handles falsy string attr", function() { var vnode = m("div", {a: ""}) o(vnode.tag).equals("div") o(vnode.attrs.a).equals("") }) o("handles number attr", function() { var vnode = m("div", {a: 1}) o(vnode.tag).equals("div") o(vnode.attrs.a).equals(1) }) o("handles falsy number attr", function() { var vnode = m("div", {a: 0}) o(vnode.tag).equals("div") o(vnode.attrs.a).equals(0) }) o("handles boolean attr", function() { var vnode = m("div", {a: true}) o(vnode.tag).equals("div") o(vnode.attrs.a).equals(true) }) o("handles falsy boolean attr", function() { var vnode = m("div", {a: false}) o(vnode.tag).equals("div") o(vnode.attrs.a).equals(false) }) o("handles only key in attrs", function() { var vnode = m("div", {key:"a"}) o(vnode.tag).equals("div") o(vnode.attrs).equals(undefined) o(vnode.key).equals("a") }) o("handles many attrs", function() { var vnode = m("div", {a: "b", c: "d"}) o(vnode.tag).equals("div") o(vnode.attrs.a).equals("b") o(vnode.attrs.c).equals("d") }) o("handles className attrs property", function() { var vnode = m("div", {className: "a"}) o(vnode.attrs.className).equals("a") }) o("handles 'class' as a verbose attribute declaration", function() { var vnode = m("[class=a]") o(vnode.attrs.className).equals("a") }) o("handles merging classes w/ class property", function() { var vnode = m(".a", {class: "b"}) o(vnode.attrs.className).equals("a b") }) o("handles merging classes w/ className property", function() { var vnode = m(".a", {className: "b"}) o(vnode.attrs.className).equals("a b") }) }) o.spec("children", function() { o("handles string single child", function() { var vnode = m("div", {}, ["a"]) o(vnode.text).equals("a") }) o("handles falsy string single child", function() { var vnode = m("div", {}, [""]) o(vnode.text).equals("") }) o("handles number single child", function() { var vnode = m("div", {}, [1]) o(vnode.text).equals(1) }) o("handles falsy number single child", function() { var vnode = m("div", {}, [0]) o(vnode.text).equals(0) }) o("handles boolean single child", function() { var vnode = m("div", {}, [true]) o(vnode.text).equals(true) }) o("handles falsy boolean single child", function() { var vnode = m("div", {}, [false]) o(vnode.text).equals("") }) o("handles null single child", function() { var vnode = m("div", {}, [null]) o(vnode.children[0]).equals(null) }) o("handles undefined single child", function() { var vnode = m("div", {}, [undefined]) o(vnode.children[0]).equals(undefined) }) o("handles multiple string children", function() { var vnode = m("div", {}, ["", "a"]) o(vnode.children[0].tag).equals("#") o(vnode.children[0].children).equals("") o(vnode.children[1].tag).equals("#") o(vnode.children[1].children).equals("a") }) o("handles multiple number children", function() { var vnode = m("div", {}, [0, 1]) o(vnode.children[0].tag).equals("#") o(vnode.children[0].children).equals(0) o(vnode.children[1].tag).equals("#") o(vnode.children[1].children).equals(1) }) o("handles multiple boolean children", function() { var vnode = m("div", {}, [false, true]) o(vnode.children[0].tag).equals("#") o(vnode.children[0].children).equals("") o(vnode.children[1].tag).equals("#") o(vnode.children[1].children).equals(true) }) o("handles multiple null/undefined child", function() { var vnode = m("div", {}, [null, undefined]) o(vnode.children[0]).equals(null) o(vnode.children[1]).equals(undefined) }) o("handles falsy number single child without attrs", function() { var vnode = m("div", 0) o(vnode.text).equals(0) }) }) o.spec("permutations", function() { o("handles null attr and children", function() { var vnode = m("div", null, [m("a"), m("b")]) o(vnode.children.length).equals(2) o(vnode.children[0].tag).equals("a") o(vnode.children[1].tag).equals("b") }) o("handles null attr and child unwrapped", function() { var vnode = m("div", null, m("a")) o(vnode.children.length).equals(1) o(vnode.children[0].tag).equals("a") }) o("handles null attr and children unwrapped", function() { var vnode = m("div", null, m("a"), m("b")) o(vnode.children.length).equals(2) o(vnode.children[0].tag).equals("a") o(vnode.children[1].tag).equals("b") }) o("handles attr and children", function() { var vnode = m("div", {a: "b"}, [m("i"), m("s")]) o(vnode.attrs.a).equals("b") o(vnode.children[0].tag).equals("i") o(vnode.children[1].tag).equals("s") }) o("handles attr and child unwrapped", function() { var vnode = m("div", {a: "b"}, m("i")) o(vnode.attrs.a).equals("b") o(vnode.children[0].tag).equals("i") }) o("handles attr and children unwrapped", function() { var vnode = m("div", {a: "b"}, m("i"), m("s")) o(vnode.attrs.a).equals("b") o(vnode.children[0].tag).equals("i") o(vnode.children[1].tag).equals("s") }) o("handles attr and text children", function() { var vnode = m("div", {a: "b"}, ["c", "d"]) o(vnode.attrs.a).equals("b") o(vnode.children[0].tag).equals("#") o(vnode.children[0].children).equals("c") o(vnode.children[1].tag).equals("#") o(vnode.children[1].children).equals("d") }) o("handles attr and single string text child", function() { var vnode = m("div", {a: "b"}, ["c"]) o(vnode.attrs.a).equals("b") o(vnode.text).equals("c") }) o("handles attr and single falsy string text child", function() { var vnode = m("div", {a: "b"}, [""]) o(vnode.attrs.a).equals("b") o(vnode.text).equals("") }) o("handles attr and single number text child", function() { var vnode = m("div", {a: "b"}, [1]) o(vnode.attrs.a).equals("b") o(vnode.text).equals(1) }) o("handles attr and single falsy number text child", function() { var vnode = m("div", {a: "b"}, [0]) o(vnode.attrs.a).equals("b") o(vnode.text).equals(0) }) o("handles attr and single boolean text child", function() { var vnode = m("div", {a: "b"}, [true]) o(vnode.attrs.a).equals("b") o(vnode.text).equals(true) }) o("handles attr and single falsy boolean text child", function() { var vnode = m("div", {a: "b"}, [0]) o(vnode.attrs.a).equals("b") o(vnode.text).equals(0) }) o("handles attr and single false boolean text child", function() { var vnode = m("div", {a: "b"}, [false]) o(vnode.attrs.a).equals("b") o(vnode.text).equals("") }) o("handles attr and single text child unwrapped", function() { var vnode = m("div", {a: "b"}, "c") o(vnode.attrs.a).equals("b") o(vnode.text).equals("c") }) o("handles attr and text children unwrapped", function() { var vnode = m("div", {a: "b"}, "c", "d") o(vnode.attrs.a).equals("b") o(vnode.children[0].tag).equals("#") o(vnode.children[0].children).equals("c") o(vnode.children[1].tag).equals("#") o(vnode.children[1].children).equals("d") }) o("handles children without attr", function() { var vnode = m("div", [m("i"), m("s")]) o(vnode.attrs).equals(undefined) o(vnode.children[0].tag).equals("i") o(vnode.children[1].tag).equals("s") }) o("handles child without attr unwrapped", function() { var vnode = m("div", m("i")) o(vnode.attrs).equals(undefined) o(vnode.children[0].tag).equals("i") }) o("handles children without attr unwrapped", function() { var vnode = m("div", m("i"), m("s")) o(vnode.attrs).equals(undefined) o(vnode.children[0].tag).equals("i") o(vnode.children[1].tag).equals("s") }) o("handles shared attrs", function() { var attrs = {a: "b"} var nodeA = m(".a", attrs) var nodeB = m(".b", attrs) o(nodeA.attrs.className).equals("a") o(nodeA.attrs.a).equals("b") o(nodeB.attrs.className).equals("b") o(nodeB.attrs.a).equals("b") }) o("doesnt modify passed attributes object", function() { var attrs = {a: "b"} m(".a", attrs) o(attrs).deepEquals({a: "b"}) }) o("handles fragment children without attr unwrapped", function() { var vnode = m("div", [m("i")], [m("s")]) o(vnode.children[0].tag).equals("[") o(vnode.children[0].children[0].tag).equals("i") o(vnode.children[1].tag).equals("[") o(vnode.children[1].children[0].tag).equals("s") }) o("handles children with nested array", function() { var vnode = m("div", [[m("i"), m("s")]]) o(vnode.children[0].tag).equals("[") o(vnode.children[0].children[0].tag).equals("i") o(vnode.children[0].children[1].tag).equals("s") }) o("handles children with deeply nested array", function() { var vnode = m("div", [[[m("i"), m("s")]]]) o(vnode.children[0].tag).equals("[") o(vnode.children[0].children[0].tag).equals("[") o(vnode.children[0].children[0].children[0].tag).equals("i") o(vnode.children[0].children[0].children[1].tag).equals("s") }) }) o.spec("components", function() { o("works with POJOs", function() { var component = { view: function() { return m("div") } } var vnode = m(component, {id: "a"}, "b") o(vnode.tag).equals(component) o(vnode.attrs.id).equals("a") o(vnode.children.length).equals(1) o(vnode.children[0].tag).equals("#") o(vnode.children[0].children).equals("b") }) o("works with functions", function() { var component = o.spy() var vnode = m(component, {id: "a"}, "b") o(component.callCount).equals(0) o(vnode.tag).equals(component) o(vnode.attrs.id).equals("a") o(vnode.children.length).equals(1) o(vnode.children[0].tag).equals("#") o(vnode.children[0].children).equals("b") }) }) }) mithril-1.1.7+dfsg/render/tests/test-input.js000066400000000000000000000126731512406206500212140ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("form inputs", function() { var $window, root, render o.beforeEach(function() { $window = domMock() render = vdom($window).render root = $window.document.createElement("div") $window.document.body.appendChild(root) }) o.afterEach(function() { while (root.firstChild) root.removeChild(root.firstChild) root.vnodes = null }) o.spec("input", function() { o("maintains focus after move", function() { var input = {tag: "input", key: 1} var a = {tag: "a", key: 2} var b = {tag: "b", key: 3} render(root, [input, a, b]) input.dom.focus() render(root, [a, input, b]) o($window.document.activeElement).equals(input.dom) }) o("maintains focus when changed manually in hook", function() { var input = {tag: "input", attrs: {oncreate: function() { input.dom.focus(); }}}; render(root, [input]) o($window.document.activeElement).equals(input.dom) }) o("syncs input value if DOM value differs from vdom value", function() { var input = {tag: "input", attrs: {value: "aaa", oninput: function() {}}} var updated = {tag: "input", attrs: {value: "aaa", oninput: function() {}}} render(root, [input]) //simulate user typing var e = $window.document.createEvent("KeyboardEvent") e.initEvent("input", true, true) input.dom.focus() input.dom.value += "a" input.dom.dispatchEvent(e) //re-render may use same vdom value as previous render call render(root, [updated]) o(updated.dom.value).equals("aaa") }) o("syncs input checked attribute if DOM value differs from vdom value", function() { var input = {tag: "input", attrs: {type: "checkbox", checked: true, onclick: function() {}}} var updated = {tag: "input", attrs: {type: "checkbox", checked: true, onclick: function() {}}} render(root, [input]) //simulate user clicking checkbox var e = $window.document.createEvent("MouseEvents") e.initEvent("click", true, true) input.dom.focus() input.dom.dispatchEvent(e) //re-render may use same vdom value as previous render call render(root, [updated]) o(updated.dom.checked).equals(true) }) }) o.spec("select", function() { o("select works without attributes", function() { var select = {tag: "select", children: [ {tag: "option", attrs: {value: "a"}, text: "aaa"}, ]} render(root, [select]) o(select.dom.value).equals("a") o(select.dom.selectedIndex).equals(0) }) o("select option can have empty string value", function() { var select = {tag: "select", children :[ {tag: "option", attrs: {value: ""}, text: "aaa"} ]} render(root, [select]) o(select.dom.firstChild.value).equals("") }) o("option value defaults to textContent unless explicitly set", function() { var select = {tag: "select", children :[ {tag: "option", text: "aaa"} ]} render(root, [select]) o(select.dom.firstChild.value).equals("aaa") o(select.dom.value).equals("aaa") //test that value changes when content changes select = {tag: "select", children :[ {tag: "option", text: "bbb"} ]} render(root, [select]) o(select.dom.firstChild.value).equals("bbb") o(select.dom.value).equals("bbb") //test that value can be set to "" in subsequent render select = {tag: "select", children :[ {tag: "option", attrs: {value: ""}, text: "aaa"} ]} render(root, [select]) o(select.dom.firstChild.value).equals("") o(select.dom.value).equals("") //test that value reverts to textContent when value omitted select = {tag: "select", children :[ {tag: "option", text: "aaa"} ]} render(root, [select]) o(select.dom.firstChild.value).equals("aaa") o(select.dom.value).equals("aaa") }) o("select yields invalid value without children", function() { var select = {tag: "select", attrs: {value: "a"}} render(root, [select]) o(select.dom.value).equals("") o(select.dom.selectedIndex).equals(-1) }) o("select value is set correctly on first render", function() { var select = {tag: "select", attrs: {value: "b"}, children: [ {tag: "option", attrs: {value: "a"}, text: "aaa"}, {tag: "option", attrs: {value: "b"}, text: "bbb"}, {tag: "option", attrs: {value: "c"}, text: "ccc"}, ]} render(root, [select]) o(select.dom.value).equals("b") o(select.dom.selectedIndex).equals(1) }) o("syncs select value if DOM value differs from vdom value", function() { function makeSelect() { return {tag: "select", attrs: {value: "b"}, children: [ {tag: "option", attrs: {value: "a"}, text: "aaa"}, {tag: "option", attrs: {value: "b"}, text: "bbb"}, {tag: "option", attrs: {value: "c"}, text: "ccc"}, ]} } render(root, [makeSelect()]) //simulate user selecting option root.firstChild.value = "c" root.firstChild.focus() //re-render may use same vdom value as previous render call render(root, [makeSelect()]) o(root.firstChild.value).equals("b") o(root.firstChild.selectedIndex).equals(1) }) }) o.spec("textarea", function() { o("updates after user input", function() { render(root, [{tag: "textarea", text: "aaa"}]) //simulate typing root.firstChild.value = "bbb" //re-render may occur after value attribute is touched render(root, [{tag: "textarea", text: "ccc"}]) o(root.firstChild.value).equals("ccc") //FIXME should fail if fix is commented out }) }) }) mithril-1.1.7+dfsg/render/tests/test-normalize.js000066400000000000000000000026301512406206500220450ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var Vnode = require("../../render/vnode") o.spec("normalize", function() { o("normalizes array into fragment", function() { var node = Vnode.normalize([]) o(node.tag).equals("[") o(node.children.length).equals(0) }) o("normalizes nested array into fragment", function() { var node = Vnode.normalize([[]]) o(node.tag).equals("[") o(node.children.length).equals(1) o(node.children[0].tag).equals("[") o(node.children[0].children.length).equals(0) }) o("normalizes string into text node", function() { var node = Vnode.normalize("a") o(node.tag).equals("#") o(node.children).equals("a") }) o("normalizes falsy string into text node", function() { var node = Vnode.normalize("") o(node.tag).equals("#") o(node.children).equals("") }) o("normalizes number into text node", function() { var node = Vnode.normalize(1) o(node.tag).equals("#") o(node.children).equals(1) }) o("normalizes falsy number into text node", function() { var node = Vnode.normalize(0) o(node.tag).equals("#") o(node.children).equals(0) }) o("normalizes boolean into text node", function() { var node = Vnode.normalize(true) o(node.tag).equals("#") o(node.children).equals(true) }) o("normalizes falsy boolean into empty text node", function() { var node = Vnode.normalize(false) o(node.tag).equals("#") o(node.children).equals("") }) }) mithril-1.1.7+dfsg/render/tests/test-normalizeChildren.js000066400000000000000000000012671512406206500235230ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var Vnode = require("../../render/vnode") o.spec("normalizeChildren", function() { o("normalizes arrays into fragments", function() { var children = Vnode.normalizeChildren([[]]) o(children[0].tag).equals("[") o(children[0].children.length).equals(0) }) o("normalizes strings into text nodes", function() { var children = Vnode.normalizeChildren(["a"]) o(children[0].tag).equals("#") o(children[0].children).equals("a") }) o("normalizes `false` values into empty string text nodes", function() { var children = Vnode.normalizeChildren([false]) o(children[0].tag).equals("#") o(children[0].children).equals("") }) }) mithril-1.1.7+dfsg/render/tests/test-onbeforeremove.js000066400000000000000000000132631512406206500230660ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var callAsync = require("../../test-utils/callAsync") var components = require("../../test-utils/components") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") var Promise = require("../../promise/promise") o.spec("onbeforeremove", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("does not call onbeforeremove when creating", function() { var create = o.spy() var vnode = {tag: "div", attrs: {onbeforeremove: create}} render(root, [vnode]) o(create.callCount).equals(0) }) o("does not call onbeforeremove when updating", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {onbeforeremove: create}} var updated = {tag: "div", attrs: {onbeforeremove: update}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(0) }) o("calls onbeforeremove when removing element", function(done) { var vnode = {tag: "div", attrs: { oninit: function() {vnode.state = {}}, onbeforeremove: remove }} render(root, [vnode]) render(root, []) function remove(node) { o(node).equals(vnode) o(this).equals(vnode.state) o(root.childNodes.length).equals(1) o(root.firstChild).equals(vnode.dom) callAsync(function() { o(root.childNodes.length).equals(0) done() }) } }) o("calls onbeforeremove when removing text", function(done) { var vnode = {tag: "#", attrs: {onbeforeremove: remove}, children: "a"} render(root, [vnode]) render(root, []) function remove(node) { o(node).equals(vnode) o(root.childNodes.length).equals(1) o(root.firstChild).equals(vnode.dom) callAsync(function() { o(root.childNodes.length).equals(0) done() }) } }) o("calls onbeforeremove when removing fragment", function(done) { var vnode = {tag: "[", attrs: {onbeforeremove: remove}, children: [{tag: "div"}]} render(root, [vnode]) render(root, []) function remove(node) { o(node).equals(vnode) o(root.childNodes.length).equals(1) o(root.firstChild).equals(vnode.dom) callAsync(function() { o(root.childNodes.length).equals(0) done() }) } }) o("calls onbeforeremove when removing html", function(done) { var vnode = {tag: "<", attrs: {onbeforeremove: remove}, children: "a"} render(root, [vnode]) render(root, []) function remove(node) { o(node).equals(vnode) o(root.childNodes.length).equals(1) o(root.firstChild).equals(vnode.dom) callAsync(function() { o(root.childNodes.length).equals(0) done() }) } }) o("calls remove after onbeforeremove resolves", function(done) { var spy = o.spy() var vnode = {tag: "<", attrs: {onbeforeremove: remove, onremove: spy}, children: "a"} render(root, [vnode]) render(root, []) function remove(node) { o(node).equals(vnode) o(root.childNodes.length).equals(1) o(root.firstChild).equals(vnode.dom) callAsync(function() { o(root.childNodes.length).equals(0) o(spy.callCount).equals(1) done() }) } }) o("does not set onbeforeremove as an event handler", function() { var remove = o.spy() var vnode = {tag: "div", attrs: {onbeforeremove: remove}, children: []} render(root, [vnode]) o(vnode.dom.onbeforeremove).equals(undefined) o(vnode.dom.attributes["onbeforeremove"]).equals(undefined) }) o("does not recycle when there's an onbeforeremove", function() { var remove = function() {} var vnode = {tag: "div", key: 1, attrs: {onbeforeremove: remove}} var updated = {tag: "div", key: 1, attrs: {onbeforeremove: remove}} render(root, [vnode]) render(root, []) render(root, [updated]) o(vnode.dom).notEquals(updated.dom) }) o("does not leave elements out of order during removal", function(done) { var remove = function() {return Promise.resolve()} var vnodes = [{tag: "div", key: 1, attrs: {onbeforeremove: remove}, text: "1"}, {tag: "div", key: 2, attrs: {onbeforeremove: remove}, text: "2"}] var updated = {tag: "div", key: 2, attrs: {onbeforeremove: remove}, text: "2"} render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(root.firstChild.firstChild.nodeValue).equals("1") callAsync(function() { o(root.childNodes.length).equals(1) o(root.firstChild.firstChild.nodeValue).equals("2") done() }) }) components.forEach(function(cmp){ o.spec(cmp.kind, function(){ var createComponent = cmp.create o("finalizes the remove phase asynchronously when promise is returned synchronously from both attrs- and tag.onbeforeremove", function(done) { var onremove = o.spy() var onbeforeremove = function(){return Promise.resolve()} var component = createComponent({ onbeforeremove: onbeforeremove, onremove: onremove, view: function() {}, }) render(root, [{tag: component, attrs: {onbeforeremove: onbeforeremove, onremove: onremove}}]) render(root, []) callAsync(function() { o(onremove.callCount).equals(2) // once for `tag`, once for `attrs` done() }) }) o("awaits promise resolution before removing the node", function(done) { var view = o.spy() var onremove = o.spy() var onbeforeremove = function(){return new Promise(function(resolve){callAsync(resolve)})} var component = createComponent({ onbeforeremove: onbeforeremove, onremove: onremove, view: view, }) render(root, [{tag: component}]) render(root, []) o(onremove.callCount).equals(0) callAsync(function(){ callAsync(function() { o(onremove.callCount).equals(1) done() }) }) }) }) }) }) mithril-1.1.7+dfsg/render/tests/test-onbeforeupdate.js000066400000000000000000000211701512406206500230470ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var components = require("../../test-utils/components") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("onbeforeupdate", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("prevents update in element", function() { var onbeforeupdate = function() {return false} var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}} var updated = {tag: "div", attrs: {id: "b", onbeforeupdate: onbeforeupdate}} render(root, [vnode]) render(root, [updated]) o(root.firstChild.attributes["id"].value).equals("a") }) o("prevents update in text", function() { var onbeforeupdate = function() {return false} var vnode = {tag: "#", attrs: {onbeforeupdate: onbeforeupdate}, children: "a"} var updated = {tag: "#", attrs: {onbeforeupdate: onbeforeupdate}, children: "b"} render(root, [vnode]) render(root, [updated]) o(root.firstChild.nodeValue).equals("a") }) o("prevents update in html", function() { var onbeforeupdate = function() {return false} var vnode = {tag: "<", attrs: {onbeforeupdate: onbeforeupdate}, children: "a"} var updated = {tag: "<", attrs: {onbeforeupdate: onbeforeupdate}, children: "b"} render(root, [vnode]) render(root, [updated]) o(root.firstChild.nodeValue).equals("a") }) o("prevents update in fragment", function() { var onbeforeupdate = function() {return false} var vnode = {tag: "[", attrs: {onbeforeupdate: onbeforeupdate}, children: [{tag: "#", children: "a"}]} var updated = {tag: "[", attrs: {onbeforeupdate: onbeforeupdate}, children: [{tag: "#", children: "b"}]} render(root, [vnode]) render(root, [updated]) o(root.firstChild.nodeValue).equals("a") }) o("does not prevent update if returning true", function() { var onbeforeupdate = function() {return true} var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}} var updated = {tag: "div", attrs: {id: "b", onbeforeupdate: onbeforeupdate}} render(root, [vnode]) render(root, [updated]) o(root.firstChild.attributes["id"].value).equals("b") }) o("accepts arguments for comparison", function() { var count = 0 var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}} var updated = {tag: "div", attrs: {id: "b", onbeforeupdate: onbeforeupdate}} render(root, [vnode]) render(root, [updated]) function onbeforeupdate(vnode, old) { count++ o(old.attrs.id).equals("a") o(vnode.attrs.id).equals("b") return old.attrs.id !== vnode.attrs.id } o(count).equals(1) o(root.firstChild.attributes["id"].value).equals("b") }) o("is not called on creation", function() { var count = 0 var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}} render(root, [vnode]) function onbeforeupdate() { count++ return true } o(count).equals(0) }) o("is called only once on update", function() { var count = 0 var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}} var updated = {tag: "div", attrs: {id: "b", onbeforeupdate: onbeforeupdate}} render(root, [vnode]) render(root, [updated]) function onbeforeupdate() { count++ return true } o(count).equals(1) }) o("doesn't fire on recycled nodes", function() { var onbeforeupdate = o.spy() var vnodes = [{tag: "div", key: 1}] var temp = [] var updated = [{tag: "div", key: 1, attrs: {onbeforeupdate: onbeforeupdate}}] render(root, vnodes) render(root, temp) render(root, updated) o(vnodes[0].dom).equals(updated[0].dom) o(updated[0].dom.nodeName).equals("DIV") o(onbeforeupdate.callCount).equals(0) }) components.forEach(function(cmp){ o.spec(cmp.kind, function(){ var createComponent = cmp.create o("prevents update in component", function() { var component = createComponent({ onbeforeupdate: function() {return false}, view: function(vnode) { return {tag: "div", children: vnode.children} }, }) var vnode = {tag: component, children: [{tag: "#", children: "a"}]} var updated = {tag: component, children: [{tag: "#", children: "b"}]} render(root, [vnode]) render(root, [updated]) o(root.firstChild.firstChild.nodeValue).equals("a") }) o("prevents update if returning false in component and false in vnode", function() { var component = createComponent({ onbeforeupdate: function() {return false}, view: function(vnode) { return {tag: "div", attrs: {id: vnode.attrs.id}} }, }) var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return false}}} var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return false}}} render(root, [vnode]) render(root, [updated]) o(root.firstChild.attributes["id"].value).equals("a") }) o("does not prevent update if returning true in component and true in vnode", function() { var component = createComponent({ onbeforeupdate: function() {return true}, view: function(vnode) { return {tag: "div", attrs: {id: vnode.attrs.id}} }, }) var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return true}}} var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return true}}} render(root, [vnode]) render(root, [updated]) o(root.firstChild.attributes["id"].value).equals("b") }) o("does not prevent update if returning false in component but true in vnode", function() { var component = createComponent({ onbeforeupdate: function() {return false}, view: function(vnode) { return {tag: "div", attrs: {id: vnode.attrs.id}} }, }) var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return true}}} var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return true}}} render(root, [vnode]) render(root, [updated]) o(root.firstChild.attributes["id"].value).equals("b") }) o("does not prevent update if returning true in component but false in vnode", function() { var component = createComponent({ onbeforeupdate: function() {return true}, view: function(vnode) { return {tag: "div", attrs: {id: vnode.attrs.id}} }, }) var vnode = {tag: component, attrs: {id: "a", onbeforeupdate: function() {return false}}} var updated = {tag: component, attrs: {id: "b", onbeforeupdate: function() {return false}}} render(root, [vnode]) render(root, [updated]) o(root.firstChild.attributes["id"].value).equals("b") }) o("does not prevent update if returning true from component", function() { var component = createComponent({ onbeforeupdate: function() {return true}, view: function(vnode) { return {tag: "div", attrs: vnode.attrs} }, }) var vnode = {tag: component, attrs: {id: "a"}} var updated = {tag: component, attrs: {id: "b"}} render(root, [vnode]) render(root, [updated]) o(root.firstChild.attributes["id"].value).equals("b") }) o("accepts arguments for comparison in component", function() { var component = createComponent({ onbeforeupdate: onbeforeupdate, view: function(vnode) { return {tag: "div", attrs: vnode.attrs} }, }) var count = 0 var vnode = {tag: component, attrs: {id: "a"}} var updated = {tag: component, attrs: {id: "b"}} render(root, [vnode]) render(root, [updated]) function onbeforeupdate(vnode, old) { count++ o(old.attrs.id).equals("a") o(vnode.attrs.id).equals("b") return old.attrs.id !== vnode.attrs.id } o(count).equals(1) o(root.firstChild.attributes["id"].value).equals("b") }) o("is not called on component creation", function() { createComponent({ onbeforeupdate: onbeforeupdate, view: function(vnode) { return {tag: "div", attrs: vnode.attrs} }, }) var count = 0 var vnode = {tag: "div", attrs: {id: "a"}} render(root, [vnode]) function onbeforeupdate() { count++ return true } o(count).equals(0) }) o("is called only once on component update", function() { var component = createComponent({ onbeforeupdate: onbeforeupdate, view: function(vnode) { return {tag: "div", attrs: vnode.attrs} }, }) var count = 0 var vnode = {tag: component, attrs: {id: "a"}} var updated = {tag: component, attrs: {id: "b"}} render(root, [vnode]) render(root, [updated]) function onbeforeupdate() { count++ return true } o(count).equals(1) }) }) }) }) mithril-1.1.7+dfsg/render/tests/test-oncreate.js000066400000000000000000000155461512406206500216570ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("oncreate", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("calls oncreate when creating element", function() { var callback = o.spy() var vnode = {tag: "div", attrs: {oncreate: callback}, state: {}} render(root, [vnode]) o(callback.callCount).equals(1) o(callback.this).equals(vnode.state) o(callback.args[0]).equals(vnode) }) o("calls oncreate when creating text", function() { var callback = o.spy() var vnode = {tag: "#", attrs: {oncreate: callback}, children: "a", state: {}} render(root, [vnode]) o(callback.callCount).equals(1) o(callback.this).equals(vnode.state) o(callback.args[0]).equals(vnode) }) o("calls oncreate when creating fragment", function() { var callback = o.spy() var vnode = {tag: "[", attrs: {oncreate: callback}, children: [], state: {}} render(root, [vnode]) o(callback.callCount).equals(1) o(callback.this).equals(vnode.state) o(callback.args[0]).equals(vnode) }) o("calls oncreate when creating html", function() { var callback = o.spy() var vnode = {tag: "<", attrs: {oncreate: callback}, children: "a", state: {}} render(root, [vnode]) o(callback.callCount).equals(1) o(callback.this).equals(vnode.state) o(callback.args[0]).equals(vnode) }) o("calls oncreate when replacing keyed", function() { var createDiv = o.spy() var createA = o.spy() var vnode = {tag: "div", key: 1, attrs: {oncreate: createDiv}, state: {}} var updated = {tag: "a", key: 1, attrs: {oncreate: createA}, state: {}} render(root, [vnode]) render(root, [updated]) o(createDiv.callCount).equals(1) o(createDiv.this).equals(vnode.state) o(createDiv.args[0]).equals(vnode) o(createA.callCount).equals(1) o(createA.this).equals(updated.state) o(createA.args[0]).equals(updated) }) o("does not call oncreate when noop", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {oncreate: create}, state: {}} var updated = {tag: "div", attrs: {oncreate: update}, state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) o(update.callCount).equals(0) }) o("does not call oncreate when updating attr", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {oncreate: create}, state: {}} var updated = {tag: "div", attrs: {oncreate: update, id: "a"}, state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) o(update.callCount).equals(0) }) o("does not call oncreate when updating children", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {oncreate: create}, children: [{tag: "a"}], state: {}} var updated = {tag: "div", attrs: {oncreate: update}, children: [{tag: "b"}], state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) o(update.callCount).equals(0) }) o("does not call oncreate when updating keyed", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", key: 1, attrs: {oncreate: create}, state: {}} var otherVnode = {tag: "a", key: 2} var updated = {tag: "div", key: 1, attrs: {oncreate: update}, state: {}} var otherUpdated = {tag: "a", key: 2} render(root, [vnode, otherVnode]) render(root, [otherUpdated, updated]) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) o(update.callCount).equals(0) }) o("does not call oncreate when removing", function() { var create = o.spy() var vnode = {tag: "div", attrs: {oncreate: create}, state: {}} render(root, [vnode]) render(root, []) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) }) o("does not recycle when there's an oncreate", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", key: 1, attrs: {oncreate: create}, state: {}} var updated = {tag: "div", key: 1, attrs: {oncreate: update}, state: {}} render(root, [vnode]) render(root, []) render(root, [updated]) o(vnode.dom).notEquals(updated.dom) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) o(update.callCount).equals(1) o(update.this).equals(updated.state) o(update.args[0]).equals(updated) }) o("calls oncreate at the same step as onupdate", function() { var create = o.spy() var update = o.spy() var callback = o.spy() var vnode = {tag: "div", attrs: {onupdate: create}, children: [], state: {}} var updated = {tag: "div", attrs: {onupdate: update}, children: [{tag: "a", attrs: {oncreate: callback}, state: {}}], state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(1) o(update.this).equals(vnode.state) o(update.args[0]).equals(updated) o(callback.callCount).equals(1) o(callback.this).equals(updated.children[0].state) o(callback.args[0]).equals(updated.children[0]) }) o("calls oncreate on unkeyed that falls into reverse list diff code path", function() { var create = o.spy() render(root, [{tag: "p"}, {tag: "div"}]) render(root, [{tag: "div", attrs: {oncreate: create}}, {tag: "div"}]) o(create.callCount).equals(1) }) o("calls oncreate on unkeyed that falls into forward list diff code path", function() { var create = o.spy() render(root, [{tag: "div"}, {tag: "p"}]) render(root, [{tag: "div"}, {tag: "div", attrs: {oncreate: create}}]) o(create.callCount).equals(1) }) o("calls oncreate after full DOM creation", function() { var created = false var vnode = {tag: "div", children: [ {tag: "a", attrs: {oncreate: create}, children: [ {tag: "b"} ]} ]} render(root, [vnode]) function create(vnode) { created = true o(vnode.dom.parentNode).notEquals(null) o(vnode.dom.childNodes.length).equals(1) } o(created).equals(true) }) o("does not set oncreate as an event handler", function() { var create = o.spy() var vnode = {tag: "div", attrs: {oncreate: create}, children: []} render(root, [vnode]) o(vnode.dom.oncreate).equals(undefined) o(vnode.dom.attributes["oncreate"]).equals(undefined) }) o("calls oncreate on recycle", function() { var create = o.spy() var vnodes = [{tag: "div", key: 1, attrs: {oncreate: create}}] var temp = [] var updated = [{tag: "div", key: 1, attrs: {oncreate: create}}] render(root, vnodes) render(root, temp) render(root, updated) o(create.callCount).equals(2) }) }) mithril-1.1.7+dfsg/render/tests/test-oninit.js000066400000000000000000000136111512406206500213460ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("oninit", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("calls oninit when creating element", function() { var callback = o.spy() var vnode = {tag: "div", attrs: {oninit: callback}, state: {}} render(root, [vnode]) o(callback.callCount).equals(1) o(callback.this).equals(vnode.state) o(callback.args[0]).equals(vnode) }) o("calls oninit when creating text", function() { var callback = o.spy() var vnode = {tag: "#", attrs: {oninit: callback}, children: "a", state: {}} render(root, [vnode]) o(callback.callCount).equals(1) o(callback.this).equals(vnode.state) o(callback.args[0]).equals(vnode) }) o("calls oninit when creating fragment", function() { var callback = o.spy() var vnode = {tag: "[", attrs: {oninit: callback}, children: [], state: {}} render(root, [vnode]) o(callback.callCount).equals(1) o(callback.this).equals(vnode.state) o(callback.args[0]).equals(vnode) }) o("calls oninit when creating html", function() { var callback = o.spy() var vnode = {tag: "<", attrs: {oninit: callback}, children: "a", state: {}} render(root, [vnode]) o(callback.callCount).equals(1) o(callback.this).equals(vnode.state) o(callback.args[0]).equals(vnode) }) o("calls oninit when replacing keyed", function() { var createDiv = o.spy() var createA = o.spy() var vnode = {tag: "div", key: 1, attrs: {oninit: createDiv}, state: {}} var updated = {tag: "a", key: 1, attrs: {oninit: createA}, state: {}} render(root, [vnode]) render(root, [updated]) o(createDiv.callCount).equals(1) o(createDiv.this).equals(vnode.state) o(createDiv.args[0]).equals(vnode) o(createA.callCount).equals(1) o(createA.this).equals(updated.state) o(createA.args[0]).equals(updated) }) o("does not call oninit when noop", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {oninit: create}, state: {}} var updated = {tag: "div", attrs: {oninit: update}, state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) o(update.callCount).equals(0) }) o("does not call oninit when updating attr", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {oninit: create}, state: {}} var updated = {tag: "div", attrs: {oninit: update, id: "a"}, state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) o(update.callCount).equals(0) }) o("does not call oninit when updating children", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {oninit: create}, children: [{tag: "a"}], state: {}} var updated = {tag: "div", attrs: {oninit: update}, children: [{tag: "b"}], state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) o(update.callCount).equals(0) }) o("does not call oninit when updating keyed", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", key: 1, attrs: {oninit: create}, state: {}} var otherVnode = {tag: "a", key: 2} var updated = {tag: "div", key: 1, attrs: {oninit: update}, state: {}} var otherUpdated = {tag: "a", key: 2} render(root, [vnode, otherVnode]) render(root, [otherUpdated, updated]) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) o(update.callCount).equals(0) }) o("does not call oninit when removing", function() { var create = o.spy() var vnode = {tag: "div", attrs: {oninit: create}, state: {}} render(root, [vnode]) render(root, []) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) }) o("calls oninit when recycling", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", key: 1, attrs: {oninit: create}, state: {}} var updated = {tag: "div", key: 1, attrs: {oninit: update}, state: {}} render(root, [vnode]) render(root, []) render(root, [updated]) o(create.callCount).equals(1) o(create.this).equals(vnode.state) o(create.args[0]).equals(vnode) o(update.callCount).equals(1) o(update.this).equals(updated.state) o(update.args[0]).equals(updated) }) o("calls oninit at the same step as onupdate", function() { var create = o.spy() var update = o.spy() var callback = o.spy() var vnode = {tag: "div", attrs: {onupdate: create}, children: [], state: {}} var updated = {tag: "div", attrs: {onupdate: update}, children: [{tag: "a", attrs: {oninit: callback}, state: {}}], state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(1) o(update.this).equals(vnode.state) o(update.args[0]).equals(updated) o(callback.callCount).equals(1) o(callback.this).equals(updated.children[0].state) o(callback.args[0]).equals(updated.children[0]) }) o("calls oninit before full DOM creation", function() { var called = false var vnode = {tag: "div", children: [ {tag: "a", attrs: {oninit: create}, children: [ {tag: "b"} ]} ]} render(root, [vnode]) function create(vnode) { called = true o(vnode.dom).equals(undefined) o(root.childNodes.length).equals(1) } o(called).equals(true) }) o("does not set oninit as an event handler", function() { var create = o.spy() var vnode = {tag: "div", attrs: {oninit: create}, children: []} render(root, [vnode]) o(vnode.dom.oninit).equals(undefined) o(vnode.dom.attributes["oninit"]).equals(undefined) }) }) mithril-1.1.7+dfsg/render/tests/test-onremove.js000066400000000000000000000122501512406206500216760ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var components = require("../../test-utils/components") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") var m = require("../../render/hyperscript") o.spec("onremove", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("does not call onremove when creating", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {onremove: create}} var updated = {tag: "div", attrs: {onremove: update}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) }) o("does not call onremove when updating", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {onremove: create}} var updated = {tag: "div", attrs: {onremove: update}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(0) }) o("calls onremove when removing element", function() { var remove = o.spy() var vnode = {tag: "div", attrs: {onremove: remove}, state: {}} render(root, [vnode]) render(root, []) o(remove.callCount).equals(1) o(remove.this).equals(vnode.state) o(remove.args[0]).equals(vnode) }) o("calls onremove when removing text", function() { var remove = o.spy() var vnode = {tag: "#", attrs: {onremove: remove}, children: "a", state: {}} render(root, [vnode]) render(root, []) o(remove.callCount).equals(1) o(remove.this).equals(vnode.state) o(remove.args[0]).equals(vnode) }) o("calls onremove when removing fragment", function() { var remove = o.spy() var vnode = {tag: "[", attrs: {onremove: remove}, children: [], state: {}} render(root, [vnode]) render(root, []) o(remove.callCount).equals(1) o(remove.this).equals(vnode.state) o(remove.args[0]).equals(vnode) }) o("calls onremove when removing html", function() { var remove = o.spy() var vnode = {tag: "<", attrs: {onremove: remove}, children: "a", state: {}} render(root, [vnode]) render(root, []) o(remove.callCount).equals(1) o(remove.this).equals(vnode.state) o(remove.args[0]).equals(vnode) }) o("does not set onremove as an event handler", function() { var remove = o.spy() var vnode = {tag: "div", attrs: {onremove: remove}, children: []} render(root, [vnode]) o(vnode.dom.onremove).equals(undefined) o(vnode.dom.attributes["onremove"]).equals(undefined) o(vnode.events).equals(undefined) }) o("calls onremove on recycle", function() { var remove = o.spy() var vnodes = [{tag: "div", key: 1}] var temp = [{tag: "div", key: 2, attrs: {onremove: remove}}] var updated = [{tag: "div", key: 1}] render(root, vnodes) render(root, temp) render(root, updated) o(remove.callCount).equals(1) }) o("does not recycle when there's an onremove", function() { var remove = o.spy() var vnode = {tag: "div", key: 1, attrs: {onremove: remove}} var updated = {tag: "div", key: 1, attrs: {onremove: remove}} render(root, [vnode]) render(root, []) render(root, [updated]) o(vnode.dom).notEquals(updated.dom) }) components.forEach(function(cmp){ o.spec(cmp.kind, function(){ var createComponent = cmp.create o("calls onremove on nested component", function() { var spy = o.spy() var comp = createComponent({ view: function() {return m(outer)} }) var outer = createComponent({ view: function() {return m(inner)} }) var inner = createComponent({ onremove: spy, view: function() {return m("div")} }) render(root, {tag: comp}) render(root, null) o(spy.callCount).equals(1) }) o("calls onremove on nested component child", function() { var spy = o.spy() var comp = createComponent({ view: function() {return m(outer)} }) var outer = createComponent({ view: function() {return m(inner, m("a", {onremove: spy}))} }) var inner = createComponent({ view: function(vnode) {return m("div", vnode.children)} }) render(root, {tag: comp}) render(root, null) o(spy.callCount).equals(1) }) o("doesn't call onremove on children when the corresponding view returns null (after removing the parent)", function() { var threw = false var spy = o.spy() var parent = createComponent({ view: function() {} }) var child = createComponent({ view: function() {}, onremove: spy }) render(root, {tag: parent, children: [child]}) try { render(root, null) } catch (e) { threw = e } o(spy.callCount).equals(0) o(threw).equals(false) }) o("doesn't call onremove on children when the corresponding view returns null (after removing the children)", function() { var threw = false var spy = o.spy() var parent = createComponent({ view: function() {} }) var child = createComponent({ view: function() {}, onremove: spy }) render(root, {tag: parent, children: [child]}) try { render(root, {tag: parent}) } catch (e) { threw = true } o(spy.callCount).equals(0) o(threw).equals(false) }) }) }) })mithril-1.1.7+dfsg/render/tests/test-onupdate.js000066400000000000000000000127651512406206500216760ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("onupdate", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("does not call onupdate when creating element", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {onupdate: create}, state: {}} var updated = {tag: "div", attrs: {onupdate: update}, state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(1) o(update.this).equals(vnode.state) o(update.args[0]).equals(updated) }) o("does not call onupdate when removing element", function() { var create = o.spy() var vnode = {tag: "div", attrs: {onupdate: create}} render(root, [vnode]) render(root, []) o(create.callCount).equals(0) }) o("does not call onupdate when replacing keyed element", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", key: 1, attrs: {onupdate: create}} var updated = {tag: "a", key: 1, attrs: {onupdate: update}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(0) }) o("does not recycle when there's an onupdate", function() { var update = o.spy() var vnode = {tag: "div", key: 1, attrs: {onupdate: update}} var updated = {tag: "div", key: 1, attrs: {onupdate: update}} render(root, [vnode]) render(root, []) render(root, [updated]) o(vnode.dom).notEquals(updated.dom) }) o("does not call old onupdate when removing the onupdate property in new vnode", function() { var create = o.spy() var vnode = {tag: "a", attrs: {onupdate: create}} var updated = {tag: "a"} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) }) o("calls onupdate when noop", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {onupdate: create}, state: {}} var updated = {tag: "div", attrs: {onupdate: update}, state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(1) o(update.this).equals(vnode.state) o(update.args[0]).equals(updated) }) o("calls onupdate when updating attr", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {onupdate: create}, state: {}} var updated = {tag: "div", attrs: {onupdate: update, id: "a"}, state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(1) o(update.this).equals(vnode.state) o(update.args[0]).equals(updated) }) o("calls onupdate when updating children", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "div", attrs: {onupdate: create}, children: [{tag: "a"}], state: {}} var updated = {tag: "div", attrs: {onupdate: update}, children: [{tag: "b"}], state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(1) o(update.this).equals(vnode.state) o(update.args[0]).equals(updated) }) o("calls onupdate when updating text", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "#", attrs: {onupdate: create}, children: "a", state: {}} var updated = {tag: "#", attrs: {onupdate: update}, children: "a", state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(1) o(update.this).equals(vnode.state) o(update.args[0]).equals(updated) }) o("calls onupdate when updating fragment", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "[", attrs: {onupdate: create}, children: [], state: {}} var updated = {tag: "[", attrs: {onupdate: update}, children: [], state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(1) o(update.this).equals(vnode.state) o(update.args[0]).equals(updated) }) o("calls onupdate when updating html", function() { var create = o.spy() var update = o.spy() var vnode = {tag: "<", attrs: {onupdate: create}, children: "a", state: {}} var updated = {tag: "<", attrs: {onupdate: update}, children: "a", state: {}} render(root, [vnode]) render(root, [updated]) o(create.callCount).equals(0) o(update.callCount).equals(1) o(update.this).equals(vnode.state) o(update.args[0]).equals(updated) }) o("calls onupdate after full DOM update", function() { var called = false var vnode = {tag: "div", attrs: {id: "1"}, children: [ {tag: "a", attrs: {id: "2"}, children: [ {tag: "b", attrs: {id: "3"}} ]} ]} var updated = {tag: "div", attrs: {id: "11"}, children: [ {tag: "a", attrs: {onupdate: update, id: "22"}, children: [ {tag: "b", attrs: {id: "33"}} ]} ]} render(root, [vnode]) render(root, [updated]) function update(vnode) { called = true o(vnode.dom.parentNode.attributes["id"].value).equals("11") o(vnode.dom.attributes["id"].value).equals("22") o(vnode.dom.childNodes[0].attributes["id"].value).equals("33") } o(called).equals(true) }) o("does not set onupdate as an event handler", function() { var update = o.spy() var vnode = {tag: "div", attrs: {onupdate: update}, children: []} render(root, [vnode]) o(vnode.dom.onupdate).equals(undefined) o(vnode.dom.attributes["onupdate"]).equals(undefined) }) }) mithril-1.1.7+dfsg/render/tests/test-render.js000066400000000000000000000205041512406206500213240ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("render", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("overwrites existing content", function() { var vnodes = [] root.appendChild($window.document.createElement("div")); render(root, vnodes) o(root.childNodes.length).equals(0) }) o("throws on invalid root node", function() { var threw = false try { render(null, []) } catch (e) { threw = true } o(threw).equals(true) }) o("does not enter infinite loop when oninit triggers render and view throws with an object literal component", function(done) { var A = { oninit: init, view: function() {throw new Error("error")} } function run() { render(root, {tag: A}) } function init() { setTimeout(function() { var threwInner = false try {run()} catch (e) {threwInner = true} o(threwInner).equals(false) done() }, 0) } var threwOuter = false try {run()} catch (e) {threwOuter = true} o(threwOuter).equals(true) }) o("does not try to re-initialize a constructibe component whose view has thrown", function() { var oninit = o.spy() var onbeforeupdate = o.spy() function A(){} A.prototype.view = function() {throw new Error("error")} A.prototype.oninit = oninit A.prototype.onbeforeupdate = onbeforeupdate var throwCount = 0 try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) o(oninit.callCount).equals(1) o(onbeforeupdate.callCount).equals(0) try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) o(oninit.callCount).equals(1) o(onbeforeupdate.callCount).equals(0) }) o("does not try to re-initialize a constructible component whose oninit has thrown", function() { var oninit = o.spy(function(){throw new Error("error")}) var onbeforeupdate = o.spy() function A(){} A.prototype.view = function(){} A.prototype.oninit = oninit A.prototype.onbeforeupdate = onbeforeupdate var throwCount = 0 try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) o(oninit.callCount).equals(1) o(onbeforeupdate.callCount).equals(0) try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) o(oninit.callCount).equals(1) o(onbeforeupdate.callCount).equals(0) }) o("does not try to re-initialize a constructible component whose constructor has thrown", function() { var oninit = o.spy() var onbeforeupdate = o.spy() function A(){throw new Error("error")} A.prototype.view = function() {} A.prototype.oninit = oninit A.prototype.onbeforeupdate = onbeforeupdate var throwCount = 0 try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) o(oninit.callCount).equals(0) o(onbeforeupdate.callCount).equals(0) try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) o(oninit.callCount).equals(0) o(onbeforeupdate.callCount).equals(0) }) o("does not try to re-initialize a closure component whose view has thrown", function() { var oninit = o.spy() var onbeforeupdate = o.spy() function A() { return { view: function() {throw new Error("error")}, oninit: oninit, onbeforeupdate: onbeforeupdate } } var throwCount = 0 try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) o(oninit.callCount).equals(1) o(onbeforeupdate.callCount).equals(0) try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) o(oninit.callCount).equals(1) o(onbeforeupdate.callCount).equals(0) }) o("does not try to re-initialize a closure component whose oninit has thrown", function() { var oninit = o.spy(function() {throw new Error("error")}) var onbeforeupdate = o.spy() function A() { return { view: function() {}, oninit: oninit, onbeforeupdate: onbeforeupdate } } var throwCount = 0 try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) o(oninit.callCount).equals(1) o(onbeforeupdate.callCount).equals(0) try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) o(oninit.callCount).equals(1) o(onbeforeupdate.callCount).equals(0) }) o("does not try to re-initialize a closure component whose closure has thrown", function() { function A() { throw new Error("error") } var throwCount = 0 try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) try {render(root, {tag: A})} catch (e) {throwCount++} o(throwCount).equals(1) }) o("lifecycle methods work in keyed children of recycled keyed", function() { var createA = o.spy() var updateA = o.spy() var removeA = o.spy() var createB = o.spy() var updateB = o.spy() var removeB = o.spy() var a = function() { return {tag: "div", key: 1, children: [ {tag: "div", key: 11, attrs: {oncreate: createA, onupdate: updateA, onremove: removeA}}, {tag: "div", key: 12} ]} } var b = function() { return {tag: "div", key: 2, children: [ {tag: "div", key: 21, attrs: {oncreate: createB, onupdate: updateB, onremove: removeB}}, {tag: "div", key: 22} ]} } render(root, a()) render(root, b()) render(root, a()) o(createA.callCount).equals(2) o(updateA.callCount).equals(0) o(removeA.callCount).equals(1) o(createB.callCount).equals(1) o(updateB.callCount).equals(0) o(removeB.callCount).equals(1) }) o("lifecycle methods work in unkeyed children of recycled keyed", function() { var createA = o.spy() var updateA = o.spy() var removeA = o.spy() var createB = o.spy() var updateB = o.spy() var removeB = o.spy() var a = function() { return {tag: "div", key: 1, children: [ {tag: "div", attrs: {oncreate: createA, onupdate: updateA, onremove: removeA}}, ]} } var b = function() { return {tag: "div", key: 2, children: [ {tag: "div", attrs: {oncreate: createB, onupdate: updateB, onremove: removeB}}, ]} } render(root, a()) render(root, b()) render(root, a()) o(createA.callCount).equals(2) o(updateA.callCount).equals(0) o(removeA.callCount).equals(1) o(createB.callCount).equals(1) o(updateB.callCount).equals(0) o(removeB.callCount).equals(1) }) o("update lifecycle methods work on children of recycled keyed", function() { var createA = o.spy() var updateA = o.spy() var removeA = o.spy() var createB = o.spy() var updateB = o.spy() var removeB = o.spy() var a = function() { return {tag: "div", key: 1, children: [ {tag: "div", attrs: {oncreate: createA, onupdate: updateA, onremove: removeA}}, ]} } var b = function() { return {tag: "div", key: 2, children: [ {tag: "div", attrs: {oncreate: createB, onupdate: updateB, onremove: removeB}}, ]} } render(root, a()) render(root, a()) o(createA.callCount).equals(1) o(updateA.callCount).equals(1) o(removeA.callCount).equals(0) render(root, b()) o(createA.callCount).equals(1) o(updateA.callCount).equals(1) o(removeA.callCount).equals(1) render(root, a()) render(root, a()) o(createA.callCount).equals(2) o(updateA.callCount).equals(2) o(removeA.callCount).equals(1) }) o("svg namespace is preserved in keyed diff (#1820)", function(){ // note that this only exerciese one branch of the keyed diff algo var svg = {tag:"svg", children: [ {tag:"g", key: 0}, {tag:"g", key: 1} ]} render(root, [svg]) o(svg.dom.namespaceURI).equals("http://www.w3.org/2000/svg") o(svg.dom.childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg") o(svg.dom.childNodes[1].namespaceURI).equals("http://www.w3.org/2000/svg") svg = {tag:"svg", children: [ {tag:"g", key: 1, attrs: {x: 1}}, {tag:"g", key: 2, attrs: {x: 2}} ]} render(root, [svg]) o(svg.dom.namespaceURI).equals("http://www.w3.org/2000/svg") o(svg.dom.childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg") o(svg.dom.childNodes[1].namespaceURI).equals("http://www.w3.org/2000/svg") }) o("the namespace of the root is passed to children", function() { render(root, [{tag: "svg"}]) o(root.childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg") render(root.childNodes[0], [{tag: "g"}]) o(root.childNodes[0].childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg") }) }) mithril-1.1.7+dfsg/render/tests/test-textContent.js000066400000000000000000000132061512406206500223650ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("textContent", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("ignores null", function() { var vnodes = [{tag: "a", text: null}] render(root, vnodes) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(0) o(vnodes[0].dom).equals(root.childNodes[0]) }) o("ignores undefined", function() { var vnodes = [{tag: "a", text: undefined}] render(root, vnodes) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(0) o(vnodes[0].dom).equals(root.childNodes[0]) }) o("creates string", function() { var vnodes = [{tag: "a", text: "a"}] render(root, vnodes) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("a") o(vnodes[0].dom).equals(root.childNodes[0]) }) o("creates falsy string", function() { var vnodes = [{tag: "a", text: ""}] render(root, vnodes) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("") o(vnodes[0].dom).equals(root.childNodes[0]) }) o("creates number", function() { var vnodes = [{tag: "a", text: 1}] render(root, vnodes) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("1") o(vnodes[0].dom).equals(root.childNodes[0]) }) o("creates falsy number", function() { var vnodes = [{tag: "a", text: 0}] render(root, vnodes) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("0") o(vnodes[0].dom).equals(root.childNodes[0]) }) o("creates boolean", function() { var vnodes = [{tag: "a", text: true}] render(root, vnodes) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("true") o(vnodes[0].dom).equals(root.childNodes[0]) }) o("creates falsy boolean", function() { var vnodes = [{tag: "a", text: false}] render(root, vnodes) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("false") o(vnodes[0].dom).equals(root.childNodes[0]) }) o("updates to string", function() { var vnodes = [{tag: "a", text: "a"}] var updated = [{tag: "a", text: "b"}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("b") o(updated[0].dom).equals(root.childNodes[0]) }) o("updates to falsy string", function() { var vnodes = [{tag: "a", text: "a"}] var updated = [{tag: "a", text: ""}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("") o(updated[0].dom).equals(root.childNodes[0]) }) o("updates to number", function() { var vnodes = [{tag: "a", text: "a"}] var updated = [{tag: "a", text: 1}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("1") o(updated[0].dom).equals(root.childNodes[0]) }) o("updates to falsy number", function() { var vnodes = [{tag: "a", text: "a"}] var updated = [{tag: "a", text: 0}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("0") o(updated[0].dom).equals(root.childNodes[0]) }) o("updates to boolean", function() { var vnodes = [{tag: "a", text: "a"}] var updated = [{tag: "a", text: true}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("true") o(updated[0].dom).equals(root.childNodes[0]) }) o("updates to falsy boolean", function() { var vnodes = [{tag: "a", text: "a"}] var updated = [{tag: "a", text: false}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("false") o(updated[0].dom).equals(root.childNodes[0]) }) o("updates with typecasting", function() { var vnodes = [{tag: "a", text: "1"}] var updated = [{tag: "a", text: 1}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("1") o(updated[0].dom).equals(root.childNodes[0]) }) o("updates from without text to with text", function() { var vnodes = [{tag: "a"}] var updated = [{tag: "a", text: "b"}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(1) o(vnodes[0].dom.childNodes[0].nodeValue).equals("b") o(updated[0].dom).equals(root.childNodes[0]) }) o("updates from with text to without text", function() { var vnodes = [{tag: "a", text: "a"}] var updated = [{tag: "a"}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(vnodes[0].dom.childNodes.length).equals(0) o(updated[0].dom).equals(root.childNodes[0]) }) }) mithril-1.1.7+dfsg/render/tests/test-trust.js000066400000000000000000000012361512406206500212270ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var trust = require("../../render/trust") o.spec("trust", function() { o("works with html", function() { var vnode = trust("") o(vnode.tag).equals("<") o(vnode.children).equals("") }) o("works with text", function() { var vnode = trust("abc") o(vnode.tag).equals("<") o(vnode.children).equals("abc") }) o("casts null to empty string", function() { var vnode = trust(null) o(vnode.tag).equals("<") o(vnode.children).equals("") }) o("casts undefined to empty string", function() { var vnode = trust(undefined) o(vnode.tag).equals("<") o(vnode.children).equals("") }) }) mithril-1.1.7+dfsg/render/tests/test-updateElement.js000066400000000000000000000167251512406206500226530ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("updateElement", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("updates attr", function() { var vnode = {tag: "a", attrs: {id: "b"}} var updated = {tag: "a", attrs: {id: "c"}} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.attributes["id"].value).equals("c") }) o("adds attr", function() { var vnode = {tag: "a", attrs: {id: "b"}} var updated = {tag: "a", attrs: {id: "c", title: "d"}} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.attributes["title"].value).equals("d") }) o("adds attr from empty attrs", function() { var vnode = {tag: "a"} var updated = {tag: "a", attrs: {title: "d"}} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.attributes["title"].value).equals("d") }) o("removes attr", function() { var vnode = {tag: "a", attrs: {id: "b", title: "d"}} var updated = {tag: "a", attrs: {id: "c"}} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o("title" in updated.dom.attributes).equals(false) }) o("removes class", function() { var vnode = {tag: "a", attrs: {id: "b", className: "d"}} var updated = {tag: "a", attrs: {id: "c"}} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o("class" in updated.dom.attributes).equals(false) }) o("creates style object", function() { var vnode = {tag: "a", attrs: {}} var updated = {tag: "a", attrs: {style: {backgroundColor: "green"}}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("green") }) o("creates style string", function() { var vnode = {tag: "a", attrs: {}} var updated = {tag: "a", attrs: {style: "background-color:green"}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("green") }) o("updates style from object to object", function() { var vnode = {tag: "a", attrs: {style: {backgroundColor: "red"}}} var updated = {tag: "a", attrs: {style: {backgroundColor: "green"}}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("green") }) o("updates style from object to string", function() { var vnode = {tag: "a", attrs: {style: {backgroundColor: "red"}}} var updated = {tag: "a", attrs: {style: "background-color:green;"}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("green") }) o("handles noop style change when style is string", function() { var vnode = {tag: "a", attrs: {style: "background-color:green;"}} var updated = {tag: "a", attrs: {style: "background-color:green;"}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("green") }) o("handles noop style change when style is object", function() { var vnode = {tag: "a", attrs: {style: {backgroundColor: "red"}}} var updated = {tag: "a", attrs: {style: {backgroundColor: "red"}}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("red") }) o("updates style from string to object", function() { var vnode = {tag: "a", attrs: {style: "background-color:red;"}} var updated = {tag: "a", attrs: {style: {backgroundColor: "green"}}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("green") }) o("updates style from string to string", function() { var vnode = {tag: "a", attrs: {style: "background-color:red;"}} var updated = {tag: "a", attrs: {style: "background-color:green;"}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("green") }) o("removes style from object to object", function() { var vnode = {tag: "a", attrs: {style: {backgroundColor: "red", border: "1px solid red"}}} var updated = {tag: "a", attrs: {style: {backgroundColor: "red"}}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("red") o(updated.dom.style.border).equals("") }) o("removes style from string to object", function() { var vnode = {tag: "a", attrs: {style: "background-color:red;border:1px solid red"}} var updated = {tag: "a", attrs: {style: {backgroundColor: "red"}}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("red") o(updated.dom.style.border).notEquals("1px solid red") }) o("removes style from object to string", function() { var vnode = {tag: "a", attrs: {style: {backgroundColor: "red", border: "1px solid red"}}} var updated = {tag: "a", attrs: {style: "background-color:red"}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("red") o(updated.dom.style.border).equals("") }) o("removes style from string to string", function() { var vnode = {tag: "a", attrs: {style: "background-color:red;border:1px solid red"}} var updated = {tag: "a", attrs: {style: "background-color:red"}} render(root, [vnode]) render(root, [updated]) o(updated.dom.style.backgroundColor).equals("red") o(updated.dom.style.border).equals("") }) o("updates style when it's same object but mutated", function() { var style = {backgroundColor: "red", color: "gold"} var vnode = {tag: "a", attrs: {style: style}} render(root, [vnode]) delete style.backgroundColor var updated = {tag: "a", attrs: {style: style}} render(root, [updated]) o(updated.dom.style.backgroundColor).equals("") o(updated.dom.style.color).equals("gold") }) o("replaces el", function() { var vnode = {tag: "a"} var updated = {tag: "b"} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeName).equals("B") }) o("updates svg class", function() { var vnode = {tag: "svg", attrs: {className: "a"}} var updated = {tag: "svg", attrs: {className: "b"}} render(root, [vnode]) render(root, [updated]) o(updated.dom.attributes["class"].value).equals("b") }) o("updates svg child", function() { var vnode = {tag: "svg", children: [{ tag: "circle" }]} var updated = {tag: "svg", children: [{ tag: "line" }]} render(root, [vnode]) render(root, [updated]) o(updated.dom.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg") }) o("restores correctly when recycling", function() { var vnode = {tag: "div", key: 1} var updated = {tag: "div", key: 2} render(root, [vnode]) var a = vnode.dom render(root, [updated]) render(root, [vnode]) var c = vnode.dom o(root.childNodes.length).equals(1) o(a).equals(c) }) o("restores correctly when recycling via map", function() { var a = {tag: "div", key: 1} var b = {tag: "div", key: 2} var c = {tag: "div", key: 3} var d = {tag: "div", key: 4} var e = {tag: "div", key: 5} var f = {tag: "div", key: 6} render(root, [a, b, c]) var x = root.childNodes[1] render(root, [d]) render(root, [e, b, f]) var y = root.childNodes[1] o(root.childNodes.length).equals(3) o(x).equals(y) }) }) mithril-1.1.7+dfsg/render/tests/test-updateFragment.js000066400000000000000000000034111512406206500230110ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("updateFragment", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("updates fragment", function() { var vnode = {tag: "[", children: [{tag: "a"}]} var updated = {tag: "[", children: [{tag: "b"}]} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeName).equals("B") }) o("adds els", function() { var vnode = {tag: "[", children: []} var updated = {tag: "[", children: [{tag: "a"}, {tag: "b"}]} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(root.firstChild) o(updated.domSize).equals(2) o(root.childNodes.length).equals(2) o(root.childNodes[0].nodeName).equals("A") o(root.childNodes[1].nodeName).equals("B") }) o("removes els", function() { var vnode = {tag: "[", children: [{tag: "a"}, {tag: "b"}]} var updated = {tag: "[", children: []} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(null) o(updated.domSize).equals(0) o(root.childNodes.length).equals(0) }) o("updates from childless fragment", function() { var vnode = {tag: "["} var updated = {tag: "[", children: [{tag: "a"}]} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeName).equals("A") }) o("updates to childless fragment", function() { var vnode = {tag: "[", children: [{tag: "a"}]} var updated = {tag: "["} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(null) o(root.childNodes.length).equals(0) }) }) mithril-1.1.7+dfsg/render/tests/test-updateHTML.js000066400000000000000000000024021512406206500220110ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("updateHTML", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("updates html", function() { var vnode = {tag: "<", children: "a"} var updated = {tag: "<", children: "b"} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(root.firstChild) o(updated.domSize).equals(1) o(updated.dom.nodeValue).equals("b") }) o("adds html", function() { var vnode = {tag: "<", children: ""} var updated = {tag: "<", children: ""} render(root, [vnode]) render(root, [updated]) o(updated.domSize).equals(2) o(updated.dom).equals(root.firstChild) o(root.childNodes.length).equals(2) o(root.childNodes[0].nodeName).equals("A") o(root.childNodes[1].nodeName).equals("B") }) o("removes html", function() { var vnode = {tag: "<", children: ""} var updated = {tag: "<", children: ""} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(null) o(updated.domSize).equals(0) o(root.childNodes.length).equals(0) }) }) mithril-1.1.7+dfsg/render/tests/test-updateNodes.js000066400000000000000000000772301512406206500223300ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var components = require("../../test-utils/components") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("updateNodes", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("handles el noop", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var updated = [{tag: "a", key: 1}, {tag: "b", key: 2}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("B") o(updated[1].dom).equals(root.childNodes[1]) }) o("handles el noop without key", function() { var vnodes = [{tag: "a"}, {tag: "b"}] var updated = [{tag: "a"}, {tag: "b"}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("B") o(updated[1].dom).equals(root.childNodes[1]) }) o("handles text noop", function() { var vnodes = [{tag: "#", children: "a"}] var updated = [{tag: "#", children: "a"}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(updated[0].dom.nodeValue).equals("a") o(updated[0].dom).equals(root.childNodes[0]) }) o("handles text noop w/ type casting", function() { var vnodes = [{tag: "#", children: 1}] var updated = [{tag: "#", children: "1"}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(updated[0].dom.nodeValue).equals("1") o(updated[0].dom).equals(root.childNodes[0]) }) o("handles falsy text noop w/ type casting", function() { var vnodes = [{tag: "#", children: 0}] var updated = [{tag: "#", children: "0"}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(updated[0].dom.nodeValue).equals("0") o(updated[0].dom).equals(root.childNodes[0]) }) o("handles html noop", function() { var vnodes = [{tag: "<", children: "a"}] var updated = [{tag: "<", children: "a"}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(updated[0].dom.nodeValue).equals("a") o(updated[0].dom).equals(root.childNodes[0]) }) o("handles fragment noop", function() { var vnodes = [{tag: "[", children: [{tag: "a"}]}] var updated = [{tag: "[", children: [{tag: "a"}]}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) }) o("handles fragment noop w/ text child", function() { var vnodes = [{tag: "[", children: [{tag: "#", children: "a"}]}] var updated = [{tag: "[", children: [{tag: "#", children: "a"}]}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(updated[0].dom.nodeValue).equals("a") o(updated[0].dom).equals(root.childNodes[0]) }) o("handles undefined to null noop", function() { var vnodes = [null, {tag: "div"}] var updated = [undefined, {tag: "div"}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) }) o("reverses els w/ even count", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}, {tag: "s", key: 4}] var updated = [{tag: "s", key: 4}, {tag: "i", key: 3}, {tag: "b", key: 2}, {tag: "a", key: 1}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(4) o(updated[0].dom.nodeName).equals("S") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("I") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("B") o(updated[2].dom).equals(root.childNodes[2]) o(updated[3].dom.nodeName).equals("A") o(updated[3].dom).equals(root.childNodes[3]) }) o("reverses els w/ odd count", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}] var updated = [{tag: "i", key: 3}, {tag: "b", key: 2}, {tag: "a", key: 1}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("I") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("B") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("A") o(updated[2].dom).equals(root.childNodes[2]) }) o("creates el at start", function() { var vnodes = [{tag: "a", key: 1}] var updated = [{tag: "b", key: 2}, {tag: "a", key: 1}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("B") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("A") o(updated[1].dom).equals(root.childNodes[1]) }) o("creates el at end", function() { var vnodes = [{tag: "a", key: 1}] var updated = [{tag: "a", key: 1}, {tag: "b", key: 2}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("B") o(updated[1].dom).equals(root.childNodes[1]) }) o("creates el in middle", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var updated = [{tag: "a", key: 1}, {tag: "i", key: 3}, {tag: "b", key: 2}] render(root, vnodes) render(root, updated) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("I") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("B") o(updated[2].dom).equals(root.childNodes[2]) }) o("creates el while reversing", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var updated = [{tag: "b", key: 2}, {tag: "i", key: 3}, {tag: "a", key: 1}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("B") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("I") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("A") o(updated[2].dom).equals(root.childNodes[2]) }) o("deletes el at start", function() { var vnodes = [{tag: "b", key: 2}, {tag: "a", key: 1}] var updated = [{tag: "a", key: 1}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) }) o("deletes el at end", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var updated = [{tag: "a", key: 1}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(1) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) }) o("deletes el at middle", function() { var vnodes = [{tag: "a", key: 1}, {tag: "i", key: 3}, {tag: "b", key: 2}] var updated = [{tag: "a", key: 1}, {tag: "b", key: 2}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("B") o(updated[1].dom).equals(root.childNodes[1]) }) o("deletes el while reversing", function() { var vnodes = [{tag: "a", key: 1}, {tag: "i", key: 3}, {tag: "b", key: 2}] var updated = [{tag: "b", key: 2}, {tag: "a", key: 1}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("B") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("A") o(updated[1].dom).equals(root.childNodes[1]) }) o("creates, deletes, reverses els at same time", function() { var vnodes = [{tag: "a", key: 1}, {tag: "i", key: 3}, {tag: "b", key: 2}] var updated = [{tag: "b", key: 2}, {tag: "a", key: 1}, {tag: "s", key: 4}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("B") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("A") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("S") o(updated[2].dom).equals(root.childNodes[2]) }) o("adds to empty array followed by el", function() { var vnodes = [{tag: "[", key: 1, children: []}, {tag: "b", key: 2}] var updated = [{tag: "[", key: 1, children: [{tag: "a"}]}, {tag: "b", key: 2}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].children[0].dom.nodeName).equals("A") o(updated[0].children[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("B") o(updated[1].dom).equals(root.childNodes[1]) }) o("reverses followed by el", function() { var vnodes = [{tag: "[", key: 1, children: [{tag: "a", key: 2}, {tag: "b", key: 3}]}, {tag: "i", key: 4}] var updated = [{tag: "[", key: 1, children: [{tag: "b", key: 3}, {tag: "a", key: 2}]}, {tag: "i", key: 4}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].children[0].dom.nodeName).equals("B") o(updated[0].children[0].dom).equals(root.childNodes[0]) o(updated[0].children[1].dom.nodeName).equals("A") o(updated[0].children[1].dom).equals(root.childNodes[1]) o(updated[1].dom.nodeName).equals("I") o(updated[1].dom).equals(root.childNodes[2]) }) o("updates empty array to html with same key", function() { var vnodes = [{tag: "[", key: 1, children: []}] var updated = [{tag: "<", key: 1, children: ""}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("B") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) }) o("updates empty html to array with same key", function() { var vnodes = [{tag: "<", key: 1, children: ""}] var updated = [{tag: "[", key: 1, children: [{tag: "a"}, {tag: "b"}]}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("B") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) }) o("updates empty array to html without key", function() { var vnodes = [{tag: "[", children: []}] var updated = [{tag: "<", children: ""}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("B") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) }) o("updates empty html to array without key", function() { var vnodes = [{tag: "<", children: ""}] var updated = [{tag: "[", children: [{tag: "a"}, {tag: "b"}]}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("B") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) }) o("updates array to html with same key", function() { var vnodes = [{tag: "[", key: 1, children: [{tag: "a"}, {tag: "b"}]}] var updated = [{tag: "<", key: 1, children: ""}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("I") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("S") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) }) o("updates html to array with same key", function() { var vnodes = [{tag: "<", key: 1, children: ""}] var updated = [{tag: "[", key: 1, children: [{tag: "i"}, {tag: "s"}]}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("I") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("S") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) }) o("updates array to html without key", function() { var vnodes = [{tag: "[", children: [{tag: "a"}, {tag: "b"}]}] var updated = [{tag: "<", children: ""}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("I") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("S") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) }) o("updates html to array without key", function() { var vnodes = [{tag: "<", children: ""}] var updated = [{tag: "[", children: [{tag: "i"}, {tag: "s"}]}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("I") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("S") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) }) o("updates empty array to html with same key followed by el", function() { var vnodes = [{tag: "[", key: 1, children: []}, {tag: "i", key: 2}] var updated = [{tag: "<", key: 1, children: ""}, {tag: "i", key: 2}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("B") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) o(updated[1].dom.nodeName).equals("I") o(updated[1].dom).equals(root.childNodes[2]) }) o("updates empty html to array with same key followed by el", function() { var vnodes = [{tag: "[", key: 1, children: []}, {tag: "i", key: 2}] var updated = [{tag: "<", key: 1, children: ""}, {tag: "i", key: 2}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("B") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) o(updated[1].dom.nodeName).equals("I") o(updated[1].dom).equals(root.childNodes[2]) }) o("populates array followed by null then el", function() { var vnodes = [{tag: "[", key: 1, children: []}, null, {tag: "i", key: 2}] var updated = [{tag: "[", key: 1, children: [{tag: "a"}, {tag: "b"}]}, null, {tag: "i", key: 2}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("B") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("I") o(updated[2].dom).equals(root.childNodes[2]) }) o("populates childless array followed by el", function() { var vnodes = [{tag: "[", key: 1}, {tag: "i", key: 2}] var updated = [{tag: "[", key: 1, children: [{tag: "a"}, {tag: "b"}]}, {tag: "i", key: 2}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("B") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) o(updated[1].dom.nodeName).equals("I") o(updated[1].dom).equals(root.childNodes[2]) }) o("populates childless array followed by null then el", function() { var vnodes = [{tag: "[", key: 1}, null, {tag: "i", key: 2}] var updated = [{tag: "[", key: 1, children: [{tag: "a"}, {tag: "b"}]}, null, {tag: "i", key: 2}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[0].domSize).equals(2) o(updated[0].dom.nextSibling.nodeName).equals("B") o(updated[0].dom.nextSibling).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("I") o(updated[2].dom).equals(root.childNodes[2]) }) o("moves from end to start", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}, {tag: "s", key: 4}] var updated = [{tag: "s", key: 4}, {tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(4) o(updated[0].dom.nodeName).equals("S") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("A") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("B") o(updated[2].dom).equals(root.childNodes[2]) o(updated[3].dom.nodeName).equals("I") o(updated[3].dom).equals(root.childNodes[3]) }) o("moves from start to end", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}, {tag: "s", key: 4}] var updated = [{tag: "b", key: 2}, {tag: "i", key: 3}, {tag: "s", key: 4}, {tag: "a", key: 1}] render(root, vnodes) render(root, updated) o(root.childNodes.length).equals(4) o(updated[0].dom.nodeName).equals("B") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("I") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("S") o(updated[2].dom).equals(root.childNodes[2]) o(updated[3].dom.nodeName).equals("A") o(updated[3].dom).equals(root.childNodes[3]) }) o("removes then recreate", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}, {tag: "s", key: 4}] var temp = [] var updated = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}, {tag: "s", key: 4}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(4) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("B") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("I") o(updated[2].dom).equals(root.childNodes[2]) o(updated[3].dom.nodeName).equals("S") o(updated[3].dom).equals(root.childNodes[3]) }) o("removes then recreate reversed", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}, {tag: "s", key: 4}] var temp = [] var updated = [{tag: "s", key: 4}, {tag: "i", key: 3}, {tag: "b", key: 2}, {tag: "a", key: 1}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(4) o(updated[0].dom.nodeName).equals("S") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("I") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("B") o(updated[2].dom).equals(root.childNodes[2]) o(updated[3].dom.nodeName).equals("A") o(updated[3].dom).equals(root.childNodes[3]) }) o("removes then recreate smaller", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var temp = [] var updated = [{tag: "a", key: 1}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(1) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) }) o("removes then recreate bigger", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var temp = [] var updated = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("B") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("I") o(updated[2].dom).equals(root.childNodes[2]) }) o("removes then create different", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var temp = [] var updated = [{tag: "i", key: 3}, {tag: "s", key: 4}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("I") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("S") o(updated[1].dom).equals(root.childNodes[1]) }) o("removes then create different smaller", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var temp = [] var updated = [{tag: "i", key: 3}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(1) o(updated[0].dom.nodeName).equals("I") o(updated[0].dom).equals(root.childNodes[0]) }) o("removes then create different bigger", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var temp = [] var updated = [{tag: "i", key: 3}, {tag: "s", key: 4}, {tag: "div", key: 5}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("I") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("S") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("DIV") o(updated[2].dom).equals(root.childNodes[2]) }) o("removes then create mixed", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var temp = [] var updated = [{tag: "a", key: 1}, {tag: "s", key: 4}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("S") o(updated[1].dom).equals(root.childNodes[1]) }) o("removes then create mixed reversed", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var temp = [] var updated = [{tag: "s", key: 4}, {tag: "a", key: 1}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("S") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("A") o(updated[1].dom).equals(root.childNodes[1]) }) o("removes then create mixed smaller", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}] var temp = [] var updated = [{tag: "a", key: 1}, {tag: "s", key: 4}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("S") o(updated[1].dom).equals(root.childNodes[1]) }) o("removes then create mixed smaller reversed", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}, {tag: "i", key: 3}] var temp = [] var updated = [{tag: "s", key: 4}, {tag: "a", key: 1}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("S") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("A") o(updated[1].dom).equals(root.childNodes[1]) }) o("removes then create mixed bigger", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var temp = [] var updated = [{tag: "a", key: 1}, {tag: "i", key: 3}, {tag: "s", key: 4}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("I") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("S") o(updated[2].dom).equals(root.childNodes[2]) }) o("removes then create mixed bigger reversed", function() { var vnodes = [{tag: "a", key: 1}, {tag: "b", key: 2}] var temp = [] var updated = [{tag: "s", key: 4}, {tag: "i", key: 3}, {tag: "a", key: 1}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(3) o(updated[0].dom.nodeName).equals("S") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("I") o(updated[1].dom).equals(root.childNodes[1]) o(updated[2].dom.nodeName).equals("A") o(updated[2].dom).equals(root.childNodes[2]) }) o("change type, position and length", function() { var vnodes = {tag: "div", children: [ undefined, {tag: "#", children: "a"} ]} var updated = {tag: "div", children: [ {tag: "[", children: [{tag: "#", children: "b"}]}, undefined, undefined ]} render(root, vnodes) render(root, updated) o(root.firstChild.childNodes.length).equals(1) }) o("removes then recreates then reverses children", function() { var vnodes = [{tag: "a", key: 1, children: [{tag: "i", key: 3}, {tag: "s", key: 4}]}, {tag: "b", key: 2}] var temp1 = [] var temp2 = [{tag: "a", key: 1, children: [{tag: "i", key: 3}, {tag: "s", key: 4}]}, {tag: "b", key: 2}] var updated = [{tag: "a", key: 1, children: [{tag: "s", key: 4}, {tag: "i", key: 3}]}, {tag: "b", key: 2}] render(root, vnodes) render(root, temp1) render(root, temp2) render(root, updated) o(root.childNodes.length).equals(2) o(updated[0].dom.nodeName).equals("A") o(updated[0].dom).equals(root.childNodes[0]) o(updated[1].dom.nodeName).equals("B") o(updated[1].dom).equals(root.childNodes[1]) o(updated[0].dom.childNodes.length).equals(2) o(updated[0].dom.childNodes[0].nodeName).equals("S") o(updated[0].dom.childNodes[1].nodeName).equals("I") }) o("removes then recreates nested", function() { var vnodes = [{tag: "a", key: 1, children: [{tag: "a", key: 3, children: [{tag: "a", key: 5}]}, {tag: "a", key: 4, children: [{tag: "a", key: 5}]}]}, {tag: "a", key: 2}] var temp = [] var updated = [{tag: "a", key: 1, children: [{tag: "a", key: 3, children: [{tag: "a", key: 5}]}, {tag: "a", key: 4, children: [{tag: "a", key: 5}]}]}, {tag: "a", key: 2}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(2) o(root.childNodes[0].childNodes.length).equals(2) o(root.childNodes[0].childNodes[0].childNodes.length).equals(1) o(root.childNodes[0].childNodes[1].childNodes.length).equals(1) o(root.childNodes[1].childNodes.length).equals(0) }) o("recycles", function() { var vnodes = [{tag: "div", key: 1}] var temp = [] var updated = [{tag: "div", key: 1}] render(root, vnodes) render(root, temp) render(root, updated) o(vnodes[0].dom).equals(updated[0].dom) o(updated[0].dom.nodeName).equals("DIV") }) o("recycles when not keyed", function() { var vnodes = [{tag: "div"}] var temp = [] var updated = [{tag: "div"}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(1) o(vnodes[0].dom).equals(updated[0].dom) o(updated[0].dom.nodeName).equals("DIV") }) o("recycles deep", function() { var vnodes = [{tag: "div", children: [{tag: "a", key: 1}]}] var temp = [{tag: "div"}] var updated = [{tag: "div", children: [{tag: "a", key: 1}]}] render(root, vnodes) render(root, temp) render(root, updated) o(vnodes[0].dom.firstChild).equals(updated[0].dom.firstChild) o(updated[0].dom.firstChild.nodeName).equals("A") }) o("mixed unkeyed tags are not broken by recycle", function() { var vnodes = [{tag: "a"}, {tag: "b"}] var temp = [{tag: "b"}] var updated = [{tag: "a"}, {tag: "b"}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(2) o(root.childNodes[0].nodeName).equals("A") o(root.childNodes[1].nodeName).equals("B") }) o("mixed unkeyed vnode types are not broken by recycle", function() { var vnodes = [{tag: "[", children: [{tag: "a"}]}, {tag: "b"}] var temp = [{tag: "b"}] var updated = [{tag: "[", children: [{tag: "a"}]}, {tag: "b"}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(2) o(root.childNodes[0].nodeName).equals("A") o(root.childNodes[1].nodeName).equals("B") }) o("cached, non-keyed nodes skip diff", function () { var onupdate = o.spy(); var cached = {tag:"a", attrs:{onupdate: onupdate}} render(root, cached) render(root, cached) o(onupdate.callCount).equals(0) }) o("cached, keyed nodes skip diff", function () { var onupdate = o.spy() var cached = {tag:"a", key:"a", attrs:{onupdate: onupdate}} render(root, cached) render(root, cached) o(onupdate.callCount).equals(0) }) o("null stays in place", function() { var create = o.spy() var update = o.spy() var remove = o.spy() var vnodes = [{tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}] var temp = [null, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}] var updated = [{tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}] render(root, vnodes) var before = vnodes[1].dom render(root, temp) render(root, updated) var after = updated[1].dom o(before).equals(after) o(create.callCount).equals(1) o(update.callCount).equals(2) o(remove.callCount).equals(0) }) o("null stays in place if not first", function() { var create = o.spy() var update = o.spy() var remove = o.spy() var vnodes = [{tag: "b"}, {tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}] var temp = [{tag: "b"}, null, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}] var updated = [{tag: "b"}, {tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}] render(root, vnodes) var before = vnodes[2].dom render(root, temp) render(root, updated) var after = updated[2].dom o(before).equals(after) o(create.callCount).equals(1) o(update.callCount).equals(2) o(remove.callCount).equals(0) }) o("node is recreated if key changes to undefined", function () { var vnode = {tag: "b", key: 1} var updated = {tag: "b"} render(root, vnode) render(root, updated) o(vnode.dom).notEquals(updated.dom) }) components.forEach(function(cmp){ o.spec(cmp.kind, function(){ var createComponent = cmp.create o("fragment child toggles from null when followed by null component then tag", function() { var component = createComponent({view: function() {return null}}) var vnodes = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}] var temp = [{tag: "[", children: [null, {tag: component}, {tag: "b"}]}] var updated = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}] render(root, vnodes) render(root, temp) render(root, updated) o(root.childNodes.length).equals(2) o(root.childNodes[0].nodeName).equals("A") o(root.childNodes[1].nodeName).equals("B") }) o("fragment child toggles from null in component when followed by null component then tag", function() { var flag = true var a = createComponent({view: function() {return flag ? {tag: "a"} : null}}) var b = createComponent({view: function() {return null}}) var vnodes = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}] var temp = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}] var updated = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}] render(root, vnodes) flag = false render(root, temp) flag = true render(root, updated) o(root.childNodes.length).equals(2) o(root.childNodes[0].nodeName).equals("A") o(root.childNodes[1].nodeName).equals("S") }) }) }) }) mithril-1.1.7+dfsg/render/tests/test-updateText.js000066400000000000000000000056671512406206500222110ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("updateText", function() { var $window, root, render o.beforeEach(function() { $window = domMock() root = $window.document.createElement("div") render = vdom($window).render }) o("updates to string", function() { var vnode = {tag: "#", children: "a"} var updated = {tag: "#", children: "b"} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeValue).equals("b") }) o("updates to falsy string", function() { var vnode = {tag: "#", children: "a"} var updated = {tag: "#", children: ""} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeValue).equals("") }) o("updates from falsy string", function() { var vnode = {tag: "#", children: ""} var updated = {tag: "#", children: "b"} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeValue).equals("b") }) o("updates to number", function() { var vnode = {tag: "#", children: "a"} var updated = {tag: "#", children: 1} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeValue).equals("1") }) o("updates to falsy number", function() { var vnode = {tag: "#", children: "a"} var updated = {tag: "#", children: 0} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeValue).equals("0") }) o("updates from falsy number", function() { var vnode = {tag: "#", children: 0} var updated = {tag: "#", children: "b"} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeValue).equals("b") }) o("updates to boolean", function() { var vnode = {tag: "#", children: "a"} var updated = {tag: "#", children: true} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeValue).equals("true") }) o("updates to falsy boolean", function() { var vnode = {tag: "#", children: "a"} var updated = {tag: "#", children: false} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeValue).equals("false") }) o("updates from falsy boolean", function() { var vnode = {tag: "#", children: false} var updated = {tag: "#", children: "b"} render(root, [vnode]) render(root, [updated]) o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) o(updated.dom.nodeValue).equals("b") }) }) mithril-1.1.7+dfsg/render/trust.js000066400000000000000000000002741512406206500171110ustar00rootroot00000000000000"use strict" var Vnode = require("../render/vnode") module.exports = function(html) { if (html == null) html = "" return Vnode("<", undefined, undefined, html, undefined, undefined) } mithril-1.1.7+dfsg/render/vnode.js000066400000000000000000000014051512406206500170400ustar00rootroot00000000000000"use strict" function Vnode(tag, key, attrs, children, text, dom) { return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: undefined, _state: undefined, events: undefined, instance: undefined, skip: false} } Vnode.normalize = function(node) { if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined) if (node != null && typeof node !== "object") return Vnode("#", undefined, undefined, node === false ? "" : node, undefined, undefined) return node } Vnode.normalizeChildren = function normalizeChildren(children) { for (var i = 0; i < children.length; i++) { children[i] = Vnode.normalize(children[i]) } return children } module.exports = Vnode mithril-1.1.7+dfsg/request.js000066400000000000000000000002101512406206500161270ustar00rootroot00000000000000"use strict" var PromisePolyfill = require("./promise/promise") module.exports = require("./request/request")(window, PromisePolyfill) mithril-1.1.7+dfsg/request/000077500000000000000000000000001512406206500156005ustar00rootroot00000000000000mithril-1.1.7+dfsg/request/request.js000066400000000000000000000126161512406206500176340ustar00rootroot00000000000000"use strict" var buildQueryString = require("../querystring/build") var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i") module.exports = function($window, Promise) { var callbackCount = 0 var oncompletion function setCompletionCallback(callback) {oncompletion = callback} function finalizer() { var count = 0 function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()} return function finalize(promise) { var then = promise.then promise.then = function() { count++ var next = then.apply(promise, arguments) next.then(complete, function(e) { complete() if (count === 0) throw e }) return finalize(next) } return promise } } function normalize(args, extra) { if (typeof args === "string") { var url = args args = extra || {} if (args.url == null) args.url = url } return args } function request(args, extra) { var finalize = finalizer() args = normalize(args, extra) var promise = new Promise(function(resolve, reject) { if (args.method == null) args.method = "GET" args.method = args.method.toUpperCase() var useBody = (args.method === "GET" || args.method === "TRACE") ? false : (typeof args.useBody === "boolean" ? args.useBody : true) if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify if (typeof args.deserialize !== "function") args.deserialize = deserialize if (typeof args.extract !== "function") args.extract = extract args.url = interpolate(args.url, args.data) if (useBody) args.data = args.serialize(args.data) else args.url = assemble(args.url, args.data) var xhr = new $window.XMLHttpRequest(), aborted = false, _abort = xhr.abort xhr.abort = function abort() { aborted = true _abort.call(xhr) } xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) if (args.serialize === JSON.stringify && useBody && !(args.headers && args.headers.hasOwnProperty("Content-Type"))) { xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") } if (args.deserialize === deserialize && !(args.headers && args.headers.hasOwnProperty("Accept"))) { xhr.setRequestHeader("Accept", "application/json, text/*") } if (args.withCredentials) xhr.withCredentials = args.withCredentials for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) { xhr.setRequestHeader(key, args.headers[key]) } if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr xhr.onreadystatechange = function() { // Don't throw errors on xhr.abort(). if(aborted) return if (xhr.readyState === 4) { try { var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args)) if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) { resolve(cast(args.type, response)) } else { var error = new Error(xhr.responseText) for (var key in response) error[key] = response[key] reject(error) } } catch (e) { reject(e) } } } if (useBody && (args.data != null)) xhr.send(args.data) else xhr.send() }) return args.background === true ? promise : finalize(promise) } function jsonp(args, extra) { var finalize = finalizer() args = normalize(args, extra) var promise = new Promise(function(resolve, reject) { var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ var script = $window.document.createElement("script") $window[callbackName] = function(data) { script.parentNode.removeChild(script) resolve(cast(args.type, data)) delete $window[callbackName] } script.onerror = function() { script.parentNode.removeChild(script) reject(new Error("JSONP request failed")) delete $window[callbackName] } if (args.data == null) args.data = {} args.url = interpolate(args.url, args.data) args.data[args.callbackKey || "callback"] = callbackName script.src = assemble(args.url, args.data) $window.document.documentElement.appendChild(script) }) return args.background === true? promise : finalize(promise) } function interpolate(url, data) { if (data == null) return url var tokens = url.match(/:[^\/]+/gi) || [] for (var i = 0; i < tokens.length; i++) { var key = tokens[i].slice(1) if (data[key] != null) { url = url.replace(tokens[i], data[key]) } } return url } function assemble(url, data) { var querystring = buildQueryString(data) if (querystring !== "") { var prefix = url.indexOf("?") < 0 ? "?" : "&" url += prefix + querystring } return url } function deserialize(data) { try {return data !== "" ? JSON.parse(data) : null} catch (e) {throw new Error(data)} } function extract(xhr) {return xhr.responseText} function cast(type, data) { if (typeof type === "function") { if (Array.isArray(data)) { for (var i = 0; i < data.length; i++) { data[i] = new type(data[i]) } } else return new type(data) } return data } return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback} } mithril-1.1.7+dfsg/request/tests/000077500000000000000000000000001512406206500167425ustar00rootroot00000000000000mithril-1.1.7+dfsg/request/tests/index.html000066400000000000000000000013611512406206500207400ustar00rootroot00000000000000 mithril-1.1.7+dfsg/request/tests/test-jsonp.js000066400000000000000000000070171512406206500214130ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var xhrMock = require("../../test-utils/xhrMock") var Request = require("../../request/request") var Promise = require("../../promise/promise") var parseQueryString = require("../../querystring/parse") o.spec("jsonp", function() { var mock, jsonp, complete o.beforeEach(function() { mock = xhrMock() var requestService = Request(mock, Promise) jsonp = requestService.jsonp complete = o.spy() requestService.setCompletionCallback(complete) }) o("works", function(done) { mock.$defineRoutes({ "GET /item": function(request) { var queryData = parseQueryString(request.query) return {status: 200, responseText: queryData["callback"] + "(" + JSON.stringify({a: 1}) + ")"} } }) jsonp({url: "/item"}).then(function(data) { o(data).deepEquals({a: 1}) }).then(done) }) o("first argument can be a string aliasing url property", function(done){ mock.$defineRoutes({ "GET /item": function(request) { var queryData = parseQueryString(request.query) return {status: 200, responseText: queryData["callback"] + "(" + JSON.stringify({a: 1}) + ")"} } }) jsonp("/item").then(function(data) { o(data).deepEquals({a: 1}) }).then(function() { done() }) }) o("works w/ other querystring params", function(done) { mock.$defineRoutes({ "GET /item": function(request) { var queryData = parseQueryString(request.query) return {status: 200, responseText: queryData["callback"] + "(" + JSON.stringify(queryData) + ")"} } }) jsonp({url: "/item", data: {a: "b", c: "d"}}).then(function(data) { delete data["callback"] o(data).deepEquals({a: "b", c: "d"}) }).then(done) }) o("works w/ custom callbackKey", function(done) { mock.$defineRoutes({ "GET /item": function(request) { var queryData = parseQueryString(request.query) return {status: 200, responseText: queryData["cb"] + "(" + JSON.stringify({a: 2}) + ")"} } }) jsonp({url: "/item", callbackKey: "cb"}).then(function(data) { o(data).deepEquals({a: 2}) }).then(done) }) o("requests don't block each other", function(done) { mock.$defineRoutes({ "GET /item": function(request) { var queryData = parseQueryString(request.query) return {status: 200, responseText: queryData["callback"] + "([])"} } }) jsonp("/item").then(function() { return jsonp("/item") }) jsonp("/item").then(function() { return jsonp("/item") }) setTimeout(function() { o(complete.callCount).equals(4) done() }, 20) }) o("requests trigger finally once with a chained then", function(done) { mock.$defineRoutes({ "GET /item": function(request) { var queryData = parseQueryString(request.query) return {status: 200, responseText: queryData["callback"] + "([])"} } }) var promise = jsonp("/item") promise.then(function() {}).then(function() {}) promise.then(function() {}).then(function() {}) setTimeout(function() { o(complete.callCount).equals(1) done() }, 20) }) o("requests does not trigger finally when background: true", function(done) { mock.$defineRoutes({ "GET /item": function(request) { var queryData = parseQueryString(request.query) return {status: 200, responseText: queryData["callback"] + "([])"} } }) jsonp("/item", {background: true}).then(function() {}) setTimeout(function() { o(complete.callCount).equals(0) done() }, 20) }) o("handles error", function(done) { jsonp({url: "/item", callbackKey: "cb"}).catch(function(e) { o(e.message).equals("JSONP request failed") done() }) }) }) mithril-1.1.7+dfsg/request/tests/test-request.js000066400000000000000000000361541512406206500217560ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var callAsync = require("../../test-utils/callAsync") var xhrMock = require("../../test-utils/xhrMock") var Request = require("../../request/request") var Promise = require("../../promise/promise") o.spec("xhr", function() { var mock, xhr, complete o.beforeEach(function() { mock = xhrMock() var requestService = Request(mock, Promise) xhr = requestService.request complete = o.spy() requestService.setCompletionCallback(complete) }) o.spec("success", function() { o("works via GET", function(done) { mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: JSON.stringify({a: 1})} } }) xhr({method: "GET", url: "/item"}).then(function(data) { o(data).deepEquals({a: 1}) }).then(function() { done() }) }) o("implicit GET method", function(done){ mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: JSON.stringify({a: 1})} } }) xhr({url: "/item"}).then(function(data) { o(data).deepEquals({a: 1}) }).then(function() { done() }) }) o("first argument can be a string aliasing url property", function(done){ mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: JSON.stringify({a: 1})} } }) xhr("/item").then(function(data) { o(data).deepEquals({a: 1}) }).then(function() { done() }) }) o("works via POST", function(done) { mock.$defineRoutes({ "POST /item": function() { return {status: 200, responseText: JSON.stringify({a: 1})} } }) xhr({method: "POST", url: "/item"}).then(function(data) { o(data).deepEquals({a: 1}) }).then(done) }) o("first argument can act as URI with second argument providing options", function(done) { mock.$defineRoutes({ "POST /item": function() { return {status: 200, responseText: JSON.stringify({a: 1})} } }) xhr("/item", {method: "POST"}).then(function(data) { o(data).deepEquals({a: 1}) }).then(done) }) o("works w/ parameterized data via GET", function(done) { mock.$defineRoutes({ "GET /item": function(request) { return {status: 200, responseText: JSON.stringify({a: request.query})} } }) xhr({method: "GET", url: "/item", data: {x: "y"}}).then(function(data) { o(data).deepEquals({a: "?x=y"}) }).then(done) }) o("works w/ parameterized data via POST", function(done) { mock.$defineRoutes({ "POST /item": function(request) { return {status: 200, responseText: JSON.stringify({a: JSON.parse(request.body)})} } }) xhr({method: "POST", url: "/item", data: {x: "y"}}).then(function(data) { o(data).deepEquals({a: {x: "y"}}) }).then(done) }) o("works w/ parameterized data containing colon via GET", function(done) { mock.$defineRoutes({ "GET /item": function(request) { return {status: 200, responseText: JSON.stringify({a: request.query})} } }) xhr({method: "GET", url: "/item", data: {x: ":y"}}).then(function(data) { o(data).deepEquals({a: "?x=%3Ay"}) }).then(done) }) o("works w/ parameterized data containing colon via POST", function(done) { mock.$defineRoutes({ "POST /item": function(request) { return {status: 200, responseText: JSON.stringify({a: JSON.parse(request.body)})} } }) xhr({method: "POST", url: "/item", data: {x: ":y"}}).then(function(data) { o(data).deepEquals({a: {x: ":y"}}) }).then(done) }) o("works w/ parameterized url via GET", function(done) { mock.$defineRoutes({ "GET /item/y": function(request) { return {status: 200, responseText: JSON.stringify({a: request.url, b: request.query})} } }) xhr({method: "GET", url: "/item/:x", data: {x: "y"}}).then(function(data) { o(data).deepEquals({a: "/item/y", b: "?x=y"}) }).then(done) }) o("works w/ parameterized url via POST", function(done) { mock.$defineRoutes({ "POST /item/y": function(request) { return {status: 200, responseText: JSON.stringify({a: request.url, b: JSON.parse(request.body)})} } }) xhr({method: "POST", url: "/item/:x", data: {x: "y"}}).then(function(data) { o(data).deepEquals({a: "/item/y", b: {x: "y"}}) }).then(done) }) o("works w/ array", function(done) { mock.$defineRoutes({ "POST /items": function(request) { return {status: 200, responseText: JSON.stringify({a: request.url, b: JSON.parse(request.body)})} } }) xhr({method: "POST", url: "/items", data: [{x: "y"}]}).then(function(data) { o(data).deepEquals({a: "/items", b: [{x: "y"}]}) }).then(done) }) o("ignores unresolved parameter via GET", function(done) { mock.$defineRoutes({ "GET /item/:x": function(request) { return {status: 200, responseText: JSON.stringify({a: request.url})} } }) xhr({method: "GET", url: "/item/:x"}).then(function(data) { o(data).deepEquals({a: "/item/:x"}) }).then(done) }) o("ignores unresolved parameter via POST", function(done) { mock.$defineRoutes({ "GET /item/:x": function(request) { return {status: 200, responseText: JSON.stringify({a: request.url})} } }) xhr({method: "GET", url: "/item/:x"}).then(function(data) { o(data).deepEquals({a: "/item/:x"}) }).then(done) }) o("type parameter works for Array responses", function(done) { var Entity = function(args) { return {_id: args.id} } mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: JSON.stringify([{id: 1}, {id: 2}, {id: 3}])} } }) xhr({method: "GET", url: "/item", type: Entity}).then(function(data) { o(data).deepEquals([{_id: 1}, {_id: 2}, {_id: 3}]) }).then(done) }) o("type parameter works for Object responses", function(done) { var Entity = function(args) { return {_id: args.id} } mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: JSON.stringify({id: 1})} } }) xhr({method: "GET", url: "/item", type: Entity}).then(function(data) { o(data).deepEquals({_id: 1}) }).then(done) }) o("serialize parameter works in GET", function(done) { var serialize = function(data) { return "id=" + data.id } mock.$defineRoutes({ "GET /item": function(request) { return {status: 200, responseText: JSON.stringify({body: request.query})} } }) xhr({method: "GET", url: "/item", serialize: serialize, data: {id: 1}}).then(function(data) { o(data.body).equals("?id=1") }).then(done) }) o("serialize parameter works in POST", function(done) { var serialize = function(data) { return "id=" + data.id } mock.$defineRoutes({ "POST /item": function(request) { return {status: 200, responseText: JSON.stringify({body: request.body})} } }) xhr({method: "POST", url: "/item", serialize: serialize, data: {id: 1}}).then(function(data) { o(data.body).equals("id=1") }).then(done) }) o("deserialize parameter works in GET", function(done) { var deserialize = function(data) { return data } mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: JSON.stringify({test: 123})} } }) xhr({method: "GET", url: "/item", deserialize: deserialize}).then(function(data) { o(data).equals('{"test":123}') }).then(done) }) o("deserialize parameter works in POST", function(done) { var deserialize = function(data) { return data } mock.$defineRoutes({ "POST /item": function() { return {status: 200, responseText: JSON.stringify({test: 123})} } }) xhr({method: "POST", url: "/item", deserialize: deserialize}).then(function(data) { o(data).equals('{"test":123}') }).then(done) }) o("extract parameter works in GET", function(done) { var extract = function() { return JSON.stringify({test: 123}) } mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: ""} } }) xhr({method: "GET", url: "/item", extract: extract}).then(function(data) { o(data).equals('{"test":123}') }).then(done) }) o("extract parameter works in POST", function(done) { var extract = function() { return JSON.stringify({test: 123}) } mock.$defineRoutes({ "POST /item": function() { return {status: 200, responseText: ""} } }) xhr({method: "POST", url: "/item", extract: extract}).then(function(data) { o(data).equals('{"test":123}') }).then(done) }) o("ignores deserialize if extract is defined", function(done) { var extract = function(data) { return data.status } var deserialize = o.spy() mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: ""} } }) xhr({method: "GET", url: "/item", extract: extract, deserialize: deserialize}).then(function(data) { o(data).equals(200) }).then(function() { o(deserialize.callCount).equals(0) }).then(done) }) o("config parameter works", function(done) { mock.$defineRoutes({ "POST /item": function() { return {status: 200, responseText: ""} } }) xhr({method: "POST", url: "/item", config: config}).then(done) function config(xhr) { o(typeof xhr.setRequestHeader).equals("function") o(typeof xhr.open).equals("function") o(typeof xhr.send).equals("function") } }) o("requests don't block each other", function(done) { mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: "[]"} } }) xhr("/item").then(function() { return xhr("/item") }) xhr("/item").then(function() { return xhr("/item") }) setTimeout(function() { o(complete.callCount).equals(4) done() }, 20) }) o("requests trigger finally once with a chained then", function(done) { mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: "[]"} } }) var promise = xhr("/item") promise.then(function() {}).then(function() {}) promise.then(function() {}).then(function() {}) setTimeout(function() { o(complete.callCount).equals(1) done() }, 20) }) o("requests does not trigger finally when background: true", function(done) { mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: "[]"} } }) xhr("/item", {background: true}).then(function() {}) setTimeout(function() { o(complete.callCount).equals(0) done() }, 20) }) o("headers are set when header arg passed", function(done) { mock.$defineRoutes({ "POST /item": function() { return {status: 200, responseText: ""} } }) xhr({method: "POST", url: "/item", config: config, headers: {"Custom-Header": "Value"}}).then(done) function config(xhr) { o(xhr.getRequestHeader("Custom-Header")).equals("Value") } }) o("headers are with higher precedence than default headers", function(done) { mock.$defineRoutes({ "POST /item": function() { return {status: 200, responseText: ""} } }) xhr({method: "POST", url: "/item", config: config, headers: {"Content-Type": "Value"}}).then(done) function config(xhr) { o(xhr.getRequestHeader("Content-Type")).equals("Value") } }) o("json headers are set to the correct default value", function(done) { mock.$defineRoutes({ "POST /item": function() { return {status: 200, responseText: ""} } }) xhr({method: "POST", url: "/item", config: config}).then(done) function config(xhr) { o(xhr.getRequestHeader("Content-Type")).equals("application/json; charset=utf-8") o(xhr.getRequestHeader("Accept")).equals("application/json, text/*") } }) o("doesn't fail on abort", function(done) { mock.$defineRoutes({ "GET /item": function() { return {status: 200, responseText: JSON.stringify({a: 1})} } }) var failed = false var resolved = false function handleAbort(xhr) { var onreadystatechange = xhr.onreadystatechange // probably not set yet var testonreadystatechange = function() { onreadystatechange.call(xhr) setTimeout(function() { // allow promises to (not) resolve first o(failed).equals(false) o(resolved).equals(false) done() }, 0) } Object.defineProperty(xhr, "onreadystatechange", { set: function(val) { onreadystatechange = val }, get: function() { return testonreadystatechange } }) xhr.abort() } xhr({method: "GET", url: "/item", config: handleAbort}).catch(function() { failed = true }) .then(function() { resolved = true }) }) o("doesn't fail on file:// status 0", function(done) { mock.$defineRoutes({ "GET /item": function() { return {status: 0, responseText: JSON.stringify({a: 1})} } }) var failed = false xhr({method: "GET", url: "file:///item"}).catch(function() { failed = true }).then(function(data) { o(failed).equals(false) o(data).deepEquals({a: 1}) }).then(function() { done() }) }) /*o("data maintains after interpolate", function() { mock.$defineRoutes({ "PUT /items/:x": function() { return {status: 200, responseText: ""} } }) var data = {x: 1, y: 2} var dataCopy = Object.assign({}, data); xhr({method: "PUT", url: "/items/:x", data}) o(data).deepEquals(dataCopy) })*/ }) o.spec("failure", function() { o("rejects on server error", function(done) { mock.$defineRoutes({ "GET /item": function() { return {status: 500, responseText: JSON.stringify({error: "error"})} } }) xhr({method: "GET", url: "/item"}).catch(function(e) { o(e instanceof Error).equals(true) o(e.message).equals(JSON.stringify({error: "error"})) }).then(done) }) o("extends Error with JSON response", function(done) { mock.$defineRoutes({ "GET /item": function() { return {status: 500, responseText: JSON.stringify({message: "error", stack: "error on line 1"})} } }) xhr({method: "GET", url: "/item"}).catch(function(e) { o(e instanceof Error).equals(true) o(e.message).equals("error") o(e.stack).equals("error on line 1") }).then(done) }) o("rejects on non-JSON server error", function(done) { mock.$defineRoutes({ "GET /item": function() { return {status: 500, responseText: "error"} } }) xhr({method: "GET", url: "/item"}).catch(function(e) { o(e.message).equals("error") }).then(done) }) o("triggers all branched catches upon rejection", function(done) { mock.$defineRoutes({ "GET /item": function() { return {status: 500, responseText: "error"} } }) var request = xhr({method: "GET", url: "/item"}) var then = o.spy() var catch1 = o.spy() var catch2 = o.spy() var catch3 = o.spy() request.catch(catch1) request.then(then, catch2) request.then(then).catch(catch3) callAsync(function() { callAsync(function() { o(catch1.callCount).equals(1) o(then.callCount).equals(0) o(catch2.callCount).equals(1) o(catch3.callCount).equals(1) done() }) }) }) o("rejects on cors-like error", function(done) { mock.$defineRoutes({ "GET /item": function() { return {status: 0} } }) xhr({method: "GET", url: "/item"}).catch(function(e) { o(e instanceof Error).equals(true) }).then(done) }) }) }) mithril-1.1.7+dfsg/route.js000066400000000000000000000001671512406206500156100ustar00rootroot00000000000000"use strict" var redrawService = require("./redraw") module.exports = require("./api/router")(window, redrawService) mithril-1.1.7+dfsg/router/000077500000000000000000000000001512406206500154305ustar00rootroot00000000000000mithril-1.1.7+dfsg/router/router.js000066400000000000000000000070741512406206500173160ustar00rootroot00000000000000"use strict" var buildQueryString = require("../querystring/build") var parseQueryString = require("../querystring/parse") module.exports = function($window) { var supportsPushState = typeof $window.history.pushState === "function" var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout function normalize(fragment) { var data = $window.location[fragment].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) if (fragment === "pathname" && data[0] !== "/") data = "/" + data return data } var asyncId function debounceAsync(callback) { return function() { if (asyncId != null) return asyncId = callAsync(function() { asyncId = null callback() }) } } function parsePath(path, queryData, hashData) { var queryIndex = path.indexOf("?") var hashIndex = path.indexOf("#") var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length if (queryIndex > -1) { var queryEnd = hashIndex > -1 ? hashIndex : path.length var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd)) for (var key in queryParams) queryData[key] = queryParams[key] } if (hashIndex > -1) { var hashParams = parseQueryString(path.slice(hashIndex + 1)) for (var key in hashParams) hashData[key] = hashParams[key] } return path.slice(0, pathEnd) } var router = {prefix: "#!"} router.getPath = function() { var type = router.prefix.charAt(0) switch (type) { case "#": return normalize("hash").slice(router.prefix.length) case "?": return normalize("search").slice(router.prefix.length) + normalize("hash") default: return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash") } } router.setPath = function(path, data, options) { var queryData = {}, hashData = {} path = parsePath(path, queryData, hashData) if (data != null) { for (var key in data) queryData[key] = data[key] path = path.replace(/:([^\/]+)/g, function(match, token) { delete queryData[token] return data[token] }) } var query = buildQueryString(queryData) if (query) path += "?" + query var hash = buildQueryString(hashData) if (hash) path += "#" + hash if (supportsPushState) { var state = options ? options.state : null var title = options ? options.title : null $window.onpopstate() if (options && options.replace) $window.history.replaceState(state, title, router.prefix + path) else $window.history.pushState(state, title, router.prefix + path) } else $window.location.href = router.prefix + path } router.defineRoutes = function(routes, resolve, reject) { function resolveRoute() { var path = router.getPath() var params = {} var pathname = parsePath(path, params, params) var state = $window.history.state if (state != null) { for (var k in state) params[k] = state[k] } for (var route in routes) { var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") if (matcher.test(pathname)) { pathname.replace(matcher, function() { var keys = route.match(/:[^\/]+/g) || [] var values = [].slice.call(arguments, 1, -2) for (var i = 0; i < keys.length; i++) { params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) } resolve(routes[route], params, path, route) }) return } } reject(path, params) } if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute) else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute resolveRoute() } return router } mithril-1.1.7+dfsg/router/tests/000077500000000000000000000000001512406206500165725ustar00rootroot00000000000000mithril-1.1.7+dfsg/router/tests/index.html000066400000000000000000000016051512406206500205710ustar00rootroot00000000000000 mithril-1.1.7+dfsg/router/tests/test-defineRoutes.js000066400000000000000000000210731512406206500225440ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var callAsync = require("../../test-utils/callAsync") var pushStateMock = require("../../test-utils/pushStateMock") var Router = require("../../router/router") o.spec("Router.defineRoutes", function() { void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) { void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) { o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() { var $window, router, onRouteChange, onFail o.beforeEach(function() { $window = pushStateMock(env) router = new Router($window) router.prefix = prefix onRouteChange = o.spy() onFail = o.spy() }) o("calls onRouteChange on init", function(done) { $window.location.href = prefix + "/a" router.defineRoutes({"/a": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) done() }) }) o("resolves to route", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) o(onFail.callCount).equals(0) done() }) }) o("resolves to route w/ escaped unicode", function(done) { $window.location.href = prefix + "/%C3%B6?%C3%B6=%C3%B6#%C3%B6=%C3%B6" router.defineRoutes({"/ö": {data: 2}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 2}, {"ö": "ö"}, "/ö?ö=ö#ö=ö", "/ö"]) o(onFail.callCount).equals(0) done() }) }) o("resolves to route w/ unicode", function(done) { $window.location.href = prefix + "/ö?ö=ö#ö=ö" router.defineRoutes({"/ö": {data: 2}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 2}, {"ö": "ö"}, "/ö?ö=ö#ö=ö", "/ö"]) o(onFail.callCount).equals(0) done() }) }) o("resolves to route on fallback mode", function(done) { $window.location.href = "file://" + prefix + "/test" router = new Router($window) router.prefix = prefix router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {}, "/test", "/test"]) o(onFail.callCount).equals(0) done() }) }) o("handles parameterized route", function(done) { $window.location.href = prefix + "/test/x" router.defineRoutes({"/test/:a": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "x"}, "/test/x", "/test/:a"]) o(onFail.callCount).equals(0) done() }) }) o("handles multi-parameterized route", function(done) { $window.location.href = prefix + "/test/x/y" router.defineRoutes({"/test/:a/:b": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "x", b: "y"}, "/test/x/y", "/test/:a/:b"]) o(onFail.callCount).equals(0) done() }) }) o("handles rest parameterized route", function(done) { $window.location.href = prefix + "/test/x/y" router.defineRoutes({"/test/:a...": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "x/y"}, "/test/x/y", "/test/:a..."]) o(onFail.callCount).equals(0) done() }) }) o("handles route with search", function(done) { $window.location.href = prefix + "/test?a=b&c=d" router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test?a=b&c=d", "/test"]) o(onFail.callCount).equals(0) done() }) }) o("handles route with hash", function(done) { $window.location.href = prefix + "/test#a=b&c=d" router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test#a=b&c=d", "/test"]) o(onFail.callCount).equals(0) done() }) }) o("handles route with search and hash", function(done) { $window.location.href = prefix + "/test?a=b#c=d" router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {a: "b", c: "d"}, "/test?a=b#c=d", "/test"]) o(onFail.callCount).equals(0) done() }) }) o("calls reject", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/other": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onFail.callCount).equals(1) o(onFail.args).deepEquals(["/test", {}]) done() }) }) o("calls reject w/ search and hash", function(done) { $window.location.href = prefix + "/test?a=b#c=d" router.defineRoutes({"/other": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onFail.callCount).equals(1) o(onFail.args).deepEquals(["/test?a=b#c=d", {a: "b", c: "d"}]) done() }) }) o("handles out of order routes", function(done) { $window.location.href = prefix + "/z/y/x" router.defineRoutes({"/z/y/x": {data: 1}, "/:a...": {data: 2}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) done() }) }) o("handles reverse out of order routes", function(done) { $window.location.href = prefix + "/z/y/x" router.defineRoutes({"/:a...": {data: 2}, "/z/y/x": {data: 1}}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) done() }) }) o("handles dynamically added out of order routes", function(done) { var routes = {} routes["/z/y/x"] = {data: 1} routes["/:a..."] = {data: 2} $window.location.href = prefix + "/z/y/x" router.defineRoutes(routes, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) done() }) }) o("handles reversed dynamically added out of order routes", function(done) { var routes = {} routes["/:a..."] = {data: 2} routes["/z/y/x"] = {data: 1} $window.location.href = prefix + "/z/y/x" router.defineRoutes(routes, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) done() }) }) o("handles mixed out of order routes", function(done) { var routes = {"/z/y/x": {data: 1}} routes["/:a..."] = {data: 2} $window.location.href = prefix + "/z/y/x" router.defineRoutes(routes, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 1}, {}, "/z/y/x", "/z/y/x"]) done() }) }) o("handles reverse mixed out of order routes", function(done) { var routes = {"/:a...": {data: 2}} routes["/z/y/x"] = {data: 12} $window.location.href = prefix + "/z/y/x" router.defineRoutes(routes, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) o(onRouteChange.args).deepEquals([{data: 2}, {a: "z/y/x"}, "/z/y/x", "/:a..."]) done() }) }) o("handles non-ascii routes", function(done) { $window.location.href = prefix + "/ö" router.defineRoutes({"/ö": "aaa"}, onRouteChange, onFail) callAsync(function() { o(onRouteChange.callCount).equals(1) done() }) }) }) }) }) }) mithril-1.1.7+dfsg/router/tests/test-getPath.js000066400000000000000000000033131512406206500215010ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var pushStateMock = require("../../test-utils/pushStateMock") var Router = require("../../router/router") o.spec("Router.getPath", function() { void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) { void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) { o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() { var $window, router, onRouteChange, onFail o.beforeEach(function() { $window = pushStateMock(env) router = new Router($window) router.prefix = prefix onRouteChange = o.spy() onFail = o.spy() }) o("gets route", function() { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}}, onRouteChange, onFail) o(router.getPath()).equals("/test") }) o("gets route w/ params", function() { $window.location.href = prefix + "/other/x/y/z?c=d#e=f" router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) o(router.getPath()).equals("/other/x/y/z?c=d#e=f") }) o("gets route w/ escaped unicode", function() { $window.location.href = prefix + "/%C3%B6?%C3%B6=%C3%B6#%C3%B6=%C3%B6" router.defineRoutes({"/test": {data: 1}, "/ö/:a/:b...": {data: 2}}, onRouteChange, onFail) o(router.getPath()).equals("/ö?ö=ö#ö=ö") }) o("gets route w/ unicode", function() { $window.location.href = prefix + "/ö?ö=ö#ö=ö" router.defineRoutes({"/test": {data: 1}, "/ö/:a/:b...": {data: 2}}, onRouteChange, onFail) o(router.getPath()).equals("/ö?ö=ö#ö=ö") }) }) }) }) }) mithril-1.1.7+dfsg/router/tests/test-setPath.js000066400000000000000000000122001512406206500215100ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var callAsync = require("../../test-utils/callAsync") var pushStateMock = require("../../test-utils/pushStateMock") var Router = require("../../router/router") o.spec("Router.setPath", function() { void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) { void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) { o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() { var $window, router, onRouteChange, onFail o.beforeEach(function() { $window = pushStateMock(env) router = new Router($window) router.prefix = prefix onRouteChange = o.spy() onFail = o.spy() }) o("setPath calls onRouteChange asynchronously", function(done) { $window.location.href = prefix + "/a" router.defineRoutes({"/a": {data: 1}, "/b": {data: 2}}, onRouteChange, onFail) callAsync(function() { router.setPath("/b") o(onRouteChange.callCount).equals(1) callAsync(function() { o(onRouteChange.callCount).equals(2) done() }) }) }) o("setPath calls onFail asynchronously", function(done) { $window.location.href = prefix + "/a" router.defineRoutes({"/a": {data: 1}, "/b": {data: 2}}, onRouteChange, onFail) callAsync(function() { router.setPath("/c") o(onFail.callCount).equals(0) callAsync(function() { o(onFail.callCount).equals(1) done() }) }) }) o("sets route via API", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) callAsync(function() { router.setPath("/other/x/y/z?c=d#e=f") o(router.getPath()).equals("/other/x/y/z?c=d#e=f") done() }) }) o("sets route w/ escaped unicode", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/ö/:a/:b...": {data: 2}}, onRouteChange, onFail) callAsync(function() { router.setPath("/%C3%B6?%C3%B6=%C3%B6#%C3%B6=%C3%B6") o(router.getPath()).equals("/ö?ö=ö#ö=ö") done() }) }) o("sets route w/ unicode", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/ö/:a/:b...": {data: 2}}, onRouteChange, onFail) callAsync(function() { router.setPath("/ö?ö=ö#ö=ö") o(router.getPath()).equals("/ö?ö=ö#ö=ö") done() }) }) o("sets route on fallback mode", function(done) { $window.location.href = "file://" + prefix + "/test" router = new Router($window) router.prefix = prefix router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) callAsync(function() { router.setPath("/other/x/y/z?c=d#e=f") o(router.getPath()).equals("/other/x/y/z?c=d#e=f") done() }) }) o("sets route via pushState/onpopstate", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) callAsync(function() { $window.history.pushState(null, null, prefix + "/other/x/y/z?c=d#e=f") $window.onpopstate() o(router.getPath()).equals("/other/x/y/z?c=d#e=f") done() }) }) o("sets parameterized route", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/other/:a/:b...": {data: 2}}, onRouteChange, onFail) callAsync(function() { router.setPath("/other/:a/:b", {a: "x", b: "y/z", c: "d", e: "f"}) o(router.getPath()).equals("/other/x/y/z?c=d&e=f") done() }) }) o("replace:true works", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/other": {data: 2}}, onRouteChange, onFail) callAsync(function() { router.setPath("/other", null, {replace: true}) $window.history.back() o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + "/") done() }) }) o("replace:false works", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/other": {data: 2}}, onRouteChange, onFail) callAsync(function() { router.setPath("/other", null, {replace: false}) $window.history.back() var slash = prefix[0] === "/" ? "" : "/" o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test") done() }) }) o("state works", function(done) { $window.location.href = prefix + "/test" router.defineRoutes({"/test": {data: 1}, "/other": {data: 2}}, onRouteChange, onFail) callAsync(function() { router.setPath("/other", null, {state: {a: 1}}) o($window.history.state).deepEquals({a: 1}) done() }) }) }) }) }) }) mithril-1.1.7+dfsg/stream.js000066400000000000000000000000721512406206500157400ustar00rootroot00000000000000"use strict" module.exports = require("./stream/stream") mithril-1.1.7+dfsg/stream/000077500000000000000000000000001512406206500154035ustar00rootroot00000000000000mithril-1.1.7+dfsg/stream/LICENSE000066400000000000000000000020641512406206500164120ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2017 Leo Horie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mithril-1.1.7+dfsg/stream/README.md000066400000000000000000000006021512406206500166600ustar00rootroot00000000000000mithril-stream [![NPM Version](https://img.shields.io/npm/v/mithril-stream.svg)](https://www.npmjs.com/package/mithril-stream) [![NPM License](https://img.shields.io/npm/l/mithril-stream.svg)](https://www.npmjs.com/package/mithril-stream) ============== Mithril's `m.stream` as a standalone module. See [mithril.js.org/stream.html](https://mithril.js.org/stream.html) for docs/usage. mithril-1.1.7+dfsg/stream/package.json000066400000000000000000000005041512406206500176700ustar00rootroot00000000000000{ "name": "mithril-stream", "version": "1.1.0", "description": "Streaming data, mithril-style", "main": "stream.js", "directories": { "test": "tests" }, "keywords": [ "stream", "reactive", "data" ], "author": "Leo Horie ", "license": "MIT", "repository": "MithrilJS/mithril.js" } mithril-1.1.7+dfsg/stream/stream.js000066400000000000000000000116231512406206500172370ustar00rootroot00000000000000/* eslint-disable */ ;(function() { "use strict" /* eslint-enable */ var guid = 0, HALT = {} function createStream() { function stream() { if (arguments.length > 0 && arguments[0] !== HALT) updateStream(stream, arguments[0]) return stream._state.value } initStream(stream) if (arguments.length > 0 && arguments[0] !== HALT) updateStream(stream, arguments[0]) return stream } function initStream(stream) { stream.constructor = createStream stream._state = {id: guid++, value: undefined, state: 0, derive: undefined, recover: undefined, deps: {}, parents: [], endStream: undefined, unregister: undefined} stream.map = stream["fantasy-land/map"] = map, stream["fantasy-land/ap"] = ap, stream["fantasy-land/of"] = createStream stream.valueOf = valueOf, stream.toJSON = toJSON, stream.toString = valueOf Object.defineProperties(stream, { end: {get: function() { if (!stream._state.endStream) { var endStream = createStream() endStream.map(function(value) { if (value === true) { unregisterStream(stream) endStream._state.unregister = function(){unregisterStream(endStream)} } return value }) stream._state.endStream = endStream } return stream._state.endStream }} }) } function updateStream(stream, value) { updateState(stream, value) for (var id in stream._state.deps) updateDependency(stream._state.deps[id], false) if (stream._state.unregister != null) stream._state.unregister() finalize(stream) } function updateState(stream, value) { stream._state.value = value stream._state.changed = true if (stream._state.state !== 2) stream._state.state = 1 } function updateDependency(stream, mustSync) { var state = stream._state, parents = state.parents if (parents.length > 0 && parents.every(active) && (mustSync || parents.some(changed))) { var value = stream._state.derive() if (value === HALT) return false updateState(stream, value) } } function finalize(stream) { stream._state.changed = false for (var id in stream._state.deps) stream._state.deps[id]._state.changed = false } function combine(fn, streams) { if (!streams.every(valid)) throw new Error("Ensure that each item passed to stream.combine/stream.merge is a stream") return initDependency(createStream(), streams, function() { return fn.apply(this, streams.concat([streams.filter(changed)])) }) } function initDependency(dep, streams, derive) { var state = dep._state state.derive = derive state.parents = streams.filter(notEnded) registerDependency(dep, state.parents) updateDependency(dep, true) return dep } function registerDependency(stream, parents) { for (var i = 0; i < parents.length; i++) { parents[i]._state.deps[stream._state.id] = stream registerDependency(stream, parents[i]._state.parents) } } function unregisterStream(stream) { for (var i = 0; i < stream._state.parents.length; i++) { var parent = stream._state.parents[i] delete parent._state.deps[stream._state.id] } for (var id in stream._state.deps) { var dependent = stream._state.deps[id] var index = dependent._state.parents.indexOf(stream) if (index > -1) dependent._state.parents.splice(index, 1) } stream._state.state = 2 //ended stream._state.deps = {} } function map(fn) {return combine(function(stream) {return fn(stream())}, [this])} function ap(stream) {return combine(function(s1, s2) {return s1()(s2())}, [stream, this])} function valueOf() {return this._state.value} function toJSON() {return this._state.value != null && typeof this._state.value.toJSON === "function" ? this._state.value.toJSON() : this._state.value} function valid(stream) {return stream._state } function active(stream) {return stream._state.state === 1} function changed(stream) {return stream._state.changed} function notEnded(stream) {return stream._state.state !== 2} function merge(streams) { return combine(function() { return streams.map(function(s) {return s()}) }, streams) } function scan(reducer, seed, stream) { var newStream = combine(function (s) { return seed = reducer(seed, s._state.value) }, [stream]) if (newStream._state.state === 0) newStream(seed) return newStream } function scanMerge(tuples, seed) { var streams = tuples.map(function(tuple) { var stream = tuple[0] if (stream._state.state === 0) stream(undefined) return stream }) var newStream = combine(function() { var changed = arguments[arguments.length - 1] streams.forEach(function(stream, idx) { if (changed.indexOf(stream) > -1) { seed = tuples[idx][1](seed, stream._state.value) } }) return seed }, streams) return newStream } createStream["fantasy-land/of"] = createStream createStream.merge = merge createStream.combine = combine createStream.scan = scan createStream.scanMerge = scanMerge createStream.HALT = HALT if (typeof module !== "undefined") module["exports"] = createStream else if (typeof window.m === "function" && !("stream" in window.m)) window.m.stream = createStream else window.m = {stream : createStream} }()); mithril-1.1.7+dfsg/stream/tests/000077500000000000000000000000001512406206500165455ustar00rootroot00000000000000mithril-1.1.7+dfsg/stream/tests/index.html000066400000000000000000000010721512406206500205420ustar00rootroot00000000000000 mithril-1.1.7+dfsg/stream/tests/test-scan.js000066400000000000000000000012661512406206500210110ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var stream = require("../stream") o.spec("scan", function() { o("defaults to seed", function() { var parent = stream() var child = stream.scan(function(out, p) { return out - p }, 123, parent) o(child()).equals(123) }) o("accumulates values as expected", function() { var parent = stream() var child = stream.scan(function(arr, p) { return arr.concat(p) }, [], parent) parent(7) parent("11") parent(undefined) parent({a: 1}) var result = child() // deepEquals fails on arrays? o(result[0]).equals(7) o(result[1]).equals("11") o(result[2]).equals(undefined) o(result[3]).deepEquals({a: 1}) }) }) mithril-1.1.7+dfsg/stream/tests/test-scanMerge.js000066400000000000000000000013471512406206500217710ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var stream = require("../stream") o.spec("scanMerge", function() { o("defaults to seed", function() { var parent1 = stream() var parent2 = stream() var child = stream.scanMerge([ [parent1, function(out, p1) { return out + p1 }], [parent2, function(out, p2) { return out + p2 }] ], -10) o(child()).equals(-10) }) o("accumulates as expected", function() { var parent1 = stream() var parent2 = stream() var child = stream.scanMerge([ [parent1, function(out, p1) { return out + p1 }], [parent2, function(out, p2) { return out + p2 + p2 }] ], "a") parent1("b") parent2("c") parent1("b") o(child()).equals("abccb") }) }) mithril-1.1.7+dfsg/stream/tests/test-stream.js000066400000000000000000000274431512406206500213650ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var Stream = require("../stream") o.spec("stream", function() { o.spec("stream", function() { o("works as getter/setter", function() { var stream = Stream(1) var initialValue = stream() stream(2) var newValue = stream() o(initialValue).equals(1) o(newValue).equals(2) }) o("has undefined value by default", function() { var stream = Stream() o(stream()).equals(undefined) }) o("can update to undefined", function() { var stream = Stream(1) stream(undefined) o(stream()).equals(undefined) }) o("can be stream of streams", function() { var stream = Stream(Stream(1)) o(stream()()).equals(1) }) }) o.spec("combine", function() { o("transforms value", function() { var stream = Stream() var doubled = Stream.combine(function(s) {return s() * 2}, [stream]) stream(2) o(doubled()).equals(4) }) o("transforms default value", function() { var stream = Stream(2) var doubled = Stream.combine(function(s) {return s() * 2}, [stream]) o(doubled()).equals(4) }) o("transforms multiple values", function() { var s1 = Stream() var s2 = Stream() var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) s1(2) s2(3) o(added()).equals(5) }) o("transforms multiple default values", function() { var s1 = Stream(2) var s2 = Stream(3) var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) o(added()).equals(5) }) o("transforms mixed default and late-bound values", function() { var s1 = Stream(2) var s2 = Stream() var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) s2(3) o(added()).equals(5) }) o("combines atomically", function() { var count = 0 var a = Stream() var b = Stream.combine(function(a) {return a() * 2}, [a]) var c = Stream.combine(function(a) {return a() * a()}, [a]) var d = Stream.combine(function(b, c) { count++ return b() + c() }, [b, c]) a(3) o(d()).equals(15) o(count).equals(1) }) o("combines default value atomically", function() { var count = 0 var a = Stream(3) var b = Stream.combine(function(a) {return a() * 2}, [a]) var c = Stream.combine(function(a) {return a() * a()}, [a]) var d = Stream.combine(function(b, c) { count++ return b() + c() }, [b, c]) o(d()).equals(15) o(count).equals(1) }) o("combine lists only changed upstreams in last arg", function() { var streams = [] var a = Stream() var b = Stream() Stream.combine(function(a, b, changed) { streams = changed }, [a, b]) a(3) b(5) o(streams.length).equals(1) o(streams[0]).equals(b) }) o("combine lists only changed upstreams in last arg with default value", function() { var streams = [] var a = Stream(3) var b = Stream(5) Stream.combine(function(a, b, changed) { streams = changed }, [a, b]) a(7) o(streams.length).equals(1) o(streams[0]).equals(a) }) o("combine can return undefined", function() { var a = Stream(1) var b = Stream.combine(function() { return undefined }, [a]) o(b()).equals(undefined) }) o("combine can return stream", function() { var a = Stream(1) var b = Stream.combine(function() { return Stream(2) }, [a]) o(b()()).equals(2) }) o("combine can return pending stream", function() { var a = Stream(1) var b = Stream.combine(function() { return Stream() }, [a]) o(b()()).equals(undefined) }) o("combine can halt", function() { var count = 0 var a = Stream(1) var b = Stream.combine(function() { return Stream.HALT }, [a])["fantasy-land/map"](function() { count++ return 1 }) o(b()).equals(undefined) o(count).equals(0) }) o("combine will throw with a helpful error if given non-stream values", function () { var spy = o.spy() var a = Stream(1) var thrown = null; try { Stream.combine(spy, [a, ""]) } catch (e) { thrown = e } o(thrown).notEquals(null) o(thrown.constructor === TypeError).equals(false) o(spy.callCount).equals(0) }) }) o.spec("merge", function() { o("transforms an array of streams to an array of values", function() { var all = Stream.merge([ Stream(10), Stream("20"), Stream({value: 30}), ]) o(all()).deepEquals([10, "20", {value: 30}]) }) o("remains pending until all streams are active", function() { var straggler = Stream() var all = Stream.merge([ Stream(10), Stream("20"), straggler, ]) o(all()).equals(undefined) straggler(30) o(all()).deepEquals([10, "20", 30]) }) o("calls run callback after all parents are active", function() { var value = 0 var id = function(value) {return value} var a = Stream() var b = Stream() Stream.merge([a.map(id), b.map(id)]).map(function(data) { value = data[0] + data[1] return undefined }) a(1) b(2) o(value).equals(3) a(3) b(4) o(value).equals(7) }) }) o.spec("end", function() { o("end stream works", function() { var stream = Stream() var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream]) stream.end(true) stream(3) o(doubled()).equals(undefined) }) o("end stream works with default value", function() { var stream = Stream(2) var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream]) stream.end(true) stream(3) o(doubled()).equals(4) }) o("cannot add downstream to ended stream", function() { var stream = Stream(2) stream.end(true) var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream]) stream(3) o(doubled()).equals(undefined) }) o("upstream does not affect ended stream", function() { var stream = Stream(2) var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream]) doubled.end(true) stream(4) o(doubled()).equals(4) }) o("end stream can be mapped to", function() { var stream = Stream() var spy = o.spy() stream.end.map(spy) o(spy.callCount).equals(0) stream.end(true) o(spy.callCount).equals(1) }) }) o.spec("valueOf", function() { o("works", function() { o(Stream(1).valueOf()).equals(1) o(Stream("a").valueOf()).equals("a") o(Stream(true).valueOf()).equals(true) o(Stream(null).valueOf()).equals(null) o(Stream(undefined).valueOf()).equals(undefined) o(Stream({a: 1}).valueOf()).deepEquals({a: 1}) o(Stream([1, 2, 3]).valueOf()).deepEquals([1, 2, 3]) o(Stream().valueOf()).equals(undefined) }) o("allows implicit value access in mathematical operations", function() { o(Stream(1) + Stream(1)).equals(2) }) }) o.spec("toString", function() { o("aliases valueOf", function() { var stream = Stream(1) o(stream.toString).equals(stream.valueOf) }) o("allows implicit value access in string operations", function() { o(Stream("a") + Stream("b")).equals("ab") }) }) o.spec("toJSON", function() { o("works", function() { o(Stream(1).toJSON()).equals(1) o(Stream("a").toJSON()).equals("a") o(Stream(true).toJSON()).equals(true) o(Stream(null).toJSON()).equals(null) o(Stream(undefined).toJSON()).equals(undefined) o(Stream({a: 1}).toJSON()).deepEquals({a: 1}) o(Stream([1, 2, 3]).toJSON()).deepEquals([1, 2, 3]) o(Stream().toJSON()).equals(undefined) o(Stream(new Date(0)).toJSON()).equals(new Date(0).toJSON()) }) o("works w/ JSON.stringify", function() { o(JSON.stringify(Stream(1))).equals(JSON.stringify(1)) o(JSON.stringify(Stream("a"))).equals(JSON.stringify("a")) o(JSON.stringify(Stream(true))).equals(JSON.stringify(true)) o(JSON.stringify(Stream(null))).equals(JSON.stringify(null)) o(JSON.stringify(Stream(undefined))).equals(JSON.stringify(undefined)) o(JSON.stringify(Stream({a: 1}))).deepEquals(JSON.stringify({a: 1})) o(JSON.stringify(Stream([1, 2, 3]))).deepEquals(JSON.stringify([1, 2, 3])) o(JSON.stringify(Stream())).equals(JSON.stringify(undefined)) o(JSON.stringify(Stream(new Date(0)))).equals(JSON.stringify(new Date(0))) }) }) o.spec("map", function() { o("works", function() { var stream = Stream() var doubled = stream["fantasy-land/map"](function(value) {return value * 2}) stream(3) o(doubled()).equals(6) }) o("works with default value", function() { var stream = Stream(3) var doubled = stream["fantasy-land/map"](function(value) {return value * 2}) o(doubled()).equals(6) }) o("works with undefined value", function() { var stream = Stream() var mapped = stream["fantasy-land/map"](function(value) {return String(value)}) stream(undefined) o(mapped()).equals("undefined") }) o("works with default undefined value", function() { var stream = Stream(undefined) var mapped = stream["fantasy-land/map"](function(value) {return String(value)}) o(mapped()).equals("undefined") }) o("works with pending stream", function() { var stream = Stream(undefined) var mapped = stream["fantasy-land/map"](function() {return Stream()}) o(mapped()()).equals(undefined) }) o("has alias", function() { var stream = Stream(undefined) o(stream["fantasy-land/map"]).equals(stream.map) }) }) o.spec("ap", function() { o("works", function() { var apply = Stream(function(value) {return value * 2}) var stream = Stream(3) var applied = stream["fantasy-land/ap"](apply) o(applied()).equals(6) apply(function(value) {return value / 3}) o(applied()).equals(1) stream(9) o(applied()).equals(3) }) o("works with undefined value", function() { var apply = Stream(function(value) {return String(value)}) var stream = Stream(undefined) var applied = stream["fantasy-land/ap"](apply) o(applied()).equals("undefined") apply(function(value) {return String(value) + "a"}) o(applied()).equals("undefineda") }) }) o.spec("fantasy-land", function() { o.spec("functor", function() { o("identity", function() { var stream = Stream(3) var mapped = stream["fantasy-land/map"](function(value) {return value}) o(stream()).equals(mapped()) }) o("composition", function() { function f(x) {return x * 2} function g(x) {return x * x} var stream = Stream(3) var mapped = stream["fantasy-land/map"](function(value) {return f(g(value))}) var composed = stream["fantasy-land/map"](g)["fantasy-land/map"](f) o(mapped()).equals(18) o(mapped()).equals(composed()) }) }) o.spec("apply", function() { o("composition", function() { var a = Stream(function(value) {return value * 2}) var u = Stream(function(value) {return value * 3}) var v = Stream(5) var mapped = v["fantasy-land/ap"](u["fantasy-land/ap"](a["fantasy-land/map"](function(f) { return function(g) { return function(x) { return f(g(x)) } } }))) var composed = v["fantasy-land/ap"](u)["fantasy-land/ap"](a) o(mapped()).equals(30) o(mapped()).equals(composed()) }) }) o.spec("applicative", function() { o("identity", function() { var a = Stream()["fantasy-land/of"](function(value) {return value}) var v = Stream(5) o(v["fantasy-land/ap"](a)()).equals(5) o(v["fantasy-land/ap"](a)()).equals(v()) }) o("homomorphism", function() { var a = Stream(0) var f = function(value) {return value * 2} var x = 3 o(a["fantasy-land/of"](x)["fantasy-land/ap"](a["fantasy-land/of"](f))()).equals(6) o(a["fantasy-land/of"](x)["fantasy-land/ap"](a["fantasy-land/of"](f))()).equals(a["fantasy-land/of"](f(x))()) }) o("interchange", function() { var u = Stream(function(value) {return value * 2}) var a = Stream() var y = 3 o(a["fantasy-land/of"](y)["fantasy-land/ap"](u)()).equals(6) o(a["fantasy-land/of"](y)["fantasy-land/ap"](u)()).equals(u["fantasy-land/ap"](a["fantasy-land/of"](function(f) {return f(y)}))()) }) }) }) }) mithril-1.1.7+dfsg/test-utils/000077500000000000000000000000001512406206500162255ustar00rootroot00000000000000mithril-1.1.7+dfsg/test-utils/README.md000066400000000000000000000003711512406206500175050ustar00rootroot00000000000000# Test utils Utilities for testing Mithril Version: 1.0 License: MIT ## About - pushStateMock - mock for `history.pushState` and `location` - ajaxMock - mock for XMLHttpRequest and JSONP transporters - parseURL - helper function for URL parsing mithril-1.1.7+dfsg/test-utils/browserMock.js000066400000000000000000000006501512406206500210610ustar00rootroot00000000000000"use strict" var pushStateMock = require("./pushStateMock") var domMock = require("./domMock") var xhrMock = require("./xhrMock") module.exports = function(env) { env = env || {} var $window = env.window = {} var dom = domMock() var xhr = xhrMock() for (var key in dom) if (!$window[key]) $window[key] = dom[key] for (var key in xhr) if (!$window[key]) $window[key] = xhr[key] pushStateMock(env) return $window }mithril-1.1.7+dfsg/test-utils/callAsync.js000066400000000000000000000001361512406206500204740ustar00rootroot00000000000000"use strict" module.exports = typeof setImmediate === "function" ? setImmediate : setTimeout mithril-1.1.7+dfsg/test-utils/components.js000066400000000000000000000012701512406206500207500ustar00rootroot00000000000000"use strict" module.exports = [ { kind: "POJO", create: function(methods) { var res = {view: function() {return {tag:"div"}}} Object.keys(methods || {}).forEach(function(m){res[m] = methods[m]}) return res } }, { kind: "constructible", create: function(methods) { function res(){} res.prototype.view = function() {return {tag:"div"}} Object.keys(methods || {}).forEach(function(m){res.prototype[m] = methods[m]}) return res } }, { kind: "closure", create: function(methods) { return function() { var res = {view: function() {return {tag:"div"}}} Object.keys(methods || {}).forEach(function(m){res[m] = methods[m]}) return res } } } ] mithril-1.1.7+dfsg/test-utils/domMock.js000066400000000000000000000416651512406206500201700ustar00rootroot00000000000000"use strict" /* Known limitations: - `option.selected` can't be set/read when the option doesn't have a `select` parent - `element.attributes` is just a map of attribute names => Attr objects stubs - ... */ /* options: - spy:(f: Function) => Function */ module.exports = function(options) { options = options || {} var spy = options.spy || function(f){return f} var spymap = [] function registerSpies(element, spies) { if(options.spy) { var i = spymap.indexOf(element) if (i === -1) { spymap.push(element, spies) } else { var existing = spymap[i + 1] for (var k in spies) existing[k] = spies[k] } } } function getSpies(element) { if (element == null || typeof element !== "object") throw new Error("Element expected") if(options.spy) return spymap[spymap.indexOf(element) + 1] } function isModernEvent(type) { return type === "transitionstart" || type === "transitionend" || type === "animationstart" || type === "animationend" } function appendChild(child) { var ancestor = this while (ancestor !== child && ancestor !== null) ancestor = ancestor.parentNode if (ancestor === child) throw new Error("Node cannot be inserted at the specified point in the hierarchy") if (child.nodeType == null) throw new Error("Argument is not a DOM element") var index = this.childNodes.indexOf(child) if (index > -1) this.childNodes.splice(index, 1) if (child.nodeType === 11) { while (child.firstChild != null) this.appendChild(child.firstChild) child.childNodes = [] } else { this.childNodes.push(child) if (child.parentNode != null && child.parentNode !== this) child.parentNode.removeChild(child) child.parentNode = this } } function removeChild(child) { var index = this.childNodes.indexOf(child) if (index > -1) { this.childNodes.splice(index, 1) child.parentNode = null } else throw new TypeError("Failed to execute 'removeChild'") } function insertBefore(child, reference) { var ancestor = this while (ancestor !== child && ancestor !== null) ancestor = ancestor.parentNode if (ancestor === child) throw new Error("Node cannot be inserted at the specified point in the hierarchy") if (child.nodeType == null) throw new Error("Argument is not a DOM element") var refIndex = this.childNodes.indexOf(reference) var index = this.childNodes.indexOf(child) if (reference !== null && refIndex < 0) throw new TypeError("Invalid argument") if (index > -1) this.childNodes.splice(index, 1) if (reference === null) this.appendChild(child) else { if (child.nodeType === 11) { this.childNodes.splice.apply(this.childNodes, [refIndex, 0].concat(child.childNodes)) while (child.firstChild) { var subchild = child.firstChild child.removeChild(subchild) subchild.parentNode = this } child.childNodes = [] } else { this.childNodes.splice(refIndex, 0, child) if (child.parentNode != null && child.parentNode !== this) child.parentNode.removeChild(child) child.parentNode = this } } } function getAttribute(name) { if (this.attributes[name] == null) return null return this.attributes[name].value } function setAttribute(name, value) { /*eslint-disable no-implicit-coercion*/ // this is the correct kind of conversion, passing a Symbol throws in browsers too. var nodeValue = "" + value /*eslint-enable no-implicit-coercion*/ this.attributes[name] = { namespaceURI: null, get value() {return nodeValue}, set value(value) { /*eslint-disable no-implicit-coercion*/ nodeValue = "" + value /*eslint-enable no-implicit-coercion*/ }, get nodeValue() {return nodeValue}, set nodeValue(value) { this.value = value } } } function setAttributeNS(ns, name, value) { this.setAttribute(name, value) this.attributes[name].namespaceURI = ns } function removeAttribute(name) { delete this.attributes[name] } function hasAttribute(name) { return name in this.attributes } var declListTokenizer = /;|"(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*'/g /** * This will split a semicolon-separated CSS declaration list into an array of * individual declarations, ignoring semicolons in strings. * * Comments are also stripped. * * @param {string} declList * @return {string[]} */ function splitDeclList(declList) { var indices = [], res = [], match // remove comments, preserving comments in strings. declList = declList.replace( /("(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*')|\/\*[\s\S]*?\*\//g, function(m, str){ return str || "" } ) /*eslint-disable no-cond-assign*/ while (match = declListTokenizer.exec(declList)) { if (match[0] === ";") indices.push(match.index) } /*eslint-enable no-cond-assign*/ for (var i = indices.length; i--;){ res.unshift(declList.slice(indices[i] + 1)) declList = declList.slice(0, indices[i]) } res.unshift(declList) return res } var activeElement var $window = { document: { createElement: function(tag) { var cssText = "" var style = {} Object.defineProperty(style, "cssText", { get: function() {return cssText}, set: function (value) { var buf = [] if (typeof value === "string") { for (var key in style) style[key] = "" var rules = splitDeclList(value) for (var i = 0; i < rules.length; i++) { var rule = rules[i] var colonIndex = rule.indexOf(":") if (colonIndex > -1) { var rawKey = rule.slice(0, colonIndex).trim() var key = rawKey.replace(/-\D/g, function(match) {return match[1].toUpperCase()}) var value = rule.slice(colonIndex + 1).trim() if (key !== "cssText") { style[key] = value buf.push(rawKey + ": " + value + ";") } } } cssText = buf.join(" ") } } }) var events = {} var element = { nodeType: 1, nodeName: tag.toUpperCase(), namespaceURI: "http://www.w3.org/1999/xhtml", appendChild: appendChild, removeChild: removeChild, insertBefore: insertBefore, hasAttribute: hasAttribute, getAttribute: getAttribute, setAttribute: setAttribute, setAttributeNS: setAttributeNS, removeAttribute: removeAttribute, parentNode: null, childNodes: [], attributes: {}, get firstChild() { return this.childNodes[0] || null }, get nextSibling() { if (this.parentNode == null) return null var index = this.parentNode.childNodes.indexOf(this) if (index < 0) throw new TypeError("Parent's childNodes is out of sync") return this.parentNode.childNodes[index + 1] || null }, set textContent(value) { this.childNodes = [] if (value !== "") this.appendChild($window.document.createTextNode(value)) }, set innerHTML(value) { while (this.firstChild) this.removeChild(this.firstChild) var stack = [this], depth = 0, voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"] value.replace(/<([a-z0-9\-]+?)((?:\s+?[^=]+?=(?:"[^"]*?"|'[^']*?'|[^\s>]*))*?)(\s*\/)?>|<\/([a-z0-9\-]+?)>|([^<]+)/g, function(match, startTag, attrs, selfClosed, endTag, text) { if (startTag) { var element = $window.document.createElement(startTag) attrs.replace(/\s+?([^=]+?)=(?:"([^"]*?)"|'([^']*?)'|([^\s>]*))/g, function(match, key, doubleQuoted, singleQuoted, unquoted) { var keyParts = key.split(":") var name = keyParts.pop() var ns = keyParts[0] var value = doubleQuoted || singleQuoted || unquoted || "" if (ns != null) element.setAttributeNS(ns, name, value) else element.setAttribute(name, value) }) stack[depth].appendChild(element) if (!selfClosed && voidElements.indexOf(startTag.toLowerCase()) < 0) stack[++depth] = element } else if (endTag) { depth-- } else if (text) { stack[depth].appendChild($window.document.createTextNode(text)) // FIXME handle html entities } }) }, get style() { return style }, set style(_){ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style#Setting_style throw new Error("setting element.style is not portable") }, get className() { return this.attributes["class"] ? this.attributes["class"].value : "" }, set className(value) { if (this.namespaceURI === "http://www.w3.org/2000/svg") throw new Error("Cannot set property className of SVGElement") else this.setAttribute("class", value) }, focus: function() {activeElement = this}, addEventListener: function(type, callback) { if (events[type] == null) events[type] = [callback] else events[type].push(callback) }, removeEventListener: function(type, callback) { if (events[type] != null) { var index = events[type].indexOf(callback) if (index > -1) events[type].splice(index, 1) } }, dispatchEvent: function(e) { if (this.nodeName === "INPUT" && this.attributes["type"] != null && this.attributes["type"].value === "checkbox" && e.type === "click") { this.checked = !this.checked } e.target = this if (events[e.type] != null) { for (var i = 0; i < events[e.type].length; i++) { events[e.type][i].call(this, e) } } e.preventDefault = function() { // TODO: should this do something? } if (typeof this["on" + e.type] === "function" && !isModernEvent(e.type)) this["on" + e.type](e) }, onclick: null, } if (element.nodeName === "A") { Object.defineProperty(element, "href", { get: function() {return this.attributes["href"] === undefined ? "" : "[FIXME implement]"}, set: function(value) {this.setAttribute("href", value)}, enumerable: true, }) } if (element.nodeName === "INPUT") { var checked Object.defineProperty(element, "checked", { get: function() {return checked === undefined ? this.attributes["checked"] !== undefined : checked}, set: function(value) {checked = Boolean(value)}, enumerable: true, }) var value = "" var valueSetter = spy(function(v) { /*eslint-disable no-implicit-coercion*/ value = v === null ? "" : "" + v /*eslint-enable no-implicit-coercion*/ }) Object.defineProperty(element, "value", { get: function() { return value }, set: valueSetter, enumerable: true, }) // we currently emulate the non-ie behavior, but emulating ie may be more useful (throw when an invalid type is set) var typeSetter = spy(function(v) { this.setAttribute("type", v) }) Object.defineProperty(element, "type", { get: function() { if (!this.hasAttribute("type")) return "text" var type = this.getAttribute("type") return (/^(?:radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image)$/) .test(type) ? type : "text" }, set: typeSetter, enumerable: true, }) registerSpies(element, { valueSetter: valueSetter, typeSetter: typeSetter }) } if (element.nodeName === "TEXTAREA") { var wasNeverSet = true var value = "" var valueSetter = spy(function(v) { wasNeverSet = false /*eslint-disable no-implicit-coercion*/ value = v === null ? "" : "" + v /*eslint-enable no-implicit-coercion*/ }) Object.defineProperty(element, "value", { get: function() { return wasNeverSet && this.firstChild ? this.firstChild.nodeValue : value }, set: valueSetter, enumerable: true, }) registerSpies(element, { valueSetter: valueSetter }) } /* eslint-disable radix */ if (element.nodeName === "CANVAS") { Object.defineProperty(element, "width", { get: function() {return this.attributes["width"] ? Math.floor(parseInt(this.attributes["width"].value) || 0) : 300}, set: function(value) {this.setAttribute("width", Math.floor(Number(value) || 0).toString())}, }) Object.defineProperty(element, "height", { get: function() {return this.attributes["height"] ? Math.floor(parseInt(this.attributes["height"].value) || 0) : 300}, set: function(value) {this.setAttribute("height", Math.floor(Number(value) || 0).toString())}, }) } /* eslint-enable radix */ function getOptions(element) { var options = [] for (var i = 0; i < element.childNodes.length; i++) { if (element.childNodes[i].nodeName === "OPTION") options.push(element.childNodes[i]) else if (element.childNodes[i].nodeName === "OPTGROUP") options = options.concat(getOptions(element.childNodes[i])) } return options } function getOptionValue(element) { return element.attributes["value"] != null ? element.attributes["value"].value : element.firstChild != null ? element.firstChild.nodeValue : "" } if (element.nodeName === "SELECT") { // var selectedValue var selectedIndex = 0 Object.defineProperty(element, "selectedIndex", { get: function() {return getOptions(this).length > 0 ? selectedIndex : -1}, set: function(value) { var options = getOptions(this) if (value >= 0 && value < options.length) { // selectedValue = getOptionValue(options[selectedIndex]) selectedIndex = value } else { // selectedValue = "" selectedIndex = -1 } }, enumerable: true, }) var valueSetter = spy(function(value) { if (value === null) { selectedIndex = -1 } else { var options = getOptions(this) /*eslint-disable no-implicit-coercion*/ var stringValue = "" + value /*eslint-enable no-implicit-coercion*/ for (var i = 0; i < options.length; i++) { if (getOptionValue(options[i]) === stringValue) { // selectedValue = stringValue selectedIndex = i return } } // selectedValue = stringValue selectedIndex = -1 } }) Object.defineProperty(element, "value", { get: function() { if (this.selectedIndex > -1) return getOptionValue(getOptions(this)[this.selectedIndex]) return "" }, set: valueSetter, enumerable: true, }) registerSpies(element, { valueSetter: valueSetter }) } if (element.nodeName === "OPTION") { var valueSetter = spy(function(value) { /*eslint-disable no-implicit-coercion*/ this.setAttribute("value", value === null ? "" : "" + value) /*eslint-enable no-implicit-coercion*/ }) Object.defineProperty(element, "value", { get: function() {return getOptionValue(this)}, set: valueSetter, enumerable: true, }) registerSpies(element, { valueSetter: valueSetter }) Object.defineProperty(element, "selected", { // TODO? handle `selected` without a parent (works in browsers) get: function() { var options = getOptions(this.parentNode) var index = options.indexOf(this) return index === this.parentNode.selectedIndex }, set: function(value) { if (value) { var options = getOptions(this.parentNode) var index = options.indexOf(this) if (index > -1) this.parentNode.selectedIndex = index } else this.parentNode.selectedIndex = 0 }, enumerable: true, }) } return element }, createElementNS: function(ns, tag, is) { var element = this.createElement(tag, is) element.nodeName = tag element.namespaceURI = ns return element }, createTextNode: function(text) { /*eslint-disable no-implicit-coercion*/ var nodeValue = "" + text /*eslint-enable no-implicit-coercion*/ return { nodeType: 3, nodeName: "#text", parentNode: null, get nodeValue() {return nodeValue}, set nodeValue(value) { /*eslint-disable no-implicit-coercion*/ nodeValue = "" + value /*eslint-enable no-implicit-coercion*/ }, } }, createDocumentFragment: function() { return { nodeType: 11, nodeName: "#document-fragment", appendChild: appendChild, insertBefore: insertBefore, removeChild: removeChild, parentNode: null, childNodes: [], get firstChild() { return this.childNodes[0] || null }, } }, createEvent: function() { return { initEvent: function(type) {this.type = type}, } }, get activeElement() {return activeElement}, }, } $window.document.documentElement = $window.document.createElement("html") $window.document.documentElement.appendChild($window.document.createElement("head")) $window.document.body = $window.document.createElement("body") $window.document.documentElement.appendChild($window.document.body) activeElement = $window.document.body if (options.spy) $window.__getSpies = getSpies return $window } mithril-1.1.7+dfsg/test-utils/parseURL.js000066400000000000000000000037611512406206500202670ustar00rootroot00000000000000"use strict" module.exports = function parseURL(url, root) { var data = {} var protocolIndex = url.indexOf("://") var pathnameIndex = protocolIndex > -1 ? url.indexOf("/", protocolIndex + 3) : url.indexOf("/") var searchIndex = url.indexOf("?") var hashIndex = url.indexOf("#") if ((pathnameIndex > searchIndex && searchIndex > -1) || (pathnameIndex > hashIndex && hashIndex > -1)) pathnameIndex = -1 if (searchIndex > hashIndex && hashIndex > -1) searchIndex = -1 var pathnameEnd = searchIndex > -1 ? searchIndex : hashIndex > -1 ? hashIndex : url.length if (protocolIndex > -1) { //it's a full URL if (pathnameIndex < 0) pathnameIndex = url.length var portIndex = url.indexOf(":", protocolIndex + 1) if (portIndex < 0) portIndex = pathnameIndex data.protocol = url.slice(0, protocolIndex + 1) data.hostname = url.slice(protocolIndex + 3, portIndex) data.port = url.slice(portIndex + 1, pathnameIndex) data.pathname = url.slice(pathnameIndex, pathnameEnd) || "/" } else { data.protocol = root.protocol data.hostname = root.hostname data.port = root.port if (pathnameIndex === 0) { //it's an absolute path data.pathname = url.slice(pathnameIndex, pathnameEnd) || "/" } else if (searchIndex !== 0 && hashIndex !== 0) { //it's a relative path var slashIndex = root.pathname.lastIndexOf("/") var path = slashIndex > -1 ? root.pathname.slice(0, slashIndex + 1) : "./" var normalized = url.slice(0, pathnameEnd).replace(/^\.$/, root.pathname.slice(slashIndex + 1)).replace(/^\.\//, "") var dotdot = /\/[^\/]+?\/\.{2}/g var pathname = path + normalized pathname = path + normalized while (dotdot.test(pathname)) pathname = pathname.replace(dotdot, "") pathname = pathname.replace(/\/\.\//g, "/").replace(/^(\/\.{2})+/, "") || "/" data.pathname = pathname } } var searchEnd = hashIndex > -1 ? hashIndex : url.length data.search = searchIndex > -1 ? url.slice(searchIndex, searchEnd) : "" data.hash = hashIndex > -1 ? url.slice(hashIndex) : "" return data } mithril-1.1.7+dfsg/test-utils/pushStateMock.js000066400000000000000000000114211512406206500213540ustar00rootroot00000000000000"use strict" var parseURL = require("../test-utils/parseURL") var callAsync = require("../test-utils/callAsync") function debouncedAsync(f) { var ref return function() { if (ref != null) return ref = callAsync(function(){ ref = null f() }) } } module.exports = function(options) { if (options == null) options = {} var $window = options.window || {} var protocol = options.protocol || "http:" var hostname = options.hostname || "localhost" var port = "" var pathname = "/" var search = "" var hash = "" var past = [{url: getURL(), isNew: true, state: null, title: null}], future = [] function getURL() { if (protocol === "file:") return protocol + "//" + pathname + search + hash return protocol + "//" + hostname + prefix(":", port) + pathname + search + hash } function setURL(value) { var data = parseURL(value, {protocol: protocol, hostname: hostname, port: port, pathname: pathname}) var isNew = false if (data.protocol != null && data.protocol !== protocol) protocol = data.protocol, isNew = true if (data.hostname != null && data.hostname !== hostname) hostname = data.hostname, isNew = true if (data.port != null && data.port !== port) port = data.port, isNew = true if (data.pathname != null && data.pathname !== pathname) pathname = data.pathname, isNew = true if (data.search != null && data.search !== search) search = data.search, isNew = true if (data.hash != null && data.hash !== hash) { hash = data.hash if (!isNew) { hashchange() } } return isNew } function prefix(prefix, value) { if (value === "") return "" return (value.charAt(0) !== prefix ? prefix : "") + value } function _hashchange() { if (typeof $window.onhashchange === "function") $window.onhashchange({type: "hashchange"}) } var hashchange = debouncedAsync(_hashchange) function popstate() { if (typeof $window.onpopstate === "function") $window.onpopstate({type: "popstate", state: $window.history.state}) } function unload() { if (typeof $window.onunload === "function") $window.onunload({type: "unload"}) } $window.location = { get protocol() { return protocol }, get hostname() { return hostname }, get port() { return port }, get pathname() { return pathname }, get search() { return search }, get hash() { return hash }, get origin() { if (protocol === "file:") return "null" return protocol + "//" + hostname + prefix(":", port) }, get host() { if (protocol === "file:") return "" return hostname + prefix(":", port) }, get href() { return getURL() }, set protocol(value) { throw new Error("Protocol is read-only") }, set hostname(value) { unload() past.push({url: getURL(), isNew: true}) future = [] hostname = value }, set port(value) { if (protocol === "file:") throw new Error("Port is read-only under `file://` protocol") unload() past.push({url: getURL(), isNew: true}) future = [] port = value }, set pathname(value) { if (protocol === "file:") throw new Error("Pathname is read-only under `file://` protocol") unload() past.push({url: getURL(), isNew: true}) future = [] pathname = prefix("/", value) }, set search(value) { unload() past.push({url: getURL(), isNew: true}) future = [] search = prefix("?", value) }, set hash(value) { var oldHash = hash past.push({url: getURL(), isNew: false}) future = [] hash = prefix("#", value) if (oldHash != hash) hashchange() }, set origin(value) { //origin is writable but ignored }, set host(value) { //host is writable but ignored in Chrome }, set href(value) { var url = getURL() var isNew = setURL(value) if (isNew) { setURL(url) unload() setURL(value) } past.push({url: url, isNew: isNew}) future = [] }, } $window.history = { pushState: function(state, title, url) { past.push({url: getURL(), isNew: false, state: state, title: title}) future = [] setURL(url) }, replaceState: function(state, title, url) { var entry = past[past.length - 1] entry.state = state entry.title = title setURL(url) }, back: function() { if (past.length > 1) { var entry = past.pop() if (entry.isNew) unload() future.push({url: getURL(), isNew: false, state: entry.state, title: entry.title}) setURL(entry.url) if (!entry.isNew) popstate() } }, forward: function() { var entry = future.pop() if (entry != null) { if (entry.isNew) unload() past.push({url: getURL(), isNew: false, state: entry.state, title: entry.title}) setURL(entry.url) if (!entry.isNew) popstate() } }, get state() { return past.length === 0 ? null : past[past.length - 1].state }, } $window.onpopstate = null, $window.onhashchange = null, $window.onunload = null return $window } mithril-1.1.7+dfsg/test-utils/tests/000077500000000000000000000000001512406206500173675ustar00rootroot00000000000000mithril-1.1.7+dfsg/test-utils/tests/index.html000066400000000000000000000017451512406206500213730ustar00rootroot00000000000000 mithril-1.1.7+dfsg/test-utils/tests/test-browserMock.js000066400000000000000000000021571512406206500232040ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var browserMock = require("../../test-utils/browserMock") var callAsync = require("../../test-utils/callAsync") o.spec("browserMock", function() { var $window o.beforeEach(function() { $window = browserMock() }) o("Mocks DOM, pushState and XHR", function() { o($window.location).notEquals(undefined) o($window.document).notEquals(undefined) o($window.XMLHttpRequest).notEquals(undefined) }) o("$window.onhashchange can be reached from the pushStateMock functions", function(done) { $window.onhashchange = o.spy() $window.location.hash = "#a" callAsync(function(){ o($window.onhashchange.callCount).equals(1) done() }) }) o("$window.onpopstate can be reached from the pushStateMock functions", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "#a") $window.history.back() o($window.onpopstate.callCount).equals(1) }) o("$window.onunload can be reached from the pushStateMock functions", function() { $window.onunload = o.spy() $window.location.href = "/a" o($window.onunload.callCount).equals(1) }) }) mithril-1.1.7+dfsg/test-utils/tests/test-callAsync.js000066400000000000000000000007571512406206500226240ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var callAsync = require("../../test-utils/callAsync") o.spec("callAsync", function() { o("works", function(done) { var count = 0 callAsync(function() { o(count).equals(1) done() }) count++ }) o("gets called before setTimeout", function(done) { var timeout callAsync(function() { clearTimeout(timeout) done() }) timeout = setTimeout(function() { throw new Error("callAsync was called too slow") }, 5) }) }) mithril-1.1.7+dfsg/test-utils/tests/test-components.js000066400000000000000000000026171512406206500230750ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var components = require("../../test-utils/components") o.spec("test-utils/components", function() { var test = o.spy(function(component) { return function() { o("works", function() { o(typeof component.kind).equals("string") var methods = {oninit: function(){}, view: function(){}} var cmp1, cmp2 if (component.kind === "POJO") { cmp1 = component.create() cmp2 = component.create(methods) } else if (component.kind === "constructible") { cmp1 = new (component.create()) cmp2 = new (component.create(methods)) } else if (component.kind === "closure") { cmp1 = component.create()() cmp2 = component.create(methods)() } else { throw new Error("unexpected component kind") } o(cmp1 != null).equals(true) o(typeof cmp1.view).equals("function") var vnode = cmp1.view() o(vnode != null).equals(true) o(vnode).deepEquals({tag: "div"}) if (component.kind !== "constructible") { o(cmp2).deepEquals(methods) } else { // deepEquals doesn't search the prototype, do it manually o(cmp2 != null).equals(true) o(cmp2.view).equals(methods.view) o(cmp2.oninit).equals(methods.oninit) } }) } }) o.after(function(){ o(test.callCount).equals(3) }) components.forEach(function(component) { o.spec(component.kind, test(component)) }) }) mithril-1.1.7+dfsg/test-utils/tests/test-domMock.js000066400000000000000000001111251512406206500222740ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var domMock = require("../../test-utils/domMock") o.spec("domMock", function() { var $document o.beforeEach(function() { $document = domMock().document }) o.spec("createElement", function() { o("works", function() { var node = $document.createElement("div") o(node.nodeType).equals(1) o(node.nodeName).equals("DIV") o(node.namespaceURI).equals("http://www.w3.org/1999/xhtml") o(node.parentNode).equals(null) o(node.childNodes.length).equals(0) o(node.firstChild).equals(null) o(node.nextSibling).equals(null) }) }) o.spec("createElementNS", function() { o("works", function() { var node = $document.createElementNS("http://www.w3.org/2000/svg", "svg") o(node.nodeType).equals(1) o(node.nodeName).equals("svg") o(node.namespaceURI).equals("http://www.w3.org/2000/svg") o(node.parentNode).equals(null) o(node.childNodes.length).equals(0) o(node.firstChild).equals(null) o(node.nextSibling).equals(null) }) }) o.spec("createTextNode", function() { o("works", function() { var node = $document.createTextNode("abc") o(node.nodeType).equals(3) o(node.nodeName).equals("#text") o(node.parentNode).equals(null) o(node.nodeValue).equals("abc") }) o("works w/ number", function() { var node = $document.createTextNode(123) o(node.nodeValue).equals("123") }) o("works w/ null", function() { var node = $document.createTextNode(null) o(node.nodeValue).equals("null") }) o("works w/ undefined", function() { var node = $document.createTextNode(undefined) o(node.nodeValue).equals("undefined") }) o("works w/ object", function() { var node = $document.createTextNode({}) o(node.nodeValue).equals("[object Object]") }) o("does not unescape HTML", function() { var node = $document.createTextNode("&") o(node.nodeValue).equals("&") }) o("nodeValue casts to string", function() { var node = $document.createTextNode("a") node.nodeValue = true o(node.nodeValue).equals("true") }) if (typeof Symbol === "function") { o("doesn't work with symbols", function(){ var threw = false try { $document.createTextNode(Symbol("nono")) } catch(e) { threw = true } o(threw).equals(true) }) o("symbols can't be used as nodeValue", function(){ var threw = false try { var node = $document.createTextNode("a") node.nodeValue = Symbol("nono") } catch(e) { threw = true } o(threw).equals(true) }) } }) o.spec("createDocumentFragment", function() { o("works", function() { var node = $document.createDocumentFragment() o(node.nodeType).equals(11) o(node.nodeName).equals("#document-fragment") o(node.parentNode).equals(null) o(node.childNodes.length).equals(0) o(node.firstChild).equals(null) }) }) o.spec("appendChild", function() { o("works", function() { var parent = $document.createElement("div") var child = $document.createElement("a") parent.appendChild(child) o(parent.childNodes.length).equals(1) o(parent.childNodes[0]).equals(child) o(parent.firstChild).equals(child) o(child.parentNode).equals(parent) }) o("moves existing", function() { var parent = $document.createElement("div") var a = $document.createElement("a") var b = $document.createElement("b") parent.appendChild(a) parent.appendChild(b) parent.appendChild(a) o(parent.childNodes.length).equals(2) o(parent.childNodes[0]).equals(b) o(parent.childNodes[1]).equals(a) o(parent.firstChild).equals(b) o(parent.firstChild.nextSibling).equals(a) o(a.parentNode).equals(parent) o(b.parentNode).equals(parent) }) o("removes from old parent", function() { var parent = $document.createElement("div") var source = $document.createElement("span") var a = $document.createElement("a") var b = $document.createElement("b") parent.appendChild(a) source.appendChild(b) parent.appendChild(b) o(source.childNodes.length).equals(0) }) o("transfers from fragment", function() { var parent = $document.createElement("div") var a = $document.createDocumentFragment("a") var b = $document.createElement("b") var c = $document.createElement("c") a.appendChild(b) a.appendChild(c) parent.appendChild(a) o(parent.childNodes.length).equals(2) o(parent.childNodes[0]).equals(b) o(parent.childNodes[1]).equals(c) o(parent.firstChild).equals(b) o(parent.firstChild.nextSibling).equals(c) o(a.childNodes.length).equals(0) o(a.firstChild).equals(null) o(a.parentNode).equals(null) o(b.parentNode).equals(parent) o(c.parentNode).equals(parent) }) o("throws if appended to self", function(done) { var div = $document.createElement("div") try {div.appendChild(div)} catch (e) {done()} }) o("throws if appended to child", function(done) { var parent = $document.createElement("div") var child = $document.createElement("a") parent.appendChild(child) try {child.appendChild(parent)} catch (e) {done()} }) o("throws if child is not element", function(done) { var parent = $document.createElement("div") var child = 1 try {parent.appendChild(child)} catch (e) {done()} }) }) o.spec("removeChild", function() { o("works", function() { var parent = $document.createElement("div") var child = $document.createElement("a") parent.appendChild(child) parent.removeChild(child) o(parent.childNodes.length).equals(0) o(parent.firstChild).equals(null) o(child.parentNode).equals(null) }) o("throws if not a child", function(done) { var parent = $document.createElement("div") var child = $document.createElement("a") try {parent.removeChild(child)} catch (e) {done()} }) }) o.spec("insertBefore", function() { o("works", function() { var parent = $document.createElement("div") var a = $document.createElement("a") var b = $document.createElement("b") parent.appendChild(a) parent.insertBefore(b, a) o(parent.childNodes.length).equals(2) o(parent.childNodes[0]).equals(b) o(parent.childNodes[1]).equals(a) o(parent.firstChild).equals(b) o(parent.firstChild.nextSibling).equals(a) o(a.parentNode).equals(parent) o(b.parentNode).equals(parent) }) o("moves existing", function() { var parent = $document.createElement("div") var a = $document.createElement("a") var b = $document.createElement("b") parent.appendChild(a) parent.appendChild(b) parent.insertBefore(b, a) o(parent.childNodes.length).equals(2) o(parent.childNodes[0]).equals(b) o(parent.childNodes[1]).equals(a) o(parent.firstChild).equals(b) o(parent.firstChild.nextSibling).equals(a) o(a.parentNode).equals(parent) o(b.parentNode).equals(parent) }) o("removes from old parent", function() { var parent = $document.createElement("div") var source = $document.createElement("span") var a = $document.createElement("a") var b = $document.createElement("b") parent.appendChild(a) source.appendChild(b) parent.insertBefore(b, a) o(source.childNodes.length).equals(0) }) o("transfers from fragment", function() { var parent = $document.createElement("div") var ref = $document.createElement("span") var a = $document.createDocumentFragment("a") var b = $document.createElement("b") var c = $document.createElement("c") parent.appendChild(ref) a.appendChild(b) a.appendChild(c) parent.insertBefore(a, ref) o(parent.childNodes.length).equals(3) o(parent.childNodes[0]).equals(b) o(parent.childNodes[1]).equals(c) o(parent.childNodes[2]).equals(ref) o(parent.firstChild).equals(b) o(parent.firstChild.nextSibling).equals(c) o(parent.firstChild.nextSibling.nextSibling).equals(ref) o(a.childNodes.length).equals(0) o(a.firstChild).equals(null) o(a.parentNode).equals(null) o(b.parentNode).equals(parent) o(c.parentNode).equals(parent) }) o("appends if second arg is null", function() { var parent = $document.createElement("div") var a = $document.createElement("a") var b = $document.createElement("b") parent.appendChild(a) parent.insertBefore(b, null) o(parent.childNodes.length).equals(2) o(parent.childNodes[0]).equals(a) o(parent.childNodes[1]).equals(b) o(parent.firstChild).equals(a) o(parent.firstChild.nextSibling).equals(b) o(a.parentNode).equals(parent) }) o("throws if appended to self", function(done) { var div = $document.createElement("div") var a = $document.createElement("a") div.appendChild(a) try {div.isnertBefore(div, a)} catch (e) {done()} }) o("throws if appended to child", function(done) { var parent = $document.createElement("div") var a = $document.createElement("a") var b = $document.createElement("b") parent.appendChild(a) a.appendChild(b) try {a.insertBefore(parent, b)} catch (e) {done()} }) o("throws if child is not element", function(done) { var parent = $document.createElement("div") var a = $document.createElement("a") parent.appendChild(a) try {parent.insertBefore(1, a)} catch (e) {done()} }) o("throws if inserted before itself", function(done) { var parent = $document.createElement("div") var a = $document.createElement("a") try {parent.insertBefore(a, a)} catch (e) {done()} }) o("throws if second arg is undefined", function(done) { var parent = $document.createElement("div") var a = $document.createElement("a") try {parent.insertBefore(a)} catch (e) {done()} }) o("throws if reference is not child", function(done) { var parent = $document.createElement("div") var a = $document.createElement("a") var b = $document.createElement("b") try {parent.insertBefore(a, b)} catch (e) {done()} }) }) o.spec("getAttribute", function() { o("works", function() { var div = $document.createElement("div") div.setAttribute("id", "aaa") o(div.getAttribute("id")).equals("aaa") }) }) o.spec("setAttribute", function() { o("works", function() { var div = $document.createElement("div") div.setAttribute("id", "aaa") o(div.attributes["id"].value).equals("aaa") o(div.attributes["id"].nodeValue).equals("aaa") o(div.attributes["id"].namespaceURI).equals(null) }) o("works w/ number", function() { var div = $document.createElement("div") div.setAttribute("id", 123) o(div.attributes["id"].value).equals("123") }) o("works w/ null", function() { var div = $document.createElement("div") div.setAttribute("id", null) o(div.attributes["id"].value).equals("null") }) o("works w/ undefined", function() { var div = $document.createElement("div") div.setAttribute("id", undefined) o(div.attributes["id"].value).equals("undefined") }) o("works w/ object", function() { var div = $document.createElement("div") div.setAttribute("id", {}) o(div.attributes["id"].value).equals("[object Object]") }) o("setting via attributes map stringifies", function() { var div = $document.createElement("div") div.setAttribute("id", "a") div.attributes["id"].value = 123 o(div.attributes["id"].value).equals("123") div.attributes["id"].nodeValue = 456 o(div.attributes["id"].value).equals("456") }) }) o.spec("hasAttribute", function() { o("works", function() { var div = $document.createElement("div") o(div.hasAttribute("id")).equals(false) div.setAttribute("id", "aaa") o(div.hasAttribute("id")).equals(true) div.removeAttribute("id") o(div.hasAttribute("id")).equals(false) }) }) o.spec("setAttributeNS", function() { o("works", function() { var div = $document.createElement("div") div.setAttributeNS("http://www.w3.org/1999/xlink", "href", "aaa") o(div.attributes["href"].value).equals("aaa") o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink") }) o("works w/ number", function() { var div = $document.createElement("div") div.setAttributeNS("http://www.w3.org/1999/xlink", "href", 123) o(div.attributes["href"].value).equals("123") o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink") }) }) o.spec("removeAttribute", function() { o("works", function() { var div = $document.createElement("div") div.setAttribute("id", "aaa") div.removeAttribute("id") o("id" in div.attributes).equals(false) }) }) o.spec("textContent", function() { o("works", function() { var div = $document.createElement("div") div.textContent = "aaa" o(div.childNodes.length).equals(1) o(div.firstChild.nodeType).equals(3) o(div.firstChild.nodeValue).equals("aaa") }) o("works with empty string", function() { var div = $document.createElement("div") div.textContent = "" o(div.childNodes.length).equals(0) }) }) o.spec("innerHTML", function() { o("works", function() { var div = $document.createElement("div") div.innerHTML = "
123234
345
" o(div.childNodes.length).equals(2) o(div.childNodes[0].nodeType).equals(1) o(div.childNodes[0].nodeName).equals("BR") o(div.childNodes[1].nodeType).equals(1) o(div.childNodes[1].nodeName).equals("A") o(div.childNodes[1].attributes["class"].value).equals("aaa") o(div.childNodes[1].attributes["id"].value).equals("xyz") o(div.childNodes[1].childNodes[0].nodeType).equals(3) o(div.childNodes[1].childNodes[0].nodeValue).equals("123") o(div.childNodes[1].childNodes[1].nodeType).equals(1) o(div.childNodes[1].childNodes[1].nodeName).equals("B") o(div.childNodes[1].childNodes[1].attributes["class"].value).equals("bbb") o(div.childNodes[1].childNodes[2].nodeType).equals(3) o(div.childNodes[1].childNodes[2].nodeValue).equals("234") o(div.childNodes[1].childNodes[3].nodeType).equals(1) o(div.childNodes[1].childNodes[3].nodeName).equals("BR") o(div.childNodes[1].childNodes[3].attributes["class"].value).equals("ccc") o(div.childNodes[1].childNodes[4].nodeType).equals(3) o(div.childNodes[1].childNodes[4].nodeValue).equals("345") }) o("headers work", function() { var div = $document.createElement("div") div.innerHTML = "

" o(div.childNodes.length).equals(6) o(div.childNodes[0].nodeType).equals(1) o(div.childNodes[0].nodeName).equals("H1") o(div.childNodes[1].nodeType).equals(1) o(div.childNodes[1].nodeName).equals("H2") o(div.childNodes[2].nodeType).equals(1) o(div.childNodes[2].nodeName).equals("H3") o(div.childNodes[3].nodeType).equals(1) o(div.childNodes[3].nodeName).equals("H4") o(div.childNodes[4].nodeType).equals(1) o(div.childNodes[4].nodeName).equals("H5") o(div.childNodes[5].nodeType).equals(1) o(div.childNodes[5].nodeName).equals("H6") }) o("detaches old elements", function() { var div = $document.createElement("div") var a = $document.createElement("a") div.appendChild(a) div.innerHTML = "" o(a.parentNode).equals(null) }) }) o.spec("focus", function() { o("body is active by default", function() { o($document.documentElement.nodeName).equals("HTML") o($document.body.nodeName).equals("BODY") o($document.documentElement.firstChild.nodeName).equals("HEAD") o($document.documentElement).equals($document.body.parentNode) o($document.activeElement).equals($document.body) }) o("focus changes activeElement", function() { var input = $document.createElement("input") $document.body.appendChild(input) input.focus() o($document.activeElement).equals(input) $document.body.removeChild(input) }) }) o.spec("style", function() { o("has style property", function() { var div = $document.createElement("div") o(typeof div.style).equals("object") }) o("setting style.cssText string works", function() { var div = $document.createElement("div") div.style.cssText = "background-color: red; border-bottom: 1px solid red;" o(div.style.backgroundColor).equals("red") o(div.style.borderBottom).equals("1px solid red") }) o("removing via setting style.cssText string works", function() { var div = $document.createElement("div") div.style.cssText = "background: red;" div.style.cssText = "" o(div.style.background).equals("") }) o("the final semicolon is optional when setting style.cssText", function() { var div = $document.createElement("div") div.style.cssText = "background: red" o(div.style.background).equals("red") o(div.style.cssText).equals("background: red;") }) o("'cssText' as a property name is ignored when setting style.cssText", function(){ var div = $document.createElement("div") div.style.cssText = "cssText: red;" o(div.style.cssText).equals("") }) o("setting style.cssText that has a semi-colon in a strings", function(){ var div = $document.createElement("div") div.style.cssText = "background: url(';'); font-family: \";\"" o(div.style.background).equals("url(';')") o(div.style.fontFamily).equals('";"') o(div.style.cssText).equals("background: url(';'); font-family: \";\";") }) o("comments in style.cssText are stripped", function(){ var div = $document.createElement("div") div.style.cssText = "/**/background/*:*/: /*>;)*/red/**/;/**/" o(div.style.background).equals("red") o(div.style.cssText).equals("background: red;") }) o("comments in strings in style.cssText are preserved", function(){ var div = $document.createElement("div") div.style.cssText = "background: url('/*foo*/')" o(div.style.background).equals("url('/*foo*/')") }) o("setting style throws", function () { var div = $document.createElement("div") var err = false try { div.style = "" } catch (e) { err = e } o(err instanceof Error).equals(true) }) }) o.spec("events", function() { o.spec("click", function() { var spy, div, e o.beforeEach(function() { spy = o.spy() div = $document.createElement("div") e = $document.createEvent("MouseEvents") e.initEvent("click", true, true) $document.body.appendChild(div) }) o.afterEach(function() { $document.body.removeChild(div) }) o("has onclick", function() { o("onclick" in div).equals(true) }) o("addEventListener works", function() { div.addEventListener("click", spy, false) div.dispatchEvent(e) o(spy.callCount).equals(1) o(spy.this).equals(div) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div) }) o("removeEventListener works", function(done) { div.addEventListener("click", spy, false) div.removeEventListener("click", spy, false) div.dispatchEvent(e) o(spy.callCount).equals(0) done() }) o("click fires onclick", function() { div.onclick = spy div.dispatchEvent(e) o(spy.callCount).equals(1) o(spy.this).equals(div) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div) }) o("click without onclick doesn't throw", function(done) { div.dispatchEvent(e) done() }) }) o.spec("transitionend", function() { var spy, div, e o.beforeEach(function() { spy = o.spy() div = $document.createElement("div") e = $document.createEvent("HTMLEvents") e.initEvent("transitionend", true, true) $document.body.appendChild(div) }) o.afterEach(function() { $document.body.removeChild(div) }) o("ontransitionend does not fire", function(done) { div.ontransitionend = spy div.dispatchEvent(e) o(spy.callCount).equals(0) done() }) }) }) o.spec("attributes", function() { o.spec("a[href]", function() { o("is empty string if no attribute", function() { var a = $document.createElement("a") o(a.href).equals("") o(a.attributes["href"]).equals(undefined) }) o("is path if attribute is set", function() { var a = $document.createElement("a") a.setAttribute("href", "") o(a.href).notEquals("") o(a.attributes["href"].value).equals("") }) o("is path if property is set", function() { var a = $document.createElement("a") a.href = "" o(a.href).notEquals("") o(a.attributes["href"].value).equals("") }) }) o.spec("input[checked]", function() { o("only exists in input elements", function() { var input = $document.createElement("input") var a = $document.createElement("a") o("checked" in input).equals(true) o("checked" in a).equals(false) }) o("tracks attribute value when unset", function() { var input = $document.createElement("input") input.setAttribute("type", "checkbox") o(input.checked).equals(false) o(input.attributes["checked"]).equals(undefined) input.setAttribute("checked", "") o(input.checked).equals(true) o(input.attributes["checked"].value).equals("") input.removeAttribute("checked") o(input.checked).equals(false) o(input.attributes["checked"]).equals(undefined) }) o("does not track attribute value when set", function() { var input = $document.createElement("input") input.setAttribute("type", "checkbox") input.checked = true o(input.checked).equals(true) o(input.attributes["checked"]).equals(undefined) input.checked = false input.setAttribute("checked", "") input.checked = true input.removeAttribute("checked") o(input.checked).equals(true) }) o("toggles on click", function() { var input = $document.createElement("input") input.setAttribute("type", "checkbox") input.checked = false var e = $document.createEvent("MouseEvents") e.initEvent("click", true, true) input.dispatchEvent(e) o(input.checked).equals(true) }) }) o.spec("input[value]", function() { o("only exists in input elements", function() { var input = $document.createElement("input") var a = $document.createElement("a") o("value" in input).equals(true) o("value" in a).equals(false) }) o("converts null to ''", function() { var input = $document.createElement("input") input.value = "x" o(input.value).equals("x") input.value = null o(input.value).equals("") }) o("converts values to strings", function() { var input = $document.createElement("input") input.value = 5 o(input.value).equals("5") input.value = 0 o(input.value).equals("0") input.value = undefined o(input.value).equals("undefined") }) if (typeof Symbol === "function") o("throws when set to a symbol", function() { var threw = false var input = $document.createElement("input") try { input.value = Symbol("") } catch (e) { o(e instanceof TypeError).equals(true) threw = true } o(input.value).equals("") o(threw).equals(true) }) }) o.spec("input[type]", function(){ o("only exists in input elements", function() { var input = $document.createElement("input") var a = $document.createElement("a") o("type" in input).equals(true) o("type" in a).equals(false) }) o("is 'text' by default", function() { var input = $document.createElement("input") o(input.type).equals("text") }) "radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image" .split("|").forEach(function(type) { o("can be set to " + type, function(){ var input = $document.createElement("input") input.type = type o(input.getAttribute("type")).equals(type) o(input.type).equals(type) }) o("bad values set the attribute, but the getter corrects to 'text', " + type, function(){ var input = $document.createElement("input") input.type = "badbad" + type o(input.getAttribute("type")).equals("badbad" + type) o(input.type).equals("text") }) }) }) o.spec("textarea[value]", function() { o("reads from child if no value was ever set", function() { var textarea = $document.createElement("textarea") textarea.appendChild($document.createTextNode("aaa")) o(textarea.value).equals("aaa") }) o("ignores child if value set", function() { var textarea = $document.createElement("textarea") textarea.value = null textarea.appendChild($document.createTextNode("aaa")) o(textarea.value).equals("") }) o("textarea[value] doesn't reflect `attributes.value`", function() { var textarea = $document.createElement("textarea") textarea.value = "aaa" textarea.setAttribute("value", "bbb") o(textarea.value).equals("aaa") }) }) o.spec("select[value] and select[selectedIndex]", function() { o("only exist in select elements", function() { var select = $document.createElement("select") var a = $document.createElement("a") o("value" in select).equals(true) o("value" in a).equals(false) o("selectedIndex" in select).equals(true) o("selectedIndex" in a).equals(false) }) o("value defaults to value at first index", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.setAttribute("value", "a") select.appendChild(option1) var option2 = $document.createElement("option") option2.setAttribute("value", "b") select.appendChild(option2) o(select.value).equals("a") o(select.selectedIndex).equals(0) }) o("value falls back to child nodeValue if no attribute", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.appendChild($document.createTextNode("a")) var option2 = $document.createElement("option") option2.appendChild($document.createTextNode("b")) select.appendChild(option1) select.appendChild(option2) o(select.value).equals("a") o(select.selectedIndex).equals(0) o(select.childNodes[0].selected).equals(true) o(select.childNodes[0].value).equals("a") o(select.childNodes[1].value).equals("b") }) o("value defaults to invalid if no options", function() { var select = $document.createElement("select") o(select.value).equals("") o(select.selectedIndex).equals(-1) }) o("setting valid value works", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.setAttribute("value", "a") select.appendChild(option1) var option2 = $document.createElement("option") option2.setAttribute("value", "b") select.appendChild(option2) var option3 = $document.createElement("option") option3.setAttribute("value", "") select.appendChild(option3) var option4 = $document.createElement("option") option4.setAttribute("value", "null") select.appendChild(option4) select.value = "b" o(select.value).equals("b") o(select.selectedIndex).equals(1) select.value = "" o(select.value).equals("") o(select.selectedIndex).equals(2) select.value = "null" o(select.value).equals("null") o(select.selectedIndex).equals(3) select.value = null o(select.value).equals("") o(select.selectedIndex).equals(-1) }) o("setting valid value works with type conversion", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.setAttribute("value", "0") select.appendChild(option1) var option2 = $document.createElement("option") option2.setAttribute("value", "undefined") select.appendChild(option2) var option3 = $document.createElement("option") option3.setAttribute("value", "") select.appendChild(option3) select.value = 0 o(select.value).equals("0") o(select.selectedIndex).equals(0) select.value = undefined o(select.value).equals("undefined") o(select.selectedIndex).equals(1) if (typeof Symbol === "function") { var threw = false try { select.value = Symbol("x") } catch (e) { threw = true } o(threw).equals(true) o(select.value).equals("undefined") o(select.selectedIndex).equals(1) } }) o("option.value = null is converted to the empty string", function() { var option = $document.createElement("option") option.value = null o(option.value).equals("") }) o("setting valid value works with optgroup", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.setAttribute("value", "a") var option2 = $document.createElement("option") option2.setAttribute("value", "b") var option3 = $document.createElement("option") option3.setAttribute("value", "c") var optgroup = $document.createElement("optgroup") optgroup.appendChild(option1) optgroup.appendChild(option2) select.appendChild(optgroup) select.appendChild(option3) select.value = "b" o(select.value).equals("b") o(select.selectedIndex).equals(1) }) o("setting valid selectedIndex works", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.setAttribute("value", "a") select.appendChild(option1) var option2 = $document.createElement("option") option2.setAttribute("value", "b") select.appendChild(option2) select.selectedIndex = 1 o(select.value).equals("b") o(select.selectedIndex).equals(1) }) o("setting option[selected] works", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.setAttribute("value", "a") select.appendChild(option1) var option2 = $document.createElement("option") option2.setAttribute("value", "b") select.appendChild(option2) select.childNodes[1].selected = true o(select.value).equals("b") o(select.selectedIndex).equals(1) }) o("unsetting option[selected] works", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.setAttribute("value", "a") select.appendChild(option1) var option2 = $document.createElement("option") option2.setAttribute("value", "b") select.appendChild(option2) select.childNodes[1].selected = true select.childNodes[1].selected = false o(select.value).equals("a") o(select.selectedIndex).equals(0) }) o("setting invalid value yields a selectedIndex of -1 and value of empty string", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.setAttribute("value", "a") select.appendChild(option1) var option2 = $document.createElement("option") option2.setAttribute("value", "b") select.appendChild(option2) select.value = "c" o(select.value).equals("") o(select.selectedIndex).equals(-1) }) o("setting invalid selectedIndex yields a selectedIndex of -1 and value of empty string", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.setAttribute("value", "a") select.appendChild(option1) var option2 = $document.createElement("option") option2.setAttribute("value", "b") select.appendChild(option2) select.selectedIndex = -2 o(select.value).equals("") o(select.selectedIndex).equals(-1) }) o("setting invalid value yields a selectedIndex of -1 and value of empty string even when there's an option whose value is empty string", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.setAttribute("value", "a") select.appendChild(option1) var option2 = $document.createElement("option") option2.setAttribute("value", "") select.appendChild(option2) select.value = "c" o(select.value).equals("") o(select.selectedIndex).equals(-1) }) o("setting invalid selectedIndex yields a selectedIndex of -1 and value of empty string even when there's an option whose value is empty string", function() { var select = $document.createElement("select") var option1 = $document.createElement("option") option1.setAttribute("value", "a") select.appendChild(option1) var option2 = $document.createElement("option") option2.setAttribute("value", "") select.appendChild(option2) select.selectedIndex = -2 o(select.value).equals("") o(select.selectedIndex).equals(-1) }) }) o.spec("canvas width and height", function() { o("setting property works", function() { var canvas = $document.createElement("canvas") canvas.width = 100 o(canvas.attributes["width"].value).equals("100") o(canvas.width).equals(100) canvas.height = 100 o(canvas.attributes["height"].value).equals("100") o(canvas.height).equals(100) }) o("setting string casts to number", function() { var canvas = $document.createElement("canvas") canvas.width = "100" o(canvas.attributes["width"].value).equals("100") o(canvas.width).equals(100) canvas.height = "100" o(canvas.attributes["height"].value).equals("100") o(canvas.height).equals(100) }) o("setting float casts to int", function() { var canvas = $document.createElement("canvas") canvas.width = 1.2 o(canvas.attributes["width"].value).equals("1") o(canvas.width).equals(1) canvas.height = 1.2 o(canvas.attributes["height"].value).equals("1") o(canvas.height).equals(1) }) o("setting percentage fails", function() { var canvas = $document.createElement("canvas") canvas.width = "100%" o(canvas.attributes["width"].value).equals("0") o(canvas.width).equals(0) canvas.height = "100%" o(canvas.attributes["height"].value).equals("0") o(canvas.height).equals(0) }) o("setting attribute works", function() { var canvas = $document.createElement("canvas") canvas.setAttribute("width", "100%") o(canvas.attributes["width"].value).equals("100%") o(canvas.width).equals(100) canvas.setAttribute("height", "100%") o(canvas.attributes["height"].value).equals("100%") o(canvas.height).equals(100) }) }) }) o.spec("className", function() { o("works", function() { var el = $document.createElement("div") el.className = "a" o(el.className).equals("a") o(el.attributes["class"].value).equals("a") }) o("setter throws in svg", function(done) { var el = $document.createElementNS("http://www.w3.org/2000/svg", "svg") try { el.className = "a" } catch (e) { done() } }) }) o.spec("spies", function() { var $window o.beforeEach(function() { $window = domMock({spy: o.spy}) }) o("basics", function() { o(typeof $window.__getSpies).equals("function") o("__getSpies" in domMock()).equals(false) }) o("input elements have spies on value and type setters", function() { var input = $window.document.createElement("input") var spies = $window.__getSpies(input) o(typeof spies).equals("object") o(spies).notEquals(null) o(typeof spies.valueSetter).equals("function") o(typeof spies.typeSetter).equals("function") o(spies.valueSetter.callCount).equals(0) o(spies.typeSetter.callCount).equals(0) input.value = "aaa" input.type = "radio" o(spies.valueSetter.callCount).equals(1) o(spies.valueSetter.this).equals(input) o(spies.valueSetter.args[0]).equals("aaa") o(spies.typeSetter.callCount).equals(1) o(spies.typeSetter.this).equals(input) o(spies.typeSetter.args[0]).equals("radio") }) o("select elements have spies on value setters", function() { var select = $window.document.createElement("select") var spies = $window.__getSpies(select) o(typeof spies).equals("object") o(spies).notEquals(null) o(typeof spies.valueSetter).equals("function") o(spies.valueSetter.callCount).equals(0) select.value = "aaa" o(spies.valueSetter.callCount).equals(1) o(spies.valueSetter.this).equals(select) o(spies.valueSetter.args[0]).equals("aaa") }) o("option elements have spies on value setters", function() { var option = $window.document.createElement("option") var spies = $window.__getSpies(option) o(typeof spies).equals("object") o(spies).notEquals(null) o(typeof spies.valueSetter).equals("function") o(spies.valueSetter.callCount).equals(0) option.value = "aaa" o(spies.valueSetter.callCount).equals(1) o(spies.valueSetter.this).equals(option) o(spies.valueSetter.args[0]).equals("aaa") }) o("textarea elements have spies on value setters", function() { var textarea = $window.document.createElement("textarea") var spies = $window.__getSpies(textarea) o(typeof spies).equals("object") o(spies).notEquals(null) o(typeof spies.valueSetter).equals("function") o(spies.valueSetter.callCount).equals(0) textarea.value = "aaa" o(spies.valueSetter.callCount).equals(1) o(spies.valueSetter.this).equals(textarea) o(spies.valueSetter.args[0]).equals("aaa") }) }) }) mithril-1.1.7+dfsg/test-utils/tests/test-parseURL.js000066400000000000000000000116151512406206500224030ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var parseURL = require("../../test-utils/parseURL") o.spec("parseURL", function() { var root = {protocol: "http:", hostname: "localhost", port: "", pathname: "/"} o.spec("full URL", function() { o("parses full URL", function() { var data = parseURL("http://www.google.com:80/test?a=b#c") o(data.protocol).equals("http:") o(data.hostname).equals("www.google.com") o(data.port).equals("80") o(data.pathname).equals("/test") o(data.search).equals("?a=b") o(data.hash).equals("#c") }) o("parses full URL omitting optionals", function() { var data = parseURL("http://www.google.com") o(data.protocol).equals("http:") o(data.hostname).equals("www.google.com") o(data.port).equals("") o(data.pathname).equals("/") o(data.search).equals("") o(data.hash).equals("") }) }) o.spec("absolute path", function() { o("parses absolute path", function() { var data = parseURL("/test?a=b#c", root) o(data.protocol).equals(root.protocol) o(data.hostname).equals(root.hostname) o(data.port).equals(root.port) o(data.pathname).equals("/test") o(data.search).equals("?a=b") o(data.hash).equals("#c") }) o("parses absolute path omitting optionals", function() { var data = parseURL("/test?a=b#c", root) o(data.protocol).equals(root.protocol) o(data.hostname).equals(root.hostname) o(data.port).equals(root.port) o(data.pathname).equals("/test") o(data.search).equals("?a=b") o(data.hash).equals("#c") }) }) o.spec("relative path", function() { o("parses relative URL", function() { var data = parseURL("test?a=b#c", root) o(data.protocol).equals(root.protocol) o(data.hostname).equals(root.hostname) o(data.port).equals(root.port) o(data.pathname).equals("/test") o(data.search).equals("?a=b") o(data.hash).equals("#c") }) o("parses relative URL omitting optionals", function() { var data = parseURL("test", root) o(data.protocol).equals(root.protocol) o(data.hostname).equals(root.hostname) o(data.port).equals(root.port) o(data.pathname).equals("/test") o(data.search).equals("") o(data.hash).equals("") }) o("parses relative URL with dot", function() { var data = parseURL("././test?a=b#c", root) o(data.protocol).equals(root.protocol) o(data.hostname).equals(root.hostname) o(data.port).equals(root.port) o(data.pathname).equals("/test") o(data.search).equals("?a=b") o(data.hash).equals("#c") }) o("parses relative URL with dotdot", function() { var data = parseURL("foo/bar/../../test?a=b#c", root) o(data.protocol).equals(root.protocol) o(data.hostname).equals(root.hostname) o(data.port).equals(root.port) o(data.pathname).equals("/test") o(data.search).equals("?a=b") o(data.hash).equals("#c") }) o("clamps invalid dotdot", function() { var data = parseURL("../../test?a=b#c", root) o(data.protocol).equals(root.protocol) o(data.hostname).equals(root.hostname) o(data.port).equals(root.port) o(data.pathname).equals("/test") o(data.search).equals("?a=b") o(data.hash).equals("#c") }) o("clamps invalid dotdot after dot", function() { var data = parseURL("./../../test?a=b#c", root) o(data.protocol).equals(root.protocol) o(data.hostname).equals(root.hostname) o(data.port).equals(root.port) o(data.pathname).equals("/test") o(data.search).equals("?a=b") o(data.hash).equals("#c") }) o("clamps invalid dotdot after valid path", function() { var data = parseURL("a/../../test?a=b#c", root) o(data.protocol).equals(root.protocol) o(data.hostname).equals(root.hostname) o(data.port).equals(root.port) o(data.pathname).equals("/test") o(data.search).equals("?a=b") o(data.hash).equals("#c") }) }) o.spec("edge cases", function() { o("handles hash w/ question mark", function() { var data = parseURL("http://www.google.com/test#a?c") o(data.pathname).equals("/test") o(data.search).equals("") o(data.hash).equals("#a?c") }) o("handles hash w/ slash", function() { var data = parseURL("http://www.google.com/test#a/c") o(data.pathname).equals("/test") o(data.search).equals("") o(data.hash).equals("#a/c") }) o("handles hash w/ colon", function() { var data = parseURL("http://www.google.com/test#a:c") o(data.pathname).equals("/test") o(data.search).equals("") o(data.hash).equals("#a:c") }) o("handles search w/ slash", function() { var data = parseURL("http://www.google.com/test?a/c") o(data.pathname).equals("/test") o(data.search).equals("?a/c") o(data.hash).equals("") }) o("handles search w/ colon", function() { var data = parseURL("http://www.google.com/test?a:c") o(data.pathname).equals("/test") o(data.search).equals("?a:c") o(data.hash).equals("") }) o("handles pathname w/ colon", function() { var data = parseURL("http://www.google.com/a:b") o(data.pathname).equals("/a:b") }) }) }) mithril-1.1.7+dfsg/test-utils/tests/test-pushStateMock.js000066400000000000000000000531461512406206500235050ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var pushStateMock = require("../../test-utils/pushStateMock") var callAsync = require("../../test-utils/callAsync") o.spec("pushStateMock", function() { var $window o.beforeEach(function() { $window = pushStateMock() }) o.spec("initial state", function() { o("has url on page load", function() { o($window.location.href).equals("http://localhost/") }) }) o.spec("set href", function() { o("changes url on location.href change", function() { var old = $window.location.href $window.location.href = "http://localhost/a" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/a") }) o("changes url on relative location.href change", function() { var old = $window.location.href $window.location.href = "a" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/a") o($window.location.pathname).equals("/a") }) o("changes url on dotdot location.href change", function() { $window.location.href = "a" var old = $window.location.href $window.location.href = ".." o(old).equals("http://localhost/a") o($window.location.href).equals("http://localhost/") o($window.location.pathname).equals("/") }) o("changes url on deep dotdot location.href change", function() { $window.location.href = "a/b/c" var old = $window.location.href $window.location.href = ".." o(old).equals("http://localhost/a/b/c") o($window.location.href).equals("http://localhost/a") o($window.location.pathname).equals("/a") }) o("does not change url on dotdot location.href change from root", function() { var old = $window.location.href $window.location.href = ".." o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/") o($window.location.pathname).equals("/") }) o("changes url on dot relative location.href change", function() { var old = $window.location.href $window.location.href = "a" $window.location.href = "./b" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/b") o($window.location.pathname).equals("/b") }) o("does not change url on dot location.href change", function() { var old = $window.location.href $window.location.href = "a" $window.location.href = "." o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/a") o($window.location.pathname).equals("/a") }) o("changes url on hash-only location.href change", function() { var old = $window.location.href $window.location.href = "#a" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/#a") o($window.location.hash).equals("#a") }) o("changes url on search-only location.href change", function() { var old = $window.location.href $window.location.href = "?a" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/?a") o($window.location.search).equals("?a") }) o("changes hash on location.href change", function() { var old = $window.location.href $window.location.href = "http://localhost/a#b" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/a#b") o($window.location.hash).equals("#b") }) o("changes search on location.href change", function() { var old = $window.location.href $window.location.href = "http://localhost/a?b" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/a?b") o($window.location.search).equals("?b") }) o("changes search and hash on location.href change", function() { var old = $window.location.href $window.location.href = "http://localhost/a?b#c" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/a?b#c") o($window.location.search).equals("?b") o($window.location.hash).equals("#c") }) o("handles search with search and hash", function() { var old = $window.location.href $window.location.href = "http://localhost/a?b?c#d" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/a?b?c#d") o($window.location.search).equals("?b?c") o($window.location.hash).equals("#d") }) o("handles hash with search and hash", function() { var old = $window.location.href $window.location.href = "http://localhost/a#b?c#d" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/a#b?c#d") o($window.location.search).equals("") o($window.location.hash).equals("#b?c#d") }) }) o.spec("set search", function() { o("changes url on location.search change", function() { var old = $window.location.href $window.location.search = "?b" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/?b") o($window.location.search).equals("?b") }) }) o.spec("set hash", function() { o("changes url on location.hash change", function() { var old = $window.location.href $window.location.hash = "#b" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/#b") o($window.location.hash).equals("#b") }) }) o.spec("set pathname", function() { o("changes url on location.pathname change", function() { var old = $window.location.href $window.location.pathname = "/a" o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/a") o($window.location.pathname).equals("/a") }) }) o.spec("set protocol", function() { o("setting protocol throws", function(done) { try { $window.location.protocol = "https://" } catch (e) { return done() } throw new Error("Expected an error") }) }) o.spec("set port", function() { o("setting origin changes href", function() { var old = $window.location.href $window.location.port = "81" o(old).equals("http://localhost/") o($window.location.port).equals("81") o($window.location.href).equals("http://localhost:81/") }) }) o.spec("set hostname", function() { o("setting hostname changes href", function() { var old = $window.location.href $window.location.hostname = "127.0.0.1" o(old).equals("http://localhost/") o($window.location.hostname).equals("127.0.0.1") o($window.location.href).equals("http://127.0.0.1/") }) }) o.spec("set origin", function() { o("setting origin is ignored", function() { var old = $window.location.href $window.location.origin = "http://127.0.0.1" o(old).equals("http://localhost/") o($window.location.origin).equals("http://localhost") }) }) o.spec("set host", function() { o("setting host is ignored", function() { var old = $window.location.href $window.location.host = "http://127.0.0.1" o(old).equals("http://localhost/") o($window.location.host).equals("localhost") }) }) o.spec("pushState", function() { o("changes url on pushstate", function() { var old = $window.location.href $window.history.pushState(null, null, "http://localhost/a") o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/a") }) o("changes search on pushstate", function() { var old = $window.location.href $window.history.pushState(null, null, "http://localhost/?a") o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/?a") o($window.location.search).equals("?a") }) o("changes search on relative pushstate", function() { var old = $window.location.href $window.history.pushState(null, null, "?a") o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/?a") o($window.location.search).equals("?a") }) o("changes hash on pushstate", function() { var old = $window.location.href $window.history.pushState(null, null, "http://localhost/#a") o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/#a") o($window.location.hash).equals("#a") }) o("changes hash on relative pushstate", function() { var old = $window.location.href $window.history.pushState(null, null, "#a") o(old).equals("http://localhost/") o($window.location.href).equals("http://localhost/#a") o($window.location.hash).equals("#a") }) }) o.spec("onpopstate", function() { o("history.back() without history does not trigger onpopstate", function() { $window.onpopstate = o.spy() $window.history.back() o($window.onpopstate.callCount).equals(0) }) o("history.back() after pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "http://localhost/a") $window.history.back() o($window.onpopstate.callCount).equals(1) o($window.onpopstate.args[0].type).equals("popstate") }) o("history.back() after relative pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "a") $window.history.back() o($window.onpopstate.callCount).equals(1) }) o("history.back() after search pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "http://localhost/?a") $window.history.back() o($window.onpopstate.callCount).equals(1) }) o("history.back() after relative search pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "?a") $window.history.back() o($window.onpopstate.callCount).equals(1) }) o("history.back() after hash pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "http://localhost/#a") $window.history.back() o($window.onpopstate.callCount).equals(1) }) o("history.back() after relative hash pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "#a") $window.history.back() o($window.onpopstate.callCount).equals(1) }) o("history.back() after replacestate does not trigger onpopstate", function() { $window.onpopstate = o.spy() $window.history.replaceState(null, null, "http://localhost/a") $window.history.back() o($window.onpopstate.callCount).equals(0) }) o("history.back() after relative replacestate does not trigger onpopstate", function() { $window.onpopstate = o.spy() $window.history.replaceState(null, null, "a") $window.history.back() o($window.onpopstate.callCount).equals(0) }) o("history.back() after relative search replacestate does not trigger onpopstate", function() { $window.onpopstate = o.spy() $window.history.replaceState(null, null, "?a") $window.history.back() o($window.onpopstate.callCount).equals(0) }) o("history.back() after relative hash replacestate does not trigger onpopstate", function() { $window.onpopstate = o.spy() $window.history.replaceState(null, null, "#a") $window.history.back() o($window.onpopstate.callCount).equals(0) }) o("history.forward() after pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "http://localhost/a") $window.history.back() $window.history.forward() o($window.onpopstate.callCount).equals(2) }) o("history.forward() after relative pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "a") $window.history.back() $window.history.forward() o($window.onpopstate.callCount).equals(2) }) o("history.forward() after search pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "http://localhost/?a") $window.history.back() $window.history.forward() o($window.onpopstate.callCount).equals(2) }) o("history.forward() after relative search pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "?a") $window.history.back() $window.history.forward() o($window.onpopstate.callCount).equals(2) }) o("history.forward() after hash pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "http://localhost/#a") $window.history.back() $window.history.forward() o($window.onpopstate.callCount).equals(2) }) o("history.forward() after relative hash pushstate triggers onpopstate", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "#a") $window.history.back() $window.history.forward() o($window.onpopstate.callCount).equals(2) }) o("history.forward() without history does not trigger onpopstate", function() { $window.onpopstate = o.spy() $window.history.forward() o($window.onpopstate.callCount).equals(0) }) o("history navigation without history does not trigger onpopstate", function() { $window.onpopstate = o.spy() $window.history.back() $window.history.forward() o($window.onpopstate.callCount).equals(0) }) o("reverse history navigation without history does not trigger onpopstate", function() { $window.onpopstate = o.spy() $window.history.forward() $window.history.back() o($window.onpopstate.callCount).equals(0) }) o("onpopstate has correct url during call", function(done) { $window.location.href = "a" $window.onpopstate = function() { o($window.location.href).equals("http://localhost/a") done() } $window.history.pushState(null, null, "b") $window.history.back() }) o("replaceState does not break forward history", function() { $window.onpopstate = o.spy() $window.history.pushState(null, null, "b") $window.history.back() o($window.onpopstate.callCount).equals(1) o($window.location.href).equals("http://localhost/") $window.history.replaceState(null, null, "a") o($window.location.href).equals("http://localhost/a") $window.history.forward() o($window.onpopstate.callCount).equals(2) o($window.location.href).equals("http://localhost/b") }) o("pushstate retains state", function() { $window.onpopstate = o.spy() $window.history.pushState({a: 1}, null, "#a") $window.history.pushState({b: 2}, null, "#b") o($window.onpopstate.callCount).equals(0) $window.history.back() o($window.onpopstate.callCount).equals(1) o($window.onpopstate.args[0].type).equals("popstate") o($window.onpopstate.args[0].state).deepEquals({a: 1}) $window.history.back() o($window.onpopstate.callCount).equals(2) o($window.onpopstate.args[0].type).equals("popstate") o($window.onpopstate.args[0].state).equals(null) $window.history.forward() o($window.onpopstate.callCount).equals(3) o($window.onpopstate.args[0].type).equals("popstate") o($window.onpopstate.args[0].state).deepEquals({a: 1}) $window.history.forward() o($window.onpopstate.callCount).equals(4) o($window.onpopstate.args[0].type).equals("popstate") o($window.onpopstate.args[0].state).deepEquals({b: 2}) }) o("replacestate replaces state", function() { $window.onpopstate = o.spy(pop) $window.history.replaceState({a: 1}, null, "a") o($window.history.state).deepEquals({a: 1}) $window.history.pushState(null, null, "a") $window.history.back() function pop(e) { o(e.state).deepEquals({a: 1}) o($window.history.state).deepEquals({a: 1}) } }) }) o.spec("onhashchance", function() { o("onhashchange triggers on location.href change", function(done) { $window.onhashchange = o.spy() $window.location.href = "http://localhost/#a" callAsync(function(){ o($window.onhashchange.callCount).equals(1) o($window.onhashchange.args[0].type).equals("hashchange") done() }) }) o("onhashchange triggers on relative location.href change", function(done) { $window.onhashchange = o.spy() $window.location.href = "#a" callAsync(function(){ o($window.onhashchange.callCount).equals(1) done() }) }) o("onhashchange triggers on location.hash change", function(done) { $window.onhashchange = o.spy() $window.location.hash = "#a" callAsync(function(){ o($window.onhashchange.callCount).equals(1) done() }) }) o("onhashchange does not trigger on page change", function(done) { $window.onhashchange = o.spy() $window.location.href = "http://localhost/a" callAsync(function(){ o($window.onhashchange.callCount).equals(0) done() }) }) o("onhashchange does not trigger on page change with different hash", function(done) { $window.location.href = "http://localhost/#a" callAsync(function(){ $window.onhashchange = o.spy() $window.location.href = "http://localhost/a#b" callAsync(function(){ o($window.onhashchange.callCount).equals(0) done() }) }) }) o("onhashchange does not trigger on page change with same hash", function(done) { $window.location.href = "http://localhost/#b" callAsync(function(){ $window.onhashchange = o.spy() $window.location.href = "http://localhost/a#b" callAsync(function(){ o($window.onhashchange.callCount).equals(0) done() }) }) }) o("onhashchange triggers on history.back()", function(done) { $window.location.href = "#a" callAsync(function(){ $window.onhashchange = o.spy() $window.history.back() callAsync(function(){ o($window.onhashchange.callCount).equals(1) done() }) }) }) o("onhashchange triggers on history.forward()", function(done) { $window.location.href = "#a" callAsync(function(){ $window.onhashchange = o.spy() $window.history.back() callAsync(function(){ $window.history.forward() callAsync(function(){ o($window.onhashchange.callCount).equals(2) done() }) }) }) }) o("onhashchange triggers once when the hash changes twice in a single tick", function(done) { $window.location.href = "#a" callAsync(function(){ $window.onhashchange = o.spy() $window.history.back() $window.history.forward() callAsync(function(){ o($window.onhashchange.callCount).equals(1) done() }) }) }) o("onhashchange does not trigger on history.back() that causes page change with different hash", function(done) { $window.location.href = "#a" $window.location.href = "a#b" callAsync(function(){ $window.onhashchange = o.spy() $window.history.back() callAsync(function(){ o($window.onhashchange.callCount).equals(0) done() }) }) }) o("onhashchange does not trigger on history.back() that causes page change with same hash", function(done) { $window.location.href = "#a" $window.location.href = "a#a" callAsync(function(){ $window.onhashchange = o.spy() $window.history.back() callAsync(function(){ o($window.onhashchange.callCount).equals(0) done() }) }) }) o("onhashchange does not trigger on history.forward() that causes page change with different hash", function(done) { $window.location.href = "#a" $window.location.href = "a#b" callAsync(function(){ $window.onhashchange = o.spy() $window.history.back() $window.history.forward() callAsync(function(){ o($window.onhashchange.callCount).equals(0) done() }) }) }) o("onhashchange does not trigger on history.forward() that causes page change with same hash", function(done) { $window.location.href = "#a" $window.location.href = "a#b" callAsync(function(){ $window.onhashchange = o.spy() $window.history.back() $window.history.forward() callAsync(function(){ o($window.onhashchange.callCount).equals(0) done() }) }) }) }) o.spec("onunload", function() { o("onunload triggers on location.href change", function() { $window.onunload = o.spy() $window.location.href = "http://localhost/a" o($window.onunload.callCount).equals(1) o($window.onunload.args[0].type).equals("unload") }) o("onunload triggers on relative location.href change", function() { $window.onunload = o.spy() $window.location.href = "a" o($window.onunload.callCount).equals(1) }) o("onunload triggers on search change via location.href", function() { $window.onunload = o.spy() $window.location.href = "http://localhost/?a" o($window.onunload.callCount).equals(1) }) o("onunload triggers on relative search change via location.href", function() { $window.onunload = o.spy() $window.location.href = "?a" o($window.onunload.callCount).equals(1) }) o("onunload does not trigger on hash change via location.href", function() { $window.onunload = o.spy() $window.location.href = "http://localhost/#a" o($window.onunload.callCount).equals(0) }) o("onunload does not trigger on relative hash change via location.href", function() { $window.onunload = o.spy() $window.location.href = "#a" o($window.onunload.callCount).equals(0) }) o("onunload does not trigger on hash-only history.back()", function() { $window.location.href = "#a" $window.onunload = o.spy() $window.history.back() o($window.onunload.callCount).equals(0) }) o("onunload does not trigger on hash-only history.forward()", function() { $window.location.href = "#a" $window.history.back() $window.onunload = o.spy() $window.history.forward() o($window.onunload.callCount).equals(0) }) o("onunload has correct url during call via location.href change", function(done) { $window.onunload = function() { o($window.location.href).equals("http://localhost/") done() } $window.location.href = "a" }) o("onunload has correct url during call via location.search change", function(done) { $window.onunload = function() { o($window.location.href).equals("http://localhost/") done() } $window.location.search = "?a" }) }) }) mithril-1.1.7+dfsg/test-utils/tests/test-xhrMock.js000066400000000000000000000101271512406206500223160ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var xhrMock = require("../../test-utils/xhrMock") var parseQueryString = require("../../querystring/parse") o.spec("xhrMock", function() { var $window o.beforeEach(function() { $window = xhrMock() }) o.spec("xhr", function() { o("works", function(done) { $window.$defineRoutes({ "GET /item": function(request) { o(request.url).equals("/item") return {status: 200, responseText: "test"} } }) var xhr = new $window.XMLHttpRequest() xhr.open("GET", "/item") xhr.onreadystatechange = function() { if (xhr.readyState === 4) { o(xhr.status).equals(200) o(xhr.responseText).equals("test") done() } } xhr.send() }) o("works w/ search", function(done) { $window.$defineRoutes({ "GET /item": function(request) { o(request.query).equals("?a=b") return {status: 200, responseText: "test"} } }) var xhr = new $window.XMLHttpRequest() xhr.open("GET", "/item?a=b") xhr.onreadystatechange = function() { if (xhr.readyState === 4) { done() } } xhr.send() }) o("works w/ body", function(done) { $window.$defineRoutes({ "POST /item": function(request) { o(request.body).equals("a=b") return {status: 200, responseText: "test"} } }) var xhr = new $window.XMLHttpRequest() xhr.open("POST", "/item") xhr.onreadystatechange = function() { if (xhr.readyState === 4) { done() } } xhr.send("a=b") }) o("handles routing error", function(done) { var xhr = new $window.XMLHttpRequest() xhr.open("GET", "/nonexistent") xhr.onreadystatechange = function() { if (xhr.readyState === 4) { o(xhr.status).equals(500) done() } } xhr.send("a=b") }) o("Setting a header twice merges the header", function() { // Source: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader var xhr = new $window.XMLHttpRequest() xhr.open("POST", "/test") xhr.setRequestHeader("Content-Type", "foo") xhr.setRequestHeader("Content-Type", "bar") o(xhr.getRequestHeader("Content-Type")).equals("foo, bar") }) }) o.spec("jsonp", function() { o("works", function(done) { $window.$defineRoutes({ "GET /test": function(request) { var queryData = parseQueryString(request.query) return {status: 200, responseText: queryData["callback"] + "(" + JSON.stringify({a: 1}) + ")"} } }) $window["cb"] = finish var script = $window.document.createElement("script") script.src = "/test?callback=cb" $window.document.documentElement.appendChild(script) function finish(data) { o(data).deepEquals({a: 1}) done() } }) o("works w/ custom callback key", function(done) { $window.$defineRoutes({ "GET /test": function(request) { var queryData = parseQueryString(request.query) return {status: 200, responseText: queryData["cb"] + "(" + JSON.stringify({a: 2}) + ")"} } }) $window.$defineJSONPCallbackKey("cb") $window["customcb"] = finish2 var script = $window.document.createElement("script") script.src = "/test?cb=customcb" $window.document.documentElement.appendChild(script) function finish2(data) { o(data).deepEquals({a: 2}) done() } }) o("works with other querystring params", function(done) { $window.$defineRoutes({ "GET /test": function(request) { var queryData = parseQueryString(request.query) return {status: 200, responseText: queryData["callback"] + "(" + JSON.stringify({a: 3}) + ")"} } }) $window["cbwithinparams"] = finish var script = $window.document.createElement("script") script.src = "/test?a=b&callback=cbwithinparams&c=d" $window.document.documentElement.appendChild(script) function finish(data) { o(data).deepEquals({a: 3}) done() } }) o("handles error", function(done) { var script = $window.document.createElement("script") script.onerror = finish script.src = "/test?cb=nonexistent" $window.document.documentElement.appendChild(script) function finish(e) { o(e.type).equals("error") done() } }) }) }) mithril-1.1.7+dfsg/test-utils/xhrMock.js000066400000000000000000000061041512406206500201770ustar00rootroot00000000000000"use strict" var callAsync = require("../test-utils/callAsync") var parseURL = require("../test-utils/parseURL") var parseQueryString = require("../querystring/parse") module.exports = function() { var routes = {} // var callback = "callback" var serverErrorHandler = function(url) { return {status: 500, responseText: "server error, most likely the URL was not defined " + url} } var $window = { XMLHttpRequest: function XMLHttpRequest() { var args = {} var headers = {} var aborted = false this.setRequestHeader = function(header, value) { /* the behavior of setHeader is not your expected setX API. If the header is already set, it'll merge with whatever you add rather than overwrite Source: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader */ if (headers[header]) { headers[header] += ", " + value; } else { headers[header] = value } } this.getRequestHeader = function(header) { return headers[header] } this.open = function(method, url, async, user, password) { var urlData = parseURL(url, {protocol: "http:", hostname: "localhost", port: "", pathname: "/"}) args.method = method args.pathname = urlData.pathname args.search = urlData.search args.async = async != null ? async : true args.user = user args.password = password } this.send = function(body) { var self = this if(!aborted) { var handler = routes[args.method + " " + args.pathname] || serverErrorHandler.bind(null, args.pathname) var data = handler({url: args.pathname, query: args.search || {}, body: body || null}) self.status = data.status self.responseText = data.responseText } else { self.status = 0 } self.readyState = 4 if (args.async === true) { callAsync(function() { if (typeof self.onreadystatechange === "function") self.onreadystatechange() }) } } this.abort = function() { aborted = true } }, document: { createElement: function(tag) { return {nodeName: tag.toUpperCase(), parentNode: null} }, documentElement: { appendChild: function(element) { element.parentNode = this if (element.nodeName === "SCRIPT") { var urlData = parseURL(element.src, {protocol: "http:", hostname: "localhost", port: "", pathname: "/"}) var handler = routes["GET " + urlData.pathname] || serverErrorHandler.bind(null, element.src) var data = handler({url: urlData.pathname, query: urlData.search, body: null}) parseQueryString(urlData.search) callAsync(function() { if (data.status === 200) { new Function("$window", "with ($window) return " + data.responseText).call($window, $window) } else if (typeof element.onerror === "function") { element.onerror({type: "error"}) } }) } }, removeChild: function(element) { element.parentNode = null }, }, }, $defineRoutes: function(rules) { routes = rules }, $defineJSONPCallbackKey: function(/* key */) { // callback = key }, } return $window } mithril-1.1.7+dfsg/tests/000077500000000000000000000000001512406206500152525ustar00rootroot00000000000000mithril-1.1.7+dfsg/tests/index.html000066400000000000000000000012461512406206500172520ustar00rootroot00000000000000 mithril-1.1.7+dfsg/tests/test-api.js000066400000000000000000000104241512406206500173370ustar00rootroot00000000000000"use strict" var o = require("../ospec/ospec") var browserMock = require("../test-utils/browserMock") var components = require("../test-utils/components") o.spec("api", function() { var m var FRAME_BUDGET = Math.floor(1000 / 60) o.beforeEach(function() { var mock = browserMock() if (typeof global !== "undefined") global.window = mock m = require("../mithril") // eslint-disable-line global-require }) o.spec("m", function() { o("works", function() { var vnode = m("div") o(vnode.tag).equals("div") }) }) o.spec("m.version", function() { o("works", function() { o(typeof m.version).equals("string") o(m.version.indexOf(".") > -1).equals(true) o(/\d/.test(m.version)).equals(true) }) }) o.spec("m.trust", function() { o("works", function() { var vnode = m.trust("
") o(vnode.tag).equals("<") o(vnode.children).equals("
") }) }) o.spec("m.fragment", function() { o("works", function() { var vnode = m.fragment({key: 123}, [m("div")]) o(vnode.tag).equals("[") o(vnode.key).equals(123) o(vnode.children.length).equals(1) o(vnode.children[0].tag).equals("div") }) }) o.spec("m.withAttr", function() { o("works", function() { var spy = o.spy() var handler = m.withAttr("value", spy) handler({currentTarget: {value: 10}}) o(spy.args[0]).equals(10) }) }) o.spec("m.parseQueryString", function() { o("works", function() { var query = m.parseQueryString("?a=1&b=2") o(query).deepEquals({a: "1", b: "2"}) }) }) o.spec("m.buildQueryString", function() { o("works", function() { var query = m.buildQueryString({a: 1, b: 2}) o(query).equals("a=1&b=2") }) }) o.spec("m.request", function() { o("works", function() { o(typeof m.request).equals("function") // TODO improve }) }) o.spec("m.jsonp", function() { o("works", function() { o(typeof m.jsonp).equals("function") // TODO improve }) }) o.spec("m.render", function() { o("works", function() { var root = window.document.createElement("div") m.render(root, m("div")) o(root.childNodes.length).equals(1) o(root.firstChild.nodeName).equals("DIV") }) }) components.forEach(function(cmp){ o.spec(cmp.kind, function(){ var createComponent = cmp.create o.spec("m.mount", function() { o("works", function() { var root = window.document.createElement("div") m.mount(root, createComponent({view: function() {return m("div")}})) o(root.childNodes.length).equals(1) o(root.firstChild.nodeName).equals("DIV") }) }) o.spec("m.route", function() { o("works", function(done) { var root = window.document.createElement("div") m.route(root, "/a", { "/a": createComponent({view: function() {return m("div")}}) }) setTimeout(function() { o(root.childNodes.length).equals(1) o(root.firstChild.nodeName).equals("DIV") done() }, FRAME_BUDGET) }) o("m.route.prefix", function(done) { var root = window.document.createElement("div") m.route.prefix("#") m.route(root, "/a", { "/a": createComponent({view: function() {return m("div")}}) }) setTimeout(function() { o(root.childNodes.length).equals(1) o(root.firstChild.nodeName).equals("DIV") done() }, FRAME_BUDGET) }) o("m.route.get", function(done) { var root = window.document.createElement("div") m.route(root, "/a", { "/a": createComponent({view: function() {return m("div")}}) }) setTimeout(function() { o(m.route.get()).equals("/a") done() }, FRAME_BUDGET) }) o("m.route.set", function(done, timeout) { timeout(100) var root = window.document.createElement("div") m.route(root, "/a", { "/:id": createComponent({view: function() {return m("div")}}) }) setTimeout(function() { m.route.set("/b") setTimeout(function() { o(m.route.get()).equals("/b") done() }, FRAME_BUDGET) }, FRAME_BUDGET) }) }) o.spec("m.redraw", function() { o("works", function(done) { var count = 0 var root = window.document.createElement("div") m.mount(root, createComponent({view: function() {count++}})) setTimeout(function() { m.redraw() o(count).equals(2) done() }, FRAME_BUDGET) }) }) }) }) }) mithril-1.1.7+dfsg/util/000077500000000000000000000000001512406206500150655ustar00rootroot00000000000000mithril-1.1.7+dfsg/util/tests/000077500000000000000000000000001512406206500162275ustar00rootroot00000000000000mithril-1.1.7+dfsg/util/tests/index.html000066400000000000000000000005201512406206500202210ustar00rootroot00000000000000 mithril-1.1.7+dfsg/util/tests/test-withAttr.js000066400000000000000000000015231512406206500213510ustar00rootroot00000000000000"use strict" var o = require("../../ospec/ospec") var withAttr = require("../../util/withAttr") o.spec("withAttr", function() { o("works", function() { var spy = o.spy() var context = { handler: withAttr("value", spy) } context.handler({currentTarget: {value: 1}}) o(spy.args).deepEquals([1]) o(spy.this).equals(context) }) o("works with attribute", function() { var target = { getAttribute: function() {return "readonly"} } var spy = o.spy() var context = { handler: withAttr("readonly", spy) } context.handler({currentTarget: target}) o(spy.args).deepEquals(["readonly"]) o(spy.this).equals(context) }) o("context arg works", function() { var spy = o.spy() var context = {} var handler = withAttr("value", spy, context) handler({currentTarget: {value: 1}}) o(spy.this).equals(context) }) }) mithril-1.1.7+dfsg/util/withAttr.js000066400000000000000000000003451512406206500172330ustar00rootroot00000000000000"use strict" module.exports = function(attrName, callback, context) { return function(e) { callback.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName)) } }