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
examples
docs
node_modules
tests
test-utils
ospec
mithril.js
mithril.min.js
archive
/coverage
/docs/lib
/examples
/mithril.js
/mithril.min.js
/node_modules

View file

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

5
.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
.vscode
.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.
Mithril supports browsers all the way back to IE9, no polyfills required.
---
### Getting started
@ -54,7 +56,7 @@ Let's create an HTML file to follow along:
```markup
<body>
<script src="http://unpkg.com/mithril/mithril.js"></script>
<script src="//unpkg.com/mithril/mithril.js"></script>
<script>
var root = document.body
@ -67,7 +69,7 @@ Let's create an HTML file to follow along:
### 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
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"))
```
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
m("h1", {class: "title"}, "My first app")
@ -231,7 +233,7 @@ var count = 0
var increment = function() {
m.request({
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},
withCredentials: true,
})

View file

@ -10,7 +10,7 @@ module.exports = function(redrawService) {
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() {
redrawService.render(root, Vnode(component))

View file

@ -28,7 +28,7 @@ module.exports = function($window) {
renderService.setEventCallback(function(e) {
if (e.redraw !== false) redraw()
})
var callbacks = []
function subscribe(key, callback) {
unsubscribe(key)
@ -38,10 +38,10 @@ module.exports = function($window) {
var index = callbacks.indexOf(key)
if (index > -1) callbacks.splice(index, 2)
}
function redraw() {
for (var i = 1; i < callbacks.length; i += 2) {
callbacks[i]()
}
}
function redraw() {
for (var i = 1; i < callbacks.length; i += 2) {
callbacks[i]()
}
}
return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
}

View file

@ -14,17 +14,19 @@ module.exports = function($window, redrawService) {
var run = function() {
if (render != null) redrawService.render(root, render(Vnode(component, attrs.key, attrs)))
}
var bail = function() {
routeService.setPath(defaultRoute, null, {replace: true})
var bail = function(path) {
if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true})
else throw new Error("Could not resolve default route " + defaultRoute)
}
routeService.defineRoutes(routes, function(payload, params, path) {
var update = lastUpdate = function(routeResolver, comp) {
if (update !== lastUpdate) return
component = comp != null && typeof comp.view === "function" ? 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)
run()
}
if (payload.view) update({}, payload)
if (payload.view || typeof payload === "function") update({}, payload)
else {
if (payload.onmatch) {
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/xhrMock.js"></script>
<script src="../../test-utils/browserMock.js"></script>
<script src="../../test-utils/component.js"></script>
<script src="../../promise/promise.js"></script>
<script src="../../render/vnode.js"></script>

View file

@ -1,6 +1,7 @@
"use strict"
var o = require("../../ospec/ospec")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var m = require("../../render/hyperscript")
@ -22,217 +23,233 @@ o.spec("mount", function() {
render = coreRenderer($window).render
})
o("throws on invalid `root` DOM node", function() {
o("throws on invalid component", function() {
var threw = false
try {
mount(null, {view: function() {}})
mount(root, {})
} catch (e) {
threw = true
}
o(threw).equals(true)
})
o("renders into `root`", function() {
mount(root, {
view : function() {
return m("div")
}
})
components.forEach(function(cmp){
o.spec(cmp.kind, function(){
var createComponent = cmp.create
o(root.firstChild.nodeName).equals("DIV")
})
o("throws on invalid `root` DOM node", function() {
var threw = false
try {
mount(null, createComponent({view: function() {}}))
} catch (e) {
threw = true
}
o(threw).equals(true)
})
o("mounting null unmounts", function() {
mount(root, {
view : function() {
return m("div")
}
})
mount(root, null)
o(root.childNodes.length).equals(0)
})
o("redraws on events", function(done) {
var onupdate = o.spy()
var oninit = o.spy()
var onclick = o.spy()
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
mount(root, {
view : function() {
return m("div", {
oninit : oninit,
onupdate : onupdate,
onclick : onclick,
})
}
})
root.firstChild.dispatchEvent(e)
o(oninit.callCount).equals(1)
o(onupdate.callCount).equals(0)
o(onclick.callCount).equals(1)
o(onclick.this).equals(root.firstChild)
o(onclick.args[0].type).equals("click")
o(onclick.args[0].target).equals(root.firstChild)
// Wrapped to give time for the rate-limited redraw to fire
setTimeout(function() {
o(onupdate.callCount).equals(1)
done()
}, FRAME_BUDGET)
})
o("redraws several mount points on events", function(done, timeout) {
timeout(60)
var onupdate0 = o.spy()
var oninit0 = o.spy()
var onclick0 = o.spy()
var onupdate1 = o.spy()
var oninit1 = o.spy()
var onclick1 = o.spy()
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
render(root, [
m("#child0"),
m("#child1")
])
mount(root.childNodes[0], {
view : function() {
return m("div", {
oninit : oninit0,
onupdate : onupdate0,
onclick : onclick0,
})
}
})
o(oninit0.callCount).equals(1)
o(onupdate0.callCount).equals(0)
mount(root.childNodes[1], {
view : function() {
return m("div", {
oninit : oninit1,
onupdate : onupdate1,
onclick : onclick1,
})
}
})
o(oninit1.callCount).equals(1)
o(onupdate1.callCount).equals(0)
root.childNodes[0].firstChild.dispatchEvent(e)
o(onclick0.callCount).equals(1)
o(onclick0.this).equals(root.childNodes[0].firstChild)
setTimeout(function() {
o(onupdate0.callCount).equals(1)
o(onupdate1.callCount).equals(1)
root.childNodes[1].firstChild.dispatchEvent(e)
o(onclick1.callCount).equals(1)
o(onclick1.this).equals(root.childNodes[1].firstChild)
setTimeout(function() {
o(onupdate0.callCount).equals(2)
o(onupdate1.callCount).equals(2)
done()
}, FRAME_BUDGET)
}, FRAME_BUDGET)
})
o("event handlers can skip redraw", function(done) {
var onupdate = o.spy()
var oninit = o.spy()
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
mount(root, {
view: function() {
return m("div", {
oninit: oninit,
onupdate: onupdate,
onclick: function(e) {
e.redraw = false
o("renders into `root`", function() {
mount(root, createComponent({
view : function() {
return m("div")
}
})
}
}))
o(root.firstChild.nodeName).equals("DIV")
})
o("mounting null unmounts", function() {
mount(root, createComponent({
view : function() {
return m("div")
}
}))
mount(root, null)
o(root.childNodes.length).equals(0)
})
o("redraws on events", function(done) {
var onupdate = o.spy()
var oninit = o.spy()
var onclick = o.spy()
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
mount(root, createComponent({
view : function() {
return m("div", {
oninit : oninit,
onupdate : onupdate,
onclick : onclick,
})
}
}))
root.firstChild.dispatchEvent(e)
o(oninit.callCount).equals(1)
o(onupdate.callCount).equals(0)
o(onclick.callCount).equals(1)
o(onclick.this).equals(root.firstChild)
o(onclick.args[0].type).equals("click")
o(onclick.args[0].target).equals(root.firstChild)
// Wrapped to give time for the rate-limited redraw to fire
setTimeout(function() {
o(onupdate.callCount).equals(1)
done()
}, FRAME_BUDGET)
})
o("redraws several mount points on events", function(done, timeout) {
timeout(60)
var onupdate0 = o.spy()
var oninit0 = o.spy()
var onclick0 = o.spy()
var onupdate1 = o.spy()
var oninit1 = o.spy()
var onclick1 = o.spy()
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
render(root, [
m("#child0"),
m("#child1")
])
mount(root.childNodes[0], createComponent({
view : function() {
return m("div", {
oninit : oninit0,
onupdate : onupdate0,
onclick : onclick0,
})
}
}))
o(oninit0.callCount).equals(1)
o(onupdate0.callCount).equals(0)
mount(root.childNodes[1], createComponent({
view : function() {
return m("div", {
oninit : oninit1,
onupdate : onupdate1,
onclick : onclick1,
})
}
}))
o(oninit1.callCount).equals(1)
o(onupdate1.callCount).equals(0)
root.childNodes[0].firstChild.dispatchEvent(e)
o(onclick0.callCount).equals(1)
o(onclick0.this).equals(root.childNodes[0].firstChild)
setTimeout(function() {
o(onupdate0.callCount).equals(1)
o(onupdate1.callCount).equals(1)
root.childNodes[1].firstChild.dispatchEvent(e)
o(onclick1.callCount).equals(1)
o(onclick1.this).equals(root.childNodes[1].firstChild)
setTimeout(function() {
o(onupdate0.callCount).equals(2)
o(onupdate1.callCount).equals(2)
done()
}, FRAME_BUDGET)
}, FRAME_BUDGET)
})
o("event handlers can skip redraw", function(done) {
var onupdate = o.spy()
var oninit = o.spy()
var e = $window.document.createEvent("MouseEvents")
e.initEvent("click", true, true)
mount(root, createComponent({
view: function() {
return m("div", {
oninit: oninit,
onupdate: onupdate,
onclick: function(e) {
e.redraw = false
}
})
}
}))
root.firstChild.dispatchEvent(e)
o(oninit.callCount).equals(1)
// Wrapped to ensure no redraw fired
setTimeout(function() {
o(onupdate.callCount).equals(0)
done()
}, FRAME_BUDGET)
})
o("redraws when the render function is run", function(done) {
var onupdate = o.spy()
var oninit = o.spy()
mount(root, createComponent({
view : function() {
return m("div", {
oninit: oninit,
onupdate: onupdate
})
}
}))
o(oninit.callCount).equals(1)
o(onupdate.callCount).equals(0)
redrawService.redraw()
// Wrapped to give time for the rate-limited redraw to fire
setTimeout(function() {
o(onupdate.callCount).equals(1)
done()
}, FRAME_BUDGET)
})
o("throttles", function(done, timeout) {
timeout(200)
var i = 0
mount(root, createComponent({view: function() {i++}}))
var before = i
redrawService.redraw()
redrawService.redraw()
redrawService.redraw()
redrawService.redraw()
var after = i
setTimeout(function(){
o(before).equals(1) // mounts synchronously
o(after).equals(1) // throttles rest
o(i).equals(2)
done()
},40)
})
})
root.firstChild.dispatchEvent(e)
o(oninit.callCount).equals(1)
// Wrapped to ensure no redraw fired
setTimeout(function() {
o(onupdate.callCount).equals(0)
done()
}, FRAME_BUDGET)
})
o("redraws when the render function is run", function(done) {
var onupdate = o.spy()
var oninit = o.spy()
mount(root, {
view : function() {
return m("div", {
oninit: oninit,
onupdate: onupdate
})
}
})
o(oninit.callCount).equals(1)
o(onupdate.callCount).equals(0)
redrawService.redraw()
// Wrapped to give time for the rate-limited redraw to fire
setTimeout(function() {
o(onupdate.callCount).equals(1)
done()
}, FRAME_BUDGET)
})
o("throttles", function(done, timeout) {
timeout(200)
var i = 0
mount(root, {view: function() {i++}})
var before = i
redrawService.redraw()
redrawService.redraw()
redrawService.redraw()
redrawService.redraw()
var after = i
setTimeout(function(){
o(before).equals(1) // mounts synchronously
o(after).equals(1) // throttles rest
o(i).equals(2)
done()
},40)
})
})

View file

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

View file

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

View file

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

View file

@ -114,7 +114,7 @@ function run(input, output) {
.replace(/(\r|\n)+/g, "\n").replace(/(\r|\n)$/, "") // remove multiline breaks
.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)) {
//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 bundle = require("../bundle")
@ -5,10 +7,10 @@ var fs = require("fs")
var ns = "bundler/tests/"
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) {
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")
fs.writeFileSync(ns + filepath, data, "utf8")
}
@ -18,255 +20,255 @@ function remove(filepath) {
o.spec("bundler", function() {
o("relative imports works", function() {
write("a.js", `var b = require("./b")`)
write("b.js", `module.exports = 1`)
write("a.js", 'var b = require("./b")')
write("b.js", "module.exports = 1")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b = 1\n}`)
o(read("out.js")).equals(";(function() {\nvar b = 1\n}());")
remove("a.js")
remove("b.js")
remove("out.js")
})
o("relative imports works with semicolons", function() {
write("a.js", `var b = require("./b");`)
write("b.js", `module.exports = 1;`)
write("a.js", 'var b = require("./b");')
write("b.js", "module.exports = 1;")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b = 1;\n}`)
o(read("out.js")).equals(";(function() {\nvar b = 1;\n}());")
remove("a.js")
remove("b.js")
remove("out.js")
})
o("relative imports works with let", function() {
write("a.js", `let b = require("./b")`)
write("b.js", `module.exports = 1`)
write("a.js", 'let b = require("./b")')
write("b.js", "module.exports = 1")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nlet b = 1\n}`)
o(read("out.js")).equals(";(function() {\nlet b = 1\n}());")
remove("a.js")
remove("b.js")
remove("out.js")
})
o("relative imports works with const", function() {
write("a.js", 'const b = require("./b")')
write("b.js", `module.exports = 1`)
write("b.js", "module.exports = 1")
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("b.js")
remove("out.js")
})
o("relative imports works with assignment", function() {
write("a.js", `var a = {}\na.b = require("./b")`)
write("b.js", `module.exports = 1`)
write("a.js", 'var a = {}\na.b = require("./b")')
write("b.js", "module.exports = 1")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar a = {}\na.b = 1\n}`)
o(read("out.js")).equals(";(function() {\nvar a = {}\na.b = 1\n}());")
remove("a.js")
remove("b.js")
remove("out.js")
})
o("relative imports works with reassignment", function() {
write("a.js", `var b = {}\nb = require("./b")`)
write("b.js", `module.exports = 1`)
write("a.js", 'var b = {}\nb = require("./b")')
write("b.js", "module.exports = 1")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b = {}\nb = 1\n}`)
o(read("out.js")).equals(";(function() {\nvar b = {}\nb = 1\n}());")
remove("a.js")
remove("b.js")
remove("out.js")
})
o("relative imports removes extra use strict", function() {
write("a.js", `"use strict"\nvar b = require("./b")`)
write("b.js", `"use strict"\nmodule.exports = 1`)
write("a.js", '"use strict"\nvar b = require("./b")')
write("b.js", '"use strict"\nmodule.exports = 1')
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("relative imports removes extra use strict using single quotes", function() {
write("a.js", `'use strict'\nvar b = require("./b")`)
write("b.js", `'use strict'\nmodule.exports = 1`)
write("a.js", "'use strict'\nvar b = require(\"./b\")")
write("b.js", "'use strict'\nmodule.exports = 1")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("relative imports removes extra use strict using mixed quotes", function() {
write("a.js", `"use strict"\nvar b = require("./b")`)
write("b.js", `'use strict'\nmodule.exports = 1`)
write("a.js", '"use strict"\nvar b = require("./b")')
write("b.js", "'use strict'\nmodule.exports = 1")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("works w/ window", function() {
write("a.js", `window.a = 1\nvar b = require("./b")`)
write("b.js", `module.exports = function() {return a}`)
write("a.js", 'window.a = 1\nvar b = require("./b")')
write("b.js", "module.exports = function() {return a}")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("works without assignment", function() {
write("a.js", `require("./b")`)
write("b.js", `1 + 1`)
write("a.js", 'require("./b")')
write("b.js", "1 + 1")
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("b.js")
remove("out.js")
})
o("works if used fluently", function() {
write("a.js", `var b = require("./b").toString()`)
write("b.js", `module.exports = []`)
write("a.js", 'var b = require("./b").toString()')
write("b.js", "module.exports = []")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("works if used fluently w/ multiline", function() {
write("a.js", `var b = require("./b")\n\t.toString()`)
write("b.js", `module.exports = []`)
write("a.js", 'var b = require("./b")\n\t.toString()')
write("b.js", "module.exports = []")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("works if used w/ curry", function() {
write("a.js", `var b = require("./b")()`)
write("b.js", `module.exports = function() {}`)
write("a.js", 'var b = require("./b")()')
write("b.js", "module.exports = function() {}")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("works if used w/ curry w/ multiline", function() {
write("a.js", `var b = require("./b")\n()`)
write("b.js", `module.exports = function() {}`)
write("a.js", 'var b = require("./b")\n()')
write("b.js", "module.exports = function() {}")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("works if used fluently in one place and not in another", function() {
write("a.js", `var b = require("./b").toString()\nvar c = require("./c")`)
write("b.js", `module.exports = []`)
write("c.js", `var b = require("./b")\nmodule.exports = function() {return b}`)
write("a.js", 'var b = require("./b").toString()\nvar c = require("./c")')
write("b.js", "module.exports = []")
write("c.js", 'var b = require("./b")\nmodule.exports = function() {return b}')
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("c.js")
remove("out.js")
})
o("works if used in sequence", function() {
write("a.js", `var b = require("./b"), c = require("./c")`)
write("b.js", `module.exports = 1`)
write("c.js", `var x\nmodule.exports = 2`)
write("a.js", 'var b = require("./b"), c = require("./c")')
write("b.js", "module.exports = 1")
write("c.js", "var x\nmodule.exports = 2")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("c.js")
remove("out.js")
})
o("works if assigned to property", function() {
write("a.js", `var x = {}\nx.b = require("./b")\nx.c = require("./c")`)
write("b.js", `var bb = 1\nmodule.exports = bb`)
write("c.js", `var cc = 2\nmodule.exports = cc`)
write("a.js", 'var x = {}\nx.b = require("./b")\nx.c = require("./c")')
write("b.js", "var bb = 1\nmodule.exports = bb")
write("c.js", "var cc = 2\nmodule.exports = cc")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("c.js")
remove("out.js")
})
o("works if assigned to property using bracket notation", function() {
write("a.js", `var x = {}\nx["b"] = require("./b")\nx["c"] = require("./c")`)
write("b.js", `var bb = 1\nmodule.exports = bb`)
write("c.js", `var cc = 2\nmodule.exports = cc`)
write("a.js", 'var x = {}\nx["b"] = require("./b")\nx["c"] = require("./c")')
write("b.js", "var bb = 1\nmodule.exports = bb")
write("c.js", "var cc = 2\nmodule.exports = cc")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("c.js")
remove("out.js")
})
o("works if collision", function() {
write("a.js", `var b = require("./b")`)
write("b.js", `var b = 1\nmodule.exports = 2`)
write("a.js", 'var b = require("./b")')
write("b.js", "var b = 1\nmodule.exports = 2")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("works if multiple aliases", function() {
write("a.js", `var b = require("./b")\n`)
write("b.js", `var b = require("./c")\nb.x = 1\nmodule.exports = b`)
write("c.js", `var b = {}\nmodule.exports = b`)
write("a.js", 'var b = require("./b")\n')
write("b.js", 'var b = require("./c")\nb.x = 1\nmodule.exports = b')
write("c.js", "var b = {}\nmodule.exports = b")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`new function() {\nvar b = {}\nb.x = 1\n}`)
o(read("out.js")).equals(";(function() {\nvar b = {}\nb.x = 1\n}());")
remove("a.js")
remove("b.js")
remove("c.js")
remove("out.js")
})
o("works if multiple collision", function() {
write("a.js", `var b = require("./b")\nvar c = require("./c")\nvar d = require("./d")`)
write("b.js", `var a = 1\nmodule.exports = a`)
write("c.js", `var a = 2\nmodule.exports = a`)
write("d.js", `var a = 3\nmodule.exports = a`)
write("a.js", 'var b = require("./b")\nvar c = require("./c")\nvar d = require("./d")')
write("b.js", "var a = 1\nmodule.exports = a")
write("c.js", "var a = 2\nmodule.exports = a")
write("d.js", "var a = 3\nmodule.exports = a")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("c.js")
@ -274,38 +276,38 @@ o.spec("bundler", function() {
remove("out.js")
})
o("works if included multiple times", function() {
write("a.js", `module.exports = 123`)
write("b.js", `var a = require("./a").toString()\nmodule.exports = a`)
write("c.js", `var a = require("./a").toString()\nvar b = require("./b")`)
write("a.js", "module.exports = 123")
write("b.js", 'var a = require("./a").toString()\nmodule.exports = a')
write("c.js", 'var a = require("./a").toString()\nvar b = require("./b")')
bundle(ns + "c.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("c.js")
})
o("works if included multiple times reverse", function() {
write("a.js", `module.exports = 123`)
write("b.js", `var a = require("./a").toString()\nmodule.exports = a`)
write("c.js", `var b = require("./b")\nvar a = require("./a").toString()`)
write("a.js", "module.exports = 123")
write("b.js", 'var a = require("./a").toString()\nmodule.exports = a')
write("c.js", 'var b = require("./b")\nvar a = require("./a").toString()')
bundle(ns + "c.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("c.js")
})
o("reuses binding if possible", function() {
write("a.js", `var b = require("./b")\nvar c = require("./c")`)
write("b.js", `var d = require("./d")\nmodule.exports = function() {return d + 1}`)
write("c.js", `var d = require("./d")\nmodule.exports = function() {return d + 2}`)
write("d.js", `module.exports = 1`)
write("a.js", 'var b = require("./b")\nvar c = require("./c")')
write("b.js", 'var d = require("./d")\nmodule.exports = function() {return d + 1}')
write("c.js", 'var d = require("./d")\nmodule.exports = function() {return d + 2}')
write("d.js", "module.exports = 1")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("c.js")
@ -313,71 +315,71 @@ o.spec("bundler", function() {
remove("out.js")
})
o("disambiguates conflicts if imported collides with itself", function() {
write("a.js", `var b = require("./b")`)
write("b.js", `var b = 1\nmodule.exports = function() {return b}`)
write("a.js", 'var b = require("./b")')
write("b.js", "var b = 1\nmodule.exports = function() {return b}")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("disambiguates conflicts if imported collides with something else", function() {
write("a.js", `var a = 1\nvar b = require("./b")`)
write("b.js", `var a = 2\nmodule.exports = function() {return a}`)
write("a.js", 'var a = 1\nvar b = require("./b")')
write("b.js", "var a = 2\nmodule.exports = function() {return a}")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("disambiguates conflicts if imported collides with function declaration", function() {
write("a.js", `function a() {}\nvar b = require("./b")`)
write("b.js", `var a = 2\nmodule.exports = function() {return a}`)
write("a.js", 'function a() {}\nvar b = require("./b")')
write("b.js", "var a = 2\nmodule.exports = function() {return a}")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("disambiguates conflicts if imported collides with another module's private", function() {
write("a.js", `var b = require("./b")\nvar c = require("./c")`)
write("b.js", `var a = 1\nmodule.exports = function() {return a}`)
write("c.js", `var a = 2\nmodule.exports = function() {return a}`)
write("a.js", 'var b = require("./b")\nvar c = require("./c")')
write("b.js", "var a = 1\nmodule.exports = function() {return a}")
write("c.js", "var a = 2\nmodule.exports = function() {return a}")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("c.js")
remove("out.js")
})
o("does not mess up strings", function() {
write("a.js", `var b = require("./b")`)
write("b.js", `var b = "b b b \\\" b"\nmodule.exports = function() {return b}`)
write("a.js", 'var b = require("./b")')
write("b.js", 'var b = "b b b \\" b"\nmodule.exports = function() {return b}')
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.js")
})
o("does not mess up properties", function() {
write("a.js", `var b = require("./b")`)
write("b.js", `var b = {b: 1}\nmodule.exports = function() {return b.b}`)
write("a.js", 'var b = require("./b")')
write("b.js", "var b = {b: 1}\nmodule.exports = function() {return b.b}")
bundle(ns + "a.js", ns + "out.js")
o(read("out.js")).equals(`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("b.js")
remove("out.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
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
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.
@ -127,6 +228,10 @@ m(ComponentWithInitialState)
// <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
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.
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.

View file

@ -5,6 +5,8 @@
- [Static members](#static-members)
- [Stream.combine](#streamcombine)
- [Stream.merge](#streammerge)
- [Stream.scan](#streamscan)
- [Stream.scanMerge](#streamscanmerge)
- [Stream.HALT](#streamhalt)
- [Stream["fantasy-land/of"]](#streamfantasy-landof)
- [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
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.
`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).
`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.
`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?` | 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.
`_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.
---
@ -87,7 +88,7 @@ The `tag` property of a vnode determines its type. There are five vnode types:
Vnode type | Example | Description
------------ | ------------------------------ | ---
Element | `{tag: "div"}` | Represents a DOM element.
Fragment | `{tag: "[", children: []}` | Represents a list of DOM elements whose parent DOM element may also contain other elements that are not in the fragment. When using the [`m()`](hyperscript.md) helper function, fragment vnodes can only be created by nesting arrays into the `children` parameter of `m()`. `m("[")` does not create a valid vnode.
Fragment | `{tag: "[", children: []}` | Represents a list of DOM elements whose parent DOM element may also contain other elements that are not in the fragment. When using the [`m()`](hyperscript.md) helper function, fragment vnodes can only be created by nesting arrays into the `children` parameter of `m()`. `m("[")` does not create a valid vnode.
Text | `{tag: "#", children: ""}` | Represents a DOM text node.
Trusted HTML | `{tag: "<", children: "<br>"}` | Represents a list of DOM elements from an HTML string.
Component | `{tag: ExampleComponent}` | If `tag` is a Javascript object with a `view` method, the vnode represents the DOM generated by rendering the component.

View file

@ -38,7 +38,7 @@ var state = {
update: function(title) {
if (state.editing != null) {
state.editing.title = title.trim()
if (state.editing.title === "") destroy(state.editing)
if (state.editing.title === "") state.destroy(state.editing)
state.editing = null
}
},
@ -104,7 +104,7 @@ var Todos = {
m("label", {ondblclick: function() {state.dispatch("edit", [todo])}}, todo.title),
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")
hyperscript.trust = require("./render/trust")

View file

@ -1,7 +1,7 @@
new function() {
;(function() {
"use strict"
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) {
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 selectorCache = {}
var hasOwn = {}.hasOwnProperty
function compileSelector(selector) {
var match, tag = "div", classes = [], attrs = {}
while (match = selectorParser.exec(selector)) {
var type = match[1], value = match[2]
if (type === "" && value !== "") tag = value
else if (type === "#") attrs.id = value
else if (type === ".") classes.push(value)
else if (match[3][0] === "[") {
var attrValue = match[6]
if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
if (match[4] === "class") classes.push(attrValue)
else attrs[match[4]] = attrValue || true
}
}
if (classes.length > 0) attrs.className = classes.join(" ")
return selectorCache[selector] = {tag: tag, attrs: attrs}
}
function execSelector(state, attrs, children) {
var hasAttrs = false, childList, text
var className = attrs.className || attrs.class
for (var key in state.attrs) {
if (hasOwn.call(state.attrs, key)) {
attrs[key] = state.attrs[key]
}
}
if (className != null) {
if (attrs.class != null) {
attrs.class = undefined
attrs.className = className
}
if (state.attrs.className != null) {
attrs.className = state.attrs.className + " " + className
}
}
for (var key in attrs) {
if (hasOwn.call(attrs, key) && key !== "key") {
hasAttrs = true
break
}
}
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)
}
function hyperscript(selector) {
if (selector == null || typeof selector !== "string" && typeof selector.view !== "function") {
// Because sloppy mode sucks
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.");
}
if (typeof selector === "string" && selectorCache[selector] === undefined) {
var match, tag, classes = [], attributes = {}
while (match = selectorParser.exec(selector)) {
var type = match[1], value = match[2]
if (type === "" && value !== "") tag = value
else if (type === "#") attributes.id = value
else if (type === ".") classes.push(value)
else if (match[3][0] === "[") {
var attrValue = match[6]
if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
if (match[4] === "class") classes.push(attrValue)
else attributes[match[4]] = attrValue || true
}
}
if (classes.length > 0) attributes.className = classes.join(" ")
selectorCache[selector] = function(attrs, children) {
var hasAttrs = false, childList, text
var className = attrs.className || attrs.class
for (var key in attributes) attrs[key] = attributes[key]
if (className !== undefined) {
if (attrs.class !== undefined) {
attrs.class = undefined
attrs.className = className
}
if (attributes.className !== undefined) attrs.className = attributes.className + " " + className
}
for (var key in attrs) {
if (key !== "key") {
hasAttrs = true
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 (typeof selector === "string") {
var cached = selectorCache[selector] || compileSelector(selector)
}
var attrs, children, childrenIndex
if (arguments[1] == null || typeof arguments[1] === "object" && arguments[1].tag === undefined && !Array.isArray(arguments[1])) {
attrs = arguments[1]
childrenIndex = 2
if (!attrs) {
attrs = {}
} else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) {
attrs = {}
start = 1
}
else childrenIndex = 1
if (arguments.length === childrenIndex + 1) {
children = Array.isArray(arguments[childrenIndex]) ? arguments[childrenIndex] : [arguments[childrenIndex]]
}
else {
if (arguments.length === start + 1) {
children = arguments[start]
if (!Array.isArray(children)) children = [children]
} else {
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) {
if (html == null) html = ""
@ -203,6 +223,7 @@ var buildQueryString = function(object) {
else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : ""))
}
}
var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i")
var _8 = function($window, Promise) {
var callbackCount = 0
var oncompletion
@ -238,14 +259,20 @@ var _8 = function($window, Promise) {
var promise0 = new Promise(function(resolve, reject) {
if (args.method == null) args.method = "GET"
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.deserialize !== "function") args.deserialize = deserialize
if (typeof args.extract !== "function") args.extract = extract
args.url = interpolate(args.url, args.data)
if (useBody) args.data = args.serialize(args.data)
else args.url = assemble(args.url, args.data)
var xhr = new $window.XMLHttpRequest()
var xhr = new $window.XMLHttpRequest(),
aborted = false,
_abort = xhr.abort
xhr.abort = function abort() {
aborted = true
_abort.call(xhr)
}
xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined)
if (args.serialize === JSON.stringify && useBody) {
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
xhr.onreadystatechange = function() {
// Don't throw errors on xhr.abort(). XMLHttpRequests ends up in a state of
// xhr.status == 0 and xhr.readyState == 4 if aborted after open, but before completion.
if (xhr.status && xhr.readyState === 4) {
// Don't throw errors on xhr.abort().
if(aborted) return
if (xhr.readyState === 4) {
try {
var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args))
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) {
resolve(cast(args.type, response))
}
else {
@ -355,30 +382,33 @@ var coreRenderer = function($window) {
for (var i = start; i < end; i++) {
var vnode = vnodes[i]
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
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
if (typeof tag === "string") {
vnode.state = {}
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
switch (tag) {
case "#": return createText(vnode)
case "<": return createHTML(vnode)
case "[": return createFragment(vnode, hooks, ns)
default: return createElement(vnode, hooks, ns)
case "#": return createText(parent, vnode, nextSibling)
case "<": return createHTML(parent, vnode, nextSibling)
case "[": return createFragment(parent, vnode, hooks, ns, nextSibling)
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) {
return vnode.dom = $doc.createTextNode(vnode.children)
function createText(parent, vnode, nextSibling) {
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 parent = {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 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(parent1)
temp.innerHTML = vnode.children
vnode.dom = temp.firstChild
vnode.domSize = temp.childNodes.length
@ -387,9 +417,10 @@ var coreRenderer = function($window) {
while (child = temp.firstChild) {
fragment.appendChild(child)
}
insertNode(parent, fragment, nextSibling)
return fragment
}
function createFragment(vnode, hooks, ns) {
function createFragment(parent, vnode, hooks, ns, nextSibling) {
var fragment = $doc.createDocumentFragment()
if (vnode.children != null) {
var children = vnode.children
@ -397,9 +428,10 @@ var coreRenderer = function($window) {
}
vnode.dom = fragment.firstChild
vnode.domSize = fragment.childNodes.length
insertNode(parent, fragment, nextSibling)
return fragment
}
function createElement(vnode, hooks, ns) {
function createElement(parent, vnode, hooks, ns, nextSibling) {
var tag = vnode.tag
switch (vnode.tag) {
case "svg": ns = "http://www.w3.org/2000/svg"; break
@ -414,6 +446,7 @@ var coreRenderer = function($window) {
if (attrs2 != null) {
setAttrs(vnode, attrs2, ns)
}
insertNode(parent, element, nextSibling)
if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
setContentEditable(vnode)
}
@ -430,19 +463,34 @@ var coreRenderer = function($window) {
}
return element
}
function createComponent(vnode, hooks, ns) {
vnode.state = Object.create(vnode.tag)
var view = vnode.tag.view
if (view.reentrantLock != null) return $emptyFragment
view.reentrantLock = true
initLifecycle(vnode.tag, vnode, hooks)
vnode.instance = Vnode.normalize(view.call(vnode.state, vnode))
view.reentrantLock = null
function initComponent(vnode, hooks) {
var sentinel
if (typeof vnode.tag.view === "function") {
vnode.state = Object.create(vnode.tag)
sentinel = vnode.state.view
if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
sentinel.$$reentrantLock$$ = true
} else {
vnode.state = void 0
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 === vnode) throw Error("A view cannot return the vnode it received as arguments")
var element = createNode(vnode.instance, hooks, ns)
var element = createNode(parent, vnode.instance, hooks, ns, nextSibling)
vnode.dom = vnode.instance.dom
vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
insertNode(parent, element, nextSibling)
return element
}
else {
@ -451,7 +499,7 @@ var coreRenderer = function($window) {
}
}
//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
else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
@ -467,15 +515,18 @@ var coreRenderer = function($window) {
if (isUnkeyed) {
for (var i = 0; i < old.length; i++) {
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 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
}
}
var recycling = isRecyclable(old, vnodes)
if (recycling) old = old.concat(old.pool)
recycling = recycling || isRecyclable(old, vnodes)
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
while (oldEnd >= oldStart && end >= start) {
var o = old[oldStart], v = vnodes[start]
@ -483,8 +534,9 @@ var coreRenderer = function($window) {
else if (o == null) oldStart++
else if (v == null) start++
else if (o.key === v.key) {
var shouldRecycle = (pool != null && oldStart >= old.length - pool.length) || ((pool == null) && recycling)
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)
}
else {
@ -493,7 +545,8 @@ var coreRenderer = function($window) {
else if (o == null) oldEnd--
else if (v == null) start++
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))
oldEnd--, start++
}
@ -506,7 +559,8 @@ var coreRenderer = function($window) {
else if (o == null) oldEnd--
else if (v == null) end--
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 (o.dom != null) nextSibling = o.dom
oldEnd--, end--
@ -517,14 +571,14 @@ var coreRenderer = function($window) {
var oldIndex = map[v.key]
if (oldIndex != null) {
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)
insertNode(parent, toFragment(movable), nextSibling)
old[oldIndex].skip = true
if (movable.dom != null) nextSibling = movable.dom
}
else {
var dom = createNode(v, hooks, undefined)
insertNode(parent, dom, nextSibling)
var dom = createNode(parent, v, hooks, undefined, nextSibling)
nextSibling = dom
}
}
@ -540,24 +594,29 @@ var coreRenderer = function($window) {
var oldTag = old.tag, tag = vnode.tag
if (oldTag === tag) {
vnode.state = old.state
vnode._state = old._state
vnode.events = old.events
if (shouldUpdate(vnode, old)) return
if (vnode.attrs != null) {
updateLifecycle(vnode.attrs, vnode, hooks, recycling)
}
if (!recycling && shouldNotUpdate(vnode, old)) return
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) {
case "#": updateText(old, vnode); break
case "<": updateHTML(parent, old, vnode, nextSibling); break
case "[": updateFragment(parent, old, vnode, hooks, nextSibling, ns); break
default: updateElement(old, vnode, hooks, ns)
case "[": updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns); break
default: updateElement(old, vnode, recycling, hooks, ns)
}
}
else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns)
}
else {
removeNode(old, null)
insertNode(parent, createNode(vnode, hooks, ns), nextSibling)
createNode(parent, vnode, hooks, ns, nextSibling)
}
}
function updateText(old, vnode) {
@ -569,12 +628,12 @@ var coreRenderer = function($window) {
function updateHTML(parent, old, vnode, nextSibling) {
if (old.children !== vnode.children) {
toFragment(old)
insertNode(parent, createHTML(vnode), nextSibling)
createHTML(parent, vnode, nextSibling)
}
else vnode.dom = old.dom, vnode.domSize = old.domSize
}
function updateFragment(parent, old, vnode, hooks, nextSibling, ns) {
updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns)
function updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns) {
updateNodes(parent, old.children, vnode.children, recycling, hooks, nextSibling, ns)
var domSize = 0, children = vnode.children
vnode.dom = null
if (children != null) {
@ -588,7 +647,7 @@ var coreRenderer = function($window) {
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
switch (vnode.tag) {
case "svg": ns = "http://www.w3.org/2000/svg"; break
@ -611,14 +670,20 @@ var coreRenderer = function($window) {
else {
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)]
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) {
vnode.instance = Vnode.normalize(vnode.tag.view.call(vnode.state, vnode))
updateLifecycle(vnode.tag, vnode, hooks, recycling)
if (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 (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)
vnode.dom = vnode.instance.dom
vnode.domSize = vnode.instance.domSize
@ -698,15 +763,15 @@ var coreRenderer = function($window) {
}
function removeNode(vnode, context) {
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)
if (result != null && typeof result.then === "function") {
expected++
result.then(continuation, continuation)
}
}
if (typeof vnode.tag !== "string" && vnode.tag.onbeforeremove) {
var result = vnode.tag.onbeforeremove.call(vnode.state, vnode)
if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeremove === "function") {
var result = vnode._state.onbeforeremove.call(vnode.state, vnode)
if (result != null && typeof result.then === "function") {
expected++
result.then(continuation, continuation)
@ -738,8 +803,8 @@ var coreRenderer = function($window) {
if (parent != null) parent.removeChild(node)
}
function onremove(vnode) {
if (vnode.attrs && vnode.attrs.onremove) vnode.attrs.onremove.call(vnode.state, vnode)
if (typeof vnode.tag !== "string" && vnode.tag.onremove) vnode.tag.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" && typeof vnode._state.onremove === "function") vnode._state.onremove.call(vnode.state, vnode)
if (vnode.instance != null) onremove(vnode.instance)
else {
var children = vnode.children
@ -768,14 +833,14 @@ var coreRenderer = function($window) {
else if (key2 === "style") updateStyle(element, old, value)
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
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
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
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 (vnode.tag === "input" && key2 === "type") {
element.setAttribute(key2, value);
element.setAttribute(key2, value)
return
}
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.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode))
}
function updateLifecycle(source, vnode, hooks, recycling) {
if (recycling) initLifecycle(source, vnode, hooks)
else if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
function updateLifecycle(source, vnode, hooks) {
if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
}
function shouldUpdate(vnode, old) {
function shouldNotUpdate(vnode, old) {
var forceVnodeUpdate, forceComponentUpdate
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) {
vnode.dom = old.dom
vnode.domSize = old.domSize
@ -891,7 +955,7 @@ var coreRenderer = function($window) {
// First time0 rendering into a node clears it out
if (dom.vnodes == null) dom.textContent = ""
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
for (var i = 0; i < hooks.length; i++) hooks[i]()
if ($doc.activeElement !== active) active.focus()
@ -923,7 +987,6 @@ var _11 = function($window) {
renderService.setEventCallback(function(e) {
if (e.redraw !== false) redraw()
})
var callbacks = []
function subscribe(key1, callback) {
unsubscribe(key1)
@ -933,11 +996,11 @@ var _11 = function($window) {
var index = callbacks.indexOf(key1)
if (index > -1) callbacks.splice(index, 2)
}
function redraw() {
for (var i = 1; i < callbacks.length; i += 2) {
callbacks[i]()
}
}
function redraw() {
for (var i = 1; i < callbacks.length; i += 2) {
callbacks[i]()
}
}
return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
}
var redrawService = _11(window)
@ -950,7 +1013,7 @@ var _16 = function(redrawService0) {
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() {
redrawService0.render(root, Vnode(component))
@ -1061,7 +1124,6 @@ var coreRouter = function($window) {
var path = router.getPath()
var params = {}
var pathname = parsePath(path, params, params)
var state = $window.history.state
if (state != null) {
for (var k in state) params[k] = state[k]
@ -1082,12 +1144,10 @@ var coreRouter = function($window) {
}
reject(path, params)
}
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
resolveRoute()
}
return router
}
var _20 = function($window, redrawService0) {
@ -1099,17 +1159,19 @@ var _20 = function($window, redrawService0) {
var run1 = function() {
if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3)))
}
var bail = function() {
routeService.setPath(defaultRoute, null, {replace: true})
var bail = function(path) {
if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true})
else throw new Error("Could not resolve default route " + defaultRoute)
}
routeService.defineRoutes(routes, function(payload, params, path) {
var update = lastUpdate = function(routeResolver, comp) {
if (update !== lastUpdate) return
component = comp != null && typeof comp.view === "function" ? 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)
run1()
}
if (payload.view) update({}, payload)
if (payload.view || typeof payload === "function") update({}, payload)
else {
if (payload.onmatch) {
Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
@ -1158,8 +1220,8 @@ m.request = requestService.request
m.jsonp = requestService.jsonp
m.parseQueryString = parseQueryString
m.buildQueryString = buildQueryString
m.version = "1.0.0"
m.version = "1.0.1"
m.vnode = Vnode
if (typeof module !== "undefined") module["exports"] = 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,
"\\")),"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]&&
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()):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,
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");
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"!==
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=
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,
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++;
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 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(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===
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]=
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||
"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,
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=
[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");
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,
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&&
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,
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=
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,
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";
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()&&
(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=
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=
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!=
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"!==
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+
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"===
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"!==
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"===
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.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,
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]++);
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"===
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+="?"+
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)||[],
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,
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||
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;B.version="1.0.0";B.vnode=x;"undefined"!==typeof module?module.exports=B:window.m=B};
(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?
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,
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()):
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,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");
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 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;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,
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=
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 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
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",
"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,
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==
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,
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!=
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,
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;
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!==
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||
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++;
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],
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,
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=
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)?
"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");
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}
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===
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++,
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)}}
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=
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,
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,
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=
[],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,
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]),
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==
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("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(),
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},
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({},
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")
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
```
#### 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
"use strict"
var fs = require("fs")
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$/)) {
require(path.normalize(process.cwd()) + "/" + pathname)
require(path.normalize(process.cwd()) + "/" + pathname) // eslint-disable-line global-require
}
})
.then(o.run)

View file

@ -1,11 +1,19 @@
/* eslint-disable no-bitwise, no-process-exit */
"use strict"
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) {
if (predicate === undefined) return new Assert(subject)
ctx[unique(subject)] = predicate
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
} else {
throw new Error("Test definition shouldn't be nested. To group tests use `o.spec()`")
}
}
o.before = hook("__before")
o.after = hook("__after")
@ -18,7 +26,10 @@ module.exports = new function init() {
predicate()
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) {
var spy = function() {
spy.this = this
@ -37,6 +48,7 @@ module.exports = new function init() {
return spy
}
o.run = function() {
results = []
start = new Date
test(spec, [], [], report)
@ -110,8 +122,8 @@ module.exports = new function init() {
}
function unique(subject) {
if (hasOwn.call(ctx, subject)) {
console.warn("A test or a spec named `" + subject + "` was already defined")
while (hasOwn.call(ctx, subject)) subject += '*'
console.warn("A test or a spec named `" + subject + "` was already defined")
while (hasOwn.call(ctx, subject)) subject += "*"
}
return subject
}

View file

@ -12,7 +12,7 @@ new function(o) {
})
o.only(".only()", function() {
o(2).equals(2)
})
}, true)
})
o.run()
@ -20,7 +20,7 @@ new function(o) {
o.spec("ospec", function() {
o.spec("sync", function() {
var a = 0, b = 0
var a = 0, b = 0, illegalAssertionThrows = false
o.before(function() {a = 1})
o.after(function() {a = 0})
@ -28,7 +28,15 @@ o.spec("ospec", function() {
o.beforeEach(function() {b = 1})
o.afterEach(function() {b = 0})
try {o("illegal assertion")} catch (e) {illegalAssertionThrows = true}
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()
spy(a)
@ -43,7 +51,7 @@ o.spec("ospec", function() {
o(undef1).notDeepEquals(undef2)
o(undef1).notDeepEquals({})
o({}).notDeepEquals(undef1)
var sparse1 = [void 1, void 2, void 3]
delete sparse1[0]
var sparse2 = [void 1, void 2, void 3]
@ -55,7 +63,7 @@ o.spec("ospec", function() {
monkeypatch1.field = 3
var monkeypatch2 = [1, 2]
monkeypatch2.field = 4
o(monkeypatch1).notDeepEquals([1, 2])
o(monkeypatch1).notDeepEquals(monkeypatch2)

View file

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

View file

@ -57,7 +57,7 @@ o.spec("promise", function() {
o.spec("resolve", function() {
o("resolves once", function(done) {
var callCount = 0
var promise = new Promise(function(resolve, reject) {
var promise = new Promise(function(resolve) {
resolve(1)
resolve(2)
callAsync(function() {resolve(3)})
@ -89,7 +89,7 @@ o.spec("promise", function() {
var promise = Promise.resolve()
state = 1
promise.then(function(value) {
promise.then(function() {
o(state).equals(2)
done()
})
@ -104,7 +104,7 @@ o.spec("promise", function() {
})
})
o("resolves asynchronously via executor", function(done) {
var promise = new Promise(function(resolve, reject) {
var promise = new Promise(function(resolve) {
callAsync(function() {resolve(1)})
})
@ -185,7 +185,7 @@ o.spec("promise", function() {
var promise = Promise.reject()
state = 1
promise.then(null, function(value) {
promise.then(null, function() {
o(state).equals(2)
done()
})
@ -232,7 +232,7 @@ o.spec("promise", function() {
})
})
o("rejects via executor on error", function(done) {
var promise = new Promise(function(resolve, reject) {
var promise = new Promise(function() {
throw 1
})
@ -281,7 +281,7 @@ o.spec("promise", function() {
}).then(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)
resolve(p)
})
@ -310,7 +310,7 @@ o.spec("promise", function() {
})
})
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))
})
@ -330,7 +330,7 @@ o.spec("promise", function() {
})
})
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)
})
var promise = Promise.resolve(pending)
@ -341,10 +341,10 @@ o.spec("promise", function() {
})
})
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)
})
var promise = new Promise(function(resolve, reject) {
var promise = new Promise(function(resolve) {
resolve(pending)
})
@ -354,7 +354,7 @@ o.spec("promise", function() {
})
})
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)
})
var promise = Promise.resolve()
@ -381,7 +381,7 @@ o.spec("promise", function() {
var pending = new Promise(function(resolve, reject) {
setTimeout(function() {reject(1)}, 10)
})
var promise = new Promise(function(resolve, reject) {
var promise = new Promise(function(resolve) {
resolve(pending)
})
@ -521,7 +521,7 @@ o.spec("promise", function() {
o.spec("race", function() {
o("resolves to first resolved", function(done) {
var a = Promise.resolve(1)
var b = new Promise(function(resolve, reject) {
var b = new Promise(function(resolve) {
callAsync(function() {resolve(2)})
})
Promise.race([a, b]).then(function(value) {
@ -542,7 +542,7 @@ o.spec("promise", function() {
})
o.spec("all", function() {
o("resolves to array", function(done) {
var a = new Promise(function(resolve, reject) {
var a = new Promise(function(resolve) {
callAsync(function() {resolve(1)})
})
var b = Promise.resolve(2)
@ -558,7 +558,7 @@ o.spec("promise", function() {
})
})
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)})
})
var b = Promise.resolve(2)
@ -584,18 +584,18 @@ o.spec("promise", function() {
var readCount = 0
var promise = Promise.resolve(1).then(function() {
return Object.create(null, {
then: {
get: function () {
++readCount
return function(onFulfilled) {
onFulfilled()
}
}
}
})
then: {
get: function () {
++readCount
return function(onFulfilled) {
onFulfilled()
}
}
}
})
})
promise.then(function(value) {
promise.then(function() {
o(readCount).equals(1)
done()
})

View file

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

View file

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

View file

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

View file

@ -14,30 +14,33 @@ module.exports = function($window) {
for (var i = start; i < end; i++) {
var vnode = vnodes[i]
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
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
if (typeof tag === "string") {
vnode.state = {}
if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
switch (tag) {
case "#": return createText(vnode)
case "<": return createHTML(vnode)
case "[": return createFragment(vnode, hooks, ns)
default: return createElement(vnode, hooks, ns)
case "#": return createText(parent, vnode, nextSibling)
case "<": return createHTML(parent, vnode, nextSibling)
case "[": return createFragment(parent, vnode, hooks, ns, nextSibling)
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) {
return vnode.dom = $doc.createTextNode(vnode.children)
function createText(parent, vnode, nextSibling) {
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 parent = {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 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(parent1)
temp.innerHTML = vnode.children
vnode.dom = temp.firstChild
@ -47,9 +50,10 @@ module.exports = function($window) {
while (child = temp.firstChild) {
fragment.appendChild(child)
}
insertNode(parent, fragment, nextSibling)
return fragment
}
function createFragment(vnode, hooks, ns) {
function createFragment(parent, vnode, hooks, ns, nextSibling) {
var fragment = $doc.createDocumentFragment()
if (vnode.children != null) {
var children = vnode.children
@ -57,9 +61,10 @@ module.exports = function($window) {
}
vnode.dom = fragment.firstChild
vnode.domSize = fragment.childNodes.length
insertNode(parent, fragment, nextSibling)
return fragment
}
function createElement(vnode, hooks, ns) {
function createElement(parent, vnode, hooks, ns, nextSibling) {
var tag = vnode.tag
switch (vnode.tag) {
case "svg": ns = "http://www.w3.org/2000/svg"; break
@ -78,6 +83,8 @@ module.exports = function($window) {
setAttrs(vnode, attrs, ns)
}
insertNode(parent, element, nextSibling)
if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
setContentEditable(vnode)
}
@ -94,19 +101,34 @@ module.exports = function($window) {
}
return element
}
function createComponent(vnode, hooks, ns) {
vnode.state = Object.create(vnode.tag)
var view = vnode.tag.view
if (view.reentrantLock != null) return $emptyFragment
view.reentrantLock = true
initLifecycle(vnode.tag, vnode, hooks)
vnode.instance = Vnode.normalize(view.call(vnode.state, vnode))
view.reentrantLock = null
function initComponent(vnode, hooks) {
var sentinel
if (typeof vnode.tag.view === "function") {
vnode.state = Object.create(vnode.tag)
sentinel = vnode.state.view
if (sentinel.$$reentrantLock$$ != null) return $emptyFragment
sentinel.$$reentrantLock$$ = true
} else {
vnode.state = void 0
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 === vnode) throw Error("A view cannot return the vnode it received as arguments")
var element = createNode(vnode.instance, hooks, ns)
var element = createNode(parent, vnode.instance, hooks, ns, nextSibling)
vnode.dom = vnode.instance.dom
vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
insertNode(parent, element, nextSibling)
return element
}
else {
@ -116,7 +138,7 @@ module.exports = function($window) {
}
//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
else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
@ -132,15 +154,18 @@ module.exports = function($window) {
if (isUnkeyed) {
for (var i = 0; i < old.length; i++) {
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 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
}
}
var recycling = isRecyclable(old, vnodes)
if (recycling) old = old.concat(old.pool)
recycling = recycling || isRecyclable(old, vnodes)
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
while (oldEnd >= oldStart && end >= start) {
@ -149,8 +174,9 @@ module.exports = function($window) {
else if (o == null) oldStart++
else if (v == null) start++
else if (o.key === v.key) {
var shouldRecycle = (pool != null && oldStart >= old.length - pool.length) || ((pool == null) && recycling)
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)
}
else {
@ -159,7 +185,8 @@ module.exports = function($window) {
else if (o == null) oldEnd--
else if (v == null) start++
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))
oldEnd--, start++
}
@ -172,7 +199,8 @@ module.exports = function($window) {
else if (o == null) oldEnd--
else if (v == null) end--
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 (o.dom != null) nextSibling = o.dom
oldEnd--, end--
@ -183,14 +211,14 @@ module.exports = function($window) {
var oldIndex = map[v.key]
if (oldIndex != null) {
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)
insertNode(parent, toFragment(movable), nextSibling)
old[oldIndex].skip = true
if (movable.dom != null) nextSibling = movable.dom
}
else {
var dom = createNode(v, hooks, undefined)
insertNode(parent, dom, nextSibling)
var dom = createNode(parent, v, hooks, undefined, nextSibling)
nextSibling = dom
}
}
@ -206,24 +234,29 @@ module.exports = function($window) {
var oldTag = old.tag, tag = vnode.tag
if (oldTag === tag) {
vnode.state = old.state
vnode._state = old._state
vnode.events = old.events
if (shouldUpdate(vnode, old)) return
if (vnode.attrs != null) {
updateLifecycle(vnode.attrs, vnode, hooks, recycling)
}
if (!recycling && shouldNotUpdate(vnode, old)) return
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) {
case "#": updateText(old, vnode); break
case "<": updateHTML(parent, old, vnode, nextSibling); break
case "[": updateFragment(parent, old, vnode, hooks, nextSibling, ns); break
default: updateElement(old, vnode, hooks, ns)
case "[": updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns); break
default: updateElement(old, vnode, recycling, hooks, ns)
}
}
else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns)
}
else {
removeNode(old, null)
insertNode(parent, createNode(vnode, hooks, ns), nextSibling)
createNode(parent, vnode, hooks, ns, nextSibling)
}
}
function updateText(old, vnode) {
@ -235,12 +268,12 @@ module.exports = function($window) {
function updateHTML(parent, old, vnode, nextSibling) {
if (old.children !== vnode.children) {
toFragment(old)
insertNode(parent, createHTML(vnode), nextSibling)
createHTML(parent, vnode, nextSibling)
}
else vnode.dom = old.dom, vnode.domSize = old.domSize
}
function updateFragment(parent, old, vnode, hooks, nextSibling, ns) {
updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns)
function updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns) {
updateNodes(parent, old.children, vnode.children, recycling, hooks, nextSibling, ns)
var domSize = 0, children = vnode.children
vnode.dom = null
if (children != null) {
@ -254,7 +287,7 @@ module.exports = function($window) {
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
switch (vnode.tag) {
case "svg": ns = "http://www.w3.org/2000/svg"; break
@ -277,14 +310,20 @@ module.exports = function($window) {
else {
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)]
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) {
vnode.instance = Vnode.normalize(vnode.tag.view.call(vnode.state, vnode))
updateLifecycle(vnode.tag, vnode, hooks, recycling)
if (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 (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)
vnode.dom = vnode.instance.dom
vnode.domSize = vnode.instance.domSize
@ -367,15 +406,15 @@ module.exports = function($window) {
}
function removeNode(vnode, context) {
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)
if (result != null && typeof result.then === "function") {
expected++
result.then(continuation, continuation)
}
}
if (typeof vnode.tag !== "string" && vnode.tag.onbeforeremove) {
var result = vnode.tag.onbeforeremove.call(vnode.state, vnode)
if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeremove === "function") {
var result = vnode._state.onbeforeremove.call(vnode.state, vnode)
if (result != null && typeof result.then === "function") {
expected++
result.then(continuation, continuation)
@ -407,8 +446,8 @@ module.exports = function($window) {
if (parent != null) parent.removeChild(node)
}
function onremove(vnode) {
if (vnode.attrs && vnode.attrs.onremove) vnode.attrs.onremove.call(vnode.state, vnode)
if (typeof vnode.tag !== "string" && vnode.tag.onremove) vnode.tag.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" && typeof vnode._state.onremove === "function") vnode._state.onremove.call(vnode.state, vnode)
if (vnode.instance != null) onremove(vnode.instance)
else {
var children = vnode.children
@ -438,14 +477,14 @@ module.exports = function($window) {
else if (key === "style") updateStyle(element, old, value)
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
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
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
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 (vnode.tag === "input" && key === "type") {
element.setAttribute(key, value);
element.setAttribute(key, value)
return
}
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.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode))
}
function updateLifecycle(source, vnode, hooks, recycling) {
if (recycling) initLifecycle(source, vnode, hooks)
else if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
function updateLifecycle(source, vnode, hooks) {
if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
}
function shouldUpdate(vnode, old) {
function shouldNotUpdate(vnode, old) {
var forceVnodeUpdate, forceComponentUpdate
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) {
vnode.dom = old.dom
vnode.domSize = old.domSize
@ -567,7 +605,7 @@ module.exports = function($window) {
if (dom.vnodes == null) dom.textContent = ""
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
for (var i = 0; i < hooks.length; i++) hooks[i]()
if ($doc.activeElement !== active) active.focus()

View file

@ -8,6 +8,7 @@
<script src="../../ospec/ospec.js"></script>
<script src="../../test-utils/callAsync.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/trust.js"></script>

View file

@ -12,19 +12,19 @@ o.spec("attributes", function() {
render = vdom($window).render
})
o.spec("customElements", function(){
o("when vnode is customElement, custom setAttribute called", function(){
var normal = [
{ tag: "input", attrs: { value: 'hello' } },
{ tag: "input", attrs: { value: 'hello' } },
{ tag: "input", attrs: { value: 'hello' } }
var normal = [
{tag: "input", attrs: {value: "hello"}},
{tag: "input", attrs: {value: "hello"}},
{tag: "input", attrs: {value: "hello"}}
]
var custom = [
{ tag: "custom-element", attrs: { custom: 'x' } },
{ tag: "input", attrs: { is: 'something-special', custom: 'x' } },
{ tag: "custom-element", attrs: { is: 'something-special', custom: 'x' } }
{tag: "custom-element", attrs: {custom: "x"}},
{tag: "input", attrs: {is: "something-special", custom: "x"}},
{tag: "custom-element", attrs: {is: "something-special", custom: "x"}}
]
var view = normal.concat(custom)
@ -43,8 +43,8 @@ o.spec("attributes", function() {
}
render(root, view)
o(spy.callCount).equals( custom.length )
o(spy.callCount).equals(custom.length)
})
})
@ -133,7 +133,7 @@ o.spec("attributes", function() {
})
o.spec("contenteditable throws on untrusted children", 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
try {
@ -141,7 +141,7 @@ o.spec("attributes", function() {
succeeded = true
}
catch(e){}
catch(e){/* ignore */}
o(succeeded).equals(false)
})
@ -154,7 +154,7 @@ o.spec("attributes", function() {
succeeded = true
}
catch(e){}
catch(e){/* ignore */}
o(succeeded).equals(false)
})
@ -167,7 +167,7 @@ o.spec("attributes", function() {
succeeded = true
}
catch(e){}
catch(e){/* ignore */}
o(succeeded).equals(true)
})
@ -180,7 +180,7 @@ o.spec("attributes", function() {
succeeded = true
}
catch(e){}
catch(e){/* ignore */}
o(succeeded).equals(true)
})

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,3 +1,5 @@
"use strict"
var o = require("../../ospec/ospec")
var m = require("../../render/hyperscript")
@ -424,7 +426,7 @@ o.spec("hyperscript", function() {
})
})
o.spec("components", function() {
o("works", function() {
o("works with POJOs", function() {
var component = {
view: function() {
return m("div")
@ -432,6 +434,19 @@ o.spec("hyperscript", function() {
}
var vnode = m(component, {id: "a"}, "b")
o(vnode.tag).equals(component)
o(vnode.attrs.id).equals("a")
o(vnode.children.length).equals(1)
o(vnode.children[0].tag).equals("#")
o(vnode.children[0].children).equals("b")
})
o("works with functions", function() {
var component = o.spy()
var vnode = m(component, {id: "a"}, "b")
o(component.callCount).equals(0)
o(vnode.tag).equals(component)
o(vnode.attrs.id).equals("a")
o(vnode.children.length).equals(1)

View file

@ -2,6 +2,7 @@
var o = require("../../ospec/ospec")
var callAsync = require("../../test-utils/callAsync")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
var Promise = require("../../promise/promise")
@ -16,7 +17,6 @@ o.spec("onbeforeremove", function() {
o("does not call onbeforeremove when creating", function() {
var create = o.spy()
var update = o.spy()
var vnode = {tag: "div", attrs: {onbeforeremove: create}}
render(root, [vnode])
@ -141,7 +141,7 @@ o.spec("onbeforeremove", function() {
o(vnode.dom.attributes["onbeforeremove"]).equals(undefined)
})
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 updated = {tag: "div", key: 1, attrs: {onbeforeremove: remove}}
@ -152,7 +152,7 @@ o.spec("onbeforeremove", function() {
o(vnode.dom).notEquals(updated.dom)
})
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 updated = {tag: "div", key: 2, attrs: {onbeforeremove: remove}, text: "2"}
@ -169,39 +169,44 @@ o.spec("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 onbeforeremove = function(){return Promise.resolve()}
var component = {
onbeforeremove: onbeforeremove,
onremove: onremove,
view: function() {},
}
render(root, [{tag: component, attrs: {onbeforeremove: onbeforeremove, onremove: onremove}}])
render(root, [])
callAsync(function() {
o(onremove.callCount).equals(2) // once for `tag`, once for `attrs`
done()
})
})
o("awaits promise resolution before removing the node", function(done) {
var view = o.spy()
var onremove = o.spy()
var onbeforeremove = function(){return new Promise(function(resolve){callAsync(resolve)})}
var component = {
onbeforeremove: onbeforeremove,
onremove: onremove,
view: view,
}
render(root, [{tag: component}])
render(root, [])
components.forEach(function(cmp){
o.spec(cmp.kind, function(){
var createComponent = cmp.create
o("finalizes the remove phase asynchronously when promise is returned synchronously from both attrs- and tag.onbeforeremove", function(done) {
var onremove = o.spy()
var onbeforeremove = function(){return Promise.resolve()}
var component = createComponent({
onbeforeremove: onbeforeremove,
onremove: onremove,
view: function() {},
})
render(root, [{tag: component, attrs: {onbeforeremove: onbeforeremove, onremove: onremove}}])
render(root, [])
callAsync(function() {
o(onremove.callCount).equals(2) // once for `tag`, once for `attrs`
done()
})
})
o("awaits promise resolution before removing the node", function(done) {
var view = o.spy()
var onremove = o.spy()
var onbeforeremove = function(){return new Promise(function(resolve){callAsync(resolve)})}
var component = createComponent({
onbeforeremove: onbeforeremove,
onremove: onremove,
view: view,
})
render(root, [{tag: component}])
render(root, [])
callAsync(function(){
o(onremove.callCount).equals(0)
callAsync(function(){
o(onremove.callCount).equals(0)
callAsync(function() {
o(onremove.callCount).equals(1)
done()
callAsync(function() {
o(onremove.callCount).equals(1)
done()
})
})
})
})
})

View file

@ -1,6 +1,7 @@
"use strict"
var o = require("../../ospec/ospec")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
@ -56,86 +57,6 @@ o.spec("onbeforeupdate", function() {
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() {
var onbeforeupdate = function() {return true}
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("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() {
var count = 0
var vnode = {tag: "div", attrs: {id: "a", onbeforeupdate: onbeforeupdate}}
@ -184,63 +89,13 @@ o.spec("onbeforeupdate", function() {
o(root.firstChild.attributes["id"].nodeValue).equals("b")
})
o("accepts arguments for comparison in component", function() {
var component = {
onbeforeupdate: onbeforeupdate,
view: function(vnode) {
return {tag: "div", attrs: vnode.attrs}
},
}
var count = 0
var vnode = {tag: component, attrs: {id: "a"}}
var updated = {tag: component, attrs: {id: "b"}}
render(root, [vnode])
render(root, [updated])
function onbeforeupdate(vnode, old) {
count++
o(old.attrs.id).equals("a")
o(vnode.attrs.id).equals("b")
return old.attrs.id !== vnode.attrs.id
}
o(count).equals(1)
o(root.firstChild.attributes["id"].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() {
var component = {
onbeforeupdate: onbeforeupdate,
view: function(vnode) {
return {tag: "div", attrs: vnode.attrs}
},
}
var count = 0
var vnode = {tag: "div", attrs: {id: "a"}}
var updated = {tag: "div", attrs: {id: "b"}}
render(root, [vnode])
function onbeforeupdate(vnode, old) {
function onbeforeupdate() {
count++
return true
}
@ -256,7 +111,7 @@ o.spec("onbeforeupdate", function() {
render(root, [vnode])
render(root, [updated])
function onbeforeupdate(vnode, old) {
function onbeforeupdate() {
count++
return true
}
@ -264,26 +119,191 @@ o.spec("onbeforeupdate", function() {
o(count).equals(1)
})
o("is called only once on component update", function() {
var component = {
onbeforeupdate: onbeforeupdate,
view: function(vnode) {
return {tag: "div", attrs: vnode.attrs}
},
}
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}}]
var count = 0
var vnode = {tag: component, attrs: {id: "a"}}
var updated = {tag: component, attrs: {id: "b"}}
render(root, vnodes)
render(root, temp)
render(root, updated)
render(root, [vnode])
render(root, [updated])
o(vnodes[0].dom).equals(updated[0].dom)
o(updated[0].dom.nodeName).equals("DIV")
o(onbeforeupdate.callCount).equals(0)
})
function onbeforeupdate(vnode, old) {
count++
return true
}
components.forEach(function(cmp){
o.spec(cmp.kind, function(){
var createComponent = cmp.create
o(count).equals(1)
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() {
var component = createComponent({
onbeforeupdate: onbeforeupdate,
view: function(vnode) {
return {tag: "div", attrs: vnode.attrs}
},
})
var count = 0
var vnode = {tag: component, attrs: {id: "a"}}
var updated = {tag: component, attrs: {id: "b"}}
render(root, [vnode])
render(root, [updated])
function onbeforeupdate(vnode, old) {
count++
o(old.attrs.id).equals("a")
o(vnode.attrs.id).equals("b")
return old.attrs.id !== vnode.attrs.id
}
o(count).equals(1)
o(root.firstChild.attributes["id"].nodeValue).equals("b")
})
o("is not called on component creation", function() {
createComponent({
onbeforeupdate: onbeforeupdate,
view: function(vnode) {
return {tag: "div", attrs: vnode.attrs}
},
})
var count = 0
var vnode = {tag: "div", attrs: {id: "a"}}
render(root, [vnode])
function onbeforeupdate() {
count++
return true
}
o(count).equals(0)
})
o("is called only once on component update", function() {
var component = createComponent({
onbeforeupdate: onbeforeupdate,
view: function(vnode) {
return {tag: "div", attrs: vnode.attrs}
},
})
var count = 0
var vnode = {tag: component, attrs: {id: "a"}}
var updated = {tag: component, attrs: {id: "b"}}
render(root, [vnode])
render(root, [updated])
function onbeforeupdate() {
count++
return true
}
o(count).equals(1)
})
})
})
})

View file

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

View file

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

View file

@ -1,6 +1,7 @@
"use strict"
var o = require("../../ospec/ospec")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
var m = require("../../render/hyperscript")
@ -80,39 +81,6 @@ o.spec("onremove", function() {
o(remove.this).equals(vnode.state)
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() {
var remove = o.spy()
var vnode = {tag: "div", attrs: {onremove: remove}, children: []}
@ -145,4 +113,43 @@ o.spec("onremove", function() {
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() {
var create = o.spy()
var update = o.spy()
var vnode = {tag: "a", attrs: {onupdate: create}}
var updated = {tag: "a"}

View file

@ -21,7 +21,7 @@ o.spec("render", function() {
o(root.childNodes.length).equals(0)
})
o("throws on invalid root node", function() {
var threw = false
try {
@ -31,8 +31,8 @@ o.spec("render", function() {
}
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 = {
oninit: init,
view: function() {throw new Error("error")}
@ -44,15 +44,231 @@ o.spec("render", function() {
setTimeout(function() {
var threwInner = false
try {run()} catch (e) {threwInner = true}
o(threwInner).equals(false)
done()
}, 0)
}
var threwOuter = false
try {run()} catch (e) {threwOuter = true}
o(threwOuter).equals(true)
})
o("does not try to re-initialize a constructibe component whose view has thrown", function() {
var oninit = o.spy()
var onbeforeupdate = o.spy()
function A(){}
A.prototype.view = function() {throw new Error("error")}
A.prototype.oninit = oninit
A.prototype.onbeforeupdate = onbeforeupdate
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
})
o("does not try to re-initialize a constructible component whose oninit has thrown", function() {
var oninit = o.spy(function(){throw new Error("error")})
var onbeforeupdate = o.spy()
function A(){}
A.prototype.view = function(){}
A.prototype.oninit = oninit
A.prototype.onbeforeupdate = onbeforeupdate
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
})
o("does not try to re-initialize a constructible component whose constructor has thrown", function() {
var oninit = o.spy()
var onbeforeupdate = o.spy()
function A(){throw new Error("error")}
A.prototype.view = function() {}
A.prototype.oninit = oninit
A.prototype.onbeforeupdate = onbeforeupdate
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(0)
o(onbeforeupdate.callCount).equals(0)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(0)
o(onbeforeupdate.callCount).equals(0)
})
o("does not try to re-initialize a closure component whose view has thrown", function() {
var oninit = o.spy()
var onbeforeupdate = o.spy()
function A() {
return {
view: function() {throw new Error("error")},
oninit: oninit,
onbeforeupdate: onbeforeupdate
}
}
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
})
o("does not try to re-initialize a closure component whose oninit has thrown", function() {
var oninit = o.spy(function() {throw new Error("error")})
var onbeforeupdate = o.spy()
function A() {
return {
view: function() {},
oninit: oninit,
onbeforeupdate: onbeforeupdate
}
}
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
o(oninit.callCount).equals(1)
o(onbeforeupdate.callCount).equals(0)
})
o("does not try to re-initialize a closure component whose closure has thrown", function() {
function A() {
throw new Error("error")
}
var throwCount = 0
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
try {render(root, {tag: A})} catch (e) {throwCount++}
o(throwCount).equals(1)
})
o("lifecycle methods work in keyed children of recycled keyed", function() {
var createA = o.spy()
var updateA = o.spy()
var removeA = o.spy()
var createB = o.spy()
var updateB = o.spy()
var removeB = o.spy()
var a = function() {
return {tag: "div", key: 1, children: [
{tag: "div", key: 11, attrs: {oncreate: createA, onupdate: updateA, onremove: removeA}},
{tag: "div", key: 12}
]}
}
var b = function() {
return {tag: "div", key: 2, children: [
{tag: "div", key: 21, attrs: {oncreate: createB, onupdate: updateB, onremove: removeB}},
{tag: "div", key: 22}
]}
}
render(root, a())
render(root, b())
render(root, a())
o(createA.callCount).equals(2)
o(updateA.callCount).equals(0)
o(removeA.callCount).equals(1)
o(createB.callCount).equals(1)
o(updateB.callCount).equals(0)
o(removeB.callCount).equals(1)
})
o("lifecycle methods work in unkeyed children of recycled keyed", function() {
var createA = o.spy()
var updateA = o.spy()
var removeA = o.spy()
var createB = o.spy()
var updateB = o.spy()
var removeB = o.spy()
var a = function() {
return {tag: "div", key: 1, children: [
{tag: "div", attrs: {oncreate: createA, onupdate: updateA, onremove: removeA}},
]}
}
var b = function() {
return {tag: "div", key: 2, children: [
{tag: "div", attrs: {oncreate: createB, onupdate: updateB, onremove: removeB}},
]}
}
render(root, a())
render(root, b())
render(root, a())
o(createA.callCount).equals(2)
o(updateA.callCount).equals(0)
o(removeA.callCount).equals(1)
o(createB.callCount).equals(1)
o(updateB.callCount).equals(0)
o(removeB.callCount).equals(1)
})
o("update lifecycle methods work on children of recycled keyed", function() {
var createA = o.spy()
var updateA = o.spy()
var removeA = o.spy()
var createB = o.spy()
var updateB = o.spy()
var removeB = o.spy()
var a = function() {
return {tag: "div", key: 1, children: [
{tag: "div", attrs: {oncreate: createA, onupdate: updateA, onremove: removeA}},
]}
}
var b = function() {
return {tag: "div", key: 2, children: [
{tag: "div", attrs: {oncreate: createB, onupdate: updateB, onremove: removeB}},
]}
}
render(root, a())
render(root, a())
o(createA.callCount).equals(1)
o(updateA.callCount).equals(1)
o(removeA.callCount).equals(0)
render(root, b())
o(createA.callCount).equals(1)
o(updateA.callCount).equals(1)
o(removeA.callCount).equals(1)
render(root, a())
render(root, a())
o(createA.callCount).equals(2)
o(updateA.callCount).equals(2)
o(removeA.callCount).equals(1)
})
})

View file

@ -213,10 +213,10 @@ o.spec("updateElement", function() {
})
o("updates svg child", function() {
var vnode = {tag: "svg", children: [{
tag: 'circle'
tag: "circle"
}]}
var updated = {tag: "svg", children: [{
tag: 'line'
tag: "line"
}]}
render(root, [vnode])
@ -235,7 +235,7 @@ o.spec("updateElement", function() {
render(root, [vnode])
var c = vnode.dom
o(root.childNodes.length).equals(1)
o(a).equals(c)
})
@ -254,7 +254,7 @@ o.spec("updateElement", function() {
render(root, [e, b, f])
var y = root.childNodes[1]
o(root.childNodes.length).equals(3)
o(x).equals(y)
})

View file

@ -1,6 +1,7 @@
"use strict"
var o = require("../../ospec/ospec")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
@ -726,7 +727,7 @@ o.spec("updateNodes", function() {
})
o("change type, position and length", function() {
var vnodes = {tag: "div", children: [
undefined,
undefined,
{tag: "#", children: "a"}
]}
var updated = {tag: "div", children: [
@ -737,7 +738,7 @@ o.spec("updateNodes", function() {
render(root, vnodes)
render(root, updated)
o(root.firstChild.childNodes.length).equals(1)
})
o("removes then recreates then reverses children", function() {
@ -838,38 +839,6 @@ o.spec("updateNodes", function() {
o(root.childNodes[0].nodeName).equals("A")
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 () {
var onupdate = o.spy();
var cached = {tag:"a", attrs:{onupdate: onupdate}}
@ -895,13 +864,13 @@ o.spec("updateNodes", function() {
var vnodes = [{tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
var temp = [null, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
var updated = [{tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
render(root, vnodes)
var before = vnodes[1].dom
render(root, temp)
render(root, updated)
var after = updated[1].dom
o(before).equals(after)
o(create.callCount).equals(1)
o(update.callCount).equals(2)
@ -914,26 +883,63 @@ o.spec("updateNodes", function() {
var vnodes = [{tag: "b"}, {tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
var temp = [{tag: "b"}, null, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
var updated = [{tag: "b"}, {tag: "div"}, {tag: "a", attrs: {oncreate: create, onupdate: update, onremove: remove}}]
render(root, vnodes)
var before = vnodes[2].dom
render(root, temp)
render(root, updated)
var after = updated[2].dom
o(before).equals(after)
o(create.callCount).equals(1)
o(update.callCount).equals(2)
o(remove.callCount).equals(0)
})
o("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 updated = {tag: "b"}
render(root, vnode)
var dom = vnode.dom
render(root, updated)
o(vnode.dom).notEquals(updated.dom)
})
components.forEach(function(cmp){
o.spec(cmp.kind, function(){
var createComponent = cmp.create
o("fragment child toggles from null when followed by null component then tag", function() {
var component = createComponent({view: function() {return null}})
var vnodes = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
var temp = [{tag: "[", children: [null, {tag: component}, {tag: "b"}]}]
var updated = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
render(root, vnodes)
render(root, temp)
render(root, updated)
o(root.childNodes.length).equals(2)
o(root.childNodes[0].nodeName).equals("A")
o(root.childNodes[1].nodeName).equals("B")
})
o("fragment child toggles from null in component when followed by null component then tag", function() {
var flag = true
var a = createComponent({view: function() {return flag ? {tag: "a"} : null}})
var b = createComponent({view: function() {return null}})
var vnodes = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
var temp = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
var updated = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
render(root, vnodes)
flag = false
render(root, temp)
flag = true
render(root, updated)
o(root.childNodes.length).equals(2)
o(root.childNodes[0].nodeName).equals("A")
o(root.childNodes[1].nodeName).equals("S")
})
})
})
})

View file

@ -1,5 +1,7 @@
"use strict"
function Vnode(tag, key, attrs, children, text, dom) {
return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: {}, 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) {
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")
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"
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.deserialize !== "function") args.deserialize = deserialize

View file

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

View file

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

View file

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

View file

@ -80,7 +80,7 @@ module.exports = function($window) {
var path = router.getPath()
var params = {}
var pathname = parsePath(path, params, params)
var state = $window.history.state
if (state != null) {
for (var k in state) params[k] = state[k]
@ -103,11 +103,11 @@ module.exports = function($window) {
reject(path, params)
}
if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
resolveRoute()
}
return router
}

View file

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

View file

@ -1 +1,3 @@
module.exports = require("./stream/stream")
"use strict"
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"
;(function() {
var guid = 0, HALT = {}
function createStream() {
function stream() {
@ -14,7 +16,7 @@ function createStream() {
}
function initStream(stream) {
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.valueOf = valueOf, stream.toJSON = toJSON, stream.toString = valueOf
@ -23,7 +25,10 @@ function initStream(stream) {
if (!stream._state.endStream) {
var endStream = createStream()
endStream.map(function(value) {
if (value === true) unregisterStream(stream), unregisterStream(endStream)
if (value === true) {
unregisterStream(stream)
endStream._state.unregister = function(){unregisterStream(endStream)}
}
return value
})
stream._state.endStream = endStream
@ -35,6 +40,7 @@ function initStream(stream) {
function updateStream(stream, value) {
updateState(stream, value)
for (var id in stream._state.deps) updateDependency(stream._state.deps[id], false)
if (stream._state.unregister != null) stream._state.unregister()
finalize(stream)
}
function updateState(stream, value) {
@ -56,7 +62,7 @@ function finalize(stream) {
}
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 fn.apply(this, streams.concat([streams.filter(changed)]))
})
@ -107,10 +113,48 @@ function merge(streams) {
return streams.map(function(s) {return s()})
}, streams)
}
function scan(reducer, seed, stream) {
var newStream = combine(function (s) {
return seed = reducer(seed, s._state.value)
}, [stream])
if (newStream._state.state === 0) newStream(seed)
return newStream
}
function scanMerge(tuples, seed) {
var streams = tuples.map(function(tuple) {
var stream = tuple[0]
if (stream._state.state === 0) stream(undefined)
return stream
})
var newStream = combine(function() {
var changed = arguments[arguments.length - 1]
streams.forEach(function(stream, idx) {
if (changed.indexOf(stream) > -1) {
seed = tuples[idx][1](seed, stream._state.value)
}
})
return seed
}, streams)
return newStream
}
createStream["fantasy-land/of"] = createStream
createStream.merge = merge
createStream.combine = combine
createStream.scan = scan
createStream.scanMerge = scanMerge
createStream.HALT = HALT
if (typeof module !== "undefined") module["exports"] = createStream
else 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 stream = require("../stream")
var scan = require("../scan")
o.spec("scan", function() {
o("defaults to seed", function() {
var parent = stream()
var child = scan(function(out, p) {
var child = stream.scan(function(out, p) {
return out - p
}, 123, parent)
o(child()).equals(123)
@ -15,14 +14,14 @@ o.spec("scan", function() {
o("accumulates values as expected", function() {
var parent = stream()
var child = scan(function(arr, p) {
var child = stream.scan(function(arr, p) {
return arr.concat(p)
}, [], parent)
parent(7)
parent("11")
parent(undefined)
parent({ a: 1 })
parent({a: 1})
var result = child()
// deepEquals fails on arrays?
@ -32,4 +31,3 @@ o.spec("scan", function() {
o(result[3]).deepEquals({a: 1})
})
})

View file

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

View file

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

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

View file

@ -3,7 +3,7 @@
module.exports = function parseURL(url, root) {
var data = {}
var protocolIndex = url.indexOf("://")
var pathnameIndex = protocolIndex > - 1 ? url.indexOf("/", protocolIndex + 3) : url.indexOf("/")
var pathnameIndex = protocolIndex > -1 ? url.indexOf("/", protocolIndex + 3) : url.indexOf("/")
var searchIndex = url.indexOf("?")
var hashIndex = url.indexOf("#")
if ((pathnameIndex > searchIndex && searchIndex > -1) || (pathnameIndex > hashIndex && hashIndex > -1)) pathnameIndex = -1

View file

@ -4,7 +4,7 @@ var parseURL = require("../test-utils/parseURL")
module.exports = function(options) {
if (options == null) options = {}
var $window = options.window || {}
var protocol = options.protocol || "http:"
var hostname = options.hostname || "localhost"
@ -33,7 +33,7 @@ module.exports = function(options) {
}
return isNew
}
function prefix(prefix, value) {
if (value === "") return ""
return (value.charAt(0) !== prefix ? prefix : "") + value

View file

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

View file

@ -17,7 +17,7 @@ o.spec("browserMock", function() {
})
o("$window.onhashchange can be reached from the pushStateMock functions", function(done) {
$window.onhashchange = o.spy()
$window.location.hash = '#a'
$window.location.hash = "#a"
callAsync(function(){
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() {
$window.onunload = o.spy()
$window.location.href = '/a'
$window.location.href = "/a"
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

@ -321,7 +321,7 @@ o.spec("domMock", function() {
o(div.getAttribute("id")).equals("aaa")
})
})
o.spec("setAttribute", function() {
o("works", function() {
var div = $document.createElement("div")
@ -393,7 +393,6 @@ o.spec("domMock", function() {
o.spec("textContent", function() {
o("works", function() {
var div = $document.createElement("div")
var a = $document.createElement("a")
div.textContent = "aaa"
o(div.childNodes.length).equals(1)
@ -402,7 +401,6 @@ o.spec("domMock", function() {
})
o("works with empty string", function() {
var div = $document.createElement("div")
var a = $document.createElement("a")
div.textContent = ""
o(div.childNodes.length).equals(0)
@ -514,8 +512,8 @@ o.spec("domMock", function() {
var div = $document.createElement("div")
div.style.cssText = "background: url(';'); font-family: \";\""
o(div.style.background).equals("url(';')")
o(div.style.fontFamily).equals("\";\"")
o(div.style.background).equals("url(';')")
o(div.style.fontFamily).equals('";"')
o(div.style.cssText).equals("background: url(';'); font-family: \";\";")
})
o("comments in style.cssText are stripped", function(){
@ -534,9 +532,10 @@ o.spec("domMock", function() {
})
o("setting style throws", function () {
var div = $document.createElement("div")
var err = false
try {
div.style = ''
div.style = ""
} catch (e) {
err = e
}
@ -919,55 +918,55 @@ o.spec("domMock", function() {
o.spec("canvas width and height", function() {
o("setting property works", function() {
var canvas = $document.createElement("canvas")
canvas.width = 100
o(canvas.attributes["width"].nodeValue).equals("100")
o(canvas.width).equals(100)
canvas.height = 100
o(canvas.attributes["height"].nodeValue).equals("100")
o(canvas.height).equals(100)
})
o("setting string casts to number", function() {
var canvas = $document.createElement("canvas")
canvas.width = "100"
o(canvas.attributes["width"].nodeValue).equals("100")
o(canvas.width).equals(100)
canvas.height = "100"
o(canvas.attributes["height"].nodeValue).equals("100")
o(canvas.height).equals(100)
})
o("setting float casts to int", function() {
var canvas = $document.createElement("canvas")
canvas.width = 1.2
o(canvas.attributes["width"].nodeValue).equals("1")
o(canvas.width).equals(1)
canvas.height = 1.2
o(canvas.attributes["height"].nodeValue).equals("1")
o(canvas.height).equals(1)
})
o("setting percentage fails", function() {
var canvas = $document.createElement("canvas")
canvas.width = "100%"
o(canvas.attributes["width"].nodeValue).equals("0")
o(canvas.width).equals(0)
canvas.height = "100%"
o(canvas.attributes["height"].nodeValue).equals("0")
o(canvas.height).equals(0)
})
o("setting attribute works", function() {
var canvas = $document.createElement("canvas")
canvas.setAttribute("width", "100%")
o(canvas.attributes["width"].nodeValue).equals("100%")
o(canvas.width).equals(100)
canvas.setAttribute("height", "100%")
o(canvas.attributes["height"].nodeValue).equals("100%")
o(canvas.height).equals(100)

View file

@ -168,13 +168,13 @@ o.spec("pushStateMock", function() {
})
o.spec("set protocol", function() {
o("setting protocol throws", function(done) {
var old = $window.location.href
try {
$window.location.protocol = "https://"
}
catch (e) {
done()
return done()
}
throw new Error("Expected an error")
})
})
o.spec("set port", function() {
@ -413,17 +413,17 @@ o.spec("pushStateMock", function() {
})
o("replaceState does not break forward history", function() {
$window.onpopstate = o.spy()
$window.history.pushState(null, null, "b")
$window.history.back()
o($window.onpopstate.callCount).equals(1)
o($window.location.href).equals("http://localhost/")
$window.history.replaceState(null, null, "a")
o($window.location.href).equals("http://localhost/a")
$window.history.forward()
o($window.onpopstate.callCount).equals(2)
@ -431,46 +431,46 @@ o.spec("pushStateMock", function() {
})
o("pushstate retains state", function() {
$window.onpopstate = o.spy()
$window.history.pushState({a: 1}, null, "#a")
$window.history.pushState({b: 2}, null, "#b")
o($window.onpopstate.callCount).equals(0)
$window.history.back()
o($window.onpopstate.callCount).equals(1)
o($window.onpopstate.args[0].type).equals("popstate")
o($window.onpopstate.args[0].state).deepEquals({a: 1})
$window.history.back()
o($window.onpopstate.callCount).equals(2)
o($window.onpopstate.args[0].type).equals("popstate")
o($window.onpopstate.args[0].state).equals(null)
$window.history.forward()
o($window.onpopstate.callCount).equals(3)
o($window.onpopstate.args[0].type).equals("popstate")
o($window.onpopstate.args[0].state).deepEquals({a: 1})
$window.history.forward()
o($window.onpopstate.callCount).equals(4)
o($window.onpopstate.args[0].type).equals("popstate")
o($window.onpopstate.args[0].state).deepEquals({b: 2})
})
o("replacestate replaces state", function() {
$window.onpopstate = o.spy(pop)
$window.history.replaceState({a: 1}, null, "a")
o($window.history.state).deepEquals({a: 1})
$window.history.pushState(null, null, "a")
$window.history.back()
function pop(e) {
o(e.state).deepEquals({a: 1})
o($window.history.state).deepEquals({a: 1})

View file

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

View file

@ -6,7 +6,7 @@ var parseQueryString = require("../querystring/parse")
module.exports = function() {
var routes = {}
var callback = "callback"
// var callback = "callback"
var serverErrorHandler = function(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
if (args.async === true) {
var s = new Date
callAsync(function() {
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 handler = routes["GET " + urlData.pathname] || serverErrorHandler.bind(null, element.src)
var data = handler({url: urlData.pathname, query: urlData.search, body: null})
var query = parseQueryString(urlData.search)
parseQueryString(urlData.search)
callAsync(function() {
if (data.status === 200) {
new Function("$window", "with ($window) return " + data.responseText).call($window, $window)
@ -83,8 +82,8 @@ module.exports = function() {
$defineRoutes: function(rules) {
routes = rules
},
$defineJSONPCallbackKey: function(key) {
callback = key
$defineJSONPCallbackKey: function(/* key */) {
// callback = key
},
}
return $window

View file

@ -2,6 +2,7 @@
var o = require("../ospec/ospec")
var browserMock = require("../test-utils/browserMock")
var components = require("../test-utils/components")
o.spec("api", function() {
var m
@ -9,13 +10,13 @@ o.spec("api", function() {
o.beforeEach(function() {
var mock = browserMock()
if (typeof global !== "undefined") global.window = mock
m = require("../mithril")
m = require("../mithril") // eslint-disable-line global-require
})
o.spec("m", function() {
o("works", function() {
var vnode = m("div")
o(vnode.tag).equals("div")
})
})
@ -29,7 +30,7 @@ o.spec("api", function() {
o.spec("m.trust", function() {
o("works", function() {
var vnode = m.trust("<br>")
o(vnode.tag).equals("<")
o(vnode.children).equals("<br>")
})
@ -37,7 +38,7 @@ o.spec("api", function() {
o.spec("m.fragment", function() {
o("works", function() {
var vnode = m.fragment({key: 123}, [m("div")])
o(vnode.tag).equals("[")
o(vnode.key).equals(123)
o(vnode.children.length).equals(1)
@ -48,115 +49,26 @@ o.spec("api", function() {
o("works", function() {
var spy = o.spy()
var handler = m.withAttr("value", spy)
handler({currentTarget: {value: 10}})
o(spy.args[0]).equals(10)
})
})
o.spec("m.parseQueryString", function() {
o("works", function() {
var query = m.parseQueryString("?a=1&b=2")
o(query).deepEquals({a: "1", b: "2"})
})
})
o.spec("m.buildQueryString", function() {
o("works", function() {
var query = m.buildQueryString({a: 1, b: 2})
o(query).equals("a=1&b=2")
})
})
o.spec("m.render", function() {
o("works", function() {
var root = window.document.createElement("div")
m.render(root, m("div"))
o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("DIV")
})
})
o.spec("m.mount", function() {
o("works", function() {
var root = window.document.createElement("div")
m.mount(root, {view: function() {return m("div")}})
o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("DIV")
})
})
o.spec("m.route", function() {
o("works", function(done) {
var root = window.document.createElement("div")
m.route(root, "/a", {
"/a": {view: function() {return m("div")}}
})
setTimeout(function() {
o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("DIV")
done()
}, FRAME_BUDGET)
})
o("m.route.prefix", function(done) {
var root = window.document.createElement("div")
m.route.prefix("#")
m.route(root, "/a", {
"/a": {view: function() {return m("div")}}
})
setTimeout(function() {
o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("DIV")
done()
}, FRAME_BUDGET)
})
o("m.route.get", function(done) {
var root = window.document.createElement("div")
m.route(root, "/a", {
"/a": {view: function() {return m("div")}}
})
setTimeout(function() {
o(m.route.get()).equals("/a")
done()
}, FRAME_BUDGET)
})
o("m.route.set", function(done, timeout) {
timeout(100)
var root = window.document.createElement("div")
m.route(root, "/a", {
"/:id": {view: function() {return m("div")}}
})
setTimeout(function() {
m.route.set("/b")
setTimeout(function() {
o(m.route.get()).equals("/b")
done()
}, FRAME_BUDGET)
}, FRAME_BUDGET)
})
})
o.spec("m.redraw", function() {
o("works", function(done) {
var count = 0
var root = window.document.createElement("div")
m.mount(root, {view: function() {count++}})
setTimeout(function() {
m.redraw()
o(count).equals(2)
done()
}, FRAME_BUDGET)
})
})
o.spec("m.request", function() {
o("works", function() {
o(typeof m.request).equals("function") // TODO improve
@ -167,4 +79,99 @@ o.spec("api", function() {
o(typeof m.jsonp).equals("function") // TODO improve
})
})
})
o.spec("m.render", function() {
o("works", function() {
var root = window.document.createElement("div")
m.render(root, m("div"))
o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("DIV")
})
})
components.forEach(function(cmp){
o.spec(cmp.kind, function(){
var createComponent = cmp.create
o.spec("m.mount", function() {
o("works", function() {
var root = window.document.createElement("div")
m.mount(root, createComponent({view: function() {return m("div")}}))
o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("DIV")
})
})
o.spec("m.route", function() {
o("works", function(done) {
var root = window.document.createElement("div")
m.route(root, "/a", {
"/a": createComponent({view: function() {return m("div")}})
})
setTimeout(function() {
o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("DIV")
done()
}, FRAME_BUDGET)
})
o("m.route.prefix", function(done) {
var root = window.document.createElement("div")
m.route.prefix("#")
m.route(root, "/a", {
"/a": createComponent({view: function() {return m("div")}})
})
setTimeout(function() {
o(root.childNodes.length).equals(1)
o(root.firstChild.nodeName).equals("DIV")
done()
}, FRAME_BUDGET)
})
o("m.route.get", function(done) {
var root = window.document.createElement("div")
m.route(root, "/a", {
"/a": createComponent({view: function() {return m("div")}})
})
setTimeout(function() {
o(m.route.get()).equals("/a")
done()
}, FRAME_BUDGET)
})
o("m.route.set", function(done, timeout) {
timeout(100)
var root = window.document.createElement("div")
m.route(root, "/a", {
"/:id": createComponent({view: function() {return m("div")}})
})
setTimeout(function() {
m.route.set("/b")
setTimeout(function() {
o(m.route.get()).equals("/b")
done()
}, FRAME_BUDGET)
}, FRAME_BUDGET)
})
})
o.spec("m.redraw", function() {
o("works", function(done) {
var count = 0
var root = window.document.createElement("div")
m.mount(root, createComponent({view: function() {count++}}))
setTimeout(function() {
m.redraw()
o(count).equals(2)
done()
}, FRAME_BUDGET)
})
})
})
})
})