components, angular dbmon

This commit is contained in:
Leo Horie 2016-05-03 23:39:01 -04:00
parent ba378d3652
commit 3282ef3f77
30 changed files with 1270 additions and 248 deletions

View file

@ -1,3 +1,5 @@
# Contributing
# FAQ
## How do I go about contributing ideas or new features?
@ -16,11 +18,15 @@ Ideally, provide code to reproduce the issue (via jsfiddle, a gist, etc). Even b
Assuming you have forked this repo, you can open the `index.html` file in a module's `tests` folder and look at console output to see only tests for that module, or you can run `ospec/bin/ospec` from the command line to run all tests under a Node.js environment. Additionally, you can modify a test to use `o.only(description, test)` instead of `o(description, test)` if you wish to run only a specific test.
There is no need to `npm install` anything in order to run the test suite, however NodeJS is required to run the test suite from the command line.
## How do I build Mithril?
Run `node bundler/bundler.js` from the command line to generate the bundled file.
If all you're trying to do is run examples in the codebase, you don't need to build Mithril, you can just open the various html files and things should just work.
To generate the bundled file, run `node bundler/bundler.js` from the command line. There is no need to `npm install` anything, but NodeJS is required to run the build script.
@ -56,7 +62,7 @@ In addition, ES6 features are usually less performant than equivalent ES5 code,
## Why doesn't the Mithril codebase use trailing semi-colons? Would a PR to add them be welcome?
I don't use them. Adding them means the semi-colon usage in the codebase will eventually become inconsistent. If you're not comfortable with ASI rules, bear in mind that this codebase is heavily optimized and large modifications will require an advanced level of javascript mastery.
I don't use them. Adding them means the semi-colon usage in the codebase will eventually become inconsistent.

View file

@ -22,7 +22,7 @@
<body>
<div id="root"></div>
<script src="../../module/module.js"></script>
<script src="../../render/normalizeChildren.js"></script>
<script src="../../render/node.js"></script>
<script src="../../render/hyperscript.js"></script>
<script src="../../render/render.js"></script>
<script>

55
examples/dbmonster/angular/app.js vendored Normal file
View file

@ -0,0 +1,55 @@
var renderStage = 0
perfMonitor.startFPSMonitor()
perfMonitor.startMemMonitor()
perfMonitor.initProfiler("render")
var AppComponent = ng.core.Component({selector: "my-app"})
.View({
directives: [ng.common.CORE_DIRECTIVES],
template: "<div>" +
"<table class='table table-striped latest-data'>" +
"<tbody>" +
"<tr *ngFor='let db of databases'>" +
"<td class='dbname'>{{db.dbname}}</td>" +
"<td class='query-count'>" +
"<span [class]='db.lastSample.countClassName'>{{db.lastSample.nbQueries}}</span>" +
"</td>" +
"<td *ngFor='let q of db.lastSample.topFiveQueries' [class]='q.elapsedClassName'>" +
"{{q.formatElapsed}}" +
"<div class='popover left'>" +
"<div class='popover-content'>{{q.query}}</div>" +
"<div class='arrow'></div>" +
"</div>" +
"</td>" +
"</tr>" +
"</tbody>" +
"</table>" +
"</div>"
})
.Class({
constructor: function() {
this.databases = []
this.update()
},
update: function() {
var self = this
self.databases = ENV.generateData(true).toArray()
setTimeout(function() {self.update()}, ENV.timeout)
if (renderStage === 0) {
renderStage = 1
perfMonitor.startProfile("render")
}
},
ngAfterViewChecked: function() {
if (renderStage === 1) {
perfMonitor.endProfile("render")
renderStage = 0
}
},
})
document.addEventListener("DOMContentLoaded", function() {
ng.core.enableProdMode()
ng.platform.browser.bootstrap(AppComponent)
})

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<link href="../lib/bootstrap.min.css" rel="stylesheet" type="text/css" />
<link href="../styles.css" rel="stylesheet" type="text/css" />
<meta charset="utf-8">
<title>dbmon (Angular2)</title>
</head>
<body>
<my-app></my-app>
<script src="https://code.angularjs.org/2.0.0-beta.17/angular2-polyfills.js"></script>
<script src="https://code.angularjs.org/2.0.0-beta.17/Rx.umd.js"></script>
<script src="https://code.angularjs.org/2.0.0-beta.17/angular2-all.umd.js"></script>
<script src="../ENV.js"></script>
<script src="http://localvoid.github.io/perf-monitor/0.1/perf-monitor.js"></script>
<script src="app.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

View file

@ -3,6 +3,10 @@
var m = require("../../../render/hyperscript")
var render = require("../../../render/render")(window).render
perfMonitor.startFPSMonitor()
perfMonitor.startMemMonitor()
perfMonitor.initProfiler("render")
var data = []
var root = document.getElementById("app")
@ -11,29 +15,29 @@ update()
function update() {
data = ENV.generateData().toArray()
Monitoring.renderRate.ping()
render(root, [view()])
perfMonitor.startProfile("render")
render(root, view())
perfMonitor.endProfile("render")
setTimeout(update, ENV.timeout)
}
function view() {
return m("div", [
m("table", { className: "table table-striped latest-data" }, [
m("table", {className: "table table-striped latest-data"}, [
m("tbody",
data.map(function(db) {
return m("tr", {key: db.dbname}, [
m("td", { className: "dbname" }, db.dbname),
m("td", { className: "query-count" }, [
m("span", { className: db.lastSample.countClassName }, db.lastSample.nbQueries)
m("td", {className: "dbname"}, db.dbname),
m("td", {className: "query-count"}, [
m("span", {className: db.lastSample.countClassName}, db.lastSample.nbQueries)
]),
db.lastSample.topFiveQueries.map(function(query) {
return m("td", { className: query.elapsedClassName }, [
m("span", query.formatElapsed),
m("div", { className: "popover left" }, [
m("div", { className: "popover-content" }, query.query),
m("div", { className: "arrow" })
return m("td", {className: query.elapsedClassName}, [
query.formatElapsed,
m("div", {className: "popover left"}, [
m("div", {className: "popover-content"}, query.query),
m("div", {className: "arrow"})
])
])
})

View file

@ -9,12 +9,11 @@
<body>
<div id="app"></div>
<script src="../../../module/module.js"></script>
<script src="../../../render/normalizeChildren.js"></script>
<script src="../../../render/node.js"></script>
<script src="../../../render/hyperscript.js"></script>
<script src="../../../render/render.js"></script>
<script src="../ENV.js"></script>
<script src="../memory-stats.js"></script>
<script src="../monitor.js"></script>
<script src="http://localvoid.github.io/perf-monitor/0.1/perf-monitor.js"></script>
<script src="app.js"></script>
</body>
</html>

View file

@ -1,36 +1,40 @@
"use strict";
"use strict"
var h = React.createElement
perfMonitor.startFPSMonitor()
perfMonitor.startMemMonitor()
perfMonitor.initProfiler("render")
var Query = React.createClass({
shouldComponentUpdate: function shouldComponentUpdate(nextProps, nextState) {
if (nextProps.elapsedClassName !== this.props.elapsedClassName) return true;
if (nextProps.formatElapsed !== this.props.formatElapsed) return true;
if (nextProps.query !== this.props.query) return true;
return false;
if (nextProps.elapsedClassName !== this.props.elapsedClassName) return true
if (nextProps.formatElapsed !== this.props.formatElapsed) return true
if (nextProps.query !== this.props.query) return true
return false
},
render: function render() {
return h("td", { className: "Query " + this.props.elapsedClassName },
return h("td", {className: "Query " + this.props.elapsedClassName},
this.props.formatElapsed,
h("div", { className: "popover left" },
h("div", { className: "popover-content" }, this.props.query),
h("div", { className: "arrow" })
h("div", {className: "popover left"},
h("div", {className: "popover-content"}, this.props.query),
h("div", {className: "arrow"})
)
);
)
}
});
})
var Database = React.createClass({
shouldComponentUpdate: function shouldComponentUpdate(nextProps, nextState) {
if (nextProps.lastMutationId === this.props.lastMutationId) return false;
return true;
if (nextProps.lastMutationId === this.props.lastMutationId) return false
return true
},
render: function render() {
var lastSample = this.props.lastSample;
return h("tr", { key: this.props.dbname },
h("td", { className: "dbname" }, this.props.dbname),
h("td", { className: "query-count" },
h("span", { className: this.props.lastSample.countClassName }, this.props.lastSample.nbQueries)
var lastSample = this.props.lastSample
return h("tr", {key: this.props.dbname},
h("td", {className: "dbname"}, this.props.dbname),
h("td", {className: "query-count"},
h("span", {className: this.props.lastSample.countClassName}, this.props.lastSample.nbQueries)
),
this.props.lastSample.topFiveQueries.map(function (query, index) {
return h(Query, {
@ -39,34 +43,40 @@ var Database = React.createClass({
elapsed: query.elapsed,
formatElapsed: query.formatElapsed,
elapsedClassName: query.elapsedClassName
});
})
})
);
)
}
});
})
var DBMon = React.createClass({
getInitialState: function getInitialState() {
return {
databases: []
};
}
},
loadSamples: function loadSamples() {
var data = ENV.generateData(true).toArray()
perfMonitor.startProfile("render")
this.setState({
databases: ENV.generateData(true).toArray()
});
Monitoring.renderRate.ping();
setTimeout(this.loadSamples, ENV.timeout);
databases: data
})
perfMonitor.endProfile("render")
setTimeout(this.loadSamples, ENV.timeout)
},
componentDidMount: function componentDidMount() {
this.loadSamples();
this.loadSamples()
},
render: function render() {
return h("div", null,
h("table", { className: "table table-striped latest-data" },
h("table", {className: "table table-striped latest-data"},
h("tbody", null, this.state.databases.map(function (database) {
return h(Database, {
key: database.dbname,
@ -74,11 +84,11 @@ var DBMon = React.createClass({
dbname: database.dbname,
samples: database.samples,
lastSample: database.lastSample
});
})
}))
)
);
)
}
});
})
ReactDOM.render(h(DBMon, null), document.getElementById('app'));
ReactDOM.render(h(DBMon, null), document.getElementById('app'))

View file

@ -8,11 +8,10 @@
</head>
<body>
<div id="app"></div>
<script src="https://fb.me/react-15.0.1.js"></script>
<script src="https://fb.me/react-dom-15.0.1.js"></script>
<script src="https://fb.me/react-15.0.2.js"></script>
<script src="https://fb.me/react-dom-15.0.2.js"></script>
<script src="../ENV.js"></script>
<script src="../memory-stats.js"></script>
<script src="../monitor.js"></script>
<script src="http://localvoid.github.io/perf-monitor/0.1/perf-monitor.js"></script>
<script src="app.js"></script>
</body>
</html>

View file

@ -8,7 +8,7 @@
<body>
<div id="root"></div>
<script src="../../module/module.js"></script>
<script src="../../render/normalizeChildren.js"></script>
<script src="../../render/node.js"></script>
<script src="../../render/hyperscript.js"></script>
<script src="../../render/render.js"></script>
<script>

View file

@ -8,7 +8,7 @@
<body>
<div id="root"></div>
<script src="../../module/module.js"></script>
<script src="../../render/normalizeChildren.js"></script>
<script src="../../render/node.js"></script>
<script src="../../render/hyperscript.js"></script>
<script src="../../render/render.js"></script>
<script>

View file

@ -8,7 +8,7 @@
<body>
<div id="root"></div>
<script src="../../module/module.js"></script>
<script src="../../render/normalizeChildren.js"></script>
<script src="../../render/node.js"></script>
<script src="../../render/hyperscript.js"></script>
<script src="../../render/render.js"></script>
<script>

View file

@ -10,34 +10,17 @@ var router = require("../../router/router")(window, "#")
var api = {
home : function() {
T.timeEnd("Setup")
return request({
method: "GET",
url: T.apiUrl + "/threads/",
})
return request({method: "GET", url: T.apiUrl + "/threads/"})
},
thread : function(id) {
T.timeEnd("Setup")
return request({
method: "GET",
url: T.apiUrl + "/comments/" + id,
}).then(T.transformResponse)
return request({method: "GET", url: T.apiUrl + "/comments/" + id}).then(T.transformResponse)
},
newThread : function(text) {
return request({
method: "POST",
url: T.apiUrl + "/threads/create",
data: {text: text},
});
return request({method: "POST", url: T.apiUrl + "/threads/create",data: {text: text}})
},
newComment : function(text, id) {
return request({
url: T.apiUrl + "/comments/create",
method: "POST",
data: {
text: text,
parent: id,
}
});
return request({method: "POST", url: T.apiUrl + "/comments/create", data: {text: text, parent: id}});
}
};
@ -82,9 +65,9 @@ function createThread() {
return false
}
function showReplying(node) {
node.replying = true
node.newComment = ""
function showReplying(vnode) {
vnode.state.replying = true
vnode.state.newComment = ""
return false
}
@ -99,39 +82,53 @@ function submitComment(node) {
}
//shared
function header() {
return [
m("p.head_links", [
m("a[href='https://github.com/koglerjs/threaditjs/tree/master/examples/mithril']", "Source"),
" | ",
m("a[href='http://threaditjs.com']", "ThreaditJS Home"),
]),
m("h2", [
m("a[href='#/']", "ThreaditJS: Mithril"),
]),
]
var Header = {
view: function() {
return [
m("p.head_links", [
m("a[href='https://github.com/koglerjs/threaditjs/tree/master/examples/mithril']", "Source"),
" | ",
m("a[href='http://threaditjs.com']", "ThreaditJS Home"),
]),
m("h2", [
m("a[href='#/']", "ThreaditJS: Mithril"),
]),
]
}
}
//home
function home() {
return {tag: "[", key: "home", attrs: {oncreate: loadThreads}, children: [
header(),
m(".main", [
loaded === false ? m("h2", "Loading") :
error ? m("h2", "Error! Try refreshing.") :
notFound ? m("h2", "Not found! Don't try refreshing!") :
[
threads.map(threadListItem),
newThread(),
]
])
]}
var Home = {
oninit: loadThreads,
view: function() {
return [
m(Header),
m(".main", [
loaded === false ? m("h2", "Loading") :
error ? m("h2", "Error! Try refreshing.") :
notFound ? m("h2", "Not found! Don't try refreshing!") : [
threads.map(function(thread) {
return [
m("p", [
m("a", {href: "#/thread/" + thread.id}, trust(T.trimTitle(thread.text))),
]),
m("p.comment_count", thread.comment_count + " comment(s)"),
m("hr"),
]
}),
m(NewThread),
]
])
]
}
}
function newThread() {
return m("form", {onsubmit: createThread}, [
m("textarea#threadText"),
m("input", {type:"submit", value: "Post!"}),
])
var NewThread = {
view: function() {
return m("form", {onsubmit: createThread}, [
m("textarea#threadText"),
m("input", {type:"submit", value: "Post!"}),
])
}
}
function threadListItem(thread) {
@ -145,39 +142,49 @@ function threadListItem(thread) {
}
//thread
function thread(args) {
if (current) T.time("Thread render")
return {tag: "[", key: args.id, attrs: {oncreate: function() {loadThread(args.id)}, onremove: unloadThread}, children: [
header(),
current ? m(".main", {oncreate: function() {T.timeEnd("Thread render")}}, [
threadNode({node: current.root})
]) : null
]}
var Thread = {
oninit: function(vnode) {
loadThread(vnode.attrs.id)
},
onremove: unloadThread,
view: function() {
if (current) T.time("Thread render")
return [
m(Header),
current ? m(".main", {oncreate: function() {T.timeEnd("Thread render")}}, [
m(ThreadNode, {node: current.root})
]) : null
]
}
}
function threadNode(args) {
return m(".comment", [
m("p", trust(args.node.text)),
m(".reply", reply(args)),
m(".children", [
args.node.children.map(function(child) {
return threadNode({node: child})
})
var ThreadNode = {
view: function(vnode) {
return m(".comment", [
m("p", trust(vnode.attrs.node.text)),
m(".reply", m(Reply, vnode.attrs)),
m(".children", [
vnode.attrs.node.children.map(function(child) {
return m(ThreadNode, {node: child})
})
])
])
])
}
}
function reply(args) {
return args.node.replying
? m("form", {onsubmit: function() {return submitComment(args.node)}}, [
m("textarea", {
value: args.node.newComment, //FIXME decouple UI state from data
oninput: function(e) {
args.node.newComment = e.target.value
},
}),
m("input", {type:"submit", value: "Reply!"}),
m(".preview", trust(T.previewComment(args.node.newComment))),
])
: m("a", {onclick: function() {return showReplying(args.node)}}, "Reply!")
var Reply = {
view: function(vnode) {
return vnode.state.replying
? m("form", {onsubmit: function() {return submitComment(vnode.attrs.node)}}, [
m("textarea", {
value: vnode.state.newComment,
oninput: function(e) {
vnode.state.newComment = e.target.value
},
}),
m("input", {type:"submit", value: "Reply!"}),
m(".preview", trust(T.previewComment(vnode.state.newComment))),
])
: m("a", {onclick: function() {return showReplying(vnode)}}, "Reply!")
}
}
//router
@ -186,10 +193,10 @@ function run() {
}
var replayRoute = router.defineRoutes({
"/thread/:id" : thread,
"/" : home
"/thread/:id" : Thread,
"/" : Home
}, function(view, args) {
render(document.body, [view(args)])
render(document.body, [m(view, args)])
}, function() {
router.setPath("/")
})

View file

@ -10,7 +10,7 @@
<body>
<script src="http://threaditjs.com/shared.js"></script>
<script src="../../module/module.js"></script>
<script src="../../render/normalizeChildren.js"></script>
<script src="../../render/node.js"></script>
<script src="../../render/hyperscript.js"></script>
<script src="../../render/trust.js"></script>
<script src="../../render/render.js"></script>

View file

@ -13,7 +13,7 @@
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script src="../../module/module.js"></script>
<script src="../../render/normalizeChildren.js"></script>
<script src="../../render/node.js"></script>
<script src="../../render/hyperscript.js"></script>
<script src="../../render/render.js"></script>
<script src="../../querystring/build.js"></script>

72
index.html Normal file
View file

@ -0,0 +1,72 @@
<!doctype html>
<html>
<head>
<style>* {font-size:40px;}</style>
</head>
<body>
<script src="./module/module.js"></script>
<script src="./render/node.js"></script>
<script src="./render/hyperscript.js"></script>
<script src="./render/render.js"></script>
<script>
var m = require("./render/hyperscript")
var render = require("./render/render")(window, run).render
var count = 10
function inc() {count++}
var Counter1 = {
oninit: function(vnode) {console.log("init", vnode)},
oncreate: function(vnode) {console.log("create", vnode)},
onupdate: function(vnode) {console.log("update", vnode)},
onremove: function(vnode) {console.log("remove", vnode)},
onbeforeremove: function(vnode, done) {console.log("before remove", vnode);done()},
view: function({attrs, state}) {
return m("a", {
onclick: () => {
count++
state.visible = !state.visible
}
}, attrs.count + (state.visible ? "visible" : ""))
}
}
/*
class Counter2 {
oninit(vnode) {
request().then(function(foo) {
vnode.state.foo = foo
})
}
onupdate(vnode) {
vnode.dom.style.borderRight = vnode.attrs.count + "px solid red"
}
view(vnode) {
return m("a", {onclick: inc}, vnode.attrs.count)
}
}
function Counter3() {
return m("a", {
onupdate: function(vnode) {
vnode.dom.style.borderRight = vnode.attrs.count + "px solid red"
},
onclick: inc
}, vnode.attrs.count)
}
*/
function view() {
return m("div", [
m("a", {onclick: inc}, count),
m("br"),
count % 7 !== 0 ? {tag: Counter1, attrs: {count: count}, text: "test", state: {}} : null,
])
}
function run() {
render(document.body, view())
}
run()
</script>
</body>
</html>

View file

@ -70,16 +70,22 @@ module.exports = new function init() {
var body = fn.toString()
var arg = (body.match(/\(([\w_$]+)/) || body.match(/([\w_$]+)\s*=>/) || []).pop()
if (body.indexOf(arg) === body.lastIndexOf(arg)) throw new Error("`" + arg + "()` should be called at least once")
fn(function done() {
if (timeout !== undefined) {
timeout = clearTimeout(timeout)
if (delay !== Infinity) record(null)
if (!isDone) next()
else throw new Error("`" + arg + "()` should only be called once")
isDone = true
}
else console.log("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms")
}, function(t) {delay = t})
try {
fn(function done() {
if (timeout !== undefined) {
timeout = clearTimeout(timeout)
if (delay !== Infinity) record(null)
if (!isDone) next()
else throw new Error("`" + arg + "()` should only be called once")
isDone = true
}
else console.log("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms")
}, function(t) {delay = t})
}
catch (e) {
record(e.message, e)
next()
}
if (timeout === 0) {
timeout = setTimeout(function() {
timeout = undefined
@ -157,11 +163,13 @@ module.exports = new function init() {
}
}
}
function record(message) {
function record(message, error) {
var result = {pass: message === null}
if (result.pass === false) {
var error = new Error
if (error.stack === undefined) new function() {try {throw error} catch (e) {error = e}}
if (error == null) {
error = new Error
if (error.stack === undefined) new function() {try {throw error} catch (e) {error = e}}
}
result.context = subjects.join(" > ")
result.message = message
result.error = error.stack
@ -170,6 +178,7 @@ module.exports = new function init() {
}
function serialize(value) {
if (value === null || typeof value === "object") return String(value)
else if (typeof value === "function") return value.name || "<anonymous function>"
try {return JSON.stringify(value)} catch (e) {return String(value)}
}
function highlight(message) {
@ -178,7 +187,7 @@ module.exports = new function init() {
function report() {
for (var i = 0, r; r = results[i]; i++) {
if (!r.pass) console.info(r.context + ": " + highlight(r.message) + "\n\n" + r.error.match(/^(?:(?!^Error|[\/\\]ospec[\/\\]ospec\.js).)*$/m) + "\n\n", hasProcess ? "" : "color:red", hasProcess ? "" : "color:black")
if (!r.pass) console.error(r.context + ": " + highlight(r.message) + "\n\n" + r.error.match(/^(?:(?!Error|[\/\\]ospec[\/\\]ospec\.js).)*$/m) + "\n\n", hasProcess ? "" : "color:red", hasProcess ? "" : "color:black")
}
console.log(results.length + " tests completed in " + Math.round(new Date - start) + "ms")
}

View file

@ -4,6 +4,7 @@
"description": "A framework for building brilliant applications",
"main": "index.js",
"scripts": {
"build": "node bundler/bundler",
"test": "node ospec/bin/ospec"
},
"author": "Leo Horie",

View file

@ -1,43 +1,51 @@
"use strict"
var normalizeChildren = require("../render/normalizeChildren")
var Node = require("../render/node")
var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:\s*=\s*("|'|)(.*?)\2)?\]/
var selectorCache = {}
function hyperscript(selector) {
if (selectorCache[selector] === undefined) {
var match, tag, id, 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 pair = attrParser.exec(match[3])
attributes[pair[1]] = pair[3] || 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 (typeof selector === "string") {
if (selectorCache[selector] === undefined) {
var match, tag, id, 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 pair = attrParser.exec(match[3])
attributes[pair[1]] = pair[3] || true
}
}
if (children instanceof Array && children.length == 1 && children[0] != null && children[0].tag === "#") text = children[0].children
else childList = children
return namespace({tag: tag || "div", key: attrs.key, attrs: hasAttrs ? attrs : undefined, children: childList, text: text})
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 (children instanceof Array && children.length == 1 && children[0] != null && children[0].tag === "#") text = children[0].children
else childList = children
var vnode = Node(tag || "div", attrs.key, hasAttrs ? attrs : undefined, childList, text, undefined)
switch (vnode.tag) {
case "svg": changeNS("http://www.w3.org/2000/svg", vnode); break
case "math": changeNS("http://www.w3.org/1998/Math/MathML", vnode); break
}
return vnode
}
}
}
var attrs, children, childrenIndex
@ -54,15 +62,8 @@ function hyperscript(selector) {
for (var i = childrenIndex; i < arguments.length; i++) children.push(arguments[i])
}
return selectorCache[selector](attrs || {}, normalizeChildren(children))
}
function namespace(vnode) {
switch (vnode.tag) {
case "svg": changeNS("http://www.w3.org/2000/svg", vnode); break
case "math": changeNS("http://www.w3.org/1998/Math/MathML", vnode); break
}
return vnode
if (typeof selector === "string") return selectorCache[selector](attrs || {}, Node.normalizeChildren(children))
return Node(selector, attrs && attrs.key, attrs, Node.normalizeChildren(children), undefined, undefined)
}
function changeNS(ns, vnode) {

16
render/node.js Normal file
View file

@ -0,0 +1,16 @@
function Node(tag, key, attrs, children, text, dom) {
return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: {}}
}
Node.normalize = function(node) {
if (node instanceof Array) return Node("[", undefined, undefined, Node.normalizeChildren(node), undefined, undefined)
else if (node != null && typeof node !== "object") return Node("#", undefined, undefined, node, undefined, undefined)
return node
}
Node.normalizeChildren = function normalizeChildren(children) {
for (var i = 0; i < children.length; i++) {
children[i] = Node.normalize(children[i])
}
return children
}
module.exports = Node

View file

@ -1,7 +0,0 @@
module.exports = function normalizeChildren(children) {
for (var i = 0; i < children.length; i++) {
if (children[i] instanceof Array) children[i] = {tag: "[", key: undefined, attrs: undefined, children: normalizeChildren(children[i]), text: undefined}
else if (children[i] != null && typeof children[i] !== "object") children[i] = {tag: "#", key: undefined, attrs: undefined, children: children[i], text: undefined}
}
return children
}

View file

@ -1,6 +1,6 @@
"use strict"
var normalizeChildren = require("../render/normalizeChildren")
var Node = require("../render/node")
module.exports = function($window, onevent) {
var $doc = $window.document
@ -16,15 +16,19 @@ module.exports = function($window, onevent) {
}
function createNode(vnode, hooks) {
var tag = vnode.tag
if (vnode.attrs && vnode.attrs.oncreate) {
hooks.push(vnode.attrs.oncreate.bind(vnode, vnode))
if (vnode.attrs) {
if (vnode.attrs.oninit) vnode.attrs.oninit.call(vnode, vnode)
if (vnode.attrs.oncreate) hooks.push(vnode.attrs.oncreate.bind(vnode, vnode))
}
switch (tag) {
case "#": return createText(vnode)
case "<": return createHTML(vnode)
case "[": return createFragment(vnode, hooks)
default: return createElement(vnode, hooks)
if (typeof tag === "string") {
switch (tag) {
case "#": return createText(vnode)
case "<": return createHTML(vnode)
case "[": return createFragment(vnode, hooks)
default: return createElement(vnode, hooks)
}
}
else return createComponent(vnode, hooks)
}
function createText(vnode) {
return vnode.dom = $doc.createTextNode(vnode.children)
@ -72,7 +76,7 @@ module.exports = function($window, onevent) {
if (vnode.text != null) {
if (vnode.text !== "") element.textContent = vnode.text
else vnode.children = [{tag: "#", children: vnode.text}]
else vnode.children = [Node("#", undefined, undefined, vnode.text, undefined, undefined)]
}
if (vnode.children != null) {
@ -81,6 +85,14 @@ module.exports = function($window, onevent) {
}
return element
}
function createComponent(vnode, hooks) {
vnode.instance = Node.normalize(vnode.tag.view(vnode))
initLifecycle(vnode.tag, vnode, hooks)
var element = createNode(vnode.instance, hooks)
vnode.dom = vnode.instance.dom
vnode.domSize = vnode.instance.domSize
return element
}
//update
function updateNodes(parent, old, vnodes, hooks, nextSibling) {
@ -98,7 +110,7 @@ module.exports = function($window, onevent) {
else if (o != null && v != null && o.key === v.key) {
oldStart++, start++
updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling)
if (recycling) insertNode(parent, toFragment(v), nextSibling)
if (recycling) insertNode(parent, toFragment(o), nextSibling)
}
else {
var o = old[oldEnd]
@ -116,7 +128,7 @@ module.exports = function($window, onevent) {
if (o === v) oldEnd--, end--
else if (o != null && v != null && o.key === v.key) {
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling)
if (recycling) insertNode(parent, toFragment(v), nextSibling)
if (recycling) insertNode(parent, toFragment(o), nextSibling)
nextSibling = o.dom
oldEnd--, end--
}
@ -148,16 +160,17 @@ module.exports = function($window, onevent) {
function updateNode(parent, old, vnode, hooks, nextSibling, recycling) {
var oldTag = old.tag, tag = vnode.tag
if (oldTag === tag) {
if (recycling) {
if (vnode.attrs && vnode.attrs.oncreate) hooks.push(vnode.attrs.oncreate.bind(vnode, vnode))
}
else if (vnode.attrs && vnode.attrs.onupdate) hooks.push(vnode.attrs.onupdate.bind(vnode, vnode))
switch (oldTag) {
case "#": updateText(old, vnode); break
case "<": updateHTML(parent, old, vnode, nextSibling); break
case "[": updateFragment(parent, old, vnode, hooks, nextSibling); break
default: updateElement(old, vnode, hooks)
vnode.state = old.state
if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks, recycling)
if (typeof oldTag === "string") {
switch (oldTag) {
case "#": updateText(old, vnode); break
case "<": updateHTML(parent, old, vnode, nextSibling); break
case "[": updateFragment(parent, old, vnode, hooks, nextSibling); break
default: updateElement(old, vnode, hooks)
}
}
else updateComponent(parent, old, vnode, hooks, nextSibling, recycling)
}
else {
removeNode(parent, old, null, false)
@ -189,7 +202,7 @@ module.exports = function($window, onevent) {
domSize += child.domSize || 1
}
}
if (domSize != 1) vnode.domSize = domSize
if (domSize !== 1) vnode.domSize = domSize
}
}
function updateElement(old, vnode, hooks) {
@ -199,11 +212,18 @@ module.exports = function($window, onevent) {
if (old.text.toString() !== vnode.text.toString()) old.dom.firstChild.nodeValue = vnode.text
}
else {
if (old.text != null) old.children = [{tag: "#", children: old.text, dom: old.dom.firstChild}]
if (vnode.text != null) vnode.children = [{tag: "#", children: vnode.text}]
if (old.text != null) old.children = [Node("#", undefined, undefined, old.text, undefined, old.dom.firstChild)]
if (vnode.text != null) vnode.children = [Node("#", undefined, undefined, vnode.text, undefined, undefined)]
updateNodes(element, old.children, vnode.children, hooks, null)
}
}
function updateComponent(parent, old, vnode, hooks, nextSibling, recycling) {
vnode.instance = Node.normalize(vnode.tag.view(vnode))
updateLifecycle(vnode.tag, vnode, hooks, recycling)
updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling)
vnode.dom = vnode.instance.dom
vnode.domSize = vnode.instance.domSize
}
function isRecyclable(old, vnodes) {
if (old.pool != null && Math.abs(old.pool.length - vnodes.length) <= Math.abs(old.length - vnodes.length)) {
var oldChildrenLength = old[0] && old[0].children && old[0].children.length || 0
@ -262,9 +282,20 @@ module.exports = function($window, onevent) {
}
}
function removeNode(parent, vnode, context, deferred) {
if (vnode.attrs && vnode.attrs.onbeforeremove && deferred === false) {
vnode.attrs.onbeforeremove.call(vnode, vnode, function() {removeNode(parent, vnode, context, true)})
return
if (deferred === false) {
var expected = 0, called = 0
var callback = function() {
if (++called === expected) removeNode(parent, vnode, context, true)
}
if (vnode.attrs && vnode.attrs.onbeforeremove) {
expected++
vnode.attrs.onbeforeremove.call(vnode, vnode, callback)
}
if (typeof vnode.tag !== "string" && vnode.tag.onbeforeremove) {
expected++
vnode.tag.onbeforeremove.call(vnode, vnode, callback)
}
if (expected > 0) return
}
onremove(vnode)
@ -285,9 +316,10 @@ module.exports = function($window, onevent) {
}
function onremove(vnode) {
if (vnode.attrs && vnode.attrs.onremove) vnode.attrs.onremove.call(vnode, vnode)
if (typeof vnode.tag !== "string" && vnode.tag.onremove) vnode.tag.onremove.call(vnode, vnode)
var children = vnode.children
if (children) {
if (children instanceof Array) {
for (var i = 0; i < children.length; i++) {
var child = children[i]
if (child != null) onremove(child)
@ -341,7 +373,7 @@ module.exports = function($window, onevent) {
}
}
function isLifecycleMethod(attr) {
return attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove"
return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove"
}
function isAttribute(attr) {
return attr === "href" || attr === "list" || attr === "form"// || attr === "type" || attr === "width" || attr === "height"
@ -364,15 +396,23 @@ module.exports = function($window, onevent) {
}
}
//lifecycle
function initLifecycle(source, vnode, hooks) {
if (source.oninit != null) source.oninit.call(vnode, vnode)
if (source.oncreate != null) hooks.push(source.oncreate.bind(vnode, vnode))
}
function updateLifecycle(source, vnode, hooks, recycling) {
if (recycling) initLifecycle(source, vnode, hooks)
else if (source.onupdate != null) hooks.push(source.onupdate.bind(vnode, vnode))
}
function render(dom, vnodes) {
//if (dom.lastRedraw + 16 > performance.now() && vnodes.length > 0) return
//dom.lastRedraw = performance.now()
var hooks = []
var active = $doc.activeElement
if (!dom.vnodes) dom.vnodes = []
if (dom.vnodes == null) dom.vnodes = []
if (!(vnodes instanceof Array)) vnodes = [vnodes]
updateNodes(dom, dom.vnodes, normalizeChildren(vnodes), hooks, null)
updateNodes(dom, dom.vnodes, Node.normalizeChildren(vnodes), hooks, null)
for (var i = 0; i < hooks.length; i++) hooks[i]()
dom.vnodes = vnodes
if ($doc.activeElement !== active) active.focus()

View file

@ -9,10 +9,14 @@
<script src="../../test-utils/callAsync.js"></script>
<script src="../../test-utils/domMock.js"></script>
<script src="../../render/normalizeChildren.js"></script>
<script src="../../render/node.js"></script>
<script src="../../render/trust.js"></script>
<script src="../../render/hyperscript.js"></script>
<script src="../../render/render.js"></script>
<script src="test-hyperscript.js"></script>
<script src="test-trust.js"></script>
<script src="test-normalize.js"></script>
<script src="test-normalizeChildren.js"></script>
<script src="test-createText.js"></script>
<script src="test-createHTML.js"></script>
<script src="test-createFragment.js"></script>
@ -23,6 +27,7 @@
<script src="test-updateFragment.js"></script>
<script src="test-updateElement.js"></script>
<script src="test-updateNodes.js"></script>
<script src="test-oninit.js"></script>
<script src="test-oncreate.js"></script>
<script src="test-onupdate.js"></script>
<script src="test-onremove.js"></script>
@ -31,6 +36,7 @@
<script src="test-event.js"></script>
<script src="test-input.js"></script>
<script src="test-textContent.js"></script>
<script src="test-component.js"></script>
<script>require("../../ospec/ospec").run()</script>
</body>

View file

@ -0,0 +1,459 @@
"use strict"
var o = require("../../ospec/ospec")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
o.spec("component", function() {
var $window, root, render
o.beforeEach(function() {
$window = domMock()
root = $window.document.createElement("div")
render = vdom($window).render
})
o.spec("basics", function() {
o("works", function() {
var component = {
view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"}
}
}
var node = {tag: component}
render(root, [node])
o(root.firstChild.nodeName).equals("DIV")
o(root.firstChild.attributes["id"].nodeValue).equals("a")
o(root.firstChild.firstChild.nodeValue).equals("b")
})
o("receives arguments", function() {
var component = {
view: function(vnode) {
return {tag: "div", attrs: vnode.attrs, text: vnode.text}
}
}
var node = {tag: component, attrs: {id: "a"}, text: "b"}
render(root, [node])
o(root.firstChild.nodeName).equals("DIV")
o(root.firstChild.attributes["id"].nodeValue).equals("a")
o(root.firstChild.firstChild.nodeValue).equals("b")
})
o("updates", function() {
var component = {
view: function(vnode) {
return {tag: "div", attrs: vnode.attrs, text: vnode.text}
}
}
render(root, [{tag: component, attrs: {id: "a"}, text: "b"}])
render(root, [{tag: component, attrs: {id: "c"}, text: "d"}])
o(root.firstChild.nodeName).equals("DIV")
o(root.firstChild.attributes["id"].nodeValue).equals("c")
o(root.firstChild.firstChild.nodeValue).equals("d")
})
o("removes", function() {
var component = {
view: function(vnode) {
return {tag: "div"}
}
}
var div = {tag: "div", key: 2}
render(root, [{tag: component, key: 1}, div])
render(root, [{tag: "div", key: 2}])
o(root.childNodes.length).equals(1)
o(root.firstChild).equals(div.dom)
})
})
o.spec("return value", function() {
o("can return fragments", function() {
var component = {
view: function(vnode) {
return [
{tag: "label"},
{tag: "input"},
]
}
}
render(root, [{tag: component}])
o(root.childNodes.length).equals(2)
o(root.childNodes[0].nodeName).equals("LABEL")
o(root.childNodes[1].nodeName).equals("INPUT")
})
o("can return string", function() {
var component = {
view: function(vnode) {
return "a"
}
}
render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("a")
})
o("can return falsy string", function() {
var component = {
view: function(vnode) {
return ""
}
}
render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("")
})
o("can return number", function() {
var component = {
view: function(vnode) {
return 1
}
}
render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("1")
})
o("can return falsy number", function() {
var component = {
view: function(vnode) {
return 0
}
}
render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("0")
})
o("can return boolean", function() {
var component = {
view: function(vnode) {
return true
}
}
render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("true")
})
o("can return falsy boolean", function() {
var component = {
view: function(vnode) {
return false
}
}
render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("false")
})
o("can update when returning fragments", function() {
var component = {
view: function(vnode) {
return [
{tag: "label"},
{tag: "input"},
]
}
}
render(root, [{tag: component}])
render(root, [{tag: component}])
o(root.childNodes.length).equals(2)
o(root.childNodes[0].nodeName).equals("LABEL")
o(root.childNodes[1].nodeName).equals("INPUT")
})
o("can update when returning primitive", function() {
var component = {
view: function(vnode) {
return "a"
}
}
render(root, [{tag: component}])
render(root, [{tag: component}])
o(root.firstChild.nodeType).equals(3)
o(root.firstChild.nodeValue).equals("a")
})
o("can remove when returning fragments", function() {
var component = {
view: function(vnode) {
return [
{tag: "label"},
{tag: "input"},
]
}
}
var div = {tag: "div", key: 2}
render(root, [{tag: component, key: 1}, div])
render(root, [{tag: "div", key: 2}])
o(root.childNodes.length).equals(1)
o(root.firstChild).equals(div.dom)
})
o("can remove when returning primitive", function() {
var component = {
view: function(vnode) {
return "a"
}
}
var div = {tag: "div", key: 2}
render(root, [{tag: component, key: 1}, div])
render(root, [{tag: "div", key: 2}])
o(root.childNodes.length).equals(1)
o(root.firstChild).equals(div.dom)
})
})
o.spec("lifecycle", function() {
o("calls oninit", function() {
var called = 0
var component = {
oninit: function(vnode) {
called++
o(vnode.tag).equals(component)
o(vnode.dom).equals(undefined)
o(root.childNodes.length).equals(0)
},
view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"}
}
}
var node = {tag: component}
render(root, [node])
o(called).equals(1)
o(root.firstChild.nodeName).equals("DIV")
o(root.firstChild.attributes["id"].nodeValue).equals("a")
o(root.firstChild.firstChild.nodeValue).equals("b")
})
o("calls oninit when returning fragment", function() {
var called = 0
var component = {
oninit: function(vnode) {
called++
o(vnode.tag).equals(component)
o(vnode.dom).equals(undefined)
o(root.childNodes.length).equals(0)
},
view: function() {
return [{tag: "div", attrs: {id: "a"}, text: "b"}]
}
}
var node = {tag: component}
render(root, [node])
o(called).equals(1)
o(root.firstChild.nodeName).equals("DIV")
o(root.firstChild.attributes["id"].nodeValue).equals("a")
o(root.firstChild.firstChild.nodeValue).equals("b")
})
o("calls oncreate", function() {
var called = 0
var component = {
oncreate: function(vnode) {
called++
o(vnode.dom).notEquals(undefined)
o(vnode.dom).equals(root.firstChild)
o(root.childNodes.length).equals(1)
},
view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"}
}
}
var node = {tag: component}
render(root, [node])
o(called).equals(1)
o(root.firstChild.nodeName).equals("DIV")
o(root.firstChild.attributes["id"].nodeValue).equals("a")
o(root.firstChild.firstChild.nodeValue).equals("b")
})
o("calls oncreate when returning fragment", function() {
var called = 0
var component = {
oncreate: function(vnode) {
called++
o(vnode.dom).notEquals(undefined)
o(vnode.dom).equals(root.firstChild)
o(root.childNodes.length).equals(1)
},
view: function() {
return [{tag: "div", attrs: {id: "a"}, text: "b"}]
}
}
var node = {tag: component}
render(root, [node])
o(called).equals(1)
o(root.firstChild.nodeName).equals("DIV")
o(root.firstChild.attributes["id"].nodeValue).equals("a")
o(root.firstChild.firstChild.nodeValue).equals("b")
})
o("calls onupdate", function() {
var called = 0
var component = {
onupdate: function(vnode) {
called++
o(vnode.dom).notEquals(undefined)
o(vnode.dom).equals(root.firstChild)
o(root.childNodes.length).equals(1)
},
view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"}
}
}
render(root, [{tag: component}])
o(called).equals(0)
render(root, [{tag: component}])
o(called).equals(1)
o(root.firstChild.nodeName).equals("DIV")
o(root.firstChild.attributes["id"].nodeValue).equals("a")
o(root.firstChild.firstChild.nodeValue).equals("b")
})
o("calls onupdate when returning fragment", function() {
var called = 0
var component = {
onupdate: function(vnode) {
called++
o(vnode.dom).notEquals(undefined)
o(vnode.dom).equals(root.firstChild)
o(root.childNodes.length).equals(1)
},
view: function() {
return [{tag: "div", attrs: {id: "a"}, text: "b"}]
}
}
render(root, [{tag: component}])
o(called).equals(0)
render(root, [{tag: component}])
o(called).equals(1)
o(root.firstChild.nodeName).equals("DIV")
o(root.firstChild.attributes["id"].nodeValue).equals("a")
o(root.firstChild.firstChild.nodeValue).equals("b")
})
o("calls onremove", function() {
var called = 0
var component = {
onremove: function(vnode) {
called++
o(vnode.dom).notEquals(undefined)
o(vnode.dom).equals(root.firstChild)
o(root.childNodes.length).equals(1)
},
view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"}
}
}
render(root, [{tag: component}])
o(called).equals(0)
render(root, [])
o(called).equals(1)
o(root.childNodes.length).equals(0)
})
o("calls onremove when returning fragment", function() {
var called = 0
var component = {
onremove: function(vnode) {
called++
o(vnode.dom).notEquals(undefined)
o(vnode.dom).equals(root.firstChild)
o(root.childNodes.length).equals(1)
},
view: function() {
return [{tag: "div", attrs: {id: "a"}, text: "b"}]
}
}
render(root, [{tag: component}])
o(called).equals(0)
render(root, [])
o(called).equals(1)
o(root.childNodes.length).equals(0)
})
o("calls onbeforeremove", function() {
var called = 0
var component = {
onbeforeremove: function(vnode, done) {
called++
o(vnode.dom).notEquals(undefined)
o(vnode.dom).equals(root.firstChild)
o(root.childNodes.length).equals(1)
done()
},
view: function() {
return {tag: "div", attrs: {id: "a"}, text: "b"}
}
}
render(root, [{tag: component}])
o(called).equals(0)
render(root, [])
o(called).equals(1)
o(root.childNodes.length).equals(0)
})
o("calls onbeforeremove when returning fragment", function() {
var called = 0
var component = {
onbeforeremove: function(vnode, done) {
called++
o(vnode.dom).notEquals(undefined)
o(vnode.dom).equals(root.firstChild)
o(root.childNodes.length).equals(1)
done()
},
view: function() {
return [{tag: "div", attrs: {id: "a"}, text: "b"}]
}
}
render(root, [{tag: component}])
o(called).equals(0)
render(root, [])
o(called).equals(1)
o(root.childNodes.length).equals(0)
})
})
})

View file

@ -376,4 +376,20 @@ o.spec("hyperscript", function() {
o(vnode.children[0].ns).equals("http://www.w3.org/1998/Math/MathML")
})
})
o.spec("components", function() {
o("works", function() {
var component = {
view: function() {
return m("div")
}
}
var vnode = m(component, {id: "a"}, "b")
o(vnode.tag).equals(component)
o(vnode.attrs.id).equals("a")
o(vnode.children.length).equals(1)
o(vnode.children[0].tag).equals("#")
o(vnode.children[0].children).equals("b")
})
})
})

View file

@ -0,0 +1,58 @@
"use strict"
var o = require("../../ospec/ospec")
var Node = require("../../render/node")
o.spec("normalize", function() {
o("normalizes array into fragment", function() {
var node = Node.normalize([])
o(node.tag).equals("[")
o(node.children.length).equals(0)
})
o("normalizes nested array into fragment", function() {
var node = Node.normalize([[]])
o(node.tag).equals("[")
o(node.children.length).equals(1)
o(node.children[0].tag).equals("[")
o(node.children[0].children.length).equals(0)
})
o("normalizes string into text node", function() {
var node = Node.normalize("a")
o(node.tag).equals("#")
o(node.children).equals("a")
})
o("normalizes falsy string into text node", function() {
var node = Node.normalize("")
o(node.tag).equals("#")
o(node.children).equals("")
})
o("normalizes number into text node", function() {
var node = Node.normalize(1)
o(node.tag).equals("#")
o(node.children).equals(1)
})
o("normalizes falsy number into text node", function() {
var node = Node.normalize(0)
o(node.tag).equals("#")
o(node.children).equals(0)
})
o("normalizes boolean into text node", function() {
var node = Node.normalize(true)
o(node.tag).equals("#")
o(node.children).equals(true)
})
o("normalizes falsy boolean into text node", function() {
var node = Node.normalize(false)
o(node.tag).equals("#")
o(node.children).equals(false)
})
})

View file

@ -0,0 +1,20 @@
"use strict"
var o = require("../../ospec/ospec")
var Node = require("../../render/node")
o.spec("normalizeChildren", function() {
o("normalizes arrays into fragments", function() {
var children = Node.normalizeChildren([[]])
o(children[0].tag).equals("[")
o(children[0].children.length).equals(0)
})
o("normalizes strings into text nodes", function() {
var children = Node.normalizeChildren(["a"])
o(children[0].tag).equals("#")
o(children[0].children).equals("a")
})
})

216
render/tests/test-oninit.js Normal file
View file

@ -0,0 +1,216 @@
"use strict"
var o = require("../../ospec/ospec")
var domMock = require("../../test-utils/domMock")
var vdom = require("../../render/render")
o.spec("oninit", function() {
var $window, root, render
o.beforeEach(function() {
$window = domMock()
root = $window.document.createElement("div")
render = vdom($window).render
})
o("calls oninit when creating element", function() {
var callback = o.spy()
var vnode = {tag: "div", attrs: {oninit: callback}}
render(root, [vnode])
o(callback.callCount).equals(1)
o(callback.this).equals(vnode)
o(callback.args[0]).equals(vnode)
})
o("calls oninit when creating text", function() {
var callback = o.spy()
var vnode = {tag: "#", attrs: {oninit: callback}, children: "a"}
render(root, [vnode])
o(callback.callCount).equals(1)
o(callback.this).equals(vnode)
o(callback.args[0]).equals(vnode)
})
o("calls oninit when creating fragment", function() {
var callback = o.spy()
var vnode = {tag: "[", attrs: {oninit: callback}, children: []}
render(root, [vnode])
o(callback.callCount).equals(1)
o(callback.this).equals(vnode)
o(callback.args[0]).equals(vnode)
})
o("calls oninit when creating html", function() {
var callback = o.spy()
var vnode = {tag: "<", attrs: {oninit: callback}, children: "a"}
render(root, [vnode])
o(callback.callCount).equals(1)
o(callback.this).equals(vnode)
o(callback.args[0]).equals(vnode)
})
o("calls oninit when replacing keyed", function() {
var createDiv = o.spy()
var createA = o.spy()
var vnode = {tag: "div", key: 1, attrs: {oninit: createDiv}}
var updated = {tag: "a", key: 1, attrs: {oninit: createA}}
render(root, [vnode])
render(root, [updated])
o(createDiv.callCount).equals(1)
o(createDiv.this).equals(vnode)
o(createDiv.args[0]).equals(vnode)
o(createA.callCount).equals(1)
o(createA.this).equals(updated)
o(createA.args[0]).equals(updated)
})
o("does not call oninit when noop", function() {
var create = o.spy()
var update = o.spy()
var vnode = {tag: "div", attrs: {oninit: create}}
var updated = {tag: "div", attrs: {oninit: update}}
render(root, [vnode])
render(root, [updated])
o(create.callCount).equals(1)
o(create.this).equals(vnode)
o(create.args[0]).equals(vnode)
o(update.callCount).equals(0)
})
o("does not call oninit when updating attr", function() {
var create = o.spy()
var update = o.spy()
var vnode = {tag: "div", attrs: {oninit: create}}
var updated = {tag: "div", attrs: {oninit: update, id: "a"}}
render(root, [vnode])
render(root, [updated])
o(create.callCount).equals(1)
o(create.this).equals(vnode)
o(create.args[0]).equals(vnode)
o(update.callCount).equals(0)
})
o("does not call oninit when updating children", function() {
var create = o.spy()
var update = o.spy()
var vnode = {tag: "div", attrs: {oninit: create}, children: [{tag: "a"}]}
var updated = {tag: "div", attrs: {oninit: update}, children: [{tag: "b"}]}
render(root, [vnode])
render(root, [updated])
o(create.callCount).equals(1)
o(create.this).equals(vnode)
o(create.args[0]).equals(vnode)
o(update.callCount).equals(0)
})
o("does not call oninit when updating keyed", function() {
var create = o.spy()
var update = o.spy()
var vnode = {tag: "div", key: 1, attrs: {oninit: create}}
var otherVnode = {tag: "a", key: 2}
var updated = {tag: "div", key: 1, attrs: {oninit: update}}
var otherUpdated = {tag: "a", key: 2}
render(root, [vnode, otherVnode])
render(root, [otherUpdated, updated])
o(create.callCount).equals(1)
o(create.this).equals(vnode)
o(create.args[0]).equals(vnode)
o(update.callCount).equals(0)
})
o("does not call oninit when removing", function() {
var create = o.spy()
var update = o.spy()
var vnode = {tag: "div", attrs: {oninit: create}}
render(root, [vnode])
render(root, [])
o(create.callCount).equals(1)
o(create.this).equals(vnode)
o(create.args[0]).equals(vnode)
})
o("calls oninit when recycling", function() {
var create = o.spy()
var update = o.spy()
var vnode = {tag: "div", key: 1, attrs: {oninit: create}}
var updated = {tag: "div", key: 1, attrs: {oninit: update}}
render(root, [vnode])
render(root, [])
render(root, [updated])
o(vnode.dom).equals(updated.dom)
o(create.callCount).equals(1)
o(create.this).equals(vnode)
o(create.args[0]).equals(vnode)
o(update.callCount).equals(1)
o(update.this).equals(updated)
o(update.args[0]).equals(updated)
})
o("calls oninit at the same step as onupdate", function() {
var create = o.spy()
var update = o.spy()
var callback = o.spy()
var vnode = {tag: "div", attrs: {onupdate: create}, children: []}
var updated = {tag: "div", attrs: {onupdate: update}, children: [{tag: "a", attrs: {oninit: callback}}]}
render(root, [vnode])
render(root, [updated])
o(create.callCount).equals(0)
o(update.callCount).equals(1)
o(update.this).equals(updated)
o(update.args[0]).equals(updated)
o(callback.callCount).equals(1)
o(callback.this).equals(updated.children[0])
o(callback.args[0]).equals(updated.children[0])
})
o("calls oninit before full DOM creation", function() {
var called = false
var vnode = {tag: "div", children: [
{tag: "a", attrs: {oninit: create}, children: [
{tag: "b"}
]}
]}
render(root, [vnode])
function create(vnode) {
called = true
o(vnode.dom).equals(undefined)
o(root.childNodes.length).equals(0)
}
o(called).equals(true)
})
o("does not set oninit as an event handler", function() {
var create = o.spy()
var vnode = {tag: "div", attrs: {oninit: create}, children: []}
render(root, [vnode])
o(vnode.dom.oninit).equals(undefined)
o(vnode.dom.attributes["oninit"]).equals(undefined)
})
o("calls oninit on recycle", function() {
var create = o.spy()
var vnodes = [{tag: "div", key: 1, attrs: {oninit: create}}]
var temp = []
var updated = [{tag: "div", key: 1, attrs: {oninit: create}}]
render(root, vnodes)
render(root, temp)
render(root, updated)
o(create.callCount).equals(2)
})
})

View file

@ -0,0 +1,20 @@
"use strict"
var o = require("../../ospec/ospec")
var domMock = require("../../test-utils/domMock")
var trust = require("../../render/trust")
o.spec("trust", function() {
o("works with html", function() {
var vnode = trust("<a></a>")
o(vnode.tag).equals("<")
o(vnode.children).equals("<a></a>")
})
o("works with text", function() {
var vnode = trust("abc")
o(vnode.tag).equals("<")
o(vnode.children).equals("abc")
})
})

View file

@ -1,3 +1,7 @@
"use strict"
var Node = require("../render/node")
module.exports = function(html) {
return {tag: "<", key: undefined, attrs: undefined, children: html, text: undefined}
return Node("<", undefined, undefined, html, undefined, undefined)
}