merge: next onto master

This commit is contained in:
Pat Cavit 2017-03-27 14:21:52 -07:00
commit 46ebb340d1
76 changed files with 3098 additions and 2017 deletions

View file

@ -1,11 +1,7 @@
coverage
.vscode .vscode
examples /coverage
docs /docs/lib
node_modules /examples
tests /mithril.js
test-utils /mithril.min.js
ospec /node_modules
mithril.js
mithril.min.js
archive

View file

@ -60,7 +60,14 @@ module.exports = {
"id-blacklist": "error", "id-blacklist": "error",
"id-length": "off", "id-length": "off",
"id-match": "error", "id-match": "error",
"indent": "off", "indent": [
"warn",
"tab",
{
"outerIIFEBody": 0,
"SwitchCase": 1
}
],
"init-declarations": "off", "init-declarations": "off",
"jsx-quotes": "error", "jsx-quotes": "error",
"key-spacing": "off", "key-spacing": "off",
@ -188,7 +195,7 @@ module.exports = {
"quotes": [ "quotes": [
"error", "error",
"double", "double",
"avoid-escape" {"avoidEscape": true}
], ],
"radix": [ "radix": [
"error", "error",
@ -209,7 +216,7 @@ module.exports = {
"space-infix-ops": "off", "space-infix-ops": "off",
"space-unary-ops": "error", "space-unary-ops": "error",
"spaced-comment": "off", "spaced-comment": "off",
"strict": "off", "strict": ["error", "global"],
"template-curly-spacing": "error", "template-curly-spacing": "error",
"valid-jsdoc": "off", "valid-jsdoc": "off",
"vars-on-top": "off", "vars-on-top": "off",
@ -217,5 +224,6 @@ module.exports = {
"wrap-regex": "error", "wrap-regex": "error",
"yield-star-spacing": "error", "yield-star-spacing": "error",
"yoda": "off" "yoda": "off"
} },
"root": true
}; };

3
.gitattributes vendored
View file

@ -1 +1,4 @@
* text=auto * text=auto
/mithril.js binary
/mithril.min.js binary
/stream/stream.js binary

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ jsconfig.json
npm-debug.log npm-debug.log
.vscode .vscode
.DS_Store .DS_Store
.eslintcache

View file

@ -44,6 +44,8 @@ Mithril is used by companies like Vimeo and Nike, and open source platforms like
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. 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 ### Getting started
@ -54,7 +56,7 @@ Let's create an HTML file to follow along:
```markup ```markup
<body> <body>
<script src="http://unpkg.com/mithril/mithril.js"></script> <script src="//unpkg.com/mithril/mithril.js"></script>
<script> <script>
var root = document.body var root = document.body
@ -67,7 +69,7 @@ Let's create an HTML file to follow along:
### Hello world ### Hello world
Let's start as small as well 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) 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 ```javascript
var root = document.body var root = document.body
@ -93,7 +95,7 @@ Let's wrap our text in an `<h1>` tag.
m.render(root, m("h1", "My first app")) m.render(root, m("h1", "My first app"))
``` ```
The `m()` function can be used to describe any HTML structure you want. So if you to add a class to the `<h1>`: The `m()` function can be used to describe any HTML structure you want. So if you need to add a class to the `<h1>`:
```javascript ```javascript
m("h1", {class: "title"}, "My first app") m("h1", {class: "title"}, "My first app")
@ -231,7 +233,7 @@ var count = 0
var increment = function() { var increment = function() {
m.request({ m.request({
method: "PUT", method: "PUT",
url: "http://rem-rest-api.herokuapp.com/api/tutorial/1", url: "//rem-rest-api.herokuapp.com/api/tutorial/1",
data: {count: count + 1}, data: {count: count + 1},
withCredentials: true, withCredentials: true,
}) })

View file

@ -10,7 +10,7 @@ module.exports = function(redrawService) {
return return
} }
if (component.view == null) throw new Error("m.mount(element, component) expects a component, not a vnode") if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode")
var run = function() { var run = function() {
redrawService.render(root, Vnode(component)) redrawService.render(root, Vnode(component))

View file

@ -14,17 +14,19 @@ module.exports = function($window, redrawService) {
var run = function() { var run = function() {
if (render != null) redrawService.render(root, render(Vnode(component, attrs.key, attrs))) if (render != null) redrawService.render(root, render(Vnode(component, attrs.key, attrs)))
} }
var bail = function() { var bail = function(path) {
routeService.setPath(defaultRoute, null, {replace: true}) 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) { routeService.defineRoutes(routes, function(payload, params, path) {
var update = lastUpdate = function(routeResolver, comp) { var update = lastUpdate = function(routeResolver, comp) {
if (update !== lastUpdate) return if (update !== lastUpdate) return
component = comp != null && typeof comp.view === "function" ? comp : "div", attrs = params, currentPath = path, lastUpdate = null component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
attrs = params, currentPath = path, lastUpdate = null
render = (routeResolver.render || identity).bind(routeResolver) render = (routeResolver.render || identity).bind(routeResolver)
run() run()
} }
if (payload.view) update({}, payload) if (payload.view || typeof payload === "function") update({}, payload)
else { else {
if (payload.onmatch) { if (payload.onmatch) {
Promise.resolve(payload.onmatch(params, path)).then(function(resolved) { Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {

View file

@ -13,6 +13,7 @@
<script src="../../test-utils/pushStateMock.js"></script> <script src="../../test-utils/pushStateMock.js"></script>
<script src="../../test-utils/xhrMock.js"></script> <script src="../../test-utils/xhrMock.js"></script>
<script src="../../test-utils/browserMock.js"></script> <script src="../../test-utils/browserMock.js"></script>
<script src="../../test-utils/component.js"></script>
<script src="../../promise/promise.js"></script> <script src="../../promise/promise.js"></script>
<script src="../../render/vnode.js"></script> <script src="../../render/vnode.js"></script>

View file

@ -1,6 +1,7 @@
"use strict" "use strict"
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock") var domMock = require("../../test-utils/domMock")
var m = require("../../render/hyperscript") var m = require("../../render/hyperscript")
@ -22,10 +23,24 @@ o.spec("mount", function() {
render = coreRenderer($window).render render = coreRenderer($window).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() { o("throws on invalid `root` DOM node", function() {
var threw = false var threw = false
try { try {
mount(null, {view: function() {}}) mount(null, createComponent({view: function() {}}))
} catch (e) { } catch (e) {
threw = true threw = true
} }
@ -33,21 +48,21 @@ o.spec("mount", function() {
}) })
o("renders into `root`", function() { o("renders into `root`", function() {
mount(root, { mount(root, createComponent({
view : function() { view : function() {
return m("div") return m("div")
} }
}) }))
o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.nodeName).equals("DIV")
}) })
o("mounting null unmounts", function() { o("mounting null unmounts", function() {
mount(root, { mount(root, createComponent({
view : function() { view : function() {
return m("div") return m("div")
} }
}) }))
mount(root, null) mount(root, null)
@ -62,7 +77,7 @@ o.spec("mount", function() {
e.initEvent("click", true, true) e.initEvent("click", true, true)
mount(root, { mount(root, createComponent({
view : function() { view : function() {
return m("div", { return m("div", {
oninit : oninit, oninit : oninit,
@ -70,7 +85,7 @@ o.spec("mount", function() {
onclick : onclick, onclick : onclick,
}) })
} }
}) }))
root.firstChild.dispatchEvent(e) root.firstChild.dispatchEvent(e)
@ -109,7 +124,7 @@ o.spec("mount", function() {
m("#child1") m("#child1")
]) ])
mount(root.childNodes[0], { mount(root.childNodes[0], createComponent({
view : function() { view : function() {
return m("div", { return m("div", {
oninit : oninit0, oninit : oninit0,
@ -117,12 +132,12 @@ o.spec("mount", function() {
onclick : onclick0, onclick : onclick0,
}) })
} }
}) }))
o(oninit0.callCount).equals(1) o(oninit0.callCount).equals(1)
o(onupdate0.callCount).equals(0) o(onupdate0.callCount).equals(0)
mount(root.childNodes[1], { mount(root.childNodes[1], createComponent({
view : function() { view : function() {
return m("div", { return m("div", {
oninit : oninit1, oninit : oninit1,
@ -130,7 +145,7 @@ o.spec("mount", function() {
onclick : onclick1, onclick : onclick1,
}) })
} }
}) }))
o(oninit1.callCount).equals(1) o(oninit1.callCount).equals(1)
o(onupdate1.callCount).equals(0) o(onupdate1.callCount).equals(0)
@ -164,7 +179,7 @@ o.spec("mount", function() {
e.initEvent("click", true, true) e.initEvent("click", true, true)
mount(root, { mount(root, createComponent({
view: function() { view: function() {
return m("div", { return m("div", {
oninit: oninit, oninit: oninit,
@ -174,7 +189,7 @@ o.spec("mount", function() {
} }
}) })
} }
}) }))
root.firstChild.dispatchEvent(e) root.firstChild.dispatchEvent(e)
@ -192,14 +207,14 @@ o.spec("mount", function() {
var onupdate = o.spy() var onupdate = o.spy()
var oninit = o.spy() var oninit = o.spy()
mount(root, { mount(root, createComponent({
view : function() { view : function() {
return m("div", { return m("div", {
oninit: oninit, oninit: oninit,
onupdate: onupdate onupdate: onupdate
}) })
} }
}) }))
o(oninit.callCount).equals(1) o(oninit.callCount).equals(1)
o(onupdate.callCount).equals(0) o(onupdate.callCount).equals(0)
@ -218,7 +233,7 @@ o.spec("mount", function() {
timeout(200) timeout(200)
var i = 0 var i = 0
mount(root, {view: function() {i++}}) mount(root, createComponent({view: function() {i++}}))
var before = i var before = i
redrawService.redraw() redrawService.redraw()
@ -236,3 +251,5 @@ o.spec("mount", function() {
},40) },40)
}) })
}) })
})
})

View file

@ -6,7 +6,6 @@ var browserMock = require("../../test-utils/browserMock")
var m = require("../../render/hyperscript") var m = require("../../render/hyperscript")
var callAsync = require("../../test-utils/callAsync") var callAsync = require("../../test-utils/callAsync")
var coreRenderer = require("../../render/render")
var apiRedraw = require("../../api/redraw") var apiRedraw = require("../../api/redraw")
var apiRouter = require("../../api/router") var apiRouter = require("../../api/router")
var Promise = require("../../promise/promise") var Promise = require("../../promise/promise")
@ -31,7 +30,7 @@ o.spec("route", function() {
o("throws on invalid `root` DOM node", function() { o("throws on invalid `root` DOM node", function() {
var threw = false var threw = false
try { try {
route(null, '/', {'/':{view: function() {}}}) route(null, "/", {"/":{view: function() {}}})
} catch (e) { } catch (e) {
threw = true threw = true
} }
@ -51,7 +50,7 @@ o.spec("route", function() {
o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.nodeName).equals("DIV")
}) })
o("routed mount points can redraw synchronously (#1275)", function() { o("routed mount points can redraw synchronously (POJO component)", function() {
var view = o.spy() var view = o.spy()
$window.location.href = prefix + "/" $window.location.href = prefix + "/"
@ -65,6 +64,39 @@ o.spec("route", function() {
}) })
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) { o("default route doesn't break back button", function(done) {
$window.location.href = "http://old.com" $window.location.href = "http://old.com"
$window.location.href = "http://new.com" $window.location.href = "http://new.com"
@ -173,7 +205,6 @@ o.spec("route", function() {
o("event handlers can skip redraw", function(done) { o("event handlers can skip redraw", function(done) {
var onupdate = o.spy() var onupdate = o.spy()
var oninit = o.spy() var oninit = o.spy()
var onclick = o.spy()
var e = $window.document.createEvent("MouseEvents") var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true) e.initEvent("click", true, true)
@ -321,11 +352,6 @@ o.spec("route", function() {
o("accepts RouteResolver with onmatch that returns Promise<undefined>", function(done) { o("accepts RouteResolver with onmatch that returns Promise<undefined>", function(done) {
var matchCount = 0 var matchCount = 0
var renderCount = 0 var renderCount = 0
var Component = {
view: function() {
return m("span")
}
}
var resolver = { var resolver = {
onmatch: function(args, requestedPath) { onmatch: function(args, requestedPath) {
@ -362,11 +388,6 @@ o.spec("route", function() {
o("accepts RouteResolver with onmatch that returns Promise<any>", function(done) { o("accepts RouteResolver with onmatch that returns Promise<any>", function(done) {
var matchCount = 0 var matchCount = 0
var renderCount = 0 var renderCount = 0
var Component = {
view: function() {
return m("span")
}
}
var resolver = { var resolver = {
onmatch: function(args, requestedPath) { onmatch: function(args, requestedPath) {
@ -404,14 +425,9 @@ o.spec("route", function() {
var matchCount = 0 var matchCount = 0
var renderCount = 0 var renderCount = 0
var spy = o.spy() var spy = o.spy()
var Component = {
view: function() {
return m("span")
}
}
var resolver = { var resolver = {
onmatch: function(args, requestedPath) { onmatch: function() {
matchCount++ matchCount++
return Promise.reject(new Error("error")) return Promise.reject(new Error("error"))
}, },
@ -466,7 +482,7 @@ o.spec("route", function() {
}) })
}) })
o("changing `vnode.key` in `render` resets the component", function(done, timeout){ o("changing `vnode.key` in `render` resets the component", function(done){
var oninit = o.spy() var oninit = o.spy()
var Component = { var Component = {
oninit: oninit, oninit: oninit,
@ -512,25 +528,19 @@ o.spec("route", function() {
}) })
o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.nodeName).equals("DIV")
o(renderCount).equals(1)
}) })
o("RouteResolver `render` does not have component semantics", function(done) { o("RouteResolver `render` does not have component semantics", function(done) {
var renderCount = 0
var A = {
view: function() {
return m("div")
}
}
$window.location.href = prefix + "/a" $window.location.href = prefix + "/a"
route(root, "/a", { route(root, "/a", {
"/a" : { "/a" : {
render: function(vnode) { render: function() {
return m("div") return m("div")
}, },
}, },
"/b" : { "/b" : {
render: function(vnode) { render: function() {
return m("div") return m("div")
}, },
}, },
@ -599,7 +609,7 @@ o.spec("route", function() {
onmatch: function() { onmatch: function() {
matchCount++ matchCount++
}, },
render: function(vnode) { render: function() {
renderCount++ renderCount++
return {tag: Component} return {tag: Component}
}, },
@ -693,7 +703,7 @@ o.spec("route", function() {
render: render render: render
}, },
"/b" : { "/b" : {
render: function(vnode){ render: function(){
redirected = true redirected = true
} }
} }
@ -805,7 +815,7 @@ o.spec("route", function() {
}) })
callAsync(function() { callAsync(function() {
route.set('/b') route.set("/b")
callAsync(function() { callAsync(function() {
callAsync(function() { callAsync(function() {
callAsync(function() { callAsync(function() {
@ -832,7 +842,7 @@ o.spec("route", function() {
render: render render: render
}, },
"/b" : { "/b" : {
onmatch: function(vnode){ onmatch: function(){
redirected = true redirected = true
return {view: function() {}} return {view: function() {}}
} }
@ -862,7 +872,7 @@ o.spec("route", function() {
render: render render: render
}, },
"/b" : { "/b" : {
render: function(vnode){ render: function(){
redirected = true redirected = true
} }
} }
@ -891,7 +901,7 @@ o.spec("route", function() {
render: render render: render
}, },
"/b" : { "/b" : {
view: function(vnode){ view: function(){
redirected = true redirected = true
} }
} }
@ -999,7 +1009,7 @@ o.spec("route", function() {
var render = o.spy(function() {return m("div")}) var render = o.spy(function() {return m("div")})
$window.location.href = prefix + "/" $window.location.href = prefix + "/"
route(root, '/', { route(root, "/", {
"/": { "/": {
onmatch: onmatch, onmatch: onmatch,
render: render render: render
@ -1048,23 +1058,23 @@ o.spec("route", function() {
o("routing with RouteResolver works more than once", function(done) { o("routing with RouteResolver works more than once", function(done) {
$window.location.href = prefix + "/a" $window.location.href = prefix + "/a"
route(root, '/a', { route(root, "/a", {
'/a': { "/a": {
render: function() { render: function() {
return m("a", "a") return m("a", "a")
} }
}, },
'/b': { "/b": {
render: function() { render: function() {
return m("b", "b") return m("b", "b")
} }
} }
}) })
route.set('/b') route.set("/b")
callAsync(function() { callAsync(function() {
route.set('/a') route.set("/a")
callAsync(function() { callAsync(function() {
o(root.firstChild.nodeName).equals("A") o(root.firstChild.nodeName).equals("A")
@ -1089,7 +1099,7 @@ o.spec("route", function() {
}) })
}) })
}, },
render: function(vnode) { render: function() {
rendered = true rendered = true
resolved = "a" resolved = "a"
} }
@ -1147,7 +1157,7 @@ o.spec("route", function() {
route.set("/b") route.set("/b")
}) })
}, },
render: function(vnode) { render: function() {
rendered = true rendered = true
resolved = "a" resolved = "a"
} }
@ -1177,7 +1187,7 @@ o.spec("route", function() {
var i = 0 var i = 0
$window.location.href = prefix + "/" $window.location.href = prefix + "/"
route(root, "/", { route(root, "/", {
"/": {view: function(v) {i++}} "/": {view: function() {i++}}
}) })
var before = i var before = i

View file

@ -1,3 +1,5 @@
"use strict"
var m = require("./index") var m = require("./index")
if (typeof module !== "undefined") module["exports"] = m if (typeof module !== "undefined") module["exports"] = m
else window.m = m else window.m = m

View file

@ -1,3 +1,4 @@
#!/usr/bin/env node #!/usr/bin/env node
"use strict"
require("../cli") require("../cli")

View file

@ -114,7 +114,7 @@ function run(input, output) {
.replace(/(\r|\n)+/g, "\n").replace(/(\r|\n)$/, "") // remove multiline breaks .replace(/(\r|\n)+/g, "\n").replace(/(\r|\n)$/, "") // remove multiline breaks
.replace(versionTag, isFile(packageFile) ? parse(packageFile).version : versionTag) // set version .replace(versionTag, isFile(packageFile) ? parse(packageFile).version : versionTag) // set version
code = "new function() {\n" + code + "\n}" code = ";(function() {\n" + code + "\n}());"
if (!isFile(output) || code !== read(output)) { if (!isFile(output) || code !== read(output)) {
//try {new Function(code); console.log("build completed at " + new Date())} catch (e) {} //try {new Function(code); console.log("build completed at " + new Date())} catch (e) {}

View file

@ -1,3 +1,5 @@
"use strict"
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var bundle = require("../bundle") var bundle = require("../bundle")
@ -5,10 +7,10 @@ var fs = require("fs")
var ns = "bundler/tests/" var ns = "bundler/tests/"
function read(filepath) { function read(filepath) {
try {return fs.readFileSync(ns + filepath, "utf8")} catch (e) {} try {return fs.readFileSync(ns + filepath, "utf8")} catch (e) {/* ignore */}
} }
function write(filepath, data) { function write(filepath, data) {
try {var exists = fs.statSync(ns + filepath).isFile()} catch (e) {} try {var exists = fs.statSync(ns + filepath).isFile()} catch (e) {/* ignore */}
if (exists) throw new Error("Don't call `write('" + filepath + "')`. Cannot overwrite file") if (exists) throw new Error("Don't call `write('" + filepath + "')`. Cannot overwrite file")
fs.writeFileSync(ns + filepath, data, "utf8") fs.writeFileSync(ns + filepath, data, "utf8")
} }
@ -18,33 +20,33 @@ function remove(filepath) {
o.spec("bundler", function() { o.spec("bundler", function() {
o("relative imports works", function() { o("relative imports works", function() {
write("a.js", `var b = require("./b")`) write("a.js", 'var b = require("./b")')
write("b.js", `module.exports = 1`) write("b.js", "module.exports = 1")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b = 1\n}`) o(read("out.js")).equals(";(function() {\nvar b = 1\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("relative imports works with semicolons", function() { o("relative imports works with semicolons", function() {
write("a.js", `var b = require("./b");`) write("a.js", 'var b = require("./b");')
write("b.js", `module.exports = 1;`) write("b.js", "module.exports = 1;")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b = 1;\n}`) o(read("out.js")).equals(";(function() {\nvar b = 1;\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("relative imports works with let", function() { o("relative imports works with let", function() {
write("a.js", `let b = require("./b")`) write("a.js", 'let b = require("./b")')
write("b.js", `module.exports = 1`) write("b.js", "module.exports = 1")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nlet b = 1\n}`) o(read("out.js")).equals(";(function() {\nlet b = 1\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
@ -52,143 +54,143 @@ o.spec("bundler", function() {
}) })
o("relative imports works with const", function() { o("relative imports works with const", function() {
write("a.js", 'const b = require("./b")') write("a.js", 'const b = require("./b")')
write("b.js", `module.exports = 1`) write("b.js", "module.exports = 1")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nconst b = 1\n}`) o(read("out.js")).equals(";(function() {\nconst b = 1\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("relative imports works with assignment", function() { o("relative imports works with assignment", function() {
write("a.js", `var a = {}\na.b = require("./b")`) write("a.js", 'var a = {}\na.b = require("./b")')
write("b.js", `module.exports = 1`) write("b.js", "module.exports = 1")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar a = {}\na.b = 1\n}`) o(read("out.js")).equals(";(function() {\nvar a = {}\na.b = 1\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("relative imports works with reassignment", function() { o("relative imports works with reassignment", function() {
write("a.js", `var b = {}\nb = require("./b")`) write("a.js", 'var b = {}\nb = require("./b")')
write("b.js", `module.exports = 1`) write("b.js", "module.exports = 1")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b = {}\nb = 1\n}`) o(read("out.js")).equals(";(function() {\nvar b = {}\nb = 1\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("relative imports removes extra use strict", function() { o("relative imports removes extra use strict", function() {
write("a.js", `"use strict"\nvar b = require("./b")`) write("a.js", '"use strict"\nvar b = require("./b")')
write("b.js", `"use strict"\nmodule.exports = 1`) write("b.js", '"use strict"\nmodule.exports = 1')
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\n"use strict"\nvar b = 1\n}`) o(read("out.js")).equals(';(function() {\n"use strict"\nvar b = 1\n}());')
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("relative imports removes extra use strict using single quotes", function() { o("relative imports removes extra use strict using single quotes", function() {
write("a.js", `'use strict'\nvar b = require("./b")`) write("a.js", "'use strict'\nvar b = require(\"./b\")")
write("b.js", `'use strict'\nmodule.exports = 1`) write("b.js", "'use strict'\nmodule.exports = 1")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\n'use strict'\nvar b = 1\n}`) o(read("out.js")).equals(";(function() {\n'use strict'\nvar b = 1\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("relative imports removes extra use strict using mixed quotes", function() { o("relative imports removes extra use strict using mixed quotes", function() {
write("a.js", `"use strict"\nvar b = require("./b")`) write("a.js", '"use strict"\nvar b = require("./b")')
write("b.js", `'use strict'\nmodule.exports = 1`) write("b.js", "'use strict'\nmodule.exports = 1")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\n"use strict"\nvar b = 1\n}`) o(read("out.js")).equals(';(function() {\n"use strict"\nvar b = 1\n}());')
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("works w/ window", function() { o("works w/ window", function() {
write("a.js", `window.a = 1\nvar b = require("./b")`) write("a.js", 'window.a = 1\nvar b = require("./b")')
write("b.js", `module.exports = function() {return a}`) write("b.js", "module.exports = function() {return a}")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nwindow.a = 1\nvar b = function() {return a}\n}`) o(read("out.js")).equals(";(function() {\nwindow.a = 1\nvar b = function() {return a}\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("works without assignment", function() { o("works without assignment", function() {
write("a.js", `require("./b")`) write("a.js", 'require("./b")')
write("b.js", `1 + 1`) write("b.js", "1 + 1")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\n1 + 1\n}`) o(read("out.js")).equals(";(function() {\n1 + 1\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("works if used fluently", function() { o("works if used fluently", function() {
write("a.js", `var b = require("./b").toString()`) write("a.js", 'var b = require("./b").toString()')
write("b.js", `module.exports = []`) write("b.js", "module.exports = []")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar _0 = []\nvar b = _0.toString()\n}`) o(read("out.js")).equals(";(function() {\nvar _0 = []\nvar b = _0.toString()\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("works if used fluently w/ multiline", function() { o("works if used fluently w/ multiline", function() {
write("a.js", `var b = require("./b")\n\t.toString()`) write("a.js", 'var b = require("./b")\n\t.toString()')
write("b.js", `module.exports = []`) write("b.js", "module.exports = []")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar _0 = []\nvar b = _0\n\t.toString()\n}`) o(read("out.js")).equals(";(function() {\nvar _0 = []\nvar b = _0\n\t.toString()\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("works if used w/ curry", function() { o("works if used w/ curry", function() {
write("a.js", `var b = require("./b")()`) write("a.js", 'var b = require("./b")()')
write("b.js", `module.exports = function() {}`) write("b.js", "module.exports = function() {}")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar _0 = function() {}\nvar b = _0()\n}`) o(read("out.js")).equals(";(function() {\nvar _0 = function() {}\nvar b = _0()\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("works if used w/ curry w/ multiline", function() { o("works if used w/ curry w/ multiline", function() {
write("a.js", `var b = require("./b")\n()`) write("a.js", 'var b = require("./b")\n()')
write("b.js", `module.exports = function() {}`) write("b.js", "module.exports = function() {}")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar _0 = function() {}\nvar b = _0\n()\n}`) o(read("out.js")).equals(";(function() {\nvar _0 = function() {}\nvar b = _0\n()\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("works if used fluently in one place and not in another", function() { 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("a.js", 'var b = require("./b").toString()\nvar c = require("./c")')
write("b.js", `module.exports = []`) write("b.js", "module.exports = []")
write("c.js", `var b = require("./b")\nmodule.exports = function() {return b}`) write("c.js", 'var b = require("./b")\nmodule.exports = function() {return b}')
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar _0 = []\nvar b = _0.toString()\nvar b0 = _0\nvar c = function() {return b0}\n}`) 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("a.js")
remove("b.js") remove("b.js")
@ -196,12 +198,12 @@ o.spec("bundler", function() {
remove("out.js") remove("out.js")
}) })
o("works if used in sequence", function() { o("works if used in sequence", function() {
write("a.js", `var b = require("./b"), c = require("./c")`) write("a.js", 'var b = require("./b"), c = require("./c")')
write("b.js", `module.exports = 1`) write("b.js", "module.exports = 1")
write("c.js", `var x\nmodule.exports = 2`) write("c.js", "var x\nmodule.exports = 2")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b = 1\nvar x\nvar c = 2\n}`) o(read("out.js")).equals(";(function() {\nvar b = 1\nvar x\nvar c = 2\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
@ -209,12 +211,12 @@ o.spec("bundler", function() {
remove("out.js") remove("out.js")
}) })
o("works if assigned to property", function() { o("works if assigned to property", function() {
write("a.js", `var x = {}\nx.b = require("./b")\nx.c = require("./c")`) write("a.js", 'var x = {}\nx.b = require("./b")\nx.c = require("./c")')
write("b.js", `var bb = 1\nmodule.exports = bb`) write("b.js", "var bb = 1\nmodule.exports = bb")
write("c.js", `var cc = 2\nmodule.exports = cc`) write("c.js", "var cc = 2\nmodule.exports = cc")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar x = {}\nvar bb = 1\nx.b = bb\nvar cc = 2\nx.c = cc\n}`) 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("a.js")
remove("b.js") remove("b.js")
@ -222,12 +224,12 @@ o.spec("bundler", function() {
remove("out.js") remove("out.js")
}) })
o("works if assigned to property using bracket notation", function() { o("works if assigned to property using bracket notation", function() {
write("a.js", `var x = {}\nx["b"] = require("./b")\nx["c"] = require("./c")`) write("a.js", 'var x = {}\nx["b"] = require("./b")\nx["c"] = require("./c")')
write("b.js", `var bb = 1\nmodule.exports = bb`) write("b.js", "var bb = 1\nmodule.exports = bb")
write("c.js", `var cc = 2\nmodule.exports = cc`) write("c.js", "var cc = 2\nmodule.exports = cc")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar x = {}\nvar bb = 1\nx["b"] = bb\nvar cc = 2\nx["c"] = cc\n}`) 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("a.js")
remove("b.js") remove("b.js")
@ -235,23 +237,23 @@ o.spec("bundler", function() {
remove("out.js") remove("out.js")
}) })
o("works if collision", function() { o("works if collision", function() {
write("a.js", `var b = require("./b")`) write("a.js", 'var b = require("./b")')
write("b.js", `var b = 1\nmodule.exports = 2`) write("b.js", "var b = 1\nmodule.exports = 2")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b0 = 1\nvar b = 2\n}`) o(read("out.js")).equals(";(function() {\nvar b0 = 1\nvar b = 2\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("works if multiple aliases", function() { o("works if multiple aliases", function() {
write("a.js", `var b = require("./b")\n`) write("a.js", 'var b = require("./b")\n')
write("b.js", `var b = require("./c")\nb.x = 1\nmodule.exports = b`) write("b.js", 'var b = require("./c")\nb.x = 1\nmodule.exports = b')
write("c.js", `var b = {}\nmodule.exports = b`) write("c.js", "var b = {}\nmodule.exports = b")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b = {}\nb.x = 1\n}`) o(read("out.js")).equals(";(function() {\nvar b = {}\nb.x = 1\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
@ -259,13 +261,13 @@ o.spec("bundler", function() {
remove("out.js") remove("out.js")
}) })
o("works if multiple collision", function() { o("works if multiple collision", function() {
write("a.js", `var b = require("./b")\nvar c = require("./c")\nvar d = require("./d")`) 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("b.js", "var a = 1\nmodule.exports = a")
write("c.js", `var a = 2\nmodule.exports = a`) write("c.js", "var a = 2\nmodule.exports = a")
write("d.js", `var a = 3\nmodule.exports = a`) write("d.js", "var a = 3\nmodule.exports = a")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar a = 1\nvar b = a\nvar a0 = 2\nvar c = a0\nvar a1 = 3\nvar d = a1\n}`) 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("a.js")
remove("b.js") remove("b.js")
@ -274,37 +276,37 @@ o.spec("bundler", function() {
remove("out.js") remove("out.js")
}) })
o("works if included multiple times", function() { o("works if included multiple times", function() {
write("a.js", `module.exports = 123`) write("a.js", "module.exports = 123")
write("b.js", `var a = require("./a").toString()\nmodule.exports = a`) write("b.js", 'var a = require("./a").toString()\nmodule.exports = a')
write("c.js", `var a = require("./a").toString()\nvar b = require("./b")`) write("c.js", 'var a = require("./a").toString()\nvar b = require("./b")')
bundle(ns + "c.js", ns + "out.js") bundle(ns + "c.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar _0 = 123\nvar a = _0.toString()\nvar a0 = _0.toString()\nvar b = a0\n}`) 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("a.js")
remove("b.js") remove("b.js")
remove("c.js") remove("c.js")
}) })
o("works if included multiple times reverse", function() { o("works if included multiple times reverse", function() {
write("a.js", `module.exports = 123`) write("a.js", "module.exports = 123")
write("b.js", `var a = require("./a").toString()\nmodule.exports = a`) write("b.js", 'var a = require("./a").toString()\nmodule.exports = a')
write("c.js", `var b = require("./b")\nvar a = require("./a").toString()`) write("c.js", 'var b = require("./b")\nvar a = require("./a").toString()')
bundle(ns + "c.js", ns + "out.js") bundle(ns + "c.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar _0 = 123\nvar a0 = _0.toString()\nvar b = a0\nvar a = _0.toString()\n}`) 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("a.js")
remove("b.js") remove("b.js")
remove("c.js") remove("c.js")
}) })
o("reuses binding if possible", function() { o("reuses binding if possible", function() {
write("a.js", `var b = require("./b")\nvar c = require("./c")`) 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("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("c.js", 'var d = require("./d")\nmodule.exports = function() {return d + 2}')
write("d.js", `module.exports = 1`) write("d.js", "module.exports = 1")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar d = 1\nvar b = function() {return d + 1}\nvar c = function() {return d + 2}\n}`) 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("a.js")
remove("b.js") remove("b.js")
@ -313,45 +315,45 @@ o.spec("bundler", function() {
remove("out.js") remove("out.js")
}) })
o("disambiguates conflicts if imported collides with itself", function() { o("disambiguates conflicts if imported collides with itself", function() {
write("a.js", `var b = require("./b")`) write("a.js", 'var b = require("./b")')
write("b.js", `var b = 1\nmodule.exports = function() {return b}`) write("b.js", "var b = 1\nmodule.exports = function() {return b}")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b0 = 1\nvar b = function() {return b0}\n}`) o(read("out.js")).equals(";(function() {\nvar b0 = 1\nvar b = function() {return b0}\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("disambiguates conflicts if imported collides with something else", function() { o("disambiguates conflicts if imported collides with something else", function() {
write("a.js", `var a = 1\nvar b = require("./b")`) write("a.js", 'var a = 1\nvar b = require("./b")')
write("b.js", `var a = 2\nmodule.exports = function() {return a}`) write("b.js", "var a = 2\nmodule.exports = function() {return a}")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar a = 1\nvar a0 = 2\nvar b = function() {return a0}\n}`) o(read("out.js")).equals(";(function() {\nvar a = 1\nvar a0 = 2\nvar b = function() {return a0}\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("disambiguates conflicts if imported collides with function declaration", function() { o("disambiguates conflicts if imported collides with function declaration", function() {
write("a.js", `function a() {}\nvar b = require("./b")`) write("a.js", 'function a() {}\nvar b = require("./b")')
write("b.js", `var a = 2\nmodule.exports = function() {return a}`) write("b.js", "var a = 2\nmodule.exports = function() {return a}")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nfunction a() {}\nvar a0 = 2\nvar b = function() {return a0}\n}`) o(read("out.js")).equals(";(function() {\nfunction a() {}\nvar a0 = 2\nvar b = function() {return a0}\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("disambiguates conflicts if imported collides with another module's private", function() { o("disambiguates conflicts if imported collides with another module's private", function() {
write("a.js", `var b = require("./b")\nvar c = require("./c")`) write("a.js", 'var b = require("./b")\nvar c = require("./c")')
write("b.js", `var a = 1\nmodule.exports = function() {return a}`) write("b.js", "var a = 1\nmodule.exports = function() {return a}")
write("c.js", `var a = 2\nmodule.exports = function() {return a}`) write("c.js", "var a = 2\nmodule.exports = function() {return a}")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar a = 1\nvar b = function() {return a}\nvar a0 = 2\nvar c = function() {return a0}\n}`) 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("a.js")
remove("b.js") remove("b.js")
@ -359,22 +361,22 @@ o.spec("bundler", function() {
remove("out.js") remove("out.js")
}) })
o("does not mess up strings", function() { o("does not mess up strings", function() {
write("a.js", `var b = require("./b")`) write("a.js", 'var b = require("./b")')
write("b.js", `var b = "b b b \\\" b"\nmodule.exports = function() {return b}`) write("b.js", 'var b = "b b b \\" b"\nmodule.exports = function() {return b}')
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b0 = "b b b \\\" b"\nvar b = function() {return b0}\n}`) o(read("out.js")).equals(';(function() {\nvar b0 = "b b b \\\" b"\nvar b = function() {return b0}\n}());')
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")
remove("out.js") remove("out.js")
}) })
o("does not mess up properties", function() { o("does not mess up properties", function() {
write("a.js", `var b = require("./b")`) write("a.js", 'var b = require("./b")')
write("b.js", `var b = {b: 1}\nmodule.exports = function() {return b.b}`) write("b.js", "var b = {b: 1}\nmodule.exports = function() {return b.b}")
bundle(ns + "a.js", ns + "out.js") bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b0 = {b: 1}\nvar b = function() {return b0.b}\n}`) o(read("out.js")).equals(";(function() {\nvar b0 = {b: 1}\nvar b = function() {return b0.b}\n}());")
remove("a.js") remove("a.js")
remove("b.js") remove("b.js")

View file

@ -101,6 +101,107 @@ To learn more about lifecycle methods, [see the lifecycle methods page](lifecycl
--- ---
### Alternate component syntaxes
#### 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, closuresComponent)
// 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 ### 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. 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.
@ -109,7 +210,7 @@ The state of a component can be accessed three ways: as a blueprint at initializ
#### At initialization #### At initialization
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. 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. In the example below, `data` is a property of the `ComponentWithInitialState` component's state object.
@ -127,6 +228,10 @@ m(ComponentWithInitialState)
// <div>Initial content</div> // <div>Initial content</div>
``` ```
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 #### 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. 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.

View file

@ -58,7 +58,7 @@ React | Mithril
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. 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 encourage 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. 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. 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.

View file

@ -5,6 +5,8 @@
- [Static members](#static-members) - [Static members](#static-members)
- [Stream.combine](#streamcombine) - [Stream.combine](#streamcombine)
- [Stream.merge](#streammerge) - [Stream.merge](#streammerge)
- [Stream.scan](#streamscan)
- [Stream.scanMerge](#streamscanmerge)
- [Stream.HALT](#streamhalt) - [Stream.HALT](#streamhalt)
- [Stream["fantasy-land/of"]](#streamfantasy-landof) - [Stream["fantasy-land/of"]](#streamfantasy-landof)
- [Instance members](#static-members) - [Instance members](#static-members)
@ -114,6 +116,39 @@ Argument | Type | Required | Description
--- ---
##### Stream.scan
Creates a new stream with the results of calling the function on every value in the stream with an accumulator and the incoming value.
`stream = Stream.scan(fn, accumulator, stream)`
Argument | Type | Required | Description
------------- | -------------------------------- | -------- | ---
`fn` | `(accumulator, value) -> result` | Yes | A function that takes an accumulator and value parameter and returns a new accumulator value
`accumulator` | `any` | Yes | The starting value for the accumulator
`stream` | `Stream` | Yes | Stream containing the values
**returns** | `Stream` | | Returns a new stream containing the result
[How to read signatures](signatures.md)
---
##### Stream.scanMerge
Takes an array of pairs of streams and scan functions and merges all those streams using the given functions into a single stream.
`stream = Stream.scanMerge(pairs, accumulator)`
Argument | Type | Required | Description
------------- | ------------------------------------------------ | -------- | ---
`pairs` | `Array<[Stream, (accumulator, value) -> value]>` | Yes | An array of tuples of stream and scan functions
`accumulator` | `any` | Yes | The starting value for the accumulator
**returns** | `Stream` | | Returns a new stream containing the result
[How to read signatures](signatures.md)
---
##### Stream.HALT ##### Stream.HALT
A special value that can be returned to stream callbacks to halt execution of downstreams A special value that can be returned to stream callbacks to halt execution of downstreams

View file

@ -73,10 +73,11 @@ Property | Type | Description
`text` | `(String|Number|Boolean)?` | This is used instead of `children` if a vnode contains a text node as its only child. This is done for performance reasons. Component vnodes never use the `text` property even if they have a text node as their only child. `text` | `(String|Number|Boolean)?` | This is used instead of `children` if a vnode contains a text node as its only child. This is done for performance reasons. Component vnodes never use the `text` property even if they have a text node as their only child.
`dom` | `Element?` | Points to the element that corresponds to the vnode. This property is `undefined` in the `oninit` lifecycle method. In fragments and trusted HTML vnodes, `dom` points to the first element in the range. `dom` | `Element?` | Points to the element that corresponds to the vnode. This property is `undefined` in the `oninit` lifecycle method. In fragments and trusted HTML vnodes, `dom` points to the first element in the range.
`domSize` | `Number?` | This is only set in fragment and trusted HTML vnodes, and it's `undefined` in all other vnode types. It defines the number of DOM elements that the vnode represents (starting from the element referenced by the `dom` property). `domSize` | `Number?` | This is only set in fragment and trusted HTML vnodes, and it's `undefined` in all other vnode types. It defines the number of DOM elements that the vnode represents (starting from the element referenced by the `dom` property).
`state` | `Object`? | An object that is persisted between redraws. It is provided by the core engine when needed. In component vnodes, the `state` inherits prototypically from the component object/class. `state` | `Object?` | An object that is persisted between redraws. It is provided by the core engine when needed. In POJO component vnodes, the `state` inherits prototypically from the component object/class. In class component vnodes it is an instance of the class. In closure components it is the object returned by the closure.
`events` | `Object?` | An object that is persisted between redraws and that stores event handlers so that they can be removed using the DOM API. The `events` property is `undefined` if there are no event handlers defined. This property is only used internally by Mithril, do not use it. `_state` | `Object?` | For components, a reference to the original `vnode.state` object, used to lookup the `view` and hooks. This property is only used internally by Mithril, do not use or modify it.
`events` | `Object?` | An object that is persisted between redraws and that stores event handlers so that they can be removed using the DOM API. The `events` property is `undefined` if there are no event handlers defined. This property is only used internally by Mithril, do not use or modify it.
`instance` | `Object?` | For components, a storage location for the value returned by the `view`. This property is only used internally by Mithril, do not use or modify it.
`skip` | `Boolean` | This property is only used internally by Mithril when diffing keyed lists, do not use or modify it.
--- ---

View file

@ -38,7 +38,7 @@ var state = {
update: function(title) { update: function(title) {
if (state.editing != null) { if (state.editing != null) {
state.editing.title = title.trim() state.editing.title = title.trim()
if (state.editing.title === "") destroy(state.editing) if (state.editing.title === "") state.destroy(state.editing)
state.editing = null state.editing = null
} }
}, },
@ -104,7 +104,7 @@ var Todos = {
m("label", {ondblclick: function() {state.dispatch("edit", [todo])}}, todo.title), m("label", {ondblclick: function() {state.dispatch("edit", [todo])}}, todo.title),
m("button.destroy", {onclick: function() {state.dispatch("destroy", [todo])}}), m("button.destroy", {onclick: function() {state.dispatch("destroy", [todo])}}),
]), ]),
m("input.edit", {onupdate: function(vnode) {ui.focus(vnode, todo)}, onkeypress: ui.save, onblur: ui.save}) m("input.edit", {onupdate: function(vnode) {ui.focus(vnode, todo)}, onkeyup: ui.save, onblur: ui.save})
]) ])
}), }),
]), ]),

View file

@ -1,3 +1,5 @@
"use strict"
var hyperscript = require("./render/hyperscript") var hyperscript = require("./render/hyperscript")
hyperscript.trust = require("./render/trust") hyperscript.trust = require("./render/trust")

View file

@ -1,7 +1,7 @@
new function() { ;(function() {
"use strict"
function Vnode(tag, key, attrs0, children, text, dom) { function Vnode(tag, key, attrs0, children, text, dom) {
return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: {}, events: undefined, instance: undefined, skip: false} return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: undefined, _state: undefined, events: undefined, instance: undefined, skip: false}
} }
Vnode.normalize = function(node) { Vnode.normalize = function(node) {
if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined) if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)
@ -16,62 +16,82 @@ Vnode.normalizeChildren = function normalizeChildren(children) {
} }
var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
var selectorCache = {} var selectorCache = {}
function hyperscript(selector) { var hasOwn = {}.hasOwnProperty
if (selector == null || typeof selector !== "string" && typeof selector.view !== "function") { function compileSelector(selector) {
throw Error("The selector must be either a string or a component."); var match, tag = "div", classes = [], attrs = {}
}
if (typeof selector === "string" && selectorCache[selector] === undefined) {
var match, tag, classes = [], attributes = {}
while (match = selectorParser.exec(selector)) { while (match = selectorParser.exec(selector)) {
var type = match[1], value = match[2] var type = match[1], value = match[2]
if (type === "" && value !== "") tag = value if (type === "" && value !== "") tag = value
else if (type === "#") attributes.id = value else if (type === "#") attrs.id = value
else if (type === ".") classes.push(value) else if (type === ".") classes.push(value)
else if (match[3][0] === "[") { else if (match[3][0] === "[") {
var attrValue = match[6] var attrValue = match[6]
if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\") if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
if (match[4] === "class") classes.push(attrValue) if (match[4] === "class") classes.push(attrValue)
else attributes[match[4]] = attrValue || true else attrs[match[4]] = attrValue || true
} }
} }
if (classes.length > 0) attributes.className = classes.join(" ") if (classes.length > 0) attrs.className = classes.join(" ")
selectorCache[selector] = function(attrs, children) { return selectorCache[selector] = {tag: tag, attrs: attrs}
}
function execSelector(state, attrs, children) {
var hasAttrs = false, childList, text var hasAttrs = false, childList, text
var className = attrs.className || attrs.class var className = attrs.className || attrs.class
for (var key in attributes) attrs[key] = attributes[key] for (var key in state.attrs) {
if (className !== undefined) { if (hasOwn.call(state.attrs, key)) {
if (attrs.class !== undefined) { attrs[key] = state.attrs[key]
}
}
if (className != null) {
if (attrs.class != null) {
attrs.class = undefined attrs.class = undefined
attrs.className = className attrs.className = className
} }
if (attributes.className !== undefined) attrs.className = attributes.className + " " + className if (state.attrs.className != null) {
attrs.className = state.attrs.className + " " + className
}
} }
for (var key in attrs) { for (var key in attrs) {
if (key !== "key") { if (hasOwn.call(attrs, key) && key !== "key") {
hasAttrs = true hasAttrs = true
break break
} }
} }
if (Array.isArray(children) && children.length == 1 && children[0] != null && children[0].tag === "#") text = children[0].children if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") {
else childList = children text = children[0].children
return Vnode(tag || "div", attrs.key, hasAttrs ? attrs : undefined, childList, text, undefined) } else {
childList = children
} }
return Vnode(state.tag, attrs.key, hasAttrs ? attrs : undefined, childList, text)
} }
var attrs, children, childrenIndex function hyperscript(selector) {
if (arguments[1] == null || typeof arguments[1] === "object" && arguments[1].tag === undefined && !Array.isArray(arguments[1])) { // Because sloppy mode sucks
attrs = arguments[1] var attrs = arguments[1], start = 2, children
childrenIndex = 2 if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") {
throw Error("The selector must be either a string or a component.");
} }
else childrenIndex = 1 if (typeof selector === "string") {
if (arguments.length === childrenIndex + 1) { var cached = selectorCache[selector] || compileSelector(selector)
children = Array.isArray(arguments[childrenIndex]) ? arguments[childrenIndex] : [arguments[childrenIndex]]
} }
else { if (!attrs) {
attrs = {}
} else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) {
attrs = {}
start = 1
}
if (arguments.length === start + 1) {
children = arguments[start]
if (!Array.isArray(children)) children = [children]
} else {
children = [] children = []
for (var i = childrenIndex; i < arguments.length; i++) children.push(arguments[i]) while (start < arguments.length) children.push(arguments[start++])
}
var normalized = Vnode.normalizeChildren(children)
if (typeof selector === "string") {
return execSelector(cached, attrs, normalized)
} else {
return Vnode(selector, attrs.key, attrs, normalized)
} }
if (typeof selector === "string") return selectorCache[selector](attrs || {}, Vnode.normalizeChildren(children))
return Vnode(selector, attrs && attrs.key, attrs || {}, Vnode.normalizeChildren(children), undefined, undefined)
} }
hyperscript.trust = function(html) { hyperscript.trust = function(html) {
if (html == null) html = "" if (html == null) html = ""
@ -203,6 +223,7 @@ var buildQueryString = function(object) {
else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : "")) else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : ""))
} }
} }
var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i")
var _8 = function($window, Promise) { var _8 = function($window, Promise) {
var callbackCount = 0 var callbackCount = 0
var oncompletion var oncompletion
@ -238,14 +259,20 @@ var _8 = function($window, Promise) {
var promise0 = new Promise(function(resolve, reject) { var promise0 = new Promise(function(resolve, reject) {
if (args.method == null) args.method = "GET" if (args.method == null) args.method = "GET"
args.method = args.method.toUpperCase() args.method = args.method.toUpperCase()
var useBody = typeof args.useBody === "boolean" ? args.useBody : args.method !== "GET" && args.method !== "TRACE" 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.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.deserialize !== "function") args.deserialize = deserialize
if (typeof args.extract !== "function") args.extract = extract if (typeof args.extract !== "function") args.extract = extract
args.url = interpolate(args.url, args.data) args.url = interpolate(args.url, args.data)
if (useBody) args.data = args.serialize(args.data) if (useBody) args.data = args.serialize(args.data)
else args.url = assemble(args.url, args.data) else args.url = assemble(args.url, args.data)
var xhr = new $window.XMLHttpRequest() 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) 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) { if (args.serialize === JSON.stringify && useBody) {
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8")
@ -259,12 +286,12 @@ var _8 = function($window, Promise) {
} }
if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
// Don't throw errors on xhr.abort(). XMLHttpRequests ends up in a state of // Don't throw errors on xhr.abort().
// xhr.status == 0 and xhr.readyState == 4 if aborted after open, but before completion. if(aborted) return
if (xhr.status && xhr.readyState === 4) { if (xhr.readyState === 4) {
try { try {
var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args)) 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) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) {
resolve(cast(args.type, response)) resolve(cast(args.type, response))
} }
else { else {
@ -355,30 +382,33 @@ var coreRenderer = function($window) {
for (var i = start; i < end; i++) { for (var i = start; i < end; i++) {
var vnode = vnodes[i] var vnode = vnodes[i]
if (vnode != null) { if (vnode != null) {
insertNode(parent, createNode(vnode, hooks, ns), nextSibling) createNode(parent, vnode, hooks, ns, nextSibling)
} }
} }
} }
function createNode(vnode, hooks, ns) { function createNode(parent, vnode, hooks, ns, nextSibling) {
var tag = vnode.tag var tag = vnode.tag
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
if (typeof tag === "string") { if (typeof tag === "string") {
vnode.state = {}
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
switch (tag) { switch (tag) {
case "#": return createText(vnode) case "#": return createText(parent, vnode, nextSibling)
case "<": return createHTML(vnode) case "<": return createHTML(parent, vnode, nextSibling)
case "[": return createFragment(vnode, hooks, ns) case "[": return createFragment(parent, vnode, hooks, ns, nextSibling)
default: return createElement(vnode, hooks, ns) default: return createElement(parent, vnode, hooks, ns, nextSibling)
} }
} }
else return createComponent(vnode, hooks, ns) else return createComponent(parent, vnode, hooks, ns, nextSibling)
} }
function createText(vnode) { function createText(parent, vnode, nextSibling) {
return vnode.dom = $doc.createTextNode(vnode.children) vnode.dom = $doc.createTextNode(vnode.children)
insertNode(parent, vnode.dom, nextSibling)
return vnode.dom
} }
function createHTML(vnode) { function createHTML(parent, vnode, nextSibling) {
var match1 = vnode.children.match(/^\s*?<(\w+)/im) || [] var match1 = vnode.children.match(/^\s*?<(\w+)/im) || []
var parent = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match1[1]] || "div" var parent1 = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match1[1]] || "div"
var temp = $doc.createElement(parent) var temp = $doc.createElement(parent1)
temp.innerHTML = vnode.children temp.innerHTML = vnode.children
vnode.dom = temp.firstChild vnode.dom = temp.firstChild
vnode.domSize = temp.childNodes.length vnode.domSize = temp.childNodes.length
@ -387,9 +417,10 @@ var coreRenderer = function($window) {
while (child = temp.firstChild) { while (child = temp.firstChild) {
fragment.appendChild(child) fragment.appendChild(child)
} }
insertNode(parent, fragment, nextSibling)
return fragment return fragment
} }
function createFragment(vnode, hooks, ns) { function createFragment(parent, vnode, hooks, ns, nextSibling) {
var fragment = $doc.createDocumentFragment() var fragment = $doc.createDocumentFragment()
if (vnode.children != null) { if (vnode.children != null) {
var children = vnode.children var children = vnode.children
@ -397,9 +428,10 @@ var coreRenderer = function($window) {
} }
vnode.dom = fragment.firstChild vnode.dom = fragment.firstChild
vnode.domSize = fragment.childNodes.length vnode.domSize = fragment.childNodes.length
insertNode(parent, fragment, nextSibling)
return fragment return fragment
} }
function createElement(vnode, hooks, ns) { function createElement(parent, vnode, hooks, ns, nextSibling) {
var tag = vnode.tag var tag = vnode.tag
switch (vnode.tag) { switch (vnode.tag) {
case "svg": ns = "http://www.w3.org/2000/svg"; break case "svg": ns = "http://www.w3.org/2000/svg"; break
@ -414,6 +446,7 @@ var coreRenderer = function($window) {
if (attrs2 != null) { if (attrs2 != null) {
setAttrs(vnode, attrs2, ns) setAttrs(vnode, attrs2, ns)
} }
insertNode(parent, element, nextSibling)
if (vnode.attrs != null && vnode.attrs.contenteditable != null) { if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
setContentEditable(vnode) setContentEditable(vnode)
} }
@ -430,19 +463,34 @@ var coreRenderer = function($window) {
} }
return element return element
} }
function createComponent(vnode, hooks, ns) { function initComponent(vnode, hooks) {
var sentinel
if (typeof vnode.tag.view === "function") {
vnode.state = Object.create(vnode.tag) vnode.state = Object.create(vnode.tag)
var view = vnode.tag.view sentinel = vnode.state.view
if (view.reentrantLock != null) return $emptyFragment if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
view.reentrantLock = true sentinel.$$reentrantLock$$ = true
initLifecycle(vnode.tag, vnode, hooks) } else {
vnode.instance = Vnode.normalize(view.call(vnode.state, vnode)) vnode.state = void 0
view.reentrantLock = null sentinel = vnode.tag
if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
sentinel.$$reentrantLock$$ = true
vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode)
}
vnode._state = vnode.state
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
initLifecycle(vnode._state, vnode, hooks)
vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
sentinel.$$reentrantLock$$ = null
}
function createComponent(parent, vnode, hooks, ns, nextSibling) {
initComponent(vnode, hooks)
if (vnode.instance != null) { if (vnode.instance != null) {
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as arguments") var element = createNode(parent, vnode.instance, hooks, ns, nextSibling)
var element = createNode(vnode.instance, hooks, ns)
vnode.dom = vnode.instance.dom vnode.dom = vnode.instance.dom
vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0 vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
insertNode(parent, element, nextSibling)
return element return element
} }
else { else {
@ -451,7 +499,7 @@ var coreRenderer = function($window) {
} }
} }
//update //update
function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { function updateNodes(parent, old, vnodes, recycling, hooks, nextSibling, ns) {
if (old === vnodes || old == null && vnodes == null) return if (old === vnodes || old == null && vnodes == null) return
else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined) else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes) else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
@ -467,15 +515,18 @@ var coreRenderer = function($window) {
if (isUnkeyed) { if (isUnkeyed) {
for (var i = 0; i < old.length; i++) { for (var i = 0; i < old.length; i++) {
if (old[i] === vnodes[i]) continue if (old[i] === vnodes[i]) continue
else if (old[i] == null && vnodes[i] != null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling)) else if (old[i] == null && vnodes[i] != null) createNode(parent, vnodes[i], hooks, ns, getNextSibling(old, i + 1, nextSibling))
else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes) else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes)
else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), false, ns) else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns)
} }
return return
} }
} }
var recycling = isRecyclable(old, vnodes) recycling = recycling || isRecyclable(old, vnodes)
if (recycling) old = old.concat(old.pool) if (recycling) {
var pool = old.pool
old = old.concat(old.pool)
}
var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map
while (oldEnd >= oldStart && end >= start) { while (oldEnd >= oldStart && end >= start) {
var o = old[oldStart], v = vnodes[start] var o = old[oldStart], v = vnodes[start]
@ -483,8 +534,9 @@ var coreRenderer = function($window) {
else if (o == null) oldStart++ else if (o == null) oldStart++
else if (v == null) start++ else if (v == null) start++
else if (o.key === v.key) { else if (o.key === v.key) {
var shouldRecycle = (pool != null && oldStart >= old.length - pool.length) || ((pool == null) && recycling)
oldStart++, start++ oldStart++, start++
updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), shouldRecycle, ns)
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
} }
else { else {
@ -493,7 +545,8 @@ var coreRenderer = function($window) {
else if (o == null) oldEnd-- else if (o == null) oldEnd--
else if (v == null) start++ else if (v == null) start++
else if (o.key === v.key) { else if (o.key === v.key) {
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling)) if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling))
oldEnd--, start++ oldEnd--, start++
} }
@ -506,7 +559,8 @@ var coreRenderer = function($window) {
else if (o == null) oldEnd-- else if (o == null) oldEnd--
else if (v == null) end-- else if (v == null) end--
else if (o.key === v.key) { else if (o.key === v.key) {
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
if (o.dom != null) nextSibling = o.dom if (o.dom != null) nextSibling = o.dom
oldEnd--, end-- oldEnd--, end--
@ -517,14 +571,14 @@ var coreRenderer = function($window) {
var oldIndex = map[v.key] var oldIndex = map[v.key]
if (oldIndex != null) { if (oldIndex != null) {
var movable = old[oldIndex] var movable = old[oldIndex]
var shouldRecycle = (pool != null && oldIndex >= old.length - pool.length) || ((pool == null) && recycling)
updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
insertNode(parent, toFragment(movable), nextSibling) insertNode(parent, toFragment(movable), nextSibling)
old[oldIndex].skip = true old[oldIndex].skip = true
if (movable.dom != null) nextSibling = movable.dom if (movable.dom != null) nextSibling = movable.dom
} }
else { else {
var dom = createNode(v, hooks, undefined) var dom = createNode(parent, v, hooks, undefined, nextSibling)
insertNode(parent, dom, nextSibling)
nextSibling = dom nextSibling = dom
} }
} }
@ -540,24 +594,29 @@ var coreRenderer = function($window) {
var oldTag = old.tag, tag = vnode.tag var oldTag = old.tag, tag = vnode.tag
if (oldTag === tag) { if (oldTag === tag) {
vnode.state = old.state vnode.state = old.state
vnode._state = old._state
vnode.events = old.events vnode.events = old.events
if (shouldUpdate(vnode, old)) return if (!recycling && shouldNotUpdate(vnode, old)) return
if (vnode.attrs != null) {
updateLifecycle(vnode.attrs, vnode, hooks, recycling)
}
if (typeof oldTag === "string") { if (typeof oldTag === "string") {
if (vnode.attrs != null) {
if (recycling) {
vnode.state = {}
initLifecycle(vnode.attrs, vnode, hooks)
}
else updateLifecycle(vnode.attrs, vnode, hooks)
}
switch (oldTag) { switch (oldTag) {
case "#": updateText(old, vnode); break case "#": updateText(old, vnode); break
case "<": updateHTML(parent, old, vnode, nextSibling); break case "<": updateHTML(parent, old, vnode, nextSibling); break
case "[": updateFragment(parent, old, vnode, hooks, nextSibling, ns); break case "[": updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns); break
default: updateElement(old, vnode, hooks, ns) default: updateElement(old, vnode, recycling, hooks, ns)
} }
} }
else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns)
} }
else { else {
removeNode(old, null) removeNode(old, null)
insertNode(parent, createNode(vnode, hooks, ns), nextSibling) createNode(parent, vnode, hooks, ns, nextSibling)
} }
} }
function updateText(old, vnode) { function updateText(old, vnode) {
@ -569,12 +628,12 @@ var coreRenderer = function($window) {
function updateHTML(parent, old, vnode, nextSibling) { function updateHTML(parent, old, vnode, nextSibling) {
if (old.children !== vnode.children) { if (old.children !== vnode.children) {
toFragment(old) toFragment(old)
insertNode(parent, createHTML(vnode), nextSibling) createHTML(parent, vnode, nextSibling)
} }
else vnode.dom = old.dom, vnode.domSize = old.domSize else vnode.dom = old.dom, vnode.domSize = old.domSize
} }
function updateFragment(parent, old, vnode, hooks, nextSibling, ns) { function updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns) {
updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns) updateNodes(parent, old.children, vnode.children, recycling, hooks, nextSibling, ns)
var domSize = 0, children = vnode.children var domSize = 0, children = vnode.children
vnode.dom = null vnode.dom = null
if (children != null) { if (children != null) {
@ -588,7 +647,7 @@ var coreRenderer = function($window) {
if (domSize !== 1) vnode.domSize = domSize if (domSize !== 1) vnode.domSize = domSize
} }
} }
function updateElement(old, vnode, hooks, ns) { function updateElement(old, vnode, recycling, hooks, ns) {
var element = vnode.dom = old.dom var element = vnode.dom = old.dom
switch (vnode.tag) { switch (vnode.tag) {
case "svg": ns = "http://www.w3.org/2000/svg"; break case "svg": ns = "http://www.w3.org/2000/svg"; break
@ -611,14 +670,20 @@ var coreRenderer = function($window) {
else { else {
if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)] if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)]
if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)] if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
updateNodes(element, old.children, vnode.children, hooks, null, ns) updateNodes(element, old.children, vnode.children, recycling, hooks, null, ns)
} }
} }
function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) { function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) {
vnode.instance = Vnode.normalize(vnode.tag.view.call(vnode.state, vnode)) if (recycling) {
updateLifecycle(vnode.tag, vnode, hooks, recycling) initComponent(vnode, hooks)
} else {
vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks)
updateLifecycle(vnode._state, vnode, hooks)
}
if (vnode.instance != null) { if (vnode.instance != null) {
if (old.instance == null) insertNode(parent, createNode(vnode.instance, hooks, ns), nextSibling) if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling)
else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns) else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns)
vnode.dom = vnode.instance.dom vnode.dom = vnode.instance.dom
vnode.domSize = vnode.instance.domSize vnode.domSize = vnode.instance.domSize
@ -698,15 +763,15 @@ var coreRenderer = function($window) {
} }
function removeNode(vnode, context) { function removeNode(vnode, context) {
var expected = 1, called = 0 var expected = 1, called = 0
if (vnode.attrs && vnode.attrs.onbeforeremove) { if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") {
var result = vnode.attrs.onbeforeremove.call(vnode.state, vnode) var result = vnode.attrs.onbeforeremove.call(vnode.state, vnode)
if (result != null && typeof result.then === "function") { if (result != null && typeof result.then === "function") {
expected++ expected++
result.then(continuation, continuation) result.then(continuation, continuation)
} }
} }
if (typeof vnode.tag !== "string" && vnode.tag.onbeforeremove) { if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeremove === "function") {
var result = vnode.tag.onbeforeremove.call(vnode.state, vnode) var result = vnode._state.onbeforeremove.call(vnode.state, vnode)
if (result != null && typeof result.then === "function") { if (result != null && typeof result.then === "function") {
expected++ expected++
result.then(continuation, continuation) result.then(continuation, continuation)
@ -738,8 +803,8 @@ var coreRenderer = function($window) {
if (parent != null) parent.removeChild(node) if (parent != null) parent.removeChild(node)
} }
function onremove(vnode) { function onremove(vnode) {
if (vnode.attrs && vnode.attrs.onremove) vnode.attrs.onremove.call(vnode.state, vnode) if (vnode.attrs && typeof vnode.attrs.onremove === "function") vnode.attrs.onremove.call(vnode.state, vnode)
if (typeof vnode.tag !== "string" && vnode.tag.onremove) vnode.tag.onremove.call(vnode.state, vnode) if (typeof vnode.tag !== "string" && typeof vnode._state.onremove === "function") vnode._state.onremove.call(vnode.state, vnode)
if (vnode.instance != null) onremove(vnode.instance) if (vnode.instance != null) onremove(vnode.instance)
else { else {
var children = vnode.children var children = vnode.children
@ -768,14 +833,14 @@ var coreRenderer = function($window) {
else if (key2 === "style") updateStyle(element, old, value) else if (key2 === "style") updateStyle(element, old, value)
else if (key2 in element && !isAttribute(key2) && ns === undefined && !isCustomElement(vnode)) { else if (key2 in element && !isAttribute(key2) && ns === undefined && !isCustomElement(vnode)) {
//setting input[value] to same value by typing on focused element moves cursor to end in Chrome //setting input[value] to same value by typing on focused element moves cursor to end in Chrome
if (vnode.tag === "input" && key2 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return if (vnode.tag === "input" && key2 === "value" && vnode.dom.value == value && vnode.dom === $doc.activeElement) return
//setting select[value] to same value while having select open blinks select dropdown in Chrome //setting select[value] to same value while having select open blinks select dropdown in Chrome
if (vnode.tag === "select" && key2 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return if (vnode.tag === "select" && key2 === "value" && vnode.dom.value == value && vnode.dom === $doc.activeElement) return
//setting option[value] to same value while having select open blinks select dropdown in Chrome //setting option[value] to same value while having select open blinks select dropdown in Chrome
if (vnode.tag === "option" && key2 === "value" && vnode.dom.value === value) return if (vnode.tag === "option" && key2 === "value" && vnode.dom.value == value) return
// If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error0 will occur. // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error0 will occur.
if (vnode.tag === "input" && key2 === "type") { if (vnode.tag === "input" && key2 === "type") {
element.setAttribute(key2, value); element.setAttribute(key2, value)
return return
} }
element[key2] = value element[key2] = value
@ -868,14 +933,13 @@ var coreRenderer = function($window) {
if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode) if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode)
if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode)) if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode))
} }
function updateLifecycle(source, vnode, hooks, recycling) { function updateLifecycle(source, vnode, hooks) {
if (recycling) initLifecycle(source, vnode, hooks) if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
else if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
} }
function shouldUpdate(vnode, old) { function shouldNotUpdate(vnode, old) {
var forceVnodeUpdate, forceComponentUpdate var forceVnodeUpdate, forceComponentUpdate
if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old) if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old)
if (typeof vnode.tag !== "string" && typeof vnode.tag.onbeforeupdate === "function") forceComponentUpdate = vnode.tag.onbeforeupdate.call(vnode.state, vnode, old) if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeupdate === "function") forceComponentUpdate = vnode._state.onbeforeupdate.call(vnode.state, vnode, old)
if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) { if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) {
vnode.dom = old.dom vnode.dom = old.dom
vnode.domSize = old.domSize vnode.domSize = old.domSize
@ -891,7 +955,7 @@ var coreRenderer = function($window) {
// First time0 rendering into a node clears it out // First time0 rendering into a node clears it out
if (dom.vnodes == null) dom.textContent = "" if (dom.vnodes == null) dom.textContent = ""
if (!Array.isArray(vnodes)) vnodes = [vnodes] if (!Array.isArray(vnodes)) vnodes = [vnodes]
updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), hooks, null, undefined) updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, undefined)
dom.vnodes = vnodes dom.vnodes = vnodes
for (var i = 0; i < hooks.length; i++) hooks[i]() for (var i = 0; i < hooks.length; i++) hooks[i]()
if ($doc.activeElement !== active) active.focus() if ($doc.activeElement !== active) active.focus()
@ -923,7 +987,6 @@ var _11 = function($window) {
renderService.setEventCallback(function(e) { renderService.setEventCallback(function(e) {
if (e.redraw !== false) redraw() if (e.redraw !== false) redraw()
}) })
var callbacks = [] var callbacks = []
function subscribe(key1, callback) { function subscribe(key1, callback) {
unsubscribe(key1) unsubscribe(key1)
@ -950,7 +1013,7 @@ var _16 = function(redrawService0) {
return return
} }
if (component.view == null) throw new Error("m.mount(element, component) expects a component, not a vnode") if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode")
var run0 = function() { var run0 = function() {
redrawService0.render(root, Vnode(component)) redrawService0.render(root, Vnode(component))
@ -1061,7 +1124,6 @@ var coreRouter = function($window) {
var path = router.getPath() var path = router.getPath()
var params = {} var params = {}
var pathname = parsePath(path, params, params) var pathname = parsePath(path, params, params)
var state = $window.history.state var state = $window.history.state
if (state != null) { if (state != null) {
for (var k in state) params[k] = state[k] for (var k in state) params[k] = state[k]
@ -1082,12 +1144,10 @@ var coreRouter = function($window) {
} }
reject(path, params) reject(path, params)
} }
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute) if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
resolveRoute() resolveRoute()
} }
return router return router
} }
var _20 = function($window, redrawService0) { var _20 = function($window, redrawService0) {
@ -1099,17 +1159,19 @@ var _20 = function($window, redrawService0) {
var run1 = function() { var run1 = function() {
if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3))) if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3)))
} }
var bail = function() { var bail = function(path) {
routeService.setPath(defaultRoute, null, {replace: true}) 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) { routeService.defineRoutes(routes, function(payload, params, path) {
var update = lastUpdate = function(routeResolver, comp) { var update = lastUpdate = function(routeResolver, comp) {
if (update !== lastUpdate) return if (update !== lastUpdate) return
component = comp != null && typeof comp.view === "function" ? comp : "div", attrs3 = params, currentPath = path, lastUpdate = null component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
attrs3 = params, currentPath = path, lastUpdate = null
render1 = (routeResolver.render || identity).bind(routeResolver) render1 = (routeResolver.render || identity).bind(routeResolver)
run1() run1()
} }
if (payload.view) update({}, payload) if (payload.view || typeof payload === "function") update({}, payload)
else { else {
if (payload.onmatch) { if (payload.onmatch) {
Promise.resolve(payload.onmatch(params, path)).then(function(resolved) { Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
@ -1158,8 +1220,8 @@ m.request = requestService.request
m.jsonp = requestService.jsonp m.jsonp = requestService.jsonp
m.parseQueryString = parseQueryString m.parseQueryString = parseQueryString
m.buildQueryString = buildQueryString m.buildQueryString = buildQueryString
m.version = "1.0.0" m.version = "1.0.1"
m.vnode = Vnode m.vnode = Vnode
if (typeof module !== "undefined") module["exports"] = m if (typeof module !== "undefined") module["exports"] = m
else window.m = m else window.m = m
} }());

85
mithril.min.js vendored
View file

@ -1,42 +1,43 @@
new function(){function x(a,c,k,d,h,m){return{tag:a,key:c,attrs:k,children:d,text:h,dom:m,domSize:void 0,state:{},events:void 0,instance:void 0,skip:!1}}function B(a){if(null==a||"string"!==typeof a&&"function"!==typeof a.view)throw Error("The selector must be either a string or a component.");if("string"===typeof a&&void 0===G[a]){for(var c,k,d=[],h={};c=N.exec(a);){var m=c[1],l=c[2];""===m&&""!==l?k=l:"#"===m?h.id=l:"."===m?d.push(l):"["===c[3][0]&&((m=c[6])&&(m=m.replace(/\\(["'])/g,"$1").replace(/\\\\/g, (function(){function B(b,d,f,g,e,n){return{tag:b,key:d,attrs:f,children:g,text:e,dom:n,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function C(b){var d=arguments[1],f=2,g;if(null==b||"string"!==typeof b&&"function"!==typeof b&&"function"!==typeof b.view)throw Error("The selector must be either a string or a component.");if("string"===typeof b){var e;if(!(e=M[b])){g="div";for(var n=[],k={};e=P.exec(b);){var q=e[1],m=e[2];""===q&&""!==m?g=m:"#"===q?k.id=m:"."===q?
"\\")),"class"===c[4]?d.push(m):h[c[4]]=m||!0)}0<d.length&&(h.className=d.join(" "));G[a]=function(a,c){var m=!1,b,r,d=a.className||a["class"],p;for(p in h)a[p]=h[p];void 0!==d&&(void 0!==a["class"]&&(a["class"]=void 0,a.className=d),void 0!==h.className&&(a.className=h.className+" "+d));for(p in a)if("key"!==p){m=!0;break}Array.isArray(c)&&1==c.length&&null!=c[0]&&"#"===c[0].tag?r=c[0].children:b=c;return x(k||"div",a.key,m?a:void 0,b,r,void 0)}}var p;null==arguments[1]||"object"===typeof arguments[1]&& n.push(m):"["===e[3][0]&&((q=e[6])&&(q=q.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===e[4]?n.push(q):k[e[4]]=q||!0)}0<n.length&&(k.className=n.join(" "));e=M[b]={tag:g,attrs:k}}}if(!d)d={};else if("object"!==typeof d||null!=d.tag||Array.isArray(d))d={},f=1;if(arguments.length===f+1)g=arguments[f],Array.isArray(g)||(g=[g]);else for(g=[];f<arguments.length;)g.push(arguments[f++]);f=B.normalizeChildren(g);if("string"===typeof b){g=!1;var l,u,n=d.className||d["class"],a;for(a in e.attrs)N.call(e.attrs,
void 0===arguments[1].tag&&!Array.isArray(arguments[1])?(p=arguments[1],d=2):d=1;if(arguments.length===d+1)c=Array.isArray(arguments[d])?arguments[d]:[arguments[d]];else for(c=[];d<arguments.length;d++)c.push(arguments[d]);return"string"===typeof a?G[a](p||{},x.normalizeChildren(c)):x(a,p&&p.key,p||{},x.normalizeChildren(c),void 0,void 0)}function O(a){var c=0,k=null,d="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;return function(){var h=Date.now();0===c||16<=h-c?(c=h, a)&&(d[a]=e.attrs[a]);null!=n&&(null!=d["class"]&&(d["class"]=void 0,d.className=n),null!=e.attrs.className&&(d.className=e.attrs.className+" "+n));for(a in d)if(N.call(d,a)&&"key"!==a){g=!0;break}Array.isArray(f)&&1===f.length&&null!=f[0]&&"#"===f[0].tag?u=f[0].children:l=f;return B(e.tag,d.key,g?d:void 0,l,u)}return B(b,d.key,d,f)}function Q(b){var d=0,f=null,g="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;return function(){var e=Date.now();0===d||16<=e-d?(d=e,b()):
a()):null===k&&(k=d(function(){k=null;a();c=Date.now()},16-(h-c)))}}x.normalize=function(a){return Array.isArray(a)?x("[",void 0,void 0,x.normalizeChildren(a),void 0,void 0):null!=a&&"object"!==typeof a?x("#",void 0,void 0,!1===a?"":a,void 0,void 0):a};x.normalizeChildren=function(a){for(var c=0;c<a.length;c++)a[c]=x.normalize(a[c]);return a};var N=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,G={};B.trust=function(a){null==a&&(a="");return x("<",void 0,void 0,a,void 0, null===f&&(f=g(function(){f=null;b();d=Date.now()},16-(e-d)))}}B.normalize=function(b){return Array.isArray(b)?B("[",void 0,void 0,B.normalizeChildren(b),void 0,void 0):null!=b&&"object"!==typeof b?B("#",void 0,void 0,!1===b?"":b,void 0,void 0):b};B.normalizeChildren=function(b){for(var d=0;d<b.length;d++)b[d]=B.normalize(b[d]);return b};var P=/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,M={},N={}.hasOwnProperty;C.trust=function(b){null==b&&(b="");return B("<",void 0,
void 0)};B.fragment=function(a,c){return x("[",a.key,a,x.normalizeChildren(c),void 0,void 0)};var w=function(a){function c(a,b){return function v(c){var l;try{if(!b||null==c||"object"!==typeof c&&"function"!==typeof c||"function"!==typeof(l=c.then))q(function(){b||0!==a.length||console.error("Possible unhandled promise rejection:",c);for(var d=0;d<a.length;d++)a[d](c);h.length=0;m.length=0;r.state=b;r.retry=function(){v(c)}});else{if(c===d)throw new TypeError("Promise can't be resolved w/ itself"); void 0,b,void 0,void 0)};C.fragment=function(b,d){return B("[",b.key,b,B.normalizeChildren(d),void 0,void 0)};var x=function(b){function d(b,a){return function r(d){var k;try{if(!a||null==d||"object"!==typeof d&&"function"!==typeof d||"function"!==typeof(k=d.then))l(function(){a||0!==b.length||console.error("Possible unhandled promise rejection:",d);for(var f=0;f<b.length;f++)b[f](d);e.length=0;n.length=0;m.state=a;m.retry=function(){r(d)}});else{if(d===g)throw new TypeError("Promise can't be resolved w/ itself");
k(l.bind(c))}}catch(P){p(P)}}}function k(a){function b(b){return function(a){0<c++||b(a)}}var c=0,d=b(p);try{a(b(l),d)}catch(D){d(D)}}if(!(this instanceof w))throw Error("Promise must be called with `new`");if("function"!==typeof a)throw new TypeError("executor must be a function");var d=this,h=[],m=[],l=c(h,!0),p=c(m,!1),r=d._instance={resolvers:h,rejectors:m},q="function"===typeof setImmediate?setImmediate:setTimeout;k(a)};w.prototype.then=function(a,c){function k(a,c,k,l){c.push(function(b){if("function"!== f(k.bind(d))}}catch(R){q(R)}}}function f(b){function a(a){return function(b){0<d++||a(b)}}var d=0,e=a(q);try{b(a(k),e)}catch(A){e(A)}}if(!(this instanceof x))throw Error("Promise must be called with `new`");if("function"!==typeof b)throw new TypeError("executor must be a function");var g=this,e=[],n=[],k=d(e,!0),q=d(n,!1),m=g._instance={resolvers:e,rejectors:n},l="function"===typeof setImmediate?setImmediate:setTimeout;f(b)};x.prototype.then=function(b,d){function f(b,d,f,k){d.push(function(a){if("function"!==
typeof a)k(b);else try{h(a(b))}catch(A){m&&m(A)}});"function"===typeof d.retry&&l===d.state&&d.retry()}var d=this._instance,h,m,l=new w(function(a,c){h=a;m=c});k(a,d.resolvers,h,!0);k(c,d.rejectors,m,!1);return l};w.prototype["catch"]=function(a){return this.then(null,a)};w.resolve=function(a){return a instanceof w?a:new w(function(c){c(a)})};w.reject=function(a){return new w(function(c,k){k(a)})};w.all=function(a){return new w(function(c,k){var d=a.length,h=0,m=[];if(0===a.length)c([]);else for(var l= typeof b)f(a);else try{e(b(a))}catch(w){n&&n(w)}});"function"===typeof g.retry&&k===g.state&&g.retry()}var g=this._instance,e,n,k=new x(function(b,d){e=b;n=d});f(b,g.resolvers,e,!0);f(d,g.rejectors,n,!1);return k};x.prototype["catch"]=function(b){return this.then(null,b)};x.resolve=function(b){return b instanceof x?b:new x(function(d){d(b)})};x.reject=function(b){return new x(function(d,f){f(b)})};x.all=function(b){return new x(function(d,f){var g=b.length,e=0,n=[];if(0===b.length)d([]);else for(var k=
0;l<a.length;l++)(function(l){function r(a){h++;m[l]=a;h===d&&c(m)}null==a[l]||"object"!==typeof a[l]&&"function"!==typeof a[l]||"function"!==typeof a[l].then?r(a[l]):a[l].then(r,k)})(l)})};w.race=function(a){return new w(function(c,k){for(var d=0;d<a.length;d++)a[d].then(c,k)})};"undefined"!==typeof window?("undefined"===typeof window.Promise&&(window.Promise=w),w=window.Promise):"undefined"!==typeof global&&("undefined"===typeof global.Promise&&(global.Promise=w),w=global.Promise);var E=function(a){function c(a, 0;k<b.length;k++)(function(k){function m(b){e++;n[k]=b;e===g&&d(n)}null==b[k]||"object"!==typeof b[k]&&"function"!==typeof b[k]||"function"!==typeof b[k].then?m(b[k]):b[k].then(m,f)})(k)})};x.race=function(b){return new x(function(d,f){for(var g=0;g<b.length;g++)b[g].then(d,f)})};"undefined"!==typeof window?("undefined"===typeof window.Promise&&(window.Promise=x),x=window.Promise):"undefined"!==typeof global&&("undefined"===typeof global.Promise&&(global.Promise=x),x=global.Promise);var F=function(b){function d(b,
d){if(Array.isArray(d))for(var h=0;h<d.length;h++)c(a+"["+h+"]",d[h]);else if("[object Object]"===Object.prototype.toString.call(d))for(h in d)c(a+"["+h+"]",d[h]);else k.push(encodeURIComponent(a)+(null!=d&&""!==d?"="+encodeURIComponent(d):""))}if("[object Object]"!==Object.prototype.toString.call(a))return"";var k=[],d;for(d in a)c(d,a[d]);return k.join("&")},I=function(a,c){function k(){function b(){0===--a&&"function"===typeof u&&u()}var a=0;return function D(c){var d=c.then;c.then=function(){a++; g){if(Array.isArray(g))for(var e=0;e<g.length;e++)d(b+"["+e+"]",g[e]);else if("[object Object]"===Object.prototype.toString.call(g))for(e in g)d(b+"["+e+"]",g[e]);else f.push(encodeURIComponent(b)+(null!=g&&""!==g?"="+encodeURIComponent(g):""))}if("[object Object]"!==Object.prototype.toString.call(b))return"";var f=[],g;for(g in b)d(g,b[g]);return f.join("&")},S=/^file:\/\//i,K=function(b,d){function f(){function a(){0===--b&&"function"===typeof u&&u()}var b=0;return function A(d){var e=d.then;d.then=
var h=d.apply(c,arguments);h.then(b,function(c){b();if(0===a)throw c;});return D(h)};return c}}function d(b,a){if("string"===typeof b){var c=b;b=a||{};null==b.url&&(b.url=c)}return b}function h(b,a){if(null==a)return b;for(var c=b.match(/:[^\/]+/gi)||[],d=0;d<c.length;d++){var h=c[d].slice(1);null!=a[h]&&(b=b.replace(c[d],a[h]))}return b}function m(b,a){var c=E(a);if(""!==c){var d=0>b.indexOf("?")?"?":"&";b+=d+c}return b}function l(a){try{return""!==a?JSON.parse(a):null}catch(A){throw Error(a);}} function(){b++;var f=e.apply(d,arguments);f.then(a,function(d){a();if(0===b)throw d;});return A(f)};return d}}function g(a,b){if("string"===typeof a){var d=a;a=b||{};null==a.url&&(a.url=d)}return a}function e(a,b){if(null==b)return a;for(var d=a.match(/:[^\/]+/gi)||[],e=0;e<d.length;e++){var f=d[e].slice(1);null!=b[f]&&(a=a.replace(d[e],b[f]))}return a}function n(a,b){var d=F(b);if(""!==d){var e=0>a.indexOf("?")?"?":"&";a+=e+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(w){throw Error(a);
function p(a){return a.responseText}function r(a,c){if("function"===typeof a)if(Array.isArray(c))for(var b=0;b<c.length;b++)c[b]=new a(c[b]);else return new a(c);return c}var q=0,u;return{request:function(b,u){var q=k();b=d(b,u);var A=new c(function(c,d){null==b.method&&(b.method="GET");b.method=b.method.toUpperCase();var k="boolean"===typeof b.useBody?b.useBody:"GET"!==b.method&&"TRACE"!==b.method;"function"!==typeof b.serialize&&(b.serialize="undefined"!==typeof FormData&&b.data instanceof FormData? }}function q(a){return a.responseText}function m(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;d<b.length;d++)b[d]=new a(b[d]);else return new a(b);return b}var l=0,u;return{request:function(a,l){var u=f();a=g(a,l);var w=new d(function(d,f){null==a.method&&(a.method="GET");a.method=a.method.toUpperCase();var g="GET"===a.method||"TRACE"===a.method?!1:"boolean"===typeof a.useBody?a.useBody:!0;"function"!==typeof a.serialize&&(a.serialize="undefined"!==typeof FormData&&a.data instanceof
function(a){return a}:JSON.stringify);"function"!==typeof b.deserialize&&(b.deserialize=l);"function"!==typeof b.extract&&(b.extract=p);b.url=h(b.url,b.data);k?b.data=b.serialize(b.data):b.url=m(b.url,b.data);var n=new a.XMLHttpRequest;n.open(b.method,b.url,"boolean"===typeof b.async?b.async:!0,"string"===typeof b.user?b.user:void 0,"string"===typeof b.password?b.password:void 0);b.serialize===JSON.stringify&&k&&n.setRequestHeader("Content-Type","application/json; charset=utf-8");b.deserialize=== FormData?function(h){return h}:JSON.stringify);"function"!==typeof a.deserialize&&(a.deserialize=k);"function"!==typeof a.extract&&(a.extract=q);a.url=e(a.url,a.data);g?a.data=a.serialize(a.data):a.url=n(a.url,a.data);var l=new b.XMLHttpRequest,u=!1,w=l.abort;l.abort=function(){u=!0;w.call(l)};l.open(a.method,a.url,"boolean"===typeof a.async?a.async:!0,"string"===typeof a.user?a.user:void 0,"string"===typeof a.password?a.password:void 0);a.serialize===JSON.stringify&&g&&l.setRequestHeader("Content-Type",
l&&n.setRequestHeader("Accept","application/json, text/*");b.withCredentials&&(n.withCredentials=b.withCredentials);for(var u in b.headers)({}).hasOwnProperty.call(b.headers,u)&&n.setRequestHeader(u,b.headers[u]);"function"===typeof b.config&&(n=b.config(n,b)||n);n.onreadystatechange=function(){if(n.status&&4===n.readyState)try{var a=b.extract!==p?b.extract(n,b):b.deserialize(b.extract(n,b));if(200<=n.status&&300>n.status||304===n.status)c(r(b.type,a));else{var g=Error(n.responseText),e;for(e in a)g[e]= "application/json; charset=utf-8");a.deserialize===k&&l.setRequestHeader("Accept","application/json, text/*");a.withCredentials&&(l.withCredentials=a.withCredentials);for(var r in a.headers)({}).hasOwnProperty.call(a.headers,r)&&l.setRequestHeader(r,a.headers[r]);"function"===typeof a.config&&(l=a.config(l,a)||l);l.onreadystatechange=function(){if(!u&&4===l.readyState)try{var h=a.extract!==q?a.extract(l,a):a.deserialize(a.extract(l,a));if(200<=l.status&&300>l.status||304===l.status||S.test(a.url))d(m(a.type,
a[e];d(g)}}catch(f){d(f)}};k&&null!=b.data?n.send(b.data):n.send()});return!0===b.background?A:q(A)},jsonp:function(b,l){var p=k();b=d(b,l);var u=new c(function(c,d){var k=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+q++,l=a.document.createElement("script");a[k]=function(d){l.parentNode.removeChild(l);c(r(b.type,d));delete a[k]};l.onerror=function(){l.parentNode.removeChild(l);d(Error("JSONP request failed"));delete a[k]};null==b.data&&(b.data={});b.url=h(b.url,b.data);b.data[b.callbackKey|| h));else{var c=Error(l.responseText),p;for(p in h)c[p]=h[p];f(c)}}catch(v){f(v)}};g&&null!=a.data?l.send(a.data):l.send()});return!0===a.background?w:u(w)},jsonp:function(a,k){var u=f();a=g(a,k);var q=new d(function(d,f){var g=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=b.document.createElement("script");b[g]=function(e){k.parentNode.removeChild(k);d(m(a.type,e));delete b[g]};k.onerror=function(){k.parentNode.removeChild(k);f(Error("JSONP request failed"));delete b[g]};null==
"callback"]=k;l.src=m(b.url,b.data);a.document.documentElement.appendChild(l)});return!0===b.background?u:p(u)},setCompletionCallback:function(a){u=a}}}(window,w),M=function(a){function c(g,e,a,b,c,d,h){for(;a<b;a++){var f=e[a];null!=f&&r(g,k(f,c,h),d)}}function k(g,e,a){var b=g.tag;null!=g.attrs&&B(g.attrs,g,e);if("string"===typeof b)switch(b){case "#":return g.dom=n.createTextNode(g.children);case "<":return d(g);case "[":var f=n.createDocumentFragment();null!=g.children&&(b=g.children,c(f,b,0, a.data&&(a.data={});a.url=e(a.url,a.data);a.data[a.callbackKey||"callback"]=g;k.src=n(a.url,a.data);b.document.documentElement.appendChild(k)});return!0===a.background?q:u(q)},setCompletionCallback:function(a){u=a}}}(window,x),O=function(b){function d(h,c,p,a,b,d,e){for(;p<a;p++){var v=c[p];null!=v&&f(h,v,b,e,d)}}function f(h,c,p,a,b){var v=c.tag;if("string"===typeof v)switch(c.state={},null!=c.attrs&&C(c.attrs,c,p),v){case "#":return c.dom=D.createTextNode(c.children),l(h,c.dom,b),c.dom;case "<":return g(h,
b.length,e,null,a));g.dom=f.firstChild;g.domSize=f.childNodes.length;return f;default:var h=g.tag;switch(g.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}var l=(b=g.attrs)&&b.is,h=a?l?n.createElementNS(a,h,{is:l}):n.createElementNS(a,h):l?n.createElement(h,{is:l}):n.createElement(h);g.dom=h;if(null!=b)for(f in l=a,b)v(g,f,null,b[f],l);null!=g.attrs&&null!=g.attrs.contenteditable?q(g):(null!=g.text&&(""!==g.text?h.textContent=g.text:g.children= c,b);case "[":var k=D.createDocumentFragment();null!=c.children&&(v=c.children,d(k,v,0,v.length,p,null,a));c.dom=k.firstChild;c.domSize=k.childNodes.length;l(h,k,b);return k;default:var m=c.tag;switch(c.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}var t=(v=c.attrs)&&v.is,m=a?t?D.createElementNS(a,m,{is:t}):D.createElementNS(a,m):t?D.createElement(m,{is:t}):D.createElement(m);c.dom=m;if(null!=v)for(k in t=a,v)A(c,k,null,v[k],t);l(h,m,b);null!=
[x("#",void 0,void 0,g.text,void 0,void 0)]),null!=g.children&&(f=g.children,c(h,f,0,f.length,e,null,a),e=g.attrs,"select"===g.tag&&null!=e&&("value"in e&&v(g,"value",null,e.value,void 0),"selectedIndex"in e&&v(g,"selectedIndex",null,e.selectedIndex,void 0))));return h}else{g.state=Object.create(g.tag);f=g.tag.view;if(null!=f.reentrantLock)g=L;else if(f.reentrantLock=!0,B(g.tag,g,e),g.instance=x.normalize(f.call(g.state,g)),f.reentrantLock=null,null!=g.instance){if(g.instance===g)throw Error("A view cannot return the vnode it received as arguments"); c.attrs&&null!=c.attrs.contenteditable?u(c):(null!=c.text&&(""!==c.text?m.textContent=c.text:c.children=[B("#",void 0,void 0,c.text,void 0,void 0)]),null!=c.children&&(h=c.children,d(m,h,0,h.length,p,null,a),h=c.attrs,"select"===c.tag&&null!=h&&("value"in h&&A(c,"value",null,h.value,void 0),"selectedIndex"in h&&A(c,"selectedIndex",null,h.selectedIndex,void 0))));return m}else return e(c,p),null!=c.instance?(p=f(h,c.instance,p,a,b),c.dom=c.instance.dom,c.domSize=null!=c.dom?c.instance.domSize:0,l(h,
e=k(g.instance,e,a);g.dom=g.instance.dom;g.domSize=null!=g.dom?g.instance.domSize:0;g=e}else g.domSize=0,g=L;return g}}function d(g){var e={caption:"table",thead:"table",tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"}[(g.children.match(/^\s*?<(\w+)/im)||[])[1]]||"div",e=n.createElement(e);e.innerHTML=g.children;g.dom=e.firstChild;g.domSize=e.childNodes.length;g=n.createDocumentFragment();for(var a;a=e.firstChild;)g.appendChild(a);return g}function h(g,e,a,b, p,b),c=p):(c.domSize=0,c=J),c}function g(h,c,p){var a={caption:"table",thead:"table",tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"}[(c.children.match(/^\s*?<(\w+)/im)||[])[1]]||"div",a=D.createElement(a);a.innerHTML=c.children;c.dom=a.firstChild;c.domSize=a.childNodes.length;c=D.createDocumentFragment();for(var b;b=a.firstChild;)c.appendChild(b);l(h,c,p);return c}function e(h,c){var a;if("function"===typeof h.tag.view){h.state=Object.create(h.tag);a=h.state.view;
d,h){if(e!==a&&(null!=e||null!=a))if(null==e)c(g,a,0,a.length,b,d,void 0);else if(null==a)u(e,0,e.length,a);else{if(e.length===a.length){for(var f=!1,t=0;t<a.length;t++)if(null!=a[t]&&null!=e[t]){f=null==a[t].key&&null==e[t].key;break}if(f){for(t=0;t<e.length;t++)e[t]!==a[t]&&(null==e[t]&&null!=a[t]?r(g,k(a[t],b,h),p(e,t+1,d)):null==a[t]?u(e,t,t+1,a):m(g,e[t],a[t],b,p(e,t+1,d),!1,h));return}}a:{if(null!=e.pool&&Math.abs(e.pool.length-a.length)<=Math.abs(e.length-a.length)&&(f=a[0]&&a[0].children&& if(null!=a.$$reentrantLock$$)return J;a.$$reentrantLock$$=!0}else{h.state=void 0;a=h.tag;if(null!=a.$$reentrantLock$$)return J;a.$$reentrantLock$$=!0;h.state=null!=h.tag.prototype&&"function"===typeof h.tag.prototype.view?new h.tag(h):h.tag(h)}h._state=h.state;null!=h.attrs&&C(h.attrs,h,c);C(h._state,h,c);h.instance=B.normalize(h._state.view.call(h.state,h));if(h.instance===h)throw Error("A view cannot return the vnode it received as argument");a.$$reentrantLock$$=null}function n(h,c,p,b,e,g,n){if(c!==
a[0].children.length||0,Math.abs((e.pool[0]&&e.pool[0].children&&e.pool[0].children.length||0)-f)<=Math.abs((e[0]&&e[0].children&&e[0].children.length||0)-f))){f=!0;break a}f=!1}f&&(e=e.concat(e.pool));for(var n=t=0,q=e.length-1,A=a.length-1,z;q>=t&&A>=n;){var y=e[t],v=a[n];if(y!==v||f)if(null==y)t++;else if(null==v)n++;else if(y.key===v.key)t++,n++,m(g,y,v,b,p(e,t,d),f,h),f&&y.tag===v.tag&&r(g,l(y),d);else if(y=e[q],y!==v||f)if(null==y)q--;else if(null==v)n++;else if(y.key===v.key)m(g,y,v,b,p(e, p&&(null!=c||null!=p))if(null==c)d(h,p,0,p.length,e,g,void 0);else if(null==p)a(c,0,c.length,p);else{if(c.length===p.length){for(var v=!1,t=0;t<p.length;t++)if(null!=p[t]&&null!=c[t]){v=null==p[t].key&&null==c[t].key;break}if(v){for(t=0;t<c.length;t++)c[t]!==p[t]&&(null==c[t]&&null!=p[t]?f(h,p[t],e,n,m(c,t+1,g)):null==p[t]?a(c,t,t+1,p):k(h,c[t],p[t],e,m(c,t+1,g),b,n));return}}if(!b)a:{if(null!=c.pool&&Math.abs(c.pool.length-p.length)<=Math.abs(c.length-p.length)&&(b=p[0]&&p[0].children&&p[0].children.length||
q+1,d),f,h),(f||n<A)&&r(g,l(y),p(e,t,d)),q--,n++;else break;else q--,n++;else t++,n++}for(;q>=t&&A>=n;){y=e[q];v=a[A];if(y!==v||f)if(null==y)q--;else{if(null!=v)if(y.key===v.key)m(g,y,v,b,p(e,q+1,d),f,h),f&&y.tag===v.tag&&r(g,l(y),d),null!=y.dom&&(d=y.dom),q--;else{if(!z){z=e;var y=q,C={},x;for(x=0;x<y;x++){var w=z[x];null!=w&&(w=w.key,null!=w&&(C[w]=x))}z=C}null!=v&&(y=z[v.key],null!=y?(C=e[y],m(g,C,v,b,p(e,q+1,d),f,h),r(g,l(C),d),e[y].skip=!0,null!=C.dom&&(d=C.dom)):(v=k(v,b,void 0),r(g,v,d),d= 0,Math.abs((c.pool[0]&&c.pool[0].children&&c.pool[0].children.length||0)-b)<=Math.abs((c[0]&&c[0].children&&c[0].children.length||0)-b))){b=!0;break a}b=!1}if(b){var u=c.pool;c=c.concat(c.pool)}for(var t=v=0,w=c.length-1,y=p.length-1,G;w>=v&&y>=t;){var r=c[v],z=p[t];if(r!==z||b)if(null==r)v++;else if(null==z)t++;else if(r.key===z.key){var A=null!=u&&v>=c.length-u.length||null==u&&b;v++;t++;k(h,r,z,e,m(c,v,g),A,n);b&&r.tag===z.tag&&l(h,q(r),g)}else if(r=c[w],r!==z||b)if(null==r)w--;else if(null==z)t++;
v))}A--}else q--,A--;if(A<n)break}c(g,a,n,A+1,b,d,h);u(e,t,q+1,a)}}function m(a,e,f,c,n,u,p){var g=e.tag;if(g===f.tag){f.state=e.state;f.events=e.events;var t;var A;null!=f.attrs&&"function"===typeof f.attrs.onbeforeupdate&&(t=f.attrs.onbeforeupdate.call(f.state,f,e));"string"!==typeof f.tag&&"function"===typeof f.tag.onbeforeupdate&&(A=f.tag.onbeforeupdate.call(f.state,f,e));void 0===t&&void 0===A||t||A?t=!1:(f.dom=e.dom,f.domSize=e.domSize,f.instance=e.instance,t=!0);if(!t)if(null!=f.attrs&&K(f.attrs, else if(r.key===z.key)A=null!=u&&w>=c.length-u.length||null==u&&b,k(h,r,z,e,m(c,w+1,g),A,n),(b||t<y)&&l(h,q(r),m(c,v,g)),w--,t++;else break;else w--,t++;else v++,t++}for(;w>=v&&y>=t;){r=c[w];z=p[y];if(r!==z||b)if(null==r)w--;else{if(null!=z)if(r.key===z.key)A=null!=u&&w>=c.length-u.length||null==u&&b,k(h,r,z,e,m(c,w+1,g),A,n),b&&r.tag===z.tag&&l(h,q(r),g),null!=r.dom&&(g=r.dom),w--;else{if(!G){G=c;var r=w,A={},E;for(E=0;E<r;E++){var x=G[E];null!=x&&(x=x.key,null!=x&&(A[x]=E))}G=A}null!=z&&(r=G[z.key],
f,c,u),"string"===typeof g)switch(g){case "#":e.children.toString()!==f.children.toString()&&(e.dom.nodeValue=f.children);f.dom=e.dom;break;case "<":e.children!==f.children?(l(e),r(a,d(f),n)):(f.dom=e.dom,f.domSize=e.domSize);break;case "[":h(a,e.children,f.children,c,n,p);e=0;c=f.children;f.dom=null;if(null!=c){for(var z=0;z<c.length;z++)a=c[z],null!=a&&null!=a.dom&&(null==f.dom&&(f.dom=a.dom),e+=a.domSize||1);1!==e&&(f.domSize=e)}break;default:a=p;n=f.dom=e.dom;switch(f.tag){case "svg":a="http://www.w3.org/2000/svg"; null!=r?(A=c[r],k(h,A,z,e,m(c,w+1,g),b,n),l(h,q(A),g),c[r].skip=!0,null!=A.dom&&(g=A.dom)):g=f(h,z,e,void 0,g))}y--}else w--,y--;if(y<t)break}d(h,p,t,y+1,e,g,n);a(c,v,w+1,p)}}function k(h,c,a,b,d,m,l){var p=c.tag;if(p===a.tag){a.state=c.state;a._state=c._state;a.events=c.events;var v;if(v=!m){var r,z;null!=a.attrs&&"function"===typeof a.attrs.onbeforeupdate&&(r=a.attrs.onbeforeupdate.call(a.state,a,c));"string"!==typeof a.tag&&"function"===typeof a._state.onbeforeupdate&&(z=a._state.onbeforeupdate.call(a.state,
break;case "math":a="http://www.w3.org/1998/Math/MathML"}"textarea"===f.tag&&(null==f.attrs&&(f.attrs={}),null!=f.text&&(f.attrs.value=f.text,f.text=void 0));u=e.attrs;p=f.attrs;g=a;if(null!=p)for(z in p)v(f,z,u&&u[z],p[z],g);if(null!=u)for(z in u)null!=p&&z in p||("className"===z&&(z="class"),"o"!==z[0]||"n"!==z[1]||D(z)?"key"!==z&&f.dom.removeAttribute(z):w(f,z,void 0));null!=f.attrs&&null!=f.attrs.contenteditable?q(f):null!=e.text&&null!=f.text&&""!==f.text?e.text.toString()!==f.text.toString()&& a,c));void 0===r&&void 0===z||r||z?v=!1:(a.dom=c.dom,a.domSize=c.domSize,a.instance=c.instance,v=!0)}if(!v)if("string"===typeof p)switch(null!=a.attrs&&(m?(a.state={},C(a.attrs,a,b)):I(a.attrs,a,b)),p){case "#":c.children.toString()!==a.children.toString()&&(c.dom.nodeValue=a.children);a.dom=c.dom;break;case "<":c.children!==a.children?(q(c),g(h,a,d)):(a.dom=c.dom,a.domSize=c.domSize);break;case "[":n(h,c.children,a.children,m,b,d,l);c=0;b=a.children;a.dom=null;if(null!=b){for(m=0;m<b.length;m++){var y=
(e.dom.firstChild.nodeValue=f.text):(null!=e.text&&(e.children=[x("#",void 0,void 0,e.text,void 0,e.dom.firstChild)]),null!=f.text&&(f.children=[x("#",void 0,void 0,f.text,void 0,void 0)]),h(n,e.children,f.children,c,null,a))}else f.instance=x.normalize(f.tag.view.call(f.state,f)),K(f.tag,f,c,u),null!=f.instance?(null==e.instance?r(a,k(f.instance,c,p),n):m(a,e.instance,f.instance,c,n,u,p),f.dom=f.instance.dom,f.domSize=f.instance.domSize):null!=e.instance?(b(e.instance,null),f.dom=void 0,f.domSize= b[m];null!=y&&null!=y.dom&&(null==a.dom&&(a.dom=y.dom),c+=y.domSize||1)}1!==c&&(a.domSize=c)}break;default:h=l;d=a.dom=c.dom;switch(a.tag){case "svg":h="http://www.w3.org/2000/svg";break;case "math":h="http://www.w3.org/1998/Math/MathML"}"textarea"===a.tag&&(null==a.attrs&&(a.attrs={}),null!=a.text&&(a.attrs.value=a.text,a.text=void 0));l=c.attrs;p=a.attrs;v=h;if(null!=p)for(y in p)A(a,y,l&&l[y],p[y],v);if(null!=l)for(y in l)null!=p&&y in p||("className"===y&&(y="class"),"o"!==y[0]||"n"!==y[1]||E(y)?
0):(f.dom=e.dom,f.domSize=e.domSize)}else b(e,null),r(a,k(f,c,p),n)}function l(a){var e=a.domSize;if(null!=e||null==a.dom){var g=n.createDocumentFragment();if(0<e){for(a=a.dom;--e;)g.appendChild(a.nextSibling);g.insertBefore(a,g.firstChild)}return g}return a.dom}function p(a,e,b){for(;e<a.length;e++)if(null!=a[e]&&null!=a[e].dom)return a[e].dom;return b}function r(a,e,b){b&&b.parentNode?a.insertBefore(e,b):a.appendChild(e)}function q(a){var e=a.children;if(null!=e&&1===e.length&&"<"===e[0].tag)e= "key"!==y&&a.dom.removeAttribute(y):x(a,y,void 0));null!=a.attrs&&null!=a.attrs.contenteditable?u(a):null!=c.text&&null!=a.text&&""!==a.text?c.text.toString()!==a.text.toString()&&(c.dom.firstChild.nodeValue=a.text):(null!=c.text&&(c.children=[B("#",void 0,void 0,c.text,void 0,c.dom.firstChild)]),null!=a.text&&(a.children=[B("#",void 0,void 0,a.text,void 0,void 0)]),n(d,c.children,a.children,m,b,null,h))}else{if(m)e(a,b);else{a.instance=B.normalize(a._state.view.call(a.state,a));if(a.instance===a)throw Error("A view cannot return the vnode it received as argument");
e[0].children,a.dom.innerHTML!==e&&(a.dom.innerHTML=e);else if(null!=a.text||null!=e&&0!==e.length)throw Error("Child node of a contenteditable must be trusted");}function u(a,e,f,c){for(;e<f;e++){var g=a[e];null!=g&&(g.skip?g.skip=!1:b(g,c))}}function b(a,e){function b(){if(++c===g&&(A(a),a.dom)){var b=a.domSize||1;if(1<b)for(var f=a.dom;--b;){var d=f.nextSibling,h=d.parentNode;null!=h&&h.removeChild(d)}b=a.dom;f=b.parentNode;null!=f&&f.removeChild(b);if(b=null!=e&&null==a.domSize)b=a.attrs,b=!(null!= null!=a.attrs&&I(a.attrs,a,b);I(a._state,a,b)}null!=a.instance?(null==c.instance?f(h,a.instance,b,l,d):k(h,c.instance,a.instance,b,d,m,l),a.dom=a.instance.dom,a.domSize=a.instance.domSize):null!=c.instance?(w(c.instance,null),a.dom=void 0,a.domSize=0):(a.dom=c.dom,a.domSize=c.domSize)}}else w(c,null),f(h,a,b,l,d)}function q(a){var c=a.domSize;if(null!=c||null==a.dom){var b=D.createDocumentFragment();if(0<c){for(a=a.dom;--c;)b.appendChild(a.nextSibling);b.insertBefore(a,b.firstChild)}return b}return a.dom}
b&&(b.oncreate||b.onupdate||b.onbeforeremove||b.onremove));b&&"string"===typeof a.tag&&(e.pool?e.pool.push(a):e.pool=[a])}}var g=1,c=0;if(a.attrs&&a.attrs.onbeforeremove){var d=a.attrs.onbeforeremove.call(a.state,a);null!=d&&"function"===typeof d.then&&(g++,d.then(b,b))}"string"!==typeof a.tag&&a.tag.onbeforeremove&&(d=a.tag.onbeforeremove.call(a.state,a),null!=d&&"function"===typeof d.then&&(g++,d.then(b,b)));b()}function A(a){a.attrs&&a.attrs.onremove&&a.attrs.onremove.call(a.state,a);"string"!== function m(a,c,b){for(;c<a.length;c++)if(null!=a[c]&&null!=a[c].dom)return a[c].dom;return b}function l(a,c,b){b&&b.parentNode?a.insertBefore(c,b):a.appendChild(c)}function u(a){var c=a.children;if(null!=c&&1===c.length&&"<"===c[0].tag)c=c[0].children,a.dom.innerHTML!==c&&(a.dom.innerHTML=c);else if(null!=a.text||null!=c&&0!==c.length)throw Error("Child node of a contenteditable must be trusted");}function a(a,c,b,d){for(;c<b;c++){var h=a[c];null!=h&&(h.skip?h.skip=!1:w(h,d))}}function w(a,c){function b(){if(++d===
typeof a.tag&&a.tag.onremove&&a.tag.onremove.call(a.state,a);if(null!=a.instance)A(a.instance);else if(a=a.children,Array.isArray(a))for(var b=0;b<a.length;b++){var g=a[b];null!=g&&A(g)}}function v(a,b,c,d,h){var e=a.dom;if("key"!==b&&"is"!==b&&(c!==d||"value"===b||"checked"===b||"selectedIndex"===b||"selected"===b&&a.dom===n.activeElement||"object"===typeof d)&&"undefined"!==typeof d&&!D(b)){var g=b.indexOf(":");if(-1<g&&"xlink"===b.substr(0,g))e.setAttributeNS("http://www.w3.org/1999/xlink",b.slice(g+ h&&(r(a),a.dom)){var b=a.domSize||1;if(1<b)for(var e=a.dom;--b;){var g=e.nextSibling,f=g.parentNode;null!=f&&f.removeChild(g)}b=a.dom;e=b.parentNode;null!=e&&e.removeChild(b);if(b=null!=c&&null==a.domSize)b=a.attrs,b=!(null!=b&&(b.oncreate||b.onupdate||b.onbeforeremove||b.onremove));b&&"string"===typeof a.tag&&(c.pool?c.pool.push(a):c.pool=[a])}}var h=1,d=0;if(a.attrs&&"function"===typeof a.attrs.onbeforeremove){var e=a.attrs.onbeforeremove.call(a.state,a);null!=e&&"function"===typeof e.then&&(h++,
1),d);else if("o"===b[0]&&"n"===b[1]&&"function"===typeof d)w(a,b,d);else if("style"===b)if(a=c,a===d&&(e.style.cssText="",a=null),null==d)e.style.cssText="";else if("string"===typeof d)e.style.cssText=d;else{"string"===typeof a&&(e.style.cssText="");for(var f in d)e.style[f]=d[f];if(null!=a&&"string"!==typeof a)for(f in a)f in d||(e.style[f]="")}else b in e&&"href"!==b&&"list"!==b&&"form"!==b&&"width"!==b&&"height"!==b&&void 0===h&&!(a.attrs.is||-1<a.tag.indexOf("-"))?"input"===a.tag&&"value"=== e.then(b,b))}"string"!==typeof a.tag&&"function"===typeof a._state.onbeforeremove&&(e=a._state.onbeforeremove.call(a.state,a),null!=e&&"function"===typeof e.then&&(h++,e.then(b,b)));b()}function r(a){a.attrs&&"function"===typeof a.attrs.onremove&&a.attrs.onremove.call(a.state,a);"string"!==typeof a.tag&&"function"===typeof a._state.onremove&&a._state.onremove.call(a.state,a);if(null!=a.instance)r(a.instance);else if(a=a.children,Array.isArray(a))for(var c=0;c<a.length;c++){var b=a[c];null!=b&&r(b)}}
b&&a.dom.value===d&&a.dom===n.activeElement||"select"===a.tag&&"value"===b&&a.dom.value===d&&a.dom===n.activeElement||"option"===a.tag&&"value"===b&&a.dom.value===d||("input"===a.tag&&"type"===b?e.setAttribute(b,d):e[b]=d):"boolean"===typeof d?d?e.setAttribute(b,""):e.removeAttribute(b):e.setAttribute("className"===b?"class":b,d)}}function D(a){return"oninit"===a||"oncreate"===a||"onupdate"===a||"onremove"===a||"onbeforeremove"===a||"onbeforeupdate"===a}function w(a,b,d){var e=a.dom,c="function"!== function A(a,c,b,d,e){var h=a.dom;if("key"!==c&&"is"!==c&&(b!==d||"value"===c||"checked"===c||"selectedIndex"===c||"selected"===c&&a.dom===D.activeElement||"object"===typeof d)&&"undefined"!==typeof d&&!E(c)){var g=c.indexOf(":");if(-1<g&&"xlink"===c.substr(0,g))h.setAttributeNS("http://www.w3.org/1999/xlink",c.slice(g+1),d);else if("o"===c[0]&&"n"===c[1]&&"function"===typeof d)x(a,c,d);else if("style"===c)if(a=b,a===d&&(h.style.cssText="",a=null),null==d)h.style.cssText="";else if("string"===typeof d)h.style.cssText=
typeof H?d:function(a){var b=d.call(e,a);H.call(e,a);return b};if(b in e)e[b]="function"===typeof d?c:null;else{var f=b.slice(2);void 0===a.events&&(a.events={});a.events[b]!==c&&(null!=a.events[b]&&e.removeEventListener(f,a.events[b],!1),"function"===typeof d&&(a.events[b]=c,e.addEventListener(f,a.events[b],!1)))}}function B(a,b,d){"function"===typeof a.oninit&&a.oninit.call(b.state,b);"function"===typeof a.oncreate&&d.push(a.oncreate.bind(b.state,b))}function K(a,b,d,c){c?B(a,b,d):"function"=== d;else{"string"===typeof a&&(h.style.cssText="");for(var f in d)h.style[f]=d[f];if(null!=a&&"string"!==typeof a)for(f in a)f in d||(h.style[f]="")}else c in h&&"href"!==c&&"list"!==c&&"form"!==c&&"width"!==c&&"height"!==c&&void 0===e&&!(a.attrs.is||-1<a.tag.indexOf("-"))?"input"===a.tag&&"value"===c&&a.dom.value==d&&a.dom===D.activeElement||"select"===a.tag&&"value"===c&&a.dom.value==d&&a.dom===D.activeElement||"option"===a.tag&&"value"===c&&a.dom.value==d||("input"===a.tag&&"type"===c?h.setAttribute(c,
typeof a.onupdate&&d.push(a.onupdate.bind(b.state,b))}var n=a.document,L=n.createDocumentFragment(),H;return{render:function(a,b){if(!a)throw Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var d=[],e=n.activeElement;null==a.vnodes&&(a.textContent="");Array.isArray(b)||(b=[b]);h(a,a.vnodes,x.normalizeChildren(b),d,null,void 0);a.vnodes=b;for(var c=0;c<d.length;c++)d[c]();n.activeElement!==e&&e.focus()},setEventCallback:function(a){return H=a}}},F=function(a){function c(a){a= d):h[c]=d):"boolean"===typeof d?d?h.setAttribute(c,""):h.removeAttribute(c):h.setAttribute("className"===c?"class":c,d)}}function E(a){return"oninit"===a||"oncreate"===a||"onupdate"===a||"onremove"===a||"onbeforeremove"===a||"onbeforeupdate"===a}function x(a,c,b){var d=a.dom,e="function"!==typeof F?b:function(a){var c=b.call(d,a);F.call(d,a);return c};if(c in d)d[c]="function"===typeof b?e:null;else{var h=c.slice(2);void 0===a.events&&(a.events={});a.events[c]!==e&&(null!=a.events[c]&&d.removeEventListener(h,
d.indexOf(a);-1<a&&d.splice(a,2)}function k(){for(var a=1;a<d.length;a+=2)d[a]()}a=M(a);a.setEventCallback(function(a){!1!==a.redraw&&k()});var d=[];return{subscribe:function(a,k){c(a);d.push(a,O(k))},unsubscribe:c,redraw:k,render:a.render}}(window);I.setCompletionCallback(F.redraw);B.mount=function(a){return function(c,k){if(null===k)a.render(c,[]),a.unsubscribe(c);else{if(null==k.view)throw Error("m.mount(element, component) expects a component, not a vnode");a.subscribe(c,function(){a.render(c, a.events[c],!1),"function"===typeof b&&(a.events[c]=e,d.addEventListener(h,a.events[c],!1)))}}function C(a,b,d){"function"===typeof a.oninit&&a.oninit.call(b.state,b);"function"===typeof a.oncreate&&d.push(a.oncreate.bind(b.state,b))}function I(a,b,d){"function"===typeof a.onupdate&&d.push(a.onupdate.bind(b.state,b))}var D=b.document,J=D.createDocumentFragment(),F;return{render:function(a,b){if(!a)throw Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var c=
x(k))});a.redraw()}}}(F);var Q=w,J=function(a){if(""===a||null==a)return{};"?"===a.charAt(0)&&(a=a.slice(1));a=a.split("&");for(var c={},k={},d=0;d<a.length;d++){var h=a[d].split("="),m=decodeURIComponent(h[0]),h=2===h.length?decodeURIComponent(h[1]):"";"true"===h?h=!0:"false"===h&&(h=!1);var l=m.split(/\]\[?|\[/),p=c;-1<m.indexOf("[")&&l.pop();for(var r=0;r<l.length;r++){var m=l[r],q=l[r+1],q=""==q||!isNaN(parseInt(q,10)),u=r===l.length-1;""===m&&(m=l.slice(0,r).join(),null==k[m]&&(k[m]=0),m=k[m]++); [],d=D.activeElement;null==a.vnodes&&(a.textContent="");Array.isArray(b)||(b=[b]);n(a,a.vnodes,B.normalizeChildren(b),!1,c,null,void 0);a.vnodes=b;for(var e=0;e<c.length;e++)c[e]();D.activeElement!==d&&d.focus()},setEventCallback:function(a){return F=a}}},H=function(b){function d(b){b=g.indexOf(b);-1<b&&g.splice(b,2)}function f(){for(var b=1;b<g.length;b+=2)g[b]()}b=O(b);b.setEventCallback(function(b){!1!==b.redraw&&f()});var g=[];return{subscribe:function(b,f){d(b);g.push(b,Q(f))},unsubscribe:d,
null==p[m]&&(p[m]=u?h:q?[]:{});p=p[m]}}return c},R=function(a){function c(d){var c=a.location[d].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===d&&"/"!==c[0]&&(c="/"+c);return c}function k(a){return function(){null==l&&(l=m(function(){l=null;a()}))}}function d(a,d,c){var b=a.indexOf("?"),h=a.indexOf("#"),k=-1<b?b:-1<h?h:a.length;if(-1<b){var b=J(a.slice(b+1,-1<h?h:a.length)),l;for(l in b)d[l]=b[l]}if(-1<h)for(l in d=J(a.slice(h+1)),d)c[l]=d[l];return a.slice(0,k)}var h="function"=== redraw:f,render:b.render}}(window);K.setCompletionCallback(H.redraw);C.mount=function(b){return function(d,f){if(null===f)b.render(d,[]),b.unsubscribe(d);else{if(null==f.view&&"function"!==typeof f)throw Error("m.mount(element, component) expects a component, not a vnode");b.subscribe(d,function(){b.render(d,B(f))});b.redraw()}}}(H);var T=x,L=function(b){if(""===b||null==b)return{};"?"===b.charAt(0)&&(b=b.slice(1));b=b.split("&");for(var d={},f={},g=0;g<b.length;g++){var e=b[g].split("="),n=decodeURIComponent(e[0]),
typeof a.history.pushState,m="function"===typeof setImmediate?setImmediate:setTimeout,l,p={prefix:"#!",getPath:function(){switch(p.prefix.charAt(0)){case "#":return c("hash").slice(p.prefix.length);case "?":return c("search").slice(p.prefix.length)+c("hash");default:return c("pathname").slice(p.prefix.length)+c("search")+c("hash")}},setPath:function(c,l,k){var b={},m={};c=d(c,b,m);if(null!=l){for(var v in l)b[v]=l[v];c=c.replace(/:([^\/]+)/g,function(a,c){delete b[c];return l[c]})}(v=E(b))&&(c+="?"+ e=2===e.length?decodeURIComponent(e[1]):"";"true"===e?e=!0:"false"===e&&(e=!1);var k=n.split(/\]\[?|\[/),q=d;-1<n.indexOf("[")&&k.pop();for(var m=0;m<k.length;m++){var n=k[m],l=k[m+1],l=""==l||!isNaN(parseInt(l,10)),u=m===k.length-1;""===n&&(n=k.slice(0,m).join(),null==f[n]&&(f[n]=0),n=f[n]++);null==q[n]&&(q[n]=u?e:l?[]:{});q=q[n]}}return d},U=function(b){function d(d){var e=b.location[d].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===d&&"/"!==e[0]&&(e="/"+e);return e}function f(b){return function(){null==
v);(m=E(m))&&(c+="#"+m);h?(m=k?k.state:null,v=k?k.title:null,a.onpopstate(),k&&k.replace?a.history.replaceState(m,v,p.prefix+c):a.history.pushState(m,v,p.prefix+c)):a.location.href=p.prefix+c},defineRoutes:function(c,l,m){function b(){var b=p.getPath(),h={},k=d(b,h,h),u=a.history.state;if(null!=u)for(var q in u)h[q]=u[q];for(var r in c)if(u=new RegExp("^"+r.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$"),u.test(k)){k.replace(u,function(){for(var a=r.match(/:[^\/]+/g)||[], k&&(k=n(function(){k=null;b()}))}}function g(b,d,e){var a=b.indexOf("?"),g=b.indexOf("#"),f=-1<a?a:-1<g?g:b.length;if(-1<a){var a=L(b.slice(a+1,-1<g?g:b.length)),k;for(k in a)d[k]=a[k]}if(-1<g)for(k in d=L(b.slice(g+1)),d)e[k]=d[k];return b.slice(0,f)}var e="function"===typeof b.history.pushState,n="function"===typeof setImmediate?setImmediate:setTimeout,k,q={prefix:"#!",getPath:function(){switch(q.prefix.charAt(0)){case "#":return d("hash").slice(q.prefix.length);case "?":return d("search").slice(q.prefix.length)+
d=[].slice.call(arguments,1,-2),k=0;k<a.length;k++)h[a[k].replace(/:|\./g,"")]=decodeURIComponent(d[k]);l(c[r],h,b,r)});return}m(b,h)}h?a.onpopstate=k(b):"#"===p.prefix.charAt(0)&&(a.onhashchange=b);b()}};return p};B.route=function(a,c){var k=R(a),d=function(a){return a},h,m,l,p,r,q=function(a,b,q){if(null==a)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");var u=function(){null!=h&&c.render(a,h(x(m,l.key,l)))},w=function(){k.setPath(b,null,{replace:!0})};k.defineRoutes(q, d("hash");default:return d("pathname").slice(q.prefix.length)+d("search")+d("hash")}},setPath:function(d,f,k){var a={},l={};d=g(d,a,l);if(null!=f){for(var m in f)a[m]=f[m];d=d.replace(/:([^\/]+)/g,function(b,d){delete a[d];return f[d]})}(m=F(a))&&(d+="?"+m);(l=F(l))&&(d+="#"+l);e?(l=k?k.state:null,m=k?k.title:null,b.onpopstate(),k&&k.replace?b.history.replaceState(l,m,q.prefix+d):b.history.pushState(l,m,q.prefix+d)):b.location.href=q.prefix+d},defineRoutes:function(d,k,n){function a(){var a=q.getPath(),
function(a,b,c){var k=r=function(a,n){k===r&&(m=null!=n&&"function"===typeof n.view?n:"div",l=b,p=c,r=null,h=(a.render||d).bind(a),u())};a.view?k({},a):a.onmatch?Q.resolve(a.onmatch(b,c)).then(function(b){k(a,b)},w):k(a,"div")},w);c.subscribe(a,u)};q.set=function(a,b,c){null!=r&&(c={replace:!0});r=null;k.setPath(a,b,c)};q.get=function(){return p};q.prefix=function(a){k.prefix=a};q.link=function(a){a.dom.setAttribute("href",k.prefix+a.attrs.href);a.dom.onclick=function(a){a.ctrlKey||a.metaKey||a.shiftKey|| e={},f=g(a,e,e),l=b.history.state;if(null!=l)for(var m in l)e[m]=l[m];for(var u in d)if(l=new RegExp("^"+u.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$"),l.test(f)){f.replace(l,function(){for(var b=u.match(/:[^\/]+/g)||[],g=[].slice.call(arguments,1,-2),f=0;f<b.length;f++)e[b[f].replace(/:|\./g,"")]=decodeURIComponent(g[f]);k(d[u],e,a,u)});return}n(a,e)}e?b.onpopstate=f(a):"#"===q.prefix.charAt(0)&&(b.onhashchange=a);a()}};return q};C.route=function(b,d){var f=U(b),g=function(b){return b},
2===a.which||(a.preventDefault(),a.redraw=!1,a=this.getAttribute("href"),0===a.indexOf(k.prefix)&&(a=a.slice(k.prefix.length)),q.set(a,void 0,void 0))}};q.param=function(a){return"undefined"!==typeof l&&"undefined"!==typeof a?l[a]:l};return q}(window,F);B.withAttr=function(a,c,k){return function(d){c.call(k||this,a in d.currentTarget?d.currentTarget[a]:d.currentTarget.getAttribute(a))}};var S=M(window);B.render=S.render;B.redraw=F.redraw;B.request=I.request;B.jsonp=I.jsonp;B.parseQueryString=J;B.buildQueryString= e,n,k,q,m,l=function(b,a,l){if(null==b)throw Error("Ensure the DOM element that was passed to `m.route` is not undefined");var u=function(){null!=e&&d.render(b,e(B(n,k.key,k)))},w=function(b){if(b!==a)f.setPath(a,null,{replace:!0});else throw Error("Could not resolve default route "+a);};f.defineRoutes(l,function(a,b,d){var f=m=function(a,l){f===m&&(n=null==l||"function"!==typeof l.view&&"function"!==typeof l?"div":l,k=b,q=d,m=null,e=(a.render||g).bind(a),u())};a.view||"function"===typeof a?f({},
E;B.version="1.0.0";B.vnode=x;"undefined"!==typeof module?module.exports=B:window.m=B}; a):a.onmatch?T.resolve(a.onmatch(b,d)).then(function(b){f(a,b)},w):f(a,"div")},w);d.subscribe(b,u)};l.set=function(b,a,d){null!=m&&(d={replace:!0});m=null;f.setPath(b,a,d)};l.get=function(){return q};l.prefix=function(b){f.prefix=b};l.link=function(b){b.dom.setAttribute("href",f.prefix+b.attrs.href);b.dom.onclick=function(a){a.ctrlKey||a.metaKey||a.shiftKey||2===a.which||(a.preventDefault(),a.redraw=!1,a=this.getAttribute("href"),0===a.indexOf(f.prefix)&&(a=a.slice(f.prefix.length)),l.set(a,void 0,
void 0))}};l.param=function(b){return"undefined"!==typeof k&&"undefined"!==typeof b?k[b]:k};return l}(window,H);C.withAttr=function(b,d,f){return function(g){d.call(f||this,b in g.currentTarget?g.currentTarget[b]:g.currentTarget.getAttribute(b))}};var V=O(window);C.render=V.render;C.redraw=H.redraw;C.request=K.request;C.jsonp=K.jsonp;C.parseQueryString=L;C.buildQueryString=F;C.version="1.0.1";C.vnode=B;"undefined"!==typeof module?module.exports=C:window.m=C})();

View file

@ -1,3 +1,5 @@
"use strict"
var redrawService = require("./redraw") var redrawService = require("./redraw")
module.exports = require("./api/mount")(redrawService) module.exports = require("./api/mount")(redrawService)

View file

@ -278,12 +278,20 @@ ospec will automatically evaluate all `*.js` files in any folder named `/tests`.
$ npm test $ npm test
``` ```
#### Installing ospec globally #### Direct use from the command line
While it's recommended to install ospec locally to maintain reproducible environments, sometimes it may be deemed appropriate to install it globally. To do so, run this command: Ospec doesn't work when installed globally. Using global scripts is generally a bad idea since you can end up with different, incompatible versions of the same package installed locally and globally.
To work around this limitation, you can use [`npm-run`](https://www.npmjs.com/package/npm-run) which enables one to run the binaries of locally installed packages.
``` ```
npm install ospec -g npm install npm-run -g
```
Then, from a project that has ospec installed as a (dev) dependency:
```
npm-run ospec
``` ```
--- ---

View file

@ -1,4 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
"use strict"
var fs = require("fs") var fs = require("fs")
var path = require("path") var path = require("path")
@ -31,9 +32,9 @@ function traverseDirectory(pathname, callback) {
}) })
} }
traverseDirectory(".", function(pathname, stat, children) { traverseDirectory(".", function(pathname) {
if (pathname.match(/(?:^|\/)tests\/.*\.js$/)) { if (pathname.match(/(?:^|\/)tests\/.*\.js$/)) {
require(path.normalize(process.cwd()) + "/" + pathname) require(path.normalize(process.cwd()) + "/" + pathname) // eslint-disable-line global-require
} }
}) })
.then(o.run) .then(o.run)

View file

@ -1,11 +1,19 @@
/* eslint-disable no-bitwise, no-process-exit */
"use strict" "use strict"
module.exports = new function init() { module.exports = new function init() {
var spec = {}, subjects = [], results = [], only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty var spec = {}, subjects = [], results, only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty
function o(subject, predicate) { function o(subject, predicate) {
if (predicate === undefined) return new Assert(subject) if (predicate === undefined) {
if (results == null) throw new Error("Assertions should not occur outside test definitions")
return new Assert(subject)
}
else if (results == null) {
ctx[unique(subject)] = predicate ctx[unique(subject)] = predicate
} else {
throw new Error("Test definition shouldn't be nested. To group tests use `o.spec()`")
}
} }
o.before = hook("__before") o.before = hook("__before")
o.after = hook("__after") o.after = hook("__after")
@ -18,7 +26,10 @@ module.exports = new function init() {
predicate() predicate()
ctx = parent ctx = parent
} }
o.only = function(subject, predicate) {o(subject, only = predicate)} o.only = function(subject, predicate, silent) {
if (!silent) console.log(highlight("/!\\ WARNING /!\\ o.only() mode"))
o(subject, only = predicate)
}
o.spy = function(fn) { o.spy = function(fn) {
var spy = function() { var spy = function() {
spy.this = this spy.this = this
@ -37,6 +48,7 @@ module.exports = new function init() {
return spy return spy
} }
o.run = function() { o.run = function() {
results = []
start = new Date start = new Date
test(spec, [], [], report) test(spec, [], [], report)
@ -111,7 +123,7 @@ module.exports = new function init() {
function unique(subject) { function unique(subject) {
if (hasOwn.call(ctx, subject)) { if (hasOwn.call(ctx, subject)) {
console.warn("A test or a spec named `" + subject + "` was already defined") console.warn("A test or a spec named `" + subject + "` was already defined")
while (hasOwn.call(ctx, subject)) subject += '*' while (hasOwn.call(ctx, subject)) subject += "*"
} }
return subject return subject
} }

View file

@ -12,7 +12,7 @@ new function(o) {
}) })
o.only(".only()", function() { o.only(".only()", function() {
o(2).equals(2) o(2).equals(2)
}) }, true)
}) })
o.run() o.run()
@ -20,7 +20,7 @@ new function(o) {
o.spec("ospec", function() { o.spec("ospec", function() {
o.spec("sync", function() { o.spec("sync", function() {
var a = 0, b = 0 var a = 0, b = 0, illegalAssertionThrows = false
o.before(function() {a = 1}) o.before(function() {a = 1})
o.after(function() {a = 0}) o.after(function() {a = 0})
@ -28,7 +28,15 @@ o.spec("ospec", function() {
o.beforeEach(function() {b = 1}) o.beforeEach(function() {b = 1})
o.afterEach(function() {b = 0}) o.afterEach(function() {b = 0})
try {o("illegal assertion")} catch (e) {illegalAssertionThrows = true}
o("assertions", function() { o("assertions", function() {
var nestedTestDeclarationThrows = false
try {o("illegal nested test", function(){})} catch (e) {nestedTestDeclarationThrows = true}
o(illegalAssertionThrows).equals(true)
o(nestedTestDeclarationThrows).equals(true)
var spy = o.spy() var spy = o.spy()
spy(a) spy(a)

View file

@ -24,7 +24,7 @@
"postversion": "git push --follow-tags" "postversion": "git push --follow-tags"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^2.10.2", "eslint": "^3.16.1",
"istanbul": "^0.4.3", "istanbul": "^0.4.3",
"marked": "^0.3.6" "marked": "^0.3.6"
}, },

View file

@ -57,7 +57,7 @@ o.spec("promise", function() {
o.spec("resolve", function() { o.spec("resolve", function() {
o("resolves once", function(done) { o("resolves once", function(done) {
var callCount = 0 var callCount = 0
var promise = new Promise(function(resolve, reject) { var promise = new Promise(function(resolve) {
resolve(1) resolve(1)
resolve(2) resolve(2)
callAsync(function() {resolve(3)}) callAsync(function() {resolve(3)})
@ -89,7 +89,7 @@ o.spec("promise", function() {
var promise = Promise.resolve() var promise = Promise.resolve()
state = 1 state = 1
promise.then(function(value) { promise.then(function() {
o(state).equals(2) o(state).equals(2)
done() done()
}) })
@ -104,7 +104,7 @@ o.spec("promise", function() {
}) })
}) })
o("resolves asynchronously via executor", function(done) { o("resolves asynchronously via executor", function(done) {
var promise = new Promise(function(resolve, reject) { var promise = new Promise(function(resolve) {
callAsync(function() {resolve(1)}) callAsync(function() {resolve(1)})
}) })
@ -185,7 +185,7 @@ o.spec("promise", function() {
var promise = Promise.reject() var promise = Promise.reject()
state = 1 state = 1
promise.then(null, function(value) { promise.then(null, function() {
o(state).equals(2) o(state).equals(2)
done() done()
}) })
@ -232,7 +232,7 @@ o.spec("promise", function() {
}) })
}) })
o("rejects via executor on error", function(done) { o("rejects via executor on error", function(done) {
var promise = new Promise(function(resolve, reject) { var promise = new Promise(function() {
throw 1 throw 1
}) })
@ -281,7 +281,7 @@ o.spec("promise", function() {
}).then(done) }).then(done)
}) })
o("absorbs resolved promise in executor resolve", function(done) { o("absorbs resolved promise in executor resolve", function(done) {
var promise = new Promise(function(resolve, reject) { var promise = new Promise(function(resolve) {
var p = Promise.resolve(1) var p = Promise.resolve(1)
resolve(p) resolve(p)
}) })
@ -310,7 +310,7 @@ o.spec("promise", function() {
}) })
}) })
o("absorbs rejected promise in executor resolve", function(done) { o("absorbs rejected promise in executor resolve", function(done) {
var promise = new Promise(function(resolve, reject) { var promise = new Promise(function(resolve) {
resolve(Promise.reject(1)) resolve(Promise.reject(1))
}) })
@ -330,7 +330,7 @@ o.spec("promise", function() {
}) })
}) })
o("absorbs pending promise that resolves via static resolver", function(done) { o("absorbs pending promise that resolves via static resolver", function(done) {
var pending = new Promise(function(resolve, reject) { var pending = new Promise(function(resolve) {
setTimeout(function() {resolve(1)}, 10) setTimeout(function() {resolve(1)}, 10)
}) })
var promise = Promise.resolve(pending) var promise = Promise.resolve(pending)
@ -341,10 +341,10 @@ o.spec("promise", function() {
}) })
}) })
o("absorbs pending promise that resolves in executor resolve", function(done) { o("absorbs pending promise that resolves in executor resolve", function(done) {
var pending = new Promise(function(resolve, reject) { var pending = new Promise(function(resolve) {
setTimeout(function() {resolve(1)}, 10) setTimeout(function() {resolve(1)}, 10)
}) })
var promise = new Promise(function(resolve, reject) { var promise = new Promise(function(resolve) {
resolve(pending) resolve(pending)
}) })
@ -354,7 +354,7 @@ o.spec("promise", function() {
}) })
}) })
o("absorbs pending promise that resolves on fulfillment", function(done) { o("absorbs pending promise that resolves on fulfillment", function(done) {
var pending = new Promise(function(resolve, reject) { var pending = new Promise(function(resolve) {
setTimeout(function() {resolve(1)}, 10) setTimeout(function() {resolve(1)}, 10)
}) })
var promise = Promise.resolve() var promise = Promise.resolve()
@ -381,7 +381,7 @@ o.spec("promise", function() {
var pending = new Promise(function(resolve, reject) { var pending = new Promise(function(resolve, reject) {
setTimeout(function() {reject(1)}, 10) setTimeout(function() {reject(1)}, 10)
}) })
var promise = new Promise(function(resolve, reject) { var promise = new Promise(function(resolve) {
resolve(pending) resolve(pending)
}) })
@ -521,7 +521,7 @@ o.spec("promise", function() {
o.spec("race", function() { o.spec("race", function() {
o("resolves to first resolved", function(done) { o("resolves to first resolved", function(done) {
var a = Promise.resolve(1) var a = Promise.resolve(1)
var b = new Promise(function(resolve, reject) { var b = new Promise(function(resolve) {
callAsync(function() {resolve(2)}) callAsync(function() {resolve(2)})
}) })
Promise.race([a, b]).then(function(value) { Promise.race([a, b]).then(function(value) {
@ -542,7 +542,7 @@ o.spec("promise", function() {
}) })
o.spec("all", function() { o.spec("all", function() {
o("resolves to array", function(done) { o("resolves to array", function(done) {
var a = new Promise(function(resolve, reject) { var a = new Promise(function(resolve) {
callAsync(function() {resolve(1)}) callAsync(function() {resolve(1)})
}) })
var b = Promise.resolve(2) var b = Promise.resolve(2)
@ -558,7 +558,7 @@ o.spec("promise", function() {
}) })
}) })
o("resolves non-promise to itself", function(done) { o("resolves non-promise to itself", function(done) {
var a = new Promise(function(resolve, reject) { var a = new Promise(function(resolve) {
callAsync(function() {resolve(1)}) callAsync(function() {resolve(1)})
}) })
var b = Promise.resolve(2) var b = Promise.resolve(2)
@ -595,7 +595,7 @@ o.spec("promise", function() {
}) })
}) })
promise.then(function(value) { promise.then(function() {
o(readCount).equals(1) o(readCount).equals(1)
done() done()
}) })

View file

@ -1 +1,3 @@
"use strict"
module.exports = require("./api/redraw")(window) module.exports = require("./api/redraw")(window)

View file

@ -1 +1,3 @@
"use strict"
module.exports = require("./render/render")(window) module.exports = require("./render/render")(window)

View file

@ -4,66 +4,97 @@ var Vnode = require("../render/vnode")
var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
var selectorCache = {} var selectorCache = {}
function hyperscript(selector) { var hasOwn = {}.hasOwnProperty
if (selector == null || typeof selector !== "string" && typeof selector.view !== "function") {
throw Error("The selector must be either a string or a component.");
}
if (typeof selector === "string" && selectorCache[selector] === undefined) { function compileSelector(selector) {
var match, tag, classes = [], attributes = {} var match, tag = "div", classes = [], attrs = {}
while (match = selectorParser.exec(selector)) { while (match = selectorParser.exec(selector)) {
var type = match[1], value = match[2] var type = match[1], value = match[2]
if (type === "" && value !== "") tag = value if (type === "" && value !== "") tag = value
else if (type === "#") attributes.id = value else if (type === "#") attrs.id = value
else if (type === ".") classes.push(value) else if (type === ".") classes.push(value)
else if (match[3][0] === "[") { else if (match[3][0] === "[") {
var attrValue = match[6] var attrValue = match[6]
if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\") if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
if (match[4] === "class") classes.push(attrValue) if (match[4] === "class") classes.push(attrValue)
else attributes[match[4]] = attrValue || true else attrs[match[4]] = attrValue || true
} }
} }
if (classes.length > 0) attributes.className = classes.join(" ") if (classes.length > 0) attrs.className = classes.join(" ")
selectorCache[selector] = function(attrs, children) { return selectorCache[selector] = {tag: tag, attrs: attrs}
}
function execSelector(state, attrs, children) {
var hasAttrs = false, childList, text var hasAttrs = false, childList, text
var className = attrs.className || attrs.class var className = attrs.className || attrs.class
for (var key in attributes) attrs[key] = attributes[key]
if (className !== undefined) { for (var key in state.attrs) {
if (attrs.class !== undefined) { if (hasOwn.call(state.attrs, key)) {
attrs[key] = state.attrs[key]
}
}
if (className != null) {
if (attrs.class != null) {
attrs.class = undefined attrs.class = undefined
attrs.className = className attrs.className = className
} }
if (attributes.className !== undefined) attrs.className = attributes.className + " " + className
if (state.attrs.className != null) {
attrs.className = state.attrs.className + " " + className
} }
}
for (var key in attrs) { for (var key in attrs) {
if (key !== "key") { if (hasOwn.call(attrs, key) && key !== "key") {
hasAttrs = true hasAttrs = true
break break
} }
} }
if (Array.isArray(children) && children.length == 1 && children[0] != null && children[0].tag === "#") text = children[0].children
else childList = children
return Vnode(tag || "div", attrs.key, hasAttrs ? attrs : undefined, childList, text, undefined) if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") {
text = children[0].children
} else {
childList = children
} }
return Vnode(state.tag, attrs.key, hasAttrs ? attrs : undefined, childList, text)
} }
var attrs, children, childrenIndex
if (arguments[1] == null || typeof arguments[1] === "object" && arguments[1].tag === undefined && !Array.isArray(arguments[1])) { function hyperscript(selector) {
attrs = arguments[1] // Because sloppy mode sucks
childrenIndex = 2 var attrs = arguments[1], start = 2, children
if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") {
throw Error("The selector must be either a string or a component.");
} }
else childrenIndex = 1
if (arguments.length === childrenIndex + 1) { if (typeof selector === "string") {
children = Array.isArray(arguments[childrenIndex]) ? arguments[childrenIndex] : [arguments[childrenIndex]] var cached = selectorCache[selector] || compileSelector(selector)
} }
else {
if (!attrs) {
attrs = {}
} else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) {
attrs = {}
start = 1
}
if (arguments.length === start + 1) {
children = arguments[start]
if (!Array.isArray(children)) children = [children]
} else {
children = [] children = []
for (var i = childrenIndex; i < arguments.length; i++) children.push(arguments[i]) while (start < arguments.length) children.push(arguments[start++])
} }
if (typeof selector === "string") return selectorCache[selector](attrs || {}, Vnode.normalizeChildren(children)) var normalized = Vnode.normalizeChildren(children)
return Vnode(selector, attrs && attrs.key, attrs || {}, Vnode.normalizeChildren(children), undefined, undefined) if (typeof selector === "string") {
return execSelector(cached, attrs, normalized)
} else {
return Vnode(selector, attrs.key, attrs, normalized)
}
} }
module.exports = hyperscript module.exports = hyperscript

View file

@ -14,30 +14,33 @@ module.exports = function($window) {
for (var i = start; i < end; i++) { for (var i = start; i < end; i++) {
var vnode = vnodes[i] var vnode = vnodes[i]
if (vnode != null) { if (vnode != null) {
insertNode(parent, createNode(vnode, hooks, ns), nextSibling) createNode(parent, vnode, hooks, ns, nextSibling)
} }
} }
} }
function createNode(vnode, hooks, ns) { function createNode(parent, vnode, hooks, ns, nextSibling) {
var tag = vnode.tag var tag = vnode.tag
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
if (typeof tag === "string") { if (typeof tag === "string") {
vnode.state = {}
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
switch (tag) { switch (tag) {
case "#": return createText(vnode) case "#": return createText(parent, vnode, nextSibling)
case "<": return createHTML(vnode) case "<": return createHTML(parent, vnode, nextSibling)
case "[": return createFragment(vnode, hooks, ns) case "[": return createFragment(parent, vnode, hooks, ns, nextSibling)
default: return createElement(vnode, hooks, ns) default: return createElement(parent, vnode, hooks, ns, nextSibling)
} }
} }
else return createComponent(vnode, hooks, ns) else return createComponent(parent, vnode, hooks, ns, nextSibling)
} }
function createText(vnode) { function createText(parent, vnode, nextSibling) {
return vnode.dom = $doc.createTextNode(vnode.children) vnode.dom = $doc.createTextNode(vnode.children)
insertNode(parent, vnode.dom, nextSibling)
return vnode.dom
} }
function createHTML(vnode) { function createHTML(parent, vnode, nextSibling) {
var match = vnode.children.match(/^\s*?<(\w+)/im) || [] var match = vnode.children.match(/^\s*?<(\w+)/im) || []
var parent = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match[1]] || "div" var parent1 = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match[1]] || "div"
var temp = $doc.createElement(parent) var temp = $doc.createElement(parent1)
temp.innerHTML = vnode.children temp.innerHTML = vnode.children
vnode.dom = temp.firstChild vnode.dom = temp.firstChild
@ -47,9 +50,10 @@ module.exports = function($window) {
while (child = temp.firstChild) { while (child = temp.firstChild) {
fragment.appendChild(child) fragment.appendChild(child)
} }
insertNode(parent, fragment, nextSibling)
return fragment return fragment
} }
function createFragment(vnode, hooks, ns) { function createFragment(parent, vnode, hooks, ns, nextSibling) {
var fragment = $doc.createDocumentFragment() var fragment = $doc.createDocumentFragment()
if (vnode.children != null) { if (vnode.children != null) {
var children = vnode.children var children = vnode.children
@ -57,9 +61,10 @@ module.exports = function($window) {
} }
vnode.dom = fragment.firstChild vnode.dom = fragment.firstChild
vnode.domSize = fragment.childNodes.length vnode.domSize = fragment.childNodes.length
insertNode(parent, fragment, nextSibling)
return fragment return fragment
} }
function createElement(vnode, hooks, ns) { function createElement(parent, vnode, hooks, ns, nextSibling) {
var tag = vnode.tag var tag = vnode.tag
switch (vnode.tag) { switch (vnode.tag) {
case "svg": ns = "http://www.w3.org/2000/svg"; break case "svg": ns = "http://www.w3.org/2000/svg"; break
@ -78,6 +83,8 @@ module.exports = function($window) {
setAttrs(vnode, attrs, ns) setAttrs(vnode, attrs, ns)
} }
insertNode(parent, element, nextSibling)
if (vnode.attrs != null && vnode.attrs.contenteditable != null) { if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
setContentEditable(vnode) setContentEditable(vnode)
} }
@ -94,19 +101,34 @@ module.exports = function($window) {
} }
return element return element
} }
function createComponent(vnode, hooks, ns) { function initComponent(vnode, hooks) {
var sentinel
if (typeof vnode.tag.view === "function") {
vnode.state = Object.create(vnode.tag) vnode.state = Object.create(vnode.tag)
var view = vnode.tag.view sentinel = vnode.state.view
if (view.reentrantLock != null) return $emptyFragment if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
view.reentrantLock = true sentinel.$$reentrantLock$$ = true
initLifecycle(vnode.tag, vnode, hooks) } else {
vnode.instance = Vnode.normalize(view.call(vnode.state, vnode)) vnode.state = void 0
view.reentrantLock = null sentinel = vnode.tag
if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
sentinel.$$reentrantLock$$ = true
vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode)
}
vnode._state = vnode.state
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
initLifecycle(vnode._state, vnode, hooks)
vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
sentinel.$$reentrantLock$$ = null
}
function createComponent(parent, vnode, hooks, ns, nextSibling) {
initComponent(vnode, hooks)
if (vnode.instance != null) { if (vnode.instance != null) {
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as arguments") var element = createNode(parent, vnode.instance, hooks, ns, nextSibling)
var element = createNode(vnode.instance, hooks, ns)
vnode.dom = vnode.instance.dom vnode.dom = vnode.instance.dom
vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0 vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
insertNode(parent, element, nextSibling)
return element return element
} }
else { else {
@ -116,7 +138,7 @@ module.exports = function($window) {
} }
//update //update
function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { function updateNodes(parent, old, vnodes, recycling, hooks, nextSibling, ns) {
if (old === vnodes || old == null && vnodes == null) return if (old === vnodes || old == null && vnodes == null) return
else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined) else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes) else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
@ -132,15 +154,18 @@ module.exports = function($window) {
if (isUnkeyed) { if (isUnkeyed) {
for (var i = 0; i < old.length; i++) { for (var i = 0; i < old.length; i++) {
if (old[i] === vnodes[i]) continue if (old[i] === vnodes[i]) continue
else if (old[i] == null && vnodes[i] != null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling)) else if (old[i] == null && vnodes[i] != null) createNode(parent, vnodes[i], hooks, ns, getNextSibling(old, i + 1, nextSibling))
else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes) else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes)
else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), false, ns) else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns)
} }
return return
} }
} }
var recycling = isRecyclable(old, vnodes) recycling = recycling || isRecyclable(old, vnodes)
if (recycling) old = old.concat(old.pool) if (recycling) {
var pool = old.pool
old = old.concat(old.pool)
}
var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map
while (oldEnd >= oldStart && end >= start) { while (oldEnd >= oldStart && end >= start) {
@ -149,8 +174,9 @@ module.exports = function($window) {
else if (o == null) oldStart++ else if (o == null) oldStart++
else if (v == null) start++ else if (v == null) start++
else if (o.key === v.key) { else if (o.key === v.key) {
var shouldRecycle = (pool != null && oldStart >= old.length - pool.length) || ((pool == null) && recycling)
oldStart++, start++ oldStart++, start++
updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), shouldRecycle, ns)
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
} }
else { else {
@ -159,7 +185,8 @@ module.exports = function($window) {
else if (o == null) oldEnd-- else if (o == null) oldEnd--
else if (v == null) start++ else if (v == null) start++
else if (o.key === v.key) { else if (o.key === v.key) {
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling)) if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling))
oldEnd--, start++ oldEnd--, start++
} }
@ -172,7 +199,8 @@ module.exports = function($window) {
else if (o == null) oldEnd-- else if (o == null) oldEnd--
else if (v == null) end-- else if (v == null) end--
else if (o.key === v.key) { else if (o.key === v.key) {
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling)
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns)
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
if (o.dom != null) nextSibling = o.dom if (o.dom != null) nextSibling = o.dom
oldEnd--, end-- oldEnd--, end--
@ -183,14 +211,14 @@ module.exports = function($window) {
var oldIndex = map[v.key] var oldIndex = map[v.key]
if (oldIndex != null) { if (oldIndex != null) {
var movable = old[oldIndex] var movable = old[oldIndex]
var shouldRecycle = (pool != null && oldIndex >= old.length - pool.length) || ((pool == null) && recycling)
updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
insertNode(parent, toFragment(movable), nextSibling) insertNode(parent, toFragment(movable), nextSibling)
old[oldIndex].skip = true old[oldIndex].skip = true
if (movable.dom != null) nextSibling = movable.dom if (movable.dom != null) nextSibling = movable.dom
} }
else { else {
var dom = createNode(v, hooks, undefined) var dom = createNode(parent, v, hooks, undefined, nextSibling)
insertNode(parent, dom, nextSibling)
nextSibling = dom nextSibling = dom
} }
} }
@ -206,24 +234,29 @@ module.exports = function($window) {
var oldTag = old.tag, tag = vnode.tag var oldTag = old.tag, tag = vnode.tag
if (oldTag === tag) { if (oldTag === tag) {
vnode.state = old.state vnode.state = old.state
vnode._state = old._state
vnode.events = old.events vnode.events = old.events
if (shouldUpdate(vnode, old)) return if (!recycling && shouldNotUpdate(vnode, old)) return
if (vnode.attrs != null) {
updateLifecycle(vnode.attrs, vnode, hooks, recycling)
}
if (typeof oldTag === "string") { if (typeof oldTag === "string") {
if (vnode.attrs != null) {
if (recycling) {
vnode.state = {}
initLifecycle(vnode.attrs, vnode, hooks)
}
else updateLifecycle(vnode.attrs, vnode, hooks)
}
switch (oldTag) { switch (oldTag) {
case "#": updateText(old, vnode); break case "#": updateText(old, vnode); break
case "<": updateHTML(parent, old, vnode, nextSibling); break case "<": updateHTML(parent, old, vnode, nextSibling); break
case "[": updateFragment(parent, old, vnode, hooks, nextSibling, ns); break case "[": updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns); break
default: updateElement(old, vnode, hooks, ns) default: updateElement(old, vnode, recycling, hooks, ns)
} }
} }
else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns)
} }
else { else {
removeNode(old, null) removeNode(old, null)
insertNode(parent, createNode(vnode, hooks, ns), nextSibling) createNode(parent, vnode, hooks, ns, nextSibling)
} }
} }
function updateText(old, vnode) { function updateText(old, vnode) {
@ -235,12 +268,12 @@ module.exports = function($window) {
function updateHTML(parent, old, vnode, nextSibling) { function updateHTML(parent, old, vnode, nextSibling) {
if (old.children !== vnode.children) { if (old.children !== vnode.children) {
toFragment(old) toFragment(old)
insertNode(parent, createHTML(vnode), nextSibling) createHTML(parent, vnode, nextSibling)
} }
else vnode.dom = old.dom, vnode.domSize = old.domSize else vnode.dom = old.dom, vnode.domSize = old.domSize
} }
function updateFragment(parent, old, vnode, hooks, nextSibling, ns) { function updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns) {
updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns) updateNodes(parent, old.children, vnode.children, recycling, hooks, nextSibling, ns)
var domSize = 0, children = vnode.children var domSize = 0, children = vnode.children
vnode.dom = null vnode.dom = null
if (children != null) { if (children != null) {
@ -254,7 +287,7 @@ module.exports = function($window) {
if (domSize !== 1) vnode.domSize = domSize if (domSize !== 1) vnode.domSize = domSize
} }
} }
function updateElement(old, vnode, hooks, ns) { function updateElement(old, vnode, recycling, hooks, ns) {
var element = vnode.dom = old.dom var element = vnode.dom = old.dom
switch (vnode.tag) { switch (vnode.tag) {
case "svg": ns = "http://www.w3.org/2000/svg"; break case "svg": ns = "http://www.w3.org/2000/svg"; break
@ -277,14 +310,20 @@ module.exports = function($window) {
else { else {
if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)] if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)]
if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)] if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
updateNodes(element, old.children, vnode.children, hooks, null, ns) updateNodes(element, old.children, vnode.children, recycling, hooks, null, ns)
} }
} }
function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) { function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) {
vnode.instance = Vnode.normalize(vnode.tag.view.call(vnode.state, vnode)) if (recycling) {
updateLifecycle(vnode.tag, vnode, hooks, recycling) initComponent(vnode, hooks)
} else {
vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode))
if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument")
if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks)
updateLifecycle(vnode._state, vnode, hooks)
}
if (vnode.instance != null) { if (vnode.instance != null) {
if (old.instance == null) insertNode(parent, createNode(vnode.instance, hooks, ns), nextSibling) if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling)
else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns) else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns)
vnode.dom = vnode.instance.dom vnode.dom = vnode.instance.dom
vnode.domSize = vnode.instance.domSize vnode.domSize = vnode.instance.domSize
@ -367,15 +406,15 @@ module.exports = function($window) {
} }
function removeNode(vnode, context) { function removeNode(vnode, context) {
var expected = 1, called = 0 var expected = 1, called = 0
if (vnode.attrs && vnode.attrs.onbeforeremove) { if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") {
var result = vnode.attrs.onbeforeremove.call(vnode.state, vnode) var result = vnode.attrs.onbeforeremove.call(vnode.state, vnode)
if (result != null && typeof result.then === "function") { if (result != null && typeof result.then === "function") {
expected++ expected++
result.then(continuation, continuation) result.then(continuation, continuation)
} }
} }
if (typeof vnode.tag !== "string" && vnode.tag.onbeforeremove) { if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeremove === "function") {
var result = vnode.tag.onbeforeremove.call(vnode.state, vnode) var result = vnode._state.onbeforeremove.call(vnode.state, vnode)
if (result != null && typeof result.then === "function") { if (result != null && typeof result.then === "function") {
expected++ expected++
result.then(continuation, continuation) result.then(continuation, continuation)
@ -407,8 +446,8 @@ module.exports = function($window) {
if (parent != null) parent.removeChild(node) if (parent != null) parent.removeChild(node)
} }
function onremove(vnode) { function onremove(vnode) {
if (vnode.attrs && vnode.attrs.onremove) vnode.attrs.onremove.call(vnode.state, vnode) if (vnode.attrs && typeof vnode.attrs.onremove === "function") vnode.attrs.onremove.call(vnode.state, vnode)
if (typeof vnode.tag !== "string" && vnode.tag.onremove) vnode.tag.onremove.call(vnode.state, vnode) if (typeof vnode.tag !== "string" && typeof vnode._state.onremove === "function") vnode._state.onremove.call(vnode.state, vnode)
if (vnode.instance != null) onremove(vnode.instance) if (vnode.instance != null) onremove(vnode.instance)
else { else {
var children = vnode.children var children = vnode.children
@ -438,14 +477,14 @@ module.exports = function($window) {
else if (key === "style") updateStyle(element, old, value) else if (key === "style") updateStyle(element, old, value)
else if (key in element && !isAttribute(key) && ns === undefined && !isCustomElement(vnode)) { else if (key in element && !isAttribute(key) && ns === undefined && !isCustomElement(vnode)) {
//setting input[value] to same value by typing on focused element moves cursor to end in Chrome //setting input[value] to same value by typing on focused element moves cursor to end in Chrome
if (vnode.tag === "input" && key === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return if (vnode.tag === "input" && key === "value" && vnode.dom.value == value && vnode.dom === $doc.activeElement) return
//setting select[value] to same value while having select open blinks select dropdown in Chrome //setting select[value] to same value while having select open blinks select dropdown in Chrome
if (vnode.tag === "select" && key === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return if (vnode.tag === "select" && key === "value" && vnode.dom.value == value && vnode.dom === $doc.activeElement) return
//setting option[value] to same value while having select open blinks select dropdown in Chrome //setting option[value] to same value while having select open blinks select dropdown in Chrome
if (vnode.tag === "option" && key === "value" && vnode.dom.value === value) return if (vnode.tag === "option" && key === "value" && vnode.dom.value == value) return
// If you assign an input type that is not supported by IE 11 with an assignment expression, an error will occur. // If you assign an input type that is not supported by IE 11 with an assignment expression, an error will occur.
if (vnode.tag === "input" && key === "type") { if (vnode.tag === "input" && key === "type") {
element.setAttribute(key, value); element.setAttribute(key, value)
return return
} }
element[key] = value element[key] = value
@ -541,14 +580,13 @@ module.exports = function($window) {
if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode) if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode)
if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode)) if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode))
} }
function updateLifecycle(source, vnode, hooks, recycling) { function updateLifecycle(source, vnode, hooks) {
if (recycling) initLifecycle(source, vnode, hooks) if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
else if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
} }
function shouldUpdate(vnode, old) { function shouldNotUpdate(vnode, old) {
var forceVnodeUpdate, forceComponentUpdate var forceVnodeUpdate, forceComponentUpdate
if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old) if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old)
if (typeof vnode.tag !== "string" && typeof vnode.tag.onbeforeupdate === "function") forceComponentUpdate = vnode.tag.onbeforeupdate.call(vnode.state, vnode, old) if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeupdate === "function") forceComponentUpdate = vnode._state.onbeforeupdate.call(vnode.state, vnode, old)
if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) { if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) {
vnode.dom = old.dom vnode.dom = old.dom
vnode.domSize = old.domSize vnode.domSize = old.domSize
@ -567,7 +605,7 @@ module.exports = function($window) {
if (dom.vnodes == null) dom.textContent = "" if (dom.vnodes == null) dom.textContent = ""
if (!Array.isArray(vnodes)) vnodes = [vnodes] if (!Array.isArray(vnodes)) vnodes = [vnodes]
updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), hooks, null, undefined) updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, undefined)
dom.vnodes = vnodes dom.vnodes = vnodes
for (var i = 0; i < hooks.length; i++) hooks[i]() for (var i = 0; i < hooks.length; i++) hooks[i]()
if ($doc.activeElement !== active) active.focus() if ($doc.activeElement !== active) active.focus()

View file

@ -8,6 +8,7 @@
<script src="../../ospec/ospec.js"></script> <script src="../../ospec/ospec.js"></script>
<script src="../../test-utils/callAsync.js"></script> <script src="../../test-utils/callAsync.js"></script>
<script src="../../test-utils/domMock.js"></script> <script src="../../test-utils/domMock.js"></script>
<script src="../../test-utils/component.js"></script>
<script src="../../render/vnode.js"></script> <script src="../../render/vnode.js"></script>
<script src="../../render/trust.js"></script> <script src="../../render/trust.js"></script>

View file

@ -16,15 +16,15 @@ o.spec("attributes", function() {
o("when vnode is customElement, custom setAttribute called", function(){ o("when vnode is customElement, custom setAttribute called", function(){
var normal = [ var normal = [
{ tag: "input", attrs: { value: 'hello' } }, {tag: "input", attrs: {value: "hello"}},
{ tag: "input", attrs: { value: 'hello' } }, {tag: "input", attrs: {value: "hello"}},
{ tag: "input", attrs: { value: 'hello' } } {tag: "input", attrs: {value: "hello"}}
] ]
var custom = [ var custom = [
{ tag: "custom-element", attrs: { custom: 'x' } }, {tag: "custom-element", attrs: {custom: "x"}},
{ tag: "input", attrs: { is: 'something-special', custom: 'x' } }, {tag: "input", attrs: {is: "something-special", custom: "x"}},
{ tag: "custom-element", attrs: { is: 'something-special', custom: 'x' } } {tag: "custom-element", attrs: {is: "something-special", custom: "x"}}
] ]
var view = normal.concat(custom) var view = normal.concat(custom)
@ -133,7 +133,7 @@ o.spec("attributes", function() {
}) })
o.spec("contenteditable throws on untrusted children", function() { o.spec("contenteditable throws on untrusted children", function() {
o("including text nodes", function() { o("including text nodes", function() {
var div = {tag: "div", attrs: {contenteditable: true}, text: ''} var div = {tag: "div", attrs: {contenteditable: true}, text: ""}
var succeeded = false var succeeded = false
try { try {
@ -141,7 +141,7 @@ o.spec("attributes", function() {
succeeded = true succeeded = true
} }
catch(e){} catch(e){/* ignore */}
o(succeeded).equals(false) o(succeeded).equals(false)
}) })
@ -154,7 +154,7 @@ o.spec("attributes", function() {
succeeded = true succeeded = true
} }
catch(e){} catch(e){/* ignore */}
o(succeeded).equals(false) o(succeeded).equals(false)
}) })
@ -167,7 +167,7 @@ o.spec("attributes", function() {
succeeded = true succeeded = true
} }
catch(e){} catch(e){/* ignore */}
o(succeeded).equals(true) o(succeeded).equals(true)
}) })
@ -180,7 +180,7 @@ o.spec("attributes", function() {
succeeded = true succeeded = true
} }
catch(e){} catch(e){/* ignore */}
o(succeeded).equals(true) o(succeeded).equals(true)
}) })

View file

@ -1,6 +1,7 @@
"use strict" "use strict"
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock") var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render") var vdom = require("../../render/render")
@ -9,16 +10,21 @@ o.spec("component", function() {
o.beforeEach(function() { o.beforeEach(function() {
$window = domMock() $window = domMock()
root = $window.document.createElement("div") root = $window.document.createElement("div")
render = vdom($window).render render = vdom($window).render
}) })
components.forEach(function(cmp){
o.spec(cmp.kind, function(){
var createComponent = cmp.create
o.spec("basics", function() { o.spec("basics", function() {
o("works", function() { o("works", function() {
var component = { var component = createComponent({
view: function() { view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"} return {tag: "div", attrs: {id: "a"}, text: "b"}
} }
} })
var node = {tag: component} var node = {tag: component}
render(root, [node]) render(root, [node])
@ -28,11 +34,11 @@ o.spec("component", function() {
o(root.firstChild.firstChild.nodeValue).equals("b") o(root.firstChild.firstChild.nodeValue).equals("b")
}) })
o("receives arguments", function() { o("receives arguments", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function(vnode) {
return {tag: "div", attrs: vnode.attrs, text: vnode.text} return {tag: "div", attrs: vnode.attrs, text: vnode.text}
} }
} })
var node = {tag: component, attrs: {id: "a"}, text: "b"} var node = {tag: component, attrs: {id: "a"}, text: "b"}
render(root, [node]) render(root, [node])
@ -42,11 +48,11 @@ o.spec("component", function() {
o(root.firstChild.firstChild.nodeValue).equals("b") o(root.firstChild.firstChild.nodeValue).equals("b")
}) })
o("updates", function() { o("updates", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function(vnode) {
return {tag: "div", attrs: vnode.attrs, text: vnode.text} return {tag: "div", attrs: vnode.attrs, text: vnode.text}
} }
} })
render(root, [{tag: component, attrs: {id: "a"}, text: "b"}]) render(root, [{tag: component, attrs: {id: "a"}, text: "b"}])
render(root, [{tag: component, attrs: {id: "c"}, text: "d"}]) render(root, [{tag: component, attrs: {id: "c"}, text: "d"}])
@ -56,11 +62,11 @@ o.spec("component", function() {
}) })
o("updates root from null", function() { o("updates root from null", function() {
var visible = false var visible = false
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return visible ? {tag: "div"} : null return visible ? {tag: "div"} : null
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
visible = true visible = true
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -69,11 +75,11 @@ o.spec("component", function() {
}) })
o("updates root from primitive", function() { o("updates root from primitive", function() {
var visible = false var visible = false
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return visible ? {tag: "div"} : false return visible ? {tag: "div"} : false
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
visible = true visible = true
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -82,11 +88,11 @@ o.spec("component", function() {
}) })
o("updates root to null", function() { o("updates root to null", function() {
var visible = true var visible = true
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return visible ? {tag: "div"} : null return visible ? {tag: "div"} : null
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
visible = false visible = false
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -95,11 +101,11 @@ o.spec("component", function() {
}) })
o("updates root to primitive", function() { o("updates root to primitive", function() {
var visible = true var visible = true
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return visible ? {tag: "div"} : false return visible ? {tag: "div"} : false
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
visible = false visible = false
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -107,22 +113,22 @@ o.spec("component", function() {
o(root.firstChild.nodeValue).equals("") o(root.firstChild.nodeValue).equals("")
}) })
o("updates root from null to null", function() { o("updates root from null to null", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return null return null
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
render(root, [{tag: component}]) render(root, [{tag: component}])
o(root.childNodes.length).equals(0) o(root.childNodes.length).equals(0)
}) })
o("removes", function() { o("removes", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return {tag: "div"} return {tag: "div"}
} }
} })
var div = {tag: "div", key: 2} var div = {tag: "div", key: 2}
render(root, [{tag: component, key: 1}, div]) render(root, [{tag: component, key: 1}, div])
render(root, [{tag: "div", key: 2}]) render(root, [{tag: "div", key: 2}])
@ -131,21 +137,21 @@ o.spec("component", function() {
o(root.firstChild).equals(div.dom) o(root.firstChild).equals(div.dom)
}) })
o("svg works when creating across component boundary", function() { o("svg works when creating across component boundary", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return {tag: "g"} 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(root.firstChild.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg")
}) })
o("svg works when updating across component boundary", function() { o("svg works when updating across component boundary", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return {tag: "g"} return {tag: "g"}
} }
} })
render(root, [{tag: "svg", children: [{tag: component}]}]) render(root, [{tag: "svg", children: [{tag: component}]}])
render(root, [{tag: "svg", children: [{tag: component}]}]) render(root, [{tag: "svg", children: [{tag: component}]}])
@ -154,14 +160,14 @@ o.spec("component", function() {
}) })
o.spec("return value", function() { o.spec("return value", function() {
o("can return fragments", function() { o("can return fragments", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return [ return [
{tag: "label"}, {tag: "label"},
{tag: "input"}, {tag: "input"},
] ]
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
o(root.childNodes.length).equals(2) o(root.childNodes.length).equals(2)
@ -169,116 +175,148 @@ o.spec("component", function() {
o(root.childNodes[1].nodeName).equals("INPUT") o(root.childNodes[1].nodeName).equals("INPUT")
}) })
o("can return string", function() { o("can return string", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return "a" return "a"
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("a") o(root.firstChild.nodeValue).equals("a")
}) })
o("can return falsy string", function() { o("can return falsy string", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return "" return ""
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("") o(root.firstChild.nodeValue).equals("")
}) })
o("can return number", function() { o("can return number", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return 1 return 1
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("1") o(root.firstChild.nodeValue).equals("1")
}) })
o("can return falsy number", function() { o("can return falsy number", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return 0 return 0
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("0") o(root.firstChild.nodeValue).equals("0")
}) })
o("can return boolean", function() { o("can return boolean", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return true return true
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("true") o(root.firstChild.nodeValue).equals("true")
}) })
o("can return falsy boolean", function() { o("can return falsy boolean", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return false return false
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3) o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("") o(root.firstChild.nodeValue).equals("")
}) })
o("can return null", function() { o("can return null", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return null return null
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
o(root.childNodes.length).equals(0) o(root.childNodes.length).equals(0)
}) })
o("can return undefined", function() { o("can return undefined", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return undefined return undefined
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
o(root.childNodes.length).equals(0) o(root.childNodes.length).equals(0)
}) })
o("throws a custom error if it returns itself", function() { o("throws a custom error if it returns itself when created", function() {
// A view that returns its vnode would otherwise trigger an infinite loop // A view that returns its vnode would otherwise trigger an infinite loop
var component = { var threw = false
var component = createComponent({
view: function(vnode) { view: function(vnode) {
return vnode return vnode
} }
} })
try { try {
render(root, [{tag: component}]) render(root, [{tag: component}])
} }
catch (e) { catch (e) {
threw = true
o(e instanceof Error).equals(true) o(e instanceof Error).equals(true)
// Call stack exception is a RangeError // Call stack exception is a RangeError
o(e instanceof RangeError).equals(false) 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() { o("can update when returning fragments", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return [ return [
{tag: "label"}, {tag: "label"},
{tag: "input"}, {tag: "input"},
] ]
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -287,11 +325,11 @@ o.spec("component", function() {
o(root.childNodes[1].nodeName).equals("INPUT") o(root.childNodes[1].nodeName).equals("INPUT")
}) })
o("can update when returning primitive", function() { o("can update when returning primitive", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return "a" return "a"
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -299,25 +337,25 @@ o.spec("component", function() {
o(root.firstChild.nodeValue).equals("a") o(root.firstChild.nodeValue).equals("a")
}) })
o("can update when returning null", function() { o("can update when returning null", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return null return null
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
render(root, [{tag: component}]) render(root, [{tag: component}])
o(root.childNodes.length).equals(0) o(root.childNodes.length).equals(0)
}) })
o("can remove when returning fragments", function() { o("can remove when returning fragments", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return [ return [
{tag: "label"}, {tag: "label"},
{tag: "input"}, {tag: "input"},
] ]
} }
} })
var div = {tag: "div", key: 2} var div = {tag: "div", key: 2}
render(root, [{tag: component, key: 1}, div]) render(root, [{tag: component, key: 1}, div])
@ -327,11 +365,11 @@ o.spec("component", function() {
o(root.firstChild).equals(div.dom) o(root.firstChild).equals(div.dom)
}) })
o("can remove when returning primitive", function() { o("can remove when returning primitive", function() {
var component = { var component = createComponent({
view: function(vnode) { view: function() {
return "a" return "a"
} }
} })
var div = {tag: "div", key: 2} var div = {tag: "div", key: 2}
render(root, [{tag: component, key: 1}, div]) render(root, [{tag: component, key: 1}, div])
@ -344,7 +382,7 @@ o.spec("component", function() {
o.spec("lifecycle", function() { o.spec("lifecycle", function() {
o("calls oninit", function() { o("calls oninit", function() {
var called = 0 var called = 0
var component = { var component = createComponent({
oninit: function(vnode) { oninit: function(vnode) {
called++ called++
@ -355,7 +393,7 @@ o.spec("component", function() {
view: function() { view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"} return {tag: "div", attrs: {id: "a"}, text: "b"}
} }
} })
var node = {tag: component} var node = {tag: component}
render(root, [node]) render(root, [node])
@ -367,7 +405,7 @@ o.spec("component", function() {
}) })
o("calls oninit when returning fragment", function() { o("calls oninit when returning fragment", function() {
var called = 0 var called = 0
var component = { var component = createComponent({
oninit: function(vnode) { oninit: function(vnode) {
called++ called++
@ -378,7 +416,7 @@ o.spec("component", function() {
view: function() { view: function() {
return [{tag: "div", attrs: {id: "a"}, text: "b"}] return [{tag: "div", attrs: {id: "a"}, text: "b"}]
} }
} })
var node = {tag: component} var node = {tag: component}
render(root, [node]) render(root, [node])
@ -391,26 +429,26 @@ o.spec("component", function() {
o("calls oninit before view", function() { o("calls oninit before view", function() {
var viewCalled = false var viewCalled = false
render(root, { render(root, createComponent({
tag: { tag: {
view: function() { view: function() {
viewCalled = true viewCalled = true
return [{tag: "div", attrs: {id: "a"}, text: "b"}] return [{tag: "div", attrs: {id: "a"}, text: "b"}]
}, },
oninit: function(vnode) { oninit: function() {
o(viewCalled).equals(false) o(viewCalled).equals(false)
}, },
} }
}) }))
}) })
o("does not calls oninit on redraw", function() { o("does not calls oninit on redraw", function() {
var init = o.spy() var init = o.spy()
var component = { var component = createComponent({
view: function() { view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"} return {tag: "div", attrs: {id: "a"}, text: "b"}
}, },
oninit: init, oninit: init,
} })
function view() { function view() {
return {tag: component} return {tag: component}
@ -423,7 +461,7 @@ o.spec("component", function() {
}) })
o("calls oncreate", function() { o("calls oncreate", function() {
var called = 0 var called = 0
var component = { var component = createComponent({
oncreate: function(vnode) { oncreate: function(vnode) {
called++ called++
@ -434,7 +472,7 @@ o.spec("component", function() {
view: function() { view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"} return {tag: "div", attrs: {id: "a"}, text: "b"}
} }
} })
var node = {tag: component} var node = {tag: component}
render(root, [node]) render(root, [node])
@ -446,12 +484,12 @@ o.spec("component", function() {
}) })
o("does not calls oncreate on redraw", function() { o("does not calls oncreate on redraw", function() {
var create = o.spy() var create = o.spy()
var component = { var component = createComponent({
view: function() { view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"} return {tag: "div", attrs: {id: "a"}, text: "b"}
}, },
oncreate: create, oncreate: create,
} })
function view() { function view() {
return {tag: component} return {tag: component}
@ -464,7 +502,7 @@ o.spec("component", function() {
}) })
o("calls oncreate when returning fragment", function() { o("calls oncreate when returning fragment", function() {
var called = 0 var called = 0
var component = { var component = createComponent({
oncreate: function(vnode) { oncreate: function(vnode) {
called++ called++
@ -475,7 +513,7 @@ o.spec("component", function() {
view: function() { view: function() {
return [{tag: "div", attrs: {id: "a"}, text: "b"}] return [{tag: "div", attrs: {id: "a"}, text: "b"}]
} }
} })
var node = {tag: component} var node = {tag: component}
render(root, [node]) render(root, [node])
@ -487,7 +525,7 @@ o.spec("component", function() {
}) })
o("calls onupdate", function() { o("calls onupdate", function() {
var called = 0 var called = 0
var component = { var component = createComponent({
onupdate: function(vnode) { onupdate: function(vnode) {
called++ called++
@ -498,7 +536,7 @@ o.spec("component", function() {
view: function() { view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"} return {tag: "div", attrs: {id: "a"}, text: "b"}
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -513,7 +551,7 @@ o.spec("component", function() {
}) })
o("calls onupdate when returning fragment", function() { o("calls onupdate when returning fragment", function() {
var called = 0 var called = 0
var component = { var component = createComponent({
onupdate: function(vnode) { onupdate: function(vnode) {
called++ called++
@ -524,7 +562,7 @@ o.spec("component", function() {
view: function() { view: function() {
return [{tag: "div", attrs: {id: "a"}, text: "b"}] return [{tag: "div", attrs: {id: "a"}, text: "b"}]
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -539,7 +577,7 @@ o.spec("component", function() {
}) })
o("calls onremove", function() { o("calls onremove", function() {
var called = 0 var called = 0
var component = { var component = createComponent({
onremove: function(vnode) { onremove: function(vnode) {
called++ called++
@ -550,7 +588,7 @@ o.spec("component", function() {
view: function() { view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"} return {tag: "div", attrs: {id: "a"}, text: "b"}
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -563,7 +601,7 @@ o.spec("component", function() {
}) })
o("calls onremove when returning fragment", function() { o("calls onremove when returning fragment", function() {
var called = 0 var called = 0
var component = { var component = createComponent({
onremove: function(vnode) { onremove: function(vnode) {
called++ called++
@ -574,7 +612,7 @@ o.spec("component", function() {
view: function() { view: function() {
return [{tag: "div", attrs: {id: "a"}, text: "b"}] return [{tag: "div", attrs: {id: "a"}, text: "b"}]
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -587,7 +625,7 @@ o.spec("component", function() {
}) })
o("calls onbeforeremove", function() { o("calls onbeforeremove", function() {
var called = 0 var called = 0
var component = { var component = createComponent({
onbeforeremove: function(vnode) { onbeforeremove: function(vnode) {
called++ called++
@ -598,7 +636,7 @@ o.spec("component", function() {
view: function() { view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"} return {tag: "div", attrs: {id: "a"}, text: "b"}
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -611,7 +649,7 @@ o.spec("component", function() {
}) })
o("calls onbeforeremove when returning fragment", function() { o("calls onbeforeremove when returning fragment", function() {
var called = 0 var called = 0
var component = { var component = createComponent({
onbeforeremove: function(vnode) { onbeforeremove: function(vnode) {
called++ called++
@ -622,7 +660,7 @@ o.spec("component", function() {
view: function() { view: function() {
return [{tag: "div", attrs: {id: "a"}, text: "b"}] return [{tag: "div", attrs: {id: "a"}, text: "b"}]
} }
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -634,13 +672,12 @@ o.spec("component", function() {
o(root.childNodes.length).equals(0) o(root.childNodes.length).equals(0)
}) })
o("does not recycle when there's an onupdate", function() { o("does not recycle when there's an onupdate", function() {
var component = { var component = createComponent({
onupdate: function() {}, onupdate: function() {},
view: function() { view: function() {
return {tag: "div"} return {tag: "div"}
} }
} })
var update = o.spy()
var vnode = {tag: component, key: 1} var vnode = {tag: component, key: 1}
var updated = {tag: component, key: 1} var updated = {tag: component, key: 1}
@ -650,41 +687,268 @@ o.spec("component", function() {
o(vnode.dom).notEquals(updated.dom) 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.spec("state", function() {
o("copies state", function() { o("initializes state", function() {
var called = 0
var data = {a: 1} var data = {a: 1}
var component = { var component = createComponent(createComponent({
data: data, data: data,
oninit: init, oninit: init,
view: function() { view: function() {
return "" return ""
} }
} }))
render(root, [{tag: component}]) render(root, [{tag: component}])
function init(vnode) { function init(vnode) {
o(vnode.state.data).deepEquals(data)
o(vnode.state.data).equals(data) o(vnode.state.data).equals(data)
//inherits state via prototype
component.x = 1
o(vnode.state.x).equals(1)
} }
}) })
o("state copy is shallow", function() { o("state proxies to the component object/prototype", function() {
var called = 0
var body = {a: 1} var body = {a: 1}
var data = [body] var data = [body]
var component = { var component = createComponent(createComponent({
data: data, data: data,
oninit: init, oninit: init,
view: function() { view: function() {
return "" return ""
} }
} }))
render(root, [{tag: component}]) render(root, [{tag: component}])
@ -695,3 +959,73 @@ o.spec("component", function() {
}) })
}) })
}) })
})
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)
})
})
})
})

View file

@ -1,3 +1,4 @@
/* eslint-disable no-script-url */
"use strict" "use strict"
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")

View file

@ -1,3 +1,5 @@
"use strict"
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var m = require("../../render/hyperscript") var m = require("../../render/hyperscript")
@ -424,7 +426,7 @@ o.spec("hyperscript", function() {
}) })
}) })
o.spec("components", function() { o.spec("components", function() {
o("works", function() { o("works with POJOs", function() {
var component = { var component = {
view: function() { view: function() {
return m("div") return m("div")
@ -432,6 +434,19 @@ o.spec("hyperscript", function() {
} }
var vnode = m(component, {id: "a"}, "b") 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.tag).equals(component)
o(vnode.attrs.id).equals("a") o(vnode.attrs.id).equals("a")
o(vnode.children.length).equals(1) o(vnode.children.length).equals(1)

View file

@ -2,6 +2,7 @@
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var callAsync = require("../../test-utils/callAsync") var callAsync = require("../../test-utils/callAsync")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock") var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render") var vdom = require("../../render/render")
var Promise = require("../../promise/promise") var Promise = require("../../promise/promise")
@ -16,7 +17,6 @@ o.spec("onbeforeremove", function() {
o("does not call onbeforeremove when creating", function() { o("does not call onbeforeremove when creating", function() {
var create = o.spy() var create = o.spy()
var update = o.spy()
var vnode = {tag: "div", attrs: {onbeforeremove: create}} var vnode = {tag: "div", attrs: {onbeforeremove: create}}
render(root, [vnode]) render(root, [vnode])
@ -141,7 +141,7 @@ o.spec("onbeforeremove", function() {
o(vnode.dom.attributes["onbeforeremove"]).equals(undefined) o(vnode.dom.attributes["onbeforeremove"]).equals(undefined)
}) })
o("does not recycle when there's an onbeforeremove", function() { o("does not recycle when there's an onbeforeremove", function() {
var remove = function(vnode) {} var remove = function() {}
var vnode = {tag: "div", key: 1, attrs: {onbeforeremove: remove}} var vnode = {tag: "div", key: 1, attrs: {onbeforeremove: remove}}
var updated = {tag: "div", key: 1, attrs: {onbeforeremove: remove}} var updated = {tag: "div", key: 1, attrs: {onbeforeremove: remove}}
@ -152,7 +152,7 @@ o.spec("onbeforeremove", function() {
o(vnode.dom).notEquals(updated.dom) o(vnode.dom).notEquals(updated.dom)
}) })
o("does not leave elements out of order during removal", function(done) { o("does not leave elements out of order during removal", function(done) {
var remove = function(vnode) {return Promise.resolve()} 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 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"} var updated = {tag: "div", key: 2, attrs: {onbeforeremove: remove}, text: "2"}
@ -169,14 +169,17 @@ o.spec("onbeforeremove", function() {
done() 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) { o("finalizes the remove phase asynchronously when promise is returned synchronously from both attrs- and tag.onbeforeremove", function(done) {
var onremove = o.spy() var onremove = o.spy()
var onbeforeremove = function(){return Promise.resolve()} var onbeforeremove = function(){return Promise.resolve()}
var component = { var component = createComponent({
onbeforeremove: onbeforeremove, onbeforeremove: onbeforeremove,
onremove: onremove, onremove: onremove,
view: function() {}, view: function() {},
} })
render(root, [{tag: component, attrs: {onbeforeremove: onbeforeremove, onremove: onremove}}]) render(root, [{tag: component, attrs: {onbeforeremove: onbeforeremove, onremove: onremove}}])
render(root, []) render(root, [])
callAsync(function() { callAsync(function() {
@ -188,11 +191,11 @@ o.spec("onbeforeremove", function() {
var view = o.spy() var view = o.spy()
var onremove = o.spy() var onremove = o.spy()
var onbeforeremove = function(){return new Promise(function(resolve){callAsync(resolve)})} var onbeforeremove = function(){return new Promise(function(resolve){callAsync(resolve)})}
var component = { var component = createComponent({
onbeforeremove: onbeforeremove, onbeforeremove: onbeforeremove,
onremove: onremove, onremove: onremove,
view: view, view: view,
} })
render(root, [{tag: component}]) render(root, [{tag: component}])
render(root, []) render(root, [])
@ -206,3 +209,5 @@ o.spec("onbeforeremove", function() {
}) })
}) })
}) })
})
})

View file

@ -1,6 +1,7 @@
"use strict" "use strict"
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock") var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render") var vdom = require("../../render/render")
@ -56,86 +57,6 @@ o.spec("onbeforeupdate", function() {
o(root.firstChild.nodeValue).equals("a") o(root.firstChild.nodeValue).equals("a")
}) })
o("prevents update in component", function() {
var component = {
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 = {
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"].nodeValue).equals("a")
})
o("does not prevent update if returning true in component and true in vnode", function() {
var component = {
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"].nodeValue).equals("b")
})
o("does not prevent update if returning false in component but true in vnode", function() {
var component = {
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"].nodeValue).equals("b")
})
o("does not prevent update if returning true in component but false in vnode", function() {
var component = {
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"].nodeValue).equals("b")
})
o("does not prevent update if returning true", function() { o("does not prevent update if returning true", function() {
var onbeforeupdate = function() {return true} var onbeforeupdate = function() {return true}
var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}} var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}}
@ -147,22 +68,6 @@ o.spec("onbeforeupdate", function() {
o(root.firstChild.attributes["id"].nodeValue).equals("b") o(root.firstChild.attributes["id"].nodeValue).equals("b")
}) })
o("does not prevent update if returning true from component", function() {
var component = {
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"].nodeValue).equals("b")
})
o("accepts arguments for comparison", function() { o("accepts arguments for comparison", function() {
var count = 0 var count = 0
var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}} var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}}
@ -184,13 +89,158 @@ o.spec("onbeforeupdate", function() {
o(root.firstChild.attributes["id"].nodeValue).equals("b") o(root.firstChild.attributes["id"].nodeValue).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"].nodeValue).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"].nodeValue).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"].nodeValue).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"].nodeValue).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"].nodeValue).equals("b")
})
o("accepts arguments for comparison in component", function() { o("accepts arguments for comparison in component", function() {
var component = { var component = createComponent({
onbeforeupdate: onbeforeupdate, onbeforeupdate: onbeforeupdate,
view: function(vnode) { view: function(vnode) {
return {tag: "div", attrs: vnode.attrs} return {tag: "div", attrs: vnode.attrs}
}, },
} })
var count = 0 var count = 0
var vnode = {tag: component, attrs: {id: "a"}} var vnode = {tag: component, attrs: {id: "a"}}
var updated = {tag: component, attrs: {id: "b"}} var updated = {tag: component, attrs: {id: "b"}}
@ -211,36 +261,20 @@ o.spec("onbeforeupdate", function() {
o(root.firstChild.attributes["id"].nodeValue).equals("b") o(root.firstChild.attributes["id"].nodeValue).equals("b")
}) })
o("is not called on creation", 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])
function onbeforeupdate(vnode, old) {
count++
return true
}
o(count).equals(0)
})
o("is not called on component creation", function() { o("is not called on component creation", function() {
var component = { createComponent({
onbeforeupdate: onbeforeupdate, onbeforeupdate: onbeforeupdate,
view: function(vnode) { view: function(vnode) {
return {tag: "div", attrs: vnode.attrs} return {tag: "div", attrs: vnode.attrs}
}, },
} })
var count = 0 var count = 0
var vnode = {tag: "div", attrs: {id: "a"}} var vnode = {tag: "div", attrs: {id: "a"}}
var updated = {tag: "div", attrs: {id: "b"}}
render(root, [vnode]) render(root, [vnode])
function onbeforeupdate(vnode, old) { function onbeforeupdate() {
count++ count++
return true return true
} }
@ -248,29 +282,13 @@ o.spec("onbeforeupdate", function() {
o(count).equals(0) 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(vnode, old) {
count++
return true
}
o(count).equals(1)
})
o("is called only once on component update", function() { o("is called only once on component update", function() {
var component = { var component = createComponent({
onbeforeupdate: onbeforeupdate, onbeforeupdate: onbeforeupdate,
view: function(vnode) { view: function(vnode) {
return {tag: "div", attrs: vnode.attrs} return {tag: "div", attrs: vnode.attrs}
}, },
} })
var count = 0 var count = 0
var vnode = {tag: component, attrs: {id: "a"}} var vnode = {tag: component, attrs: {id: "a"}}
@ -279,7 +297,7 @@ o.spec("onbeforeupdate", function() {
render(root, [vnode]) render(root, [vnode])
render(root, [updated]) render(root, [updated])
function onbeforeupdate(vnode, old) { function onbeforeupdate() {
count++ count++
return true return true
} }
@ -287,3 +305,5 @@ o.spec("onbeforeupdate", function() {
o(count).equals(1) o(count).equals(1)
}) })
}) })
})
})

View file

@ -128,7 +128,6 @@ o.spec("oncreate", function() {
}) })
o("does not call oncreate when removing", function() { o("does not call oncreate when removing", function() {
var create = o.spy() var create = o.spy()
var update = o.spy()
var vnode = {tag: "div", attrs: {oncreate: create}, state: {}} var vnode = {tag: "div", attrs: {oncreate: create}, state: {}}
render(root, [vnode]) render(root, [vnode])

View file

@ -128,7 +128,6 @@ o.spec("oninit", function() {
}) })
o("does not call oninit when removing", function() { o("does not call oninit when removing", function() {
var create = o.spy() var create = o.spy()
var update = o.spy()
var vnode = {tag: "div", attrs: {oninit: create}, state: {}} var vnode = {tag: "div", attrs: {oninit: create}, state: {}}
render(root, [vnode]) render(root, [vnode])
@ -187,7 +186,7 @@ o.spec("oninit", function() {
called = true called = true
o(vnode.dom).equals(undefined) o(vnode.dom).equals(undefined)
o(root.childNodes.length).equals(0) o(root.childNodes.length).equals(1)
} }
o(called).equals(true) o(called).equals(true)
}) })

View file

@ -1,6 +1,7 @@
"use strict" "use strict"
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock") var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render") var vdom = require("../../render/render")
var m = require("../../render/hyperscript") var m = require("../../render/hyperscript")
@ -80,39 +81,6 @@ o.spec("onremove", function() {
o(remove.this).equals(vnode.state) o(remove.this).equals(vnode.state)
o(remove.args[0]).equals(vnode) o(remove.args[0]).equals(vnode)
}) })
o("calls onremove on nested component", function() {
var spy = o.spy()
var comp = {
view: function() {return m(outer)}
}
var outer = {
view: function() {return m(inner)}
}
var inner = {
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 = {
view: function() {return m(outer)}
}
var outer = {
view: function() {return m(inner, m("a", {onremove: spy}))}
}
var inner = {
view: function(vnode) {return m("div", vnode.children)}
}
render(root, {tag: comp})
render(root, null)
o(spy.callCount).equals(1)
})
o("does not set onremove as an event handler", function() { o("does not set onremove as an event handler", function() {
var remove = o.spy() var remove = o.spy()
var vnode = {tag: "div", attrs: {onremove: remove}, children: []} var vnode = {tag: "div", attrs: {onremove: remove}, children: []}
@ -145,4 +113,43 @@ o.spec("onremove", function() {
o(vnode.dom).notEquals(updated.dom) 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)
})
})
})
}) })

View file

@ -59,7 +59,6 @@ o.spec("onupdate", function() {
}) })
o("does not call old onupdate when removing the onupdate property in new vnode", function() { o("does not call old onupdate when removing the onupdate property in new vnode", function() {
var create = o.spy() var create = o.spy()
var update = o.spy()
var vnode = {tag: "a", attrs: {onupdate: create}} var vnode = {tag: "a", attrs: {onupdate: create}}
var updated = {tag: "a"} var updated = {tag: "a"}

View file

@ -32,7 +32,7 @@ o.spec("render", function() {
o(threw).equals(true) o(threw).equals(true)
}) })
o("does not enter infinite loop when oninit triggers render and view throws", function(done) { o("does not enter infinite loop when oninit triggers render and view throws with an object literal component", function(done) {
var A = { var A = {
oninit: init, oninit: init,
view: function() {throw new Error("error")} view: function() {throw new Error("error")}
@ -55,4 +55,220 @@ o.spec("render", function() {
o(threwOuter).equals(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)
})
}) })

View file

@ -213,10 +213,10 @@ o.spec("updateElement", function() {
}) })
o("updates svg child", function() { o("updates svg child", function() {
var vnode = {tag: "svg", children: [{ var vnode = {tag: "svg", children: [{
tag: 'circle' tag: "circle"
}]} }]}
var updated = {tag: "svg", children: [{ var updated = {tag: "svg", children: [{
tag: 'line' tag: "line"
}]} }]}
render(root, [vnode]) render(root, [vnode])

View file

@ -1,6 +1,7 @@
"use strict" "use strict"
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock") var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render") var vdom = require("../../render/render")
@ -838,38 +839,6 @@ o.spec("updateNodes", function() {
o(root.childNodes[0].nodeName).equals("A") o(root.childNodes[0].nodeName).equals("A")
o(root.childNodes[1].nodeName).equals("B") o(root.childNodes[1].nodeName).equals("B")
}) })
o("fragment child toggles from null when followed by null component then tag", function() {
var component = {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 = {view: function() {return flag ? {tag: "a"} : null}}
var b = {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")
})
o("cached, non-keyed nodes skip diff", function () { o("cached, non-keyed nodes skip diff", function () {
var onupdate = o.spy(); var onupdate = o.spy();
var cached = {tag:"a", attrs:{onupdate: onupdate}} var cached = {tag:"a", attrs:{onupdate: onupdate}}
@ -926,14 +895,51 @@ o.spec("updateNodes", function() {
o(update.callCount).equals(2) o(update.callCount).equals(2)
o(remove.callCount).equals(0) o(remove.callCount).equals(0)
}) })
o("component is recreated if key changes to undefined", function () { o("node is recreated if key changes to undefined", function () {
var vnode = {tag: "b", key: 1} var vnode = {tag: "b", key: 1}
var updated = {tag: "b"} var updated = {tag: "b"}
render(root, vnode) render(root, vnode)
var dom = vnode.dom
render(root, updated) render(root, updated)
o(vnode.dom).notEquals(updated.dom) 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")
})
})
})
}) })

View file

@ -1,5 +1,7 @@
"use strict"
function Vnode(tag, key, attrs, children, text, dom) { function Vnode(tag, key, attrs, children, text, dom) {
return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: {}, events: undefined, instance: undefined, skip: false} 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) { Vnode.normalize = function(node) {
if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined) if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)

View file

@ -1,2 +1,4 @@
"use strict"
var PromisePolyfill = require("./promise/promise") var PromisePolyfill = require("./promise/promise")
module.exports = require("./request/request")(window, PromisePolyfill) module.exports = require("./request/request")(window, PromisePolyfill)

View file

@ -45,7 +45,7 @@ module.exports = function($window, Promise) {
if (args.method == null) args.method = "GET" if (args.method == null) args.method = "GET"
args.method = args.method.toUpperCase() args.method = args.method.toUpperCase()
var useBody = typeof args.useBody === "boolean" ? args.useBody : args.method !== "GET" && args.method !== "TRACE" 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.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.deserialize !== "function") args.deserialize = deserialize

View file

@ -7,7 +7,7 @@ var Promise = require("../../promise/promise")
var parseQueryString = require("../../querystring/parse") var parseQueryString = require("../../querystring/parse")
o.spec("jsonp", function() { o.spec("jsonp", function() {
var mock, jsonp, spy, complete var mock, jsonp, complete
o.beforeEach(function() { o.beforeEach(function() {
mock = xhrMock() mock = xhrMock()
var requestService = Request(mock, Promise) var requestService = Request(mock, Promise)
@ -28,7 +28,6 @@ o.spec("jsonp", function() {
}).then(done) }).then(done)
}) })
o("first argument can be a string aliasing url property", function(done){ o("first argument can be a string aliasing url property", function(done){
var s = new Date
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function(request) {
var queryData = parseQueryString(request.query) var queryData = parseQueryString(request.query)
@ -104,7 +103,7 @@ o.spec("jsonp", function() {
return {status: 200, responseText: queryData["callback"] + "([])"} return {status: 200, responseText: queryData["callback"] + "([])"}
} }
}) })
var promise = jsonp("/item", {background: true}).then(function() {}) jsonp("/item", {background: true}).then(function() {})
setTimeout(function() { setTimeout(function() {
o(complete.callCount).equals(0) o(complete.callCount).equals(0)

View file

@ -18,7 +18,6 @@ o.spec("xhr", function() {
o.spec("success", function() { o.spec("success", function() {
o("works via GET", function(done) { o("works via GET", function(done) {
var s = new Date
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function() { "GET /item": function() {
return {status: 200, responseText: JSON.stringify({a: 1})} return {status: 200, responseText: JSON.stringify({a: 1})}
@ -31,7 +30,6 @@ o.spec("xhr", function() {
}) })
}) })
o("implicit GET method", function(done){ o("implicit GET method", function(done){
var s = new Date
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function() { "GET /item": function() {
return {status: 200, responseText: JSON.stringify({a: 1})} return {status: 200, responseText: JSON.stringify({a: 1})}
@ -44,7 +42,6 @@ o.spec("xhr", function() {
}) })
}) })
o("first argument can be a string aliasing url property", function(done){ o("first argument can be a string aliasing url property", function(done){
var s = new Date
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function() { "GET /item": function() {
return {status: 200, responseText: JSON.stringify({a: 1})} return {status: 200, responseText: JSON.stringify({a: 1})}
@ -172,7 +169,7 @@ o.spec("xhr", function() {
} }
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 200, responseText: JSON.stringify([{id: 1}, {id: 2}, {id: 3}])} return {status: 200, responseText: JSON.stringify([{id: 1}, {id: 2}, {id: 3}])}
} }
}) })
@ -186,7 +183,7 @@ o.spec("xhr", function() {
} }
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 200, responseText: JSON.stringify({id: 1})} return {status: 200, responseText: JSON.stringify({id: 1})}
} }
}) })
@ -228,12 +225,12 @@ o.spec("xhr", function() {
} }
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 200, responseText: JSON.stringify({test: 123})} return {status: 200, responseText: JSON.stringify({test: 123})}
} }
}) })
xhr({method: "GET", url: "/item", deserialize: deserialize}).then(function(data) { xhr({method: "GET", url: "/item", deserialize: deserialize}).then(function(data) {
o(data).equals("{\"test\":123}") o(data).equals('{"test":123}')
}).then(done) }).then(done)
}) })
o("deserialize parameter works in POST", function(done) { o("deserialize parameter works in POST", function(done) {
@ -242,40 +239,40 @@ o.spec("xhr", function() {
} }
mock.$defineRoutes({ mock.$defineRoutes({
"POST /item": function(request) { "POST /item": function() {
return {status: 200, responseText: JSON.stringify({test: 123})} return {status: 200, responseText: JSON.stringify({test: 123})}
} }
}) })
xhr({method: "POST", url: "/item", deserialize: deserialize}).then(function(data) { xhr({method: "POST", url: "/item", deserialize: deserialize}).then(function(data) {
o(data).equals("{\"test\":123}") o(data).equals('{"test":123}')
}).then(done) }).then(done)
}) })
o("extract parameter works in GET", function(done) { o("extract parameter works in GET", function(done) {
var extract = function(data) { var extract = function() {
return JSON.stringify({test: 123}) return JSON.stringify({test: 123})
} }
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 200, responseText: ""} return {status: 200, responseText: ""}
} }
}) })
xhr({method: "GET", url: "/item", extract: extract}).then(function(data) { xhr({method: "GET", url: "/item", extract: extract}).then(function(data) {
o(data).equals("{\"test\":123}") o(data).equals('{"test":123}')
}).then(done) }).then(done)
}) })
o("extract parameter works in POST", function(done) { o("extract parameter works in POST", function(done) {
var extract = function(data) { var extract = function() {
return JSON.stringify({test: 123}) return JSON.stringify({test: 123})
} }
mock.$defineRoutes({ mock.$defineRoutes({
"POST /item": function(request) { "POST /item": function() {
return {status: 200, responseText: ""} return {status: 200, responseText: ""}
} }
}) })
xhr({method: "POST", url: "/item", extract: extract}).then(function(data) { xhr({method: "POST", url: "/item", extract: extract}).then(function(data) {
o(data).equals("{\"test\":123}") o(data).equals('{"test":123}')
}).then(done) }).then(done)
}) })
o("ignores deserialize if extract is defined", function(done) { o("ignores deserialize if extract is defined", function(done) {
@ -285,7 +282,7 @@ o.spec("xhr", function() {
var deserialize = o.spy() var deserialize = o.spy()
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 200, responseText: ""} return {status: 200, responseText: ""}
} }
}) })
@ -297,7 +294,7 @@ o.spec("xhr", function() {
}) })
o("config parameter works", function(done) { o("config parameter works", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"POST /item": function(request) { "POST /item": function() {
return {status: 200, responseText: ""} return {status: 200, responseText: ""}
} }
}) })
@ -311,7 +308,7 @@ o.spec("xhr", function() {
}) })
o("requests don't block each other", function(done) { o("requests don't block each other", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 200, responseText: "[]"} return {status: 200, responseText: "[]"}
} }
}) })
@ -328,7 +325,7 @@ o.spec("xhr", function() {
}) })
o("requests trigger finally once with a chained then", function(done) { o("requests trigger finally once with a chained then", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 200, responseText: "[]"} return {status: 200, responseText: "[]"}
} }
}) })
@ -342,11 +339,11 @@ o.spec("xhr", function() {
}) })
o("requests does not trigger finally when background: true", function(done) { o("requests does not trigger finally when background: true", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 200, responseText: "[]"} return {status: 200, responseText: "[]"}
} }
}) })
var promise = xhr("/item", {background: true}).then(function() {}) xhr("/item", {background: true}).then(function() {})
setTimeout(function() { setTimeout(function() {
o(complete.callCount).equals(0) o(complete.callCount).equals(0)
@ -355,7 +352,7 @@ o.spec("xhr", function() {
}) })
o("headers are set when header arg passed", function(done) { o("headers are set when header arg passed", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"POST /item": function(request) { "POST /item": function() {
return {status: 200, responseText: ""} return {status: 200, responseText: ""}
} }
}) })
@ -367,7 +364,7 @@ o.spec("xhr", function() {
}) })
o("headers are with higher precedence than default headers", function(done) { o("headers are with higher precedence than default headers", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"POST /item": function(request) { "POST /item": function() {
return {status: 200, responseText: ""} return {status: 200, responseText: ""}
} }
}) })
@ -379,7 +376,7 @@ o.spec("xhr", function() {
}) })
o("json headers are set to the correct default value", function(done) { o("json headers are set to the correct default value", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"POST /item": function(request) { "POST /item": function() {
return {status: 200, responseText: ""} return {status: 200, responseText: ""}
} }
}) })
@ -391,7 +388,6 @@ o.spec("xhr", function() {
} }
}) })
o("doesn't fail on abort", function(done) { o("doesn't fail on abort", function(done) {
var s = new Date
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function() { "GET /item": function() {
return {status: 200, responseText: JSON.stringify({a: 1})} return {status: 200, responseText: JSON.stringify({a: 1})}
@ -410,9 +406,9 @@ o.spec("xhr", function() {
done() done()
}, 0) }, 0)
} }
Object.defineProperty(xhr, 'onreadystatechange', { Object.defineProperty(xhr, "onreadystatechange", {
set: function(val) { onreadystatechange = val } set: function(val) { onreadystatechange = val },
, get: function() { return testonreadystatechange } get: function() { return testonreadystatechange }
}) })
xhr.abort() xhr.abort()
} }
@ -424,7 +420,6 @@ o.spec("xhr", function() {
}) })
}) })
o("doesn't fail on file:// status 0", function(done) { o("doesn't fail on file:// status 0", function(done) {
var s = new Date
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function() { "GET /item": function() {
return {status: 0, responseText: JSON.stringify({a: 1})} return {status: 0, responseText: JSON.stringify({a: 1})}
@ -456,7 +451,7 @@ o.spec("xhr", function() {
o.spec("failure", function() { o.spec("failure", function() {
o("rejects on server error", function(done) { o("rejects on server error", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 500, responseText: JSON.stringify({error: "error"})} return {status: 500, responseText: JSON.stringify({error: "error"})}
} }
}) })
@ -467,7 +462,7 @@ o.spec("xhr", function() {
}) })
o("extends Error with JSON response", function(done) { o("extends Error with JSON response", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 500, responseText: JSON.stringify({message: "error", stack: "error on line 1"})} return {status: 500, responseText: JSON.stringify({message: "error", stack: "error on line 1"})}
} }
}) })
@ -479,7 +474,7 @@ o.spec("xhr", function() {
}) })
o("rejects on non-JSON server error", function(done) { o("rejects on non-JSON server error", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 500, responseText: "error"} return {status: 500, responseText: "error"}
} }
}) })
@ -489,7 +484,7 @@ o.spec("xhr", function() {
}) })
o("triggers all branched catches upon rejection", function(done) { o("triggers all branched catches upon rejection", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 500, responseText: "error"} return {status: 500, responseText: "error"}
} }
}) })
@ -515,7 +510,7 @@ o.spec("xhr", function() {
}) })
o("rejects on cors-like error", function(done) { o("rejects on cors-like error", function(done) {
mock.$defineRoutes({ mock.$defineRoutes({
"GET /item": function(request) { "GET /item": function() {
return {status: 0} return {status: 0}
} }
}) })

View file

@ -1,3 +1,5 @@
"use strict"
var redrawService = require("./redraw") var redrawService = require("./redraw")
module.exports = require("./api/router")(window, redrawService) module.exports = require("./api/router")(window, redrawService)

View file

@ -6,7 +6,7 @@ var Router = require("../../router/router")
o.spec("Router.getPath", function() { o.spec("Router.getPath", function() {
void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) { void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
void ["#", "?", "", "#!", "?!", '/foo'].forEach(function(prefix) { void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) {
o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() { o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() {
var $window, router, onRouteChange, onFail var $window, router, onRouteChange, onFail

View file

@ -1 +1,3 @@
"use strict"
module.exports = require("./stream/stream") module.exports = require("./stream/stream")

13
stream/package.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "mithril-stream",
"version": "1.0.0",
"description": "Streaming data, mithril-style",
"main": "stream.js",
"directories": {
"test": "tests"
},
"keywords": [ "stream", "reactive", "data" ],
"author": "Leo Horie <lhorie@hotmail.com>",
"license": "MIT",
"repository": "lhorie/mithril.js"
}

View file

@ -1,13 +0,0 @@
"use strict"
var combine = require("./stream").combine
module.exports = function (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
}

View file

@ -1,25 +0,0 @@
"use strict"
var combine = require("./stream").combine
module.exports = function(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
}

View file

@ -1,5 +1,7 @@
"use strict" "use strict"
;(function() {
var guid = 0, HALT = {} var guid = 0, HALT = {}
function createStream() { function createStream() {
function stream() { function stream() {
@ -14,7 +16,7 @@ function createStream() {
} }
function initStream(stream) { function initStream(stream) {
stream.constructor = createStream stream.constructor = createStream
stream._state = {id: guid++, value: undefined, state: 0, derive: undefined, recover: undefined, deps: {}, parents: [], endStream: undefined} 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.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 stream.valueOf = valueOf, stream.toJSON = toJSON, stream.toString = valueOf
@ -23,7 +25,10 @@ function initStream(stream) {
if (!stream._state.endStream) { if (!stream._state.endStream) {
var endStream = createStream() var endStream = createStream()
endStream.map(function(value) { endStream.map(function(value) {
if (value === true) unregisterStream(stream), unregisterStream(endStream) if (value === true) {
unregisterStream(stream)
endStream._state.unregister = function(){unregisterStream(endStream)}
}
return value return value
}) })
stream._state.endStream = endStream stream._state.endStream = endStream
@ -35,6 +40,7 @@ function initStream(stream) {
function updateStream(stream, value) { function updateStream(stream, value) {
updateState(stream, value) updateState(stream, value)
for (var id in stream._state.deps) updateDependency(stream._state.deps[id], false) for (var id in stream._state.deps) updateDependency(stream._state.deps[id], false)
if (stream._state.unregister != null) stream._state.unregister()
finalize(stream) finalize(stream)
} }
function updateState(stream, value) { function updateState(stream, value) {
@ -56,7 +62,7 @@ function finalize(stream) {
} }
function combine(fn, streams) { function combine(fn, streams) {
if (!streams.every(valid)) throw new Error("Ensure that each item passed to m.prop.combine/m.prop.merge is a stream") 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 initDependency(createStream(), streams, function() {
return fn.apply(this, streams.concat([streams.filter(changed)])) return fn.apply(this, streams.concat([streams.filter(changed)]))
}) })
@ -107,10 +113,48 @@ function merge(streams) {
return streams.map(function(s) {return s()}) return streams.map(function(s) {return s()})
}, streams) }, 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["fantasy-land/of"] = createStream
createStream.merge = merge createStream.merge = merge
createStream.combine = combine createStream.combine = combine
createStream.scan = scan
createStream.scanMerge = scanMerge
createStream.HALT = HALT createStream.HALT = HALT
if (typeof module !== "undefined") module["exports"] = createStream if (typeof module !== "undefined") module["exports"] = createStream
else window.stream = createStream else if (typeof window.m === "function" && !("stream" in window.m)) window.m.stream = createStream
else window.m = {stream : createStream}
}());

View file

@ -2,12 +2,11 @@
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var stream = require("../stream") var stream = require("../stream")
var scan = require("../scan")
o.spec("scan", function() { o.spec("scan", function() {
o("defaults to seed", function() { o("defaults to seed", function() {
var parent = stream() var parent = stream()
var child = scan(function(out, p) { var child = stream.scan(function(out, p) {
return out - p return out - p
}, 123, parent) }, 123, parent)
o(child()).equals(123) o(child()).equals(123)
@ -15,7 +14,7 @@ o.spec("scan", function() {
o("accumulates values as expected", function() { o("accumulates values as expected", function() {
var parent = stream() var parent = stream()
var child = scan(function(arr, p) { var child = stream.scan(function(arr, p) {
return arr.concat(p) return arr.concat(p)
}, [], parent) }, [], parent)
@ -32,4 +31,3 @@ o.spec("scan", function() {
o(result[3]).deepEquals({a: 1}) o(result[3]).deepEquals({a: 1})
}) })
}) })

View file

@ -2,14 +2,13 @@
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var stream = require("../stream") var stream = require("../stream")
var scanMerge = require("../scanMerge")
o.spec("scanMerge", function() { o.spec("scanMerge", function() {
o("defaults to seed", function() { o("defaults to seed", function() {
var parent1 = stream() var parent1 = stream()
var parent2 = stream() var parent2 = stream()
var child = scanMerge([ var child = stream.scanMerge([
[parent1, function(out, p1) { [parent1, function(out, p1) {
return out + p1 return out + p1
}], }],
@ -25,7 +24,7 @@ o.spec("scanMerge", function() {
var parent1 = stream() var parent1 = stream()
var parent2 = stream() var parent2 = stream()
var child = scanMerge([ var child = stream.scanMerge([
[parent1, function(out, p1) { [parent1, function(out, p1) {
return out + p1 return out + p1
}], }],

View file

@ -1,7 +1,6 @@
"use strict" "use strict"
var o = require("../../ospec/ospec") var o = require("../../ospec/ospec")
var callAsync = require("../../test-utils/callAsync")
var Stream = require("../stream") var Stream = require("../stream")
o.spec("stream", function() { o.spec("stream", function() {
@ -105,7 +104,7 @@ o.spec("stream", function() {
var streams = [] var streams = []
var a = Stream() var a = Stream()
var b = Stream() var b = Stream()
var c = Stream.combine(function(a, b, changed) { Stream.combine(function(a, b, changed) {
streams = changed streams = changed
}, [a, b]) }, [a, b])
@ -119,7 +118,7 @@ o.spec("stream", function() {
var streams = [] var streams = []
var a = Stream(3) var a = Stream(3)
var b = Stream(5) var b = Stream(5)
var c = Stream.combine(function(a, b, changed) { Stream.combine(function(a, b, changed) {
streams = changed streams = changed
}, [a, b]) }, [a, b])
@ -130,7 +129,7 @@ o.spec("stream", function() {
}) })
o("combine can return undefined", function() { o("combine can return undefined", function() {
var a = Stream(1) var a = Stream(1)
var b = Stream.combine(function(a) { var b = Stream.combine(function() {
return undefined return undefined
}, [a]) }, [a])
@ -138,7 +137,7 @@ o.spec("stream", function() {
}) })
o("combine can return stream", function() { o("combine can return stream", function() {
var a = Stream(1) var a = Stream(1)
var b = Stream.combine(function(a) { var b = Stream.combine(function() {
return Stream(2) return Stream(2)
}, [a]) }, [a])
@ -146,7 +145,7 @@ o.spec("stream", function() {
}) })
o("combine can return pending stream", function() { o("combine can return pending stream", function() {
var a = Stream(1) var a = Stream(1)
var b = Stream.combine(function(a) { var b = Stream.combine(function() {
return Stream() return Stream()
}, [a]) }, [a])
@ -155,22 +154,22 @@ o.spec("stream", function() {
o("combine can halt", function() { o("combine can halt", function() {
var count = 0 var count = 0
var a = Stream(1) var a = Stream(1)
var b = Stream.combine(function(a) { var b = Stream.combine(function() {
return Stream.HALT return Stream.HALT
}, [a]) }, [a])["fantasy-land/map"](function() {
["fantasy-land/map"](function() {
count++ count++
return 1 return 1
}) })
o(b()).equals(undefined) o(b()).equals(undefined)
o(count).equals(0)
}) })
o("combine will throw with a helpful error if given non-stream values", function () { o("combine will throw with a helpful error if given non-stream values", function () {
var spy = o.spy() var spy = o.spy()
var a = Stream(1) var a = Stream(1)
var thrown = null; var thrown = null;
try { try {
var b = Stream.combine(spy, [a, '']) Stream.combine(spy, [a, ""])
} catch (e) { } catch (e) {
thrown = e thrown = e
} }
@ -210,8 +209,9 @@ o.spec("stream", function() {
var a = Stream() var a = Stream()
var b = Stream() var b = Stream()
var all = Stream.merge([a.map(id), b.map(id)]).map(function(data) { Stream.merge([a.map(id), b.map(id)]).map(function(data) {
value = data[0] + data[1] value = data[0] + data[1]
return undefined
}) })
a(1) a(1)
@ -263,6 +263,18 @@ o.spec("stream", function() {
o(doubled()).equals(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.spec("valueOf", function() {
o("works", function() { o("works", function() {
@ -344,7 +356,7 @@ o.spec("stream", function() {
}) })
o("works with pending stream", function() { o("works with pending stream", function() {
var stream = Stream(undefined) var stream = Stream(undefined)
var mapped = stream["fantasy-land/map"](function(value) {return Stream()}) var mapped = stream["fantasy-land/map"](function() {return Stream()})
o(mapped()()).equals(undefined) o(mapped()()).equals(undefined)
}) })

29
test-utils/components.js Normal file
View file

@ -0,0 +1,29 @@
"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
}
}
}
]

View file

@ -96,7 +96,7 @@ module.exports = function() {
declList = declList.replace( declList = declList.replace(
/("(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*')|\/\*[\s\S]*?\*\//g, /("(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*')|\/\*[\s\S]*?\*\//g,
function(m, str){ function(m, str){
return str || '' return str || ""
} }
) )
/*eslint-disable no-cond-assign*/ /*eslint-disable no-cond-assign*/
@ -115,7 +115,7 @@ module.exports = function() {
var activeElement var activeElement
var $window = { var $window = {
document: { document: {
createElement: function(tag, is) { createElement: function(tag) {
var cssText = "" var cssText = ""
var style = {} var style = {}
Object.defineProperty(style, "cssText", { Object.defineProperty(style, "cssText", {
@ -211,11 +211,11 @@ module.exports = function() {
else this.setAttribute("class", value) else this.setAttribute("class", value)
}, },
focus: function() {activeElement = this}, focus: function() {activeElement = this},
addEventListener: function(type, callback, useCapture) { addEventListener: function(type, callback) {
if (events[type] == null) events[type] = [callback] if (events[type] == null) events[type] = [callback]
else events[type].push(callback) else events[type].push(callback)
}, },
removeEventListener: function(type, callback, useCapture) { removeEventListener: function(type, callback) {
if (events[type] != null) { if (events[type] != null) {
var index = events[type].indexOf(callback) var index = events[type].indexOf(callback)
if (index > -1) events[type].splice(index, 1) if (index > -1) events[type].splice(index, 1)
@ -241,7 +241,6 @@ module.exports = function() {
} }
if (element.nodeName === "A") { if (element.nodeName === "A") {
var href
Object.defineProperty(element, "href", { Object.defineProperty(element, "href", {
get: function() {return this.attributes["href"] === undefined ? "" : "[FIXME implement]"}, get: function() {return this.attributes["href"] === undefined ? "" : "[FIXME implement]"},
set: function(value) {this.setAttribute("href", value)}, set: function(value) {this.setAttribute("href", value)},
@ -272,6 +271,8 @@ module.exports = function() {
}) })
} }
/* eslint-disable radix */
if (element.nodeName === "CANVAS") { if (element.nodeName === "CANVAS") {
Object.defineProperty(element, "width", { Object.defineProperty(element, "width", {
get: function() {return this.attributes["width"] ? Math.floor(parseInt(this.attributes["width"].nodeValue) || 0) : 300}, get: function() {return this.attributes["width"] ? Math.floor(parseInt(this.attributes["width"].nodeValue) || 0) : 300},
@ -283,6 +284,8 @@ module.exports = function() {
}) })
} }
/* eslint-enable radix */
function getOptions(element) { function getOptions(element) {
var options = [] var options = []
for (var i = 0; i < element.childNodes.length; i++) { for (var i = 0; i < element.childNodes.length; i++) {
@ -297,17 +300,18 @@ module.exports = function() {
element.firstChild != null ? element.firstChild.nodeValue : "" element.firstChild != null ? element.firstChild.nodeValue : ""
} }
if (element.nodeName === "SELECT") { if (element.nodeName === "SELECT") {
var selectedValue, selectedIndex = 0 // var selectedValue
var selectedIndex = 0
Object.defineProperty(element, "selectedIndex", { Object.defineProperty(element, "selectedIndex", {
get: function() {return getOptions(this).length > 0 ? selectedIndex : -1}, get: function() {return getOptions(this).length > 0 ? selectedIndex : -1},
set: function(value) { set: function(value) {
var options = getOptions(this) var options = getOptions(this)
if (value >= 0 && value < options.length) { if (value >= 0 && value < options.length) {
selectedValue = getOptionValue(options[selectedIndex]) // selectedValue = getOptionValue(options[selectedIndex])
selectedIndex = value selectedIndex = value
} }
else { else {
selectedValue = "" // selectedValue = ""
selectedIndex = -1 selectedIndex = -1
} }
}, },
@ -323,12 +327,12 @@ module.exports = function() {
var stringValue = String(value) var stringValue = String(value)
for (var i = 0; i < options.length; i++) { for (var i = 0; i < options.length; i++) {
if (getOptionValue(options[i]) === stringValue) { if (getOptionValue(options[i]) === stringValue) {
selectedValue = stringValue // selectedValue = stringValue
selectedIndex = i selectedIndex = i
return return
} }
} }
selectedValue = stringValue // selectedValue = stringValue
selectedIndex = -1 selectedIndex = -1
}, },
enumerable: true, enumerable: true,

View file

@ -14,12 +14,14 @@
<script src="../../test-utils/xhrMock.js"></script> <script src="../../test-utils/xhrMock.js"></script>
<script src="../../test-utils/domMock.js"></script> <script src="../../test-utils/domMock.js"></script>
<script src="../../test-utils/browserMock.js"></script> <script src="../../test-utils/browserMock.js"></script>
<script src="../../test-utils/component.js"></script>
<script src="test-callAsync.js"></script> <script src="test-callAsync.js"></script>
<script src="test-parseURL.js"></script> <script src="test-parseURL.js"></script>
<script src="test-pushStateMock.js"></script> <script src="test-pushStateMock.js"></script>
<script src="test-xhrMock.js"></script> <script src="test-xhrMock.js"></script>
<script src="test-domMock.js"></script> <script src="test-domMock.js"></script>
<script src="test-browserMock.js"></script> <script src="test-browserMock.js"></script>
<script src="test-component.js"></script>
<script>require("../../ospec/ospec").run()</script> <script>require("../../ospec/ospec").run()</script>
</body> </body>

View file

@ -17,7 +17,7 @@ o.spec("browserMock", function() {
}) })
o("$window.onhashchange can be reached from the pushStateMock functions", function(done) { o("$window.onhashchange can be reached from the pushStateMock functions", function(done) {
$window.onhashchange = o.spy() $window.onhashchange = o.spy()
$window.location.hash = '#a' $window.location.hash = "#a"
callAsync(function(){ callAsync(function(){
o($window.onhashchange.callCount).equals(1) o($window.onhashchange.callCount).equals(1)
@ -33,7 +33,7 @@ o.spec("browserMock", function() {
}) })
o("$window.onunload can be reached from the pushStateMock functions", function() { o("$window.onunload can be reached from the pushStateMock functions", function() {
$window.onunload = o.spy() $window.onunload = o.spy()
$window.location.href = '/a' $window.location.href = "/a"
o($window.onunload.callCount).equals(1) o($window.onunload.callCount).equals(1)
}) })

View file

@ -0,0 +1,54 @@
"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))
})
})

View file

@ -393,7 +393,6 @@ o.spec("domMock", function() {
o.spec("textContent", function() { o.spec("textContent", function() {
o("works", function() { o("works", function() {
var div = $document.createElement("div") var div = $document.createElement("div")
var a = $document.createElement("a")
div.textContent = "aaa" div.textContent = "aaa"
o(div.childNodes.length).equals(1) o(div.childNodes.length).equals(1)
@ -402,7 +401,6 @@ o.spec("domMock", function() {
}) })
o("works with empty string", function() { o("works with empty string", function() {
var div = $document.createElement("div") var div = $document.createElement("div")
var a = $document.createElement("a")
div.textContent = "" div.textContent = ""
o(div.childNodes.length).equals(0) o(div.childNodes.length).equals(0)
@ -515,7 +513,7 @@ o.spec("domMock", function() {
div.style.cssText = "background: url(';'); font-family: \";\"" div.style.cssText = "background: url(';'); font-family: \";\""
o(div.style.background).equals("url(';')") o(div.style.background).equals("url(';')")
o(div.style.fontFamily).equals("\";\"") o(div.style.fontFamily).equals('";"')
o(div.style.cssText).equals("background: url(';'); font-family: \";\";") o(div.style.cssText).equals("background: url(';'); font-family: \";\";")
}) })
o("comments in style.cssText are stripped", function(){ o("comments in style.cssText are stripped", function(){
@ -534,9 +532,10 @@ o.spec("domMock", function() {
}) })
o("setting style throws", function () { o("setting style throws", function () {
var div = $document.createElement("div")
var err = false var err = false
try { try {
div.style = '' div.style = ""
} catch (e) { } catch (e) {
err = e err = e
} }

View file

@ -168,13 +168,13 @@ o.spec("pushStateMock", function() {
}) })
o.spec("set protocol", function() { o.spec("set protocol", function() {
o("setting protocol throws", function(done) { o("setting protocol throws", function(done) {
var old = $window.location.href
try { try {
$window.location.protocol = "https://" $window.location.protocol = "https://"
} }
catch (e) { catch (e) {
done() return done()
} }
throw new Error("Expected an error")
}) })
}) })
o.spec("set port", function() { o.spec("set port", function() {

View file

@ -5,13 +5,13 @@ var xhrMock = require("../../test-utils/xhrMock")
var parseQueryString = require("../../querystring/parse") var parseQueryString = require("../../querystring/parse")
o.spec("xhrMock", function() { o.spec("xhrMock", function() {
var $window, ajax var $window
o.beforeEach(function() { o.beforeEach(function() {
$window = xhrMock() $window = xhrMock()
}) })
o.spec("xhr", function() { o.spec("xhr", function() {
o("works", function(done, timeout) { o("works", function(done) {
$window.$defineRoutes({ $window.$defineRoutes({
"GET /item": function(request) { "GET /item": function(request) {
o(request.url).equals("/item") o(request.url).equals("/item")
@ -29,7 +29,7 @@ o.spec("xhrMock", function() {
} }
xhr.send() xhr.send()
}) })
o("works w/ search", function(done, timeout) { o("works w/ search", function(done) {
$window.$defineRoutes({ $window.$defineRoutes({
"GET /item": function(request) { "GET /item": function(request) {
o(request.query).equals("?a=b") o(request.query).equals("?a=b")
@ -45,7 +45,7 @@ o.spec("xhrMock", function() {
} }
xhr.send() xhr.send()
}) })
o("works w/ body", function(done, timeout) { o("works w/ body", function(done) {
$window.$defineRoutes({ $window.$defineRoutes({
"POST /item": function(request) { "POST /item": function(request) {
o(request.body).equals("a=b") o(request.body).equals("a=b")
@ -61,7 +61,7 @@ o.spec("xhrMock", function() {
} }
xhr.send("a=b") xhr.send("a=b")
}) })
o("handles routing error", function(done, timeout) { o("handles routing error", function(done) {
var xhr = new $window.XMLHttpRequest() var xhr = new $window.XMLHttpRequest()
xhr.open("GET", "/nonexistent") xhr.open("GET", "/nonexistent")
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
@ -113,7 +113,7 @@ o.spec("xhrMock", function() {
done() done()
} }
}) })
o("works with other querystring params", function(done, timeout) { o("works with other querystring params", function(done) {
$window.$defineRoutes({ $window.$defineRoutes({
"GET /test": function(request) { "GET /test": function(request) {
var queryData = parseQueryString(request.query) var queryData = parseQueryString(request.query)

View file

@ -6,7 +6,7 @@ var parseQueryString = require("../querystring/parse")
module.exports = function() { module.exports = function() {
var routes = {} var routes = {}
var callback = "callback" // var callback = "callback"
var serverErrorHandler = function(url) { var serverErrorHandler = function(url) {
return {status: 500, responseText: "server error, most likely the URL was not defined " + url} return {status: 500, responseText: "server error, most likely the URL was not defined " + url}
} }
@ -43,7 +43,6 @@ module.exports = function() {
} }
self.readyState = 4 self.readyState = 4
if (args.async === true) { if (args.async === true) {
var s = new Date
callAsync(function() { callAsync(function() {
if (typeof self.onreadystatechange === "function") self.onreadystatechange() if (typeof self.onreadystatechange === "function") self.onreadystatechange()
}) })
@ -64,7 +63,7 @@ module.exports = function() {
var urlData = parseURL(element.src, {protocol: "http:", hostname: "localhost", port: "", pathname: "/"}) var urlData = parseURL(element.src, {protocol: "http:", hostname: "localhost", port: "", pathname: "/"})
var handler = routes["GET " + urlData.pathname] || serverErrorHandler.bind(null, element.src) var handler = routes["GET " + urlData.pathname] || serverErrorHandler.bind(null, element.src)
var data = handler({url: urlData.pathname, query: urlData.search, body: null}) var data = handler({url: urlData.pathname, query: urlData.search, body: null})
var query = parseQueryString(urlData.search) parseQueryString(urlData.search)
callAsync(function() { callAsync(function() {
if (data.status === 200) { if (data.status === 200) {
new Function("$window", "with ($window) return " + data.responseText).call($window, $window) new Function("$window", "with ($window) return " + data.responseText).call($window, $window)
@ -83,8 +82,8 @@ module.exports = function() {
$defineRoutes: function(rules) { $defineRoutes: function(rules) {
routes = rules routes = rules
}, },
$defineJSONPCallbackKey: function(key) { $defineJSONPCallbackKey: function(/* key */) {
callback = key // callback = key
}, },
} }
return $window return $window

View file

@ -2,6 +2,7 @@
var o = require("../ospec/ospec") var o = require("../ospec/ospec")
var browserMock = require("../test-utils/browserMock") var browserMock = require("../test-utils/browserMock")
var components = require("../test-utils/components")
o.spec("api", function() { o.spec("api", function() {
var m var m
@ -9,7 +10,7 @@ o.spec("api", function() {
o.beforeEach(function() { o.beforeEach(function() {
var mock = browserMock() var mock = browserMock()
if (typeof global !== "undefined") global.window = mock if (typeof global !== "undefined") global.window = mock
m = require("../mithril") m = require("../mithril") // eslint-disable-line global-require
}) })
o.spec("m", function() { o.spec("m", function() {
@ -68,6 +69,16 @@ o.spec("api", function() {
o(query).equals("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.spec("m.render", function() {
o("works", function() { o("works", function() {
var root = window.document.createElement("div") var root = window.document.createElement("div")
@ -77,10 +88,14 @@ o.spec("api", function() {
o(root.firstChild.nodeName).equals("DIV") 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.spec("m.mount", function() {
o("works", function() { o("works", function() {
var root = window.document.createElement("div") var root = window.document.createElement("div")
m.mount(root, {view: function() {return m("div")}}) m.mount(root, createComponent({view: function() {return m("div")}}))
o(root.childNodes.length).equals(1) o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.nodeName).equals("DIV")
@ -90,7 +105,7 @@ o.spec("api", function() {
o("works", function(done) { o("works", function(done) {
var root = window.document.createElement("div") var root = window.document.createElement("div")
m.route(root, "/a", { m.route(root, "/a", {
"/a": {view: function() {return m("div")}} "/a": createComponent({view: function() {return m("div")}})
}) })
setTimeout(function() { setTimeout(function() {
@ -104,7 +119,7 @@ o.spec("api", function() {
var root = window.document.createElement("div") var root = window.document.createElement("div")
m.route.prefix("#") m.route.prefix("#")
m.route(root, "/a", { m.route(root, "/a", {
"/a": {view: function() {return m("div")}} "/a": createComponent({view: function() {return m("div")}})
}) })
setTimeout(function() { setTimeout(function() {
@ -117,7 +132,7 @@ o.spec("api", function() {
o("m.route.get", function(done) { o("m.route.get", function(done) {
var root = window.document.createElement("div") var root = window.document.createElement("div")
m.route(root, "/a", { m.route(root, "/a", {
"/a": {view: function() {return m("div")}} "/a": createComponent({view: function() {return m("div")}})
}) })
setTimeout(function() { setTimeout(function() {
@ -130,7 +145,7 @@ o.spec("api", function() {
timeout(100) timeout(100)
var root = window.document.createElement("div") var root = window.document.createElement("div")
m.route(root, "/a", { m.route(root, "/a", {
"/:id": {view: function() {return m("div")}} "/:id": createComponent({view: function() {return m("div")}})
}) })
setTimeout(function() { setTimeout(function() {
@ -147,7 +162,7 @@ o.spec("api", function() {
o("works", function(done) { o("works", function(done) {
var count = 0 var count = 0
var root = window.document.createElement("div") var root = window.document.createElement("div")
m.mount(root, {view: function() {count++}}) m.mount(root, createComponent({view: function() {count++}}))
setTimeout(function() { setTimeout(function() {
m.redraw() m.redraw()
@ -157,14 +172,6 @@ o.spec("api", function() {
}, FRAME_BUDGET) }, FRAME_BUDGET)
}) })
}) })
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
}) })
}) })
}) })