Merge branch 'next' of https://github.com/lhorie/mithril.js into test-fix

This commit is contained in:
impinball 2016-03-02 14:05:21 -05:00
commit 199da9c21b
19 changed files with 4732 additions and 2868 deletions

View file

@ -68,7 +68,7 @@ window.templateConverter = (function () {
if (el.attrs.class) {
virtual += "." + el.attrs.class.replace(/\s+/g, ".")
el.attrs.class = undefined
delete el.attrs.class
}
each(Object.keys(el.attrs).sort(), function (attrName) {

View file

@ -165,8 +165,8 @@ var MyComponent = {
m.render(document.body, [
//the two lines below are equivalent
m(component, {data: "world"}),
m.component(component, {data: "world"})
m(MyComponent, {data: "world"}),
m.component(MyComponent, {data: "world"})
])
```
@ -362,7 +362,7 @@ var MyComponent = {
},
view: function(ctrl) {
return m("ul", [
ctrl.things().map(function(name) {
ctrl.things().map(function(thing) {
return m("li", thing.name)
})
]);
@ -590,7 +590,7 @@ where:
- **Component component**
A component is supposed to be an Object with two keys: `controller` and `view`. Each of these should point to a Javascript function. If a contoller is not specified, Mithril will automatically create an empty controller function.
A component is supposed to be an Object with two keys: `controller` and `view`. Each of these should point to a Javascript function. If a controller is not specified, Mithril will automatically create an empty controller function.
- **Object attributes**

View file

@ -10,7 +10,7 @@
---
Redraws the view for the currently active component. Use [`m.mount()`](mithril.mount.md) or [`m.route()`](mithril.route.md) to activate a component.
Redraws the view. Use [`m.mount()`](mithril.mount.md) or [`m.route()`](mithril.route.md) to activate a component.
Calling `m.redraw` triggers a redraw regardless of whether AJAX requests (and other asynchronous services) are completed. Therefore, you should ensure that templates have null checks in place to account for the possibility of variables being uninitialized when the forced redraw occurs.

View file

@ -4,7 +4,7 @@
If you already have your HTML written and want to convert it into a Mithril template, you can use the tool below for one-off manual conversion.
[Template Converter](tools/template-converter.html)
[Template Converter](http://arthurclemens.github.io/mithril-template-converter/index.html)
---

19
mithril.d.ts vendored
View file

@ -27,7 +27,7 @@ declare module _mithril {
MithrilVirtualElement<T> |
MithrilComponent<T>>
): MithrilVirtualElement<T>;
/**
* Initializes a component for use with m.render, m.mount, etc.
*
@ -127,6 +127,19 @@ declare module _mithril {
callbackThis: any
): (e: Event) => any;
/**
* Returns a event handler that can be bound to an element, firing with
* the specified property.
*
* @param attributeName Name of the element's attribute to bind to.
* @param property The property to bind.
* @return A function suitable for listening to an event.
*/
withAttr<T>(
attributeName: string,
property: MithrilBasicProperty<T>
) : (e: Event) => any;
/**
* @deprecated Use m.mount instead
*/
@ -561,7 +574,7 @@ declare module _mithril {
* @see MithrilControllerConstructor
*/
interface MithrilControllerFunction<T extends MithrilController> {
(): T;
(opts?: any): T;
}
/**
@ -603,7 +616,7 @@ declare module _mithril {
*
* @see m.component
*/
view(ctrl: T): MithrilVirtualElement<T>;
view(ctrl?: T, opts?: any): MithrilVirtualElement<T>;
}
/**

2677
mithril.js

File diff suppressed because it is too large Load diff

4
mithril.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -47,6 +47,9 @@ window.mock = (function () {
"use strict"
var window = {}
window.window = window
var document = window.document = {
// FIXME: add document.createRange().createContextualFragment()
@ -138,7 +141,7 @@ window.mock = (function () {
name = name.toLowerCase()
var out = []
function traverse(node){
function traverse(node) {
if (node.childNodes && node.childNodes.length > 0) {
node.childNodes.forEach(function (curr) {
if (curr.nodeName.toLowerCase() === name) {
@ -158,7 +161,7 @@ window.mock = (function () {
window.scrollTo = function () {}
;(function (window) {
;(function () {
// This is an actual conforming implementation of the
// requestAnimationFrame spec, with the nonstandard extension of
// rAF.$resolve for running the callbacks. It works in Node and the
@ -214,7 +217,7 @@ window.mock = (function () {
}
window.requestAnimationFrame = requestAnimationFrame
})(window)
})()
window.XMLHttpRequest = (function () {
function Request() {

8
test/isolation-test.html Normal file
View file

@ -0,0 +1,8 @@
<!doctype html>
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.0.3/es5-shim.min.js"></script>-->
<script src="test.js"></script>
<script src="mock.js"></script>
<script src="../mithril.js"></script>
<p>Open the console to see the test report</p>
<script src="./isolation-test.js"></script>

159
test/isolation-test.js Normal file
View file

@ -0,0 +1,159 @@
/* global m, test, mock */
(function () {
"use strict"
m.deps(mock.window)
test(function () { // eslint-disable-line max-statements
var root = mock.document.createElement("div")
var retain = false
var flag = true
var loaded1 = null
var loaded2 = null
var loaded1a = null
var loaded2a = null
var Comp1 = {
controller: function () {
loaded1 = true
this.onunload = function () {
loaded1 = false
}
},
view: function () {
if (retain) {
return {subtree: "retain"}
} else {
return m("a", {
config: function (el, init, ctx) {
if (!init) {
loaded1a = true
ctx.onunload = function () {
loaded1a = false
}
}
}
})
}
}
}
var Comp2 = {
controller: function () {
loaded2 = true
this.onunload = function () {
loaded2 = false
}
},
view: function () {
if (retain) {
return {subtree: "retain"}
} else {
return m("b", {
config: function (el, init, ctx) {
if (!init) {
loaded2a = true
ctx.onunload = function () {
loaded2a = false
}
}
}
})
}
}
}
var Root = {
view: function () {
return flag ? Comp1 : Comp2
}
}
m.mount(root, Root)
mock.requestAnimationFrame.$resolve()
// loaded 1
var result1 = loaded1 === true &&
loaded2 === null &&
loaded1a === true &&
loaded2a === null
retain = true
m.redraw(true)
mock.requestAnimationFrame.$resolve()
// retained
var result2 = loaded1 === true &&
loaded2 === null &&
loaded1a === true &&
loaded2a === null
flag = false
m.redraw(true)
mock.requestAnimationFrame.$resolve()
// loaded 2 while retained: both controllers are alive at the same time
// because dom element is retained
var result3 = loaded1 === true &&
loaded2 === true &&
loaded1a === true &&
loaded2a === null
retain = false
m.redraw(true)
mock.requestAnimationFrame.$resolve()
// unretained, i.e. 2 is now dynamic
var result4 = loaded1 === false &&
loaded2 === true &&
loaded1a === false &&
loaded2a === true
flag = true
m.redraw(true)
mock.requestAnimationFrame.$resolve()
// loaded 1 while dynamic
var result5 = loaded1 === true &&
loaded2 === false &&
loaded1a === true &&
loaded2a === false
return result1 && result2 && result3 && result4 && result5
})
/*
test(function() {
var root = mock.document.createElement("div")
var redraws = 0, data
var Root = {
view: function() {
return Comp
}
}
var Comp = {
controller: function() {
this.foo = m.request({method: "GET", url: "/foo"})
},
view: function(ctrl) {
redraws++
data = ctrl.foo()
return m("div")
}
}
m.mount(root, Root)
mock.requestAnimationFrame.$resolve()
mock.XMLHttpRequest.$instances.pop().onreadystatechange()
return redraws == 1 && data.url == "/foo"
})
*/
test.print(function (value) {
console.log(value) // eslint-disable-line no-console
})
})()

View file

@ -5,4 +5,4 @@
<script src="../mithril.js"></script>
<script src="mithril-tests.js"></script>
<p>Open the console to see the test report</p>
<p>Open the console to see the test report</p>

View file

@ -1,48 +0,0 @@
<p>Typing in the fields below should not move the cursor to the end of the input. Especially in Chrome</p>
<p>All inputs should update with the same value</p>
<p>Typing in an input should not prevent it from being updated by other inputs</p>
<div id="test"></div>
<script src="../mithril.js"></script>
<script>
var app = {}
app.controller = function() {
this.title = m.prop("hello world");
}
app.view = function(ctrl) {
return m("body", [
m("h1", ["Title: ", ctrl.title()]),
m("input[list=data]", {
onkeyup: m.withAttr("value", ctrl.title),
value: ctrl.title()
}),
m("datalist#data", [
m("option", "John"),
m("option", "Bob"),
m("option", "Mary")
]),
m("br"),
m("textarea", {
onkeyup: m.withAttr("value", ctrl.title),
value: ctrl.title()
}),
m("br"),
m("textarea", {
onkeyup: m.withAttr("value", ctrl.title)
}, ctrl.title()),
m("br"),
m("div[contenteditable]", {
style: {border: "1px solid #888"},
onkeyup: m.withAttr("innerHTML", ctrl.title)
}, ctrl.title()),
m("br"),
m("div[contenteditable]", {
style: {border: "1px solid #888"},
onkeyup: m.withAttr("innerHTML", ctrl.title)
}, m.trust(ctrl.title())),
]);
}
m.module(document.getElementById("test"), app);
</script>

View file

@ -1,135 +0,0 @@
<!doctype html>
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.0.3/es5-shim.min.js"></script>-->
<script src="test.js"></script>
<script src="mock.js"></script>
<script src="../mithril.js"></script>
<p>Open the console to see the test report</p>
<script>
function testMithril(mock) {
m.deps(mock)
test(function() {
var root = mock.document.createElement("div")
var retain = false
var flag = true
var loaded1 = null, loaded2 = null, loaded1a = null, loaded2a = null
var Root = {
view: function() {
return flag ? Comp1 : Comp2
}
}
var Comp1 = {
controller: function() {
loaded1 = true
this.onunload = function() {
loaded1 = false
}
},
view: function(ctrl) {
return retain ? {subtree: "retain"} : m("a", {config: function(el, init, ctx) {
if (!init) {
loaded1a = true
ctx.onunload = function() {
loaded1a = false
}
}
}})
}
}
var Comp2 = {
controller: function() {
loaded2 = true
this.onunload = function() {
loaded2 = false
}
},
view: function(ctrl) {
return retain ? {subtree: "retain"} : m("b", {config: function(el, init, ctx) {
if (!init) {
loaded2a = true
ctx.onunload = function() {
loaded2a = false
}
}
}})
}
}
m.mount(root, Root)
mock.requestAnimationFrame.$resolve()
//loaded 1
var result1 = loaded1 === true && loaded2 === null && loaded1a === true && loaded2a === null
retain = true
m.redraw(true)
mock.requestAnimationFrame.$resolve()
//retained
var result2 = loaded1 === true && loaded2 === null && loaded1a === true && loaded2a === null
flag = false
m.redraw(true)
mock.requestAnimationFrame.$resolve()
//loaded 2 while retained: both controllers are alive at the same time because dom element is retained
var result3 = loaded1 === true && loaded2 === true && loaded1a === true && loaded2a === null
retain = false
m.redraw(true)
mock.requestAnimationFrame.$resolve()
//unretained, i.e. 2 is now dynamic
var result4 = loaded1 === false && loaded2 === true && loaded1a === false && loaded2a === true
flag = true
m.redraw(true)
mock.requestAnimationFrame.$resolve()
//loaded 1 while dynamic
var result5 = loaded1 === true && loaded2 === false && loaded1a === true && loaded2a === false
return result1 && result2 && result3 && result4 && result5
})
/*
test(function() {
var root = mock.document.createElement("div")
var redraws = 0, data
var Root = {
view: function() {
return Comp
}
}
var Comp = {
controller: function() {
this.foo = m.request({method: "GET", url: "/foo"})
},
view: function(ctrl) {
redraws++
data = ctrl.foo()
return m("div")
}
}
m.mount(root, Root)
mock.requestAnimationFrame.$resolve()
mock.XMLHttpRequest.$instances.pop().onreadystatechange()
return redraws == 1 && data.url == "/foo"
})
*/
}
//mocks
testMithril(mock.window)
test.print(function(value) {console.log(value)})
</script>

File diff suppressed because it is too large Load diff

View file

@ -1,40 +1,49 @@
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (item) {
for (var i = 0; i < this.length; i++) {
if (this[i] === item) return i
}
return -1
}
}
if (!Array.prototype.map) {
Array.prototype.map = function (callback) {
var results = []
for (var i = 0; i < this.length; i++) {
results[i] = callback(this[i], i, this)
}
return results
}
}
if (!Array.prototype.filter) {
Array.prototype.filter = function (callback) {
var results = []
for (var i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) results.push(this[i])
}
return results
}
}
if (!Object.keys) {
Object.keys = function () {
var keys = []
for (var i in this) keys.push(i)
return keys
}
}
(function (global) { // eslint-disable-line max-statements
"use strict"
var mock = {}
mock.window = (function () {
var window = {}
/* eslint-disable no-extend-native */
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (item) {
for (var i = 0; i < this.length; i++) {
if (this[i] === item) return i
}
return -1
}
}
if (!Array.prototype.map) {
Array.prototype.map = function (callback) {
var results = []
for (var i = 0; i < this.length; i++) {
results[i] = callback(this[i], i, this)
}
return results
}
}
if (!Array.prototype.filter) {
Array.prototype.filter = function (callback) {
var results = []
for (var i = 0; i < this.length; i++) {
if (callback(this[i], i, this)) results.push(this[i])
}
return results
}
}
if (!Object.keys) {
Object.keys = function () {
var keys = []
for (var i in this) if ({}.hasOwnProperty.call(this, i)) {
keys.push(i)
}
return keys
}
}
/* eslint-enable no-extend-native */
var window = global.mock = {window: window}
window.window = window
window.document = {}
window.document.childNodes = []
window.document.createElement = function (tag) {
@ -46,6 +55,7 @@ mock.window = (function () {
appendChild: window.document.appendChild,
removeChild: window.document.removeChild,
replaceChild: window.document.replaceChild,
insertBefore: function (node, reference) {
node.parentNode = this
var referenceIndex = this.childNodes.indexOf(reference)
@ -54,15 +64,18 @@ mock.window = (function () {
if (referenceIndex < 0) this.childNodes.push(node)
else this.childNodes.splice(referenceIndex, 0, node)
},
insertAdjacentHTML: function (position, html) {
//todo: accept markup
// todo: accept markup
if (position === "beforebegin") {
this.parentNode.insertBefore(window.document.createTextNode(html), this)
}
else if (position === "beforeend") {
this.parentNode.insertBefore(
window.document.createTextNode(html),
this)
} else if (position === "beforeend") {
this.appendChild(window.document.createTextNode(html))
}
},
setAttribute: function (name, value) {
this[name] = value.toString()
},
@ -70,7 +83,7 @@ mock.window = (function () {
this.namespaceURI = namespace
this[name] = value.toString()
},
getAttribute: function (name, value) {
getAttribute: function (name) {
return this[name]
},
addEventListener: function () {},
@ -104,16 +117,18 @@ mock.window = (function () {
this.childNodes.splice(index, 1)
child.parentNode = null
}
//getElementsByTagName is only used by JSONP tests, it's not required by Mithril
// getElementsByTagName is only used by JSONP tests, it's not required by
// Mithril
window.document.getElementsByTagName = function (name){
name = name.toLowerCase()
var out = []
var traverse = function (node){
function traverse(node) {
if (node.childNodes && node.childNodes.length > 0){
node.childNodes.map(function (curr){
if (curr.nodeName.toLowerCase() === name)
if (curr.nodeName.toLowerCase() === name) {
out.push(curr)
}
traverse(curr)
})
}
@ -136,8 +151,9 @@ mock.window = (function () {
callback()
}
}
window.XMLHttpRequest = (function () {
var request = function () {
function XMLHttpRequest() {
this.$headers = {}
this.setRequestHeader = function (key, value) {
this.$headers[key] = value
@ -150,21 +166,28 @@ mock.window = (function () {
this.responseText = JSON.stringify(this)
this.readyState = 4
this.status = 200
request.$instances.push(this)
XMLHttpRequest.$instances.push(this)
}
}
request.$instances = []
return request
}())
window.location = {search: "", pathname: "", hash: ""},
XMLHttpRequest.$instances = []
return XMLHttpRequest
})()
window.location = {search: "", pathname: "", hash: ""}
window.history = {}
window.history.$$length = 0
window.history.pushState = function (data, title, url) {
window.history.$$length++
window.location.pathname = window.location.search = window.location.hash = url
},
window.history.replaceState = function (data, title, url) {
window.location.pathname = window.location.search = window.location.hash = url
window.location.pathname =
window.location.search =
window.location.hash = url
}
return window
}())
window.history.replaceState = function (data, title, url) {
window.location.pathname =
window.location.search =
window.location.hash = url
}
})(this)

View file

@ -1,97 +0,0 @@
<!doctype html>
<html>
<head>
<title>SVG test</title>
<style>
.path {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: dash 5s linear alternate infinite;
-webkit-animation: dash 5s linear alternate infinite;
}
@keyframes dash {
from {
stroke-dashoffset: 1000;
}
to {
stroke-dashoffset: 0;
}
}
@-webkit-keyframes dash {
from {
stroke-dashoffset: 1000;
}
to {
stroke-dashoffset: 0;
}
}
</style>
</head>
<body>
<p>Since it's not possible to test SVG functionality from a NodeJS environment, this page can be used to test it in a browser.</p>
<p>This page should contain:</p>
<ul>
<li>an HTML link labeled "HTML link"</li>
<li>an SVG link labeled "SVG link"</li>
<li>a tilted blue square</li>
<li>a cat picture</li>
<li>an animated line drawing</li>
<li>a clock with the current time</li>
</ul>
<p>The links should open in a new tab. All items should display title tooltips when hovered over.</p>
<div id="test"></div>
<script src="../mithril.js"></script>
<script>
var svg = [
m("a[href='http://google.com'][target='_blank'][title='HTML link']", "HTML link"),
m("br"),
m("svg[width=180][height=200]", [
m("rect[x=50][y=50][height=100][width=100][transform='translate(30) rotate(45 50 50)'][title='Square']", {style: {stroke: "#000", fill: "#0086b2"}}),
m("a[href='http://google.com'][title='SVG link'][target=_new]", {style: {textDecoration: "underline"}}, [
m("text[x=0][y=20]", "SVG Link")
])
]),
m("svg[height='201px'][width='201px']", [
m("image[href='http://placekitten.com/201/201'][height='200px'][width='200px'][title='Cat picture']")
]),
m("svg[enable-background='new 0 0 340 333'][height='333px'][viewBox='0 0 340 333'][width='340px'][x='0px'][y='0px'][title='Line drawings']", [
m("path.path[d='M66.039,133.545c0,0-21-57,18-67s49-4,65,8s30,41,53,27s66,4,58,32s-5,44,18,57s22,46,0,45s-54-40-68-16s-40,88-83,48s11-61-11-80s-79-7-70-41C46.039,146.545,53.039,128.545,66.039,133.545z'][fill='#FFFFFF'][stroke='#000000'][stroke-miterlimit='10'][stroke-width='4']")
]),
m("svg[height='270px'][width='270px'][viewBox='0 0 270 270']", [
m("g[transform='translate(150,150)'][title='Clock']", [
m("g", [
m("circle[r='108'][fill='none'][stroke-width='4'][stroke='gray']"),
m("circle[r='97'][fill='none'][stroke-width='11'][stroke='black'][stroke-dasharray='4,46.789082'][transform='rotate(-1.5)']"),
m("circle[r='100'][fill='none'][stroke-width='5'][stroke='black'][stroke-dasharray='2,8.471976'][transform='rotate(-.873)']"),
]),
m("g[transform='rotate(180)']", [
m("g[id='hour']", [
m("line[stroke-width='5'][y2='75'][stroke-linecap='round'][stroke='blue'][opacity='.5']"),
m("animateTransform[attributeName='transform'][type='rotate'][repeatCount='indefinite'][dur='12h'][by='360']"),
m("circle[r='7']")
]),
m("g[id='minute']", [
m("line[stroke-width='4'][y2='93'][stroke-linecap='round'][stroke='green'][opacity='.9']"),
m("animateTransform[attributeName='transform'][type='rotate'][repeatCount='indefinite'][dur='60min'][by='360']"),
m("circle[r='6'][fill='red']")
]),
m("g[id='second']", [
m("line[stroke-width='2'][y1='-20'][y2=102][stroke-linecap='round'][stroke='red']"),
m("animateTransform[attributeName='transform'][type='rotate'][repeatCount='indefinite'][dur='60s'][by='360']"),
m("circle[r='4'][fill='blue']")
])
])
]),
m("script", 'var a=new Date,b=parseInt(a.getHours());b=b>12?b-12:b;var c=parseInt(a.getMinutes()),d=parseInt(a.getSeconds()),e=6*d,f=6*(c+d/60),g=30*(b+c/60+d/3600),h=document.getElementById("hour"),i=document.getElementById("minute"),j=document.getElementById("second");h.setAttribute("transform","rotate("+g.toString()+")"),i.setAttribute("transform","rotate("+f.toString()+")"),j.setAttribute("transform","rotate("+e.toString()+")")'),
]),
m("svg[height='200px'][width='200px']", [
m("foreignObject[x=0][y=0][width='100px'][height='100px'][transform='translate(0,0)']", m('div', {xmlns: "http://www.w3.org/1999/xhtml"}, m.trust('this is a piece of html rendered as <a href="http://www.w3.org/TR/SVG11/extend.html">SVG foreignObject</strong>')))
])
]
m.render(document.getElementById("test"), svg)
</script>
</body>
</html>

View file

@ -1,28 +1,38 @@
if (!this.console) {
var log = function (value) { document.write("<pre>" + value + "</pre>") }
this.console = {log: log, error: log}
}
/* eslint-env browser */
function test(condition) {
test.total++
(function (global) {
"use strict"
try {
if (!condition()) throw new Error("failed")
function log(value) {
document.write("<pre>" + value + "</pre>")
}
catch (e) {
console.error(e)
test.failures.push(condition)
}
}
test.total = 0
test.failures = []
test.print = function (print) {
for (var i = 0; i < test.failures.length; i++) {
print(test.failures[i].toString())
}
print("tests: " + test.total + "\nfailures: " + test.failures.length)
if (test.failures.length > 0) {
throw new Error(test.failures.length + " tests did not pass")
if (!global.console) {
global.console = {log: log, error: log}
}
}
global.test = test
function test(condition) {
test.total++
try {
if (!condition()) throw new Error("failed")
} catch (e) {
console.error(e) // eslint-disable-line no-console
test.failures.push(condition)
}
}
test.total = 0
test.failures = []
test.print = function (print) {
for (var i = 0; i < test.failures.length; i++) {
print(test.failures[i].toString())
}
print("tests: " + test.total + "\nfailures: " + test.failures.length)
if (test.failures.length > 0) {
throw new Error(test.failures.length + " tests did not pass")
}
}
})(this)

View file