Merge pull request #1 from lhorie/next

This commit is contained in:
Isiah Meadows 2015-11-02 10:47:43 -05:00
commit f53e783a57
40 changed files with 6233 additions and 10845 deletions

12
.eslintignore Normal file
View file

@ -0,0 +1,12 @@
# Most of these are build artifacts.
node_modules
**/*.min.js
archive
deploy
mithril.closure-compiler-externs.js
# This is merely a dependency for the documentation.
docs/layout/lib
# TODO: These are temporary, and need to be eventually enabled.
mithril.js

93
.eslintrc Normal file
View file

@ -0,0 +1,93 @@
{
"extends": "eslint:recommended",
"env": {
"browser": true
},
"rules": {
"indent": [2, "tab"],
"no-trailing-spaces": 2,
"linebreak-style": [2, "windows"],
"curly": [2, "multi-line"],
"dot-location": [2, "property"],
"dot-notation": 2,
"eqeqeq": [2, "allow-null"],
"no-alert": 2,
"no-caller": 2,
"guard-for-in": 2,
"no-empty-label": 2,
"no-eval": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-floating-decimal": 2,
"no-implied-eval": 2,
"no-invalid-this": 2,
"no-iterator": 2,
"no-lone-blocks": 2,
"no-loop-func": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-native-reassign": 2,
"no-new-func": 2,
"no-new-wrappers": 2,
"no-octal-escape": 2,
"no-proto": 2,
"no-self-compare": 2,
"no-sequences": 2,
"no-throw-literal": 2,
"no-unused-expressions": 2,
"no-useless-call": 2,
"no-void": 2,
"no-warning-comments": 1,
"radix": 2,
"wrap-iife": [2, "inside"],
"strict": [2, "function"],
"no-catch-shadow": 2,
"no-label-var": 2,
"no-shadow-restricted-names": 2,
"no-undef-init": 2,
"no-use-before-define": [2, "nofunc"],
"array-bracket-spacing": 2,
"block-spacing": 2,
"brace-style": [2, "1tbs", {"allowSingleLine": true}],
"camelcase": 2,
"comma-spacing": 2,
"comma-style": 2,
"consistent-this": [2, "self"],
"eol-last": 2,
"func-style": [2, "declaration"],
"key-spacing": 2,
"max-nested-callbacks": [2, 4],
"new-parens": 2,
"no-array-constructor": 2,
"no-lonely-if": 2,
"no-new-object": 2,
"no-multiple-empty-lines": [2, {"max": 2}],
"no-spaced-func": 2,
"no-unneeded-ternary": 2,
"object-curly-spacing": 2,
"one-var": [2, {"initialized": "never"}],
"operator-assignment": 2,
"operator-linebreak": 2,
"padded-blocks": [2, "never"],
"quote-props": [2, "as-needed"],
"quotes": [2, "double", "avoid-escape"],
"semi-spacing": 0,
"semi": [2, "never"],
"space-after-keywords": 2,
"space-before-function-paren": [2, {
"anonymous": "always",
"named": "never"
}],
"space-before-keywords": 2,
"space-in-parens": 2,
"space-infix-ops": [2, {"int32Hint": false}],
"space-unary-ops": 2,
"spaced-comment": [2, "always"],
"max-depth": [2, 4],
"max-len": [2, 80, 4],
"max-statements": [2, 20]
}
}

View file

@ -1,11 +1,5 @@
language: node_js
node_js:
- "0.10"
- "0.12"
- "4.1"
script:
- grunt test
- grunt teste2e
- stable
sudo: false

View file

@ -1,11 +1,11 @@
module.exports = function(grunt) {
var _ = require("lodash");
var version = "0.2.1";
/* eslint-env node */
module.exports = function (grunt) { // eslint-disable-line
var version = "0.2.1"
var inputFolder = "./docs";
var tempFolder = "./temp";
var archiveFolder = "./archive";
var outputFolder = "../mithril";
var inputFolder = "./docs"
var tempFolder = "./temp"
var archiveFolder = "./archive"
var outputFolder = "../mithril"
var guide = [
"auto-redrawing",
@ -22,7 +22,8 @@ module.exports = function(grunt) {
"routing",
"tools",
"web-services"
];
]
var api = [
"change-log",
"roadmap",
@ -42,178 +43,344 @@ module.exports = function(grunt) {
"mithril.trust",
"mithril.withAttr",
"mithril.xhr"
];
]
var md2htmlTasks = {}
function makeTasks(layout, pages) {
pages.forEach(function (name) {
var src = inputFolder + "/" + name + ".md"
var title = ""
if (grunt.file.exists(src)) {
title = grunt.file.read(src).split(/\n/)[0].substring(3) +
" - "
}
var md2htmlTasks = {};
var makeTasks = function(layout, pages) {
pages.map(function(name) {
var src = inputFolder + "/" + name + ".md";
var title = (grunt.file.exists(src)) ? grunt.file.read(src).split(/\n/)[0].substring(3) + ' - ' : '';
md2htmlTasks[name] = {
options: {layout: inputFolder + "/layout/" + layout + ".html", templateData: { "topic": title }},
files: [{src: [src], dest: tempFolder + "/" + name + ".html"}]
options: {
layout: inputFolder + "/layout/" + layout + ".html",
templateData: {topic: title}
},
files: [{
src: [src],
dest: tempFolder + "/" + name + ".html"
}]
}
})
};
makeTasks("guide", guide);
makeTasks("api", api);
}
var sauceBrowsers =[
{ browserName: 'firefox', version: '19', platform: 'XP' },
{ browserName: "internet explorer", platform: "XP", version: "6"},
{ browserName: "safari", platform: "OS X 10.9", version: "7"},
{ browserName: "iPad", platform: "OS X 10.9", version: "7.1"},
{ browserName: "opera", platform: "Linux", version: "12"},
{ browserName: "chrome", platform: "XP", version: "26"},
{ browserName: "chrome", platform: "Windows 8", version: "26"}
];
makeTasks("guide", guide)
makeTasks("api", api)
var sauceOnTestComplete = function(result, callback) {
var request = require('request');
var currentVersionArchiveFolder = archiveFolder + "/v" + version
var user = process.env.SAUCE_USERNAME;
var pass = process.env.SAUCE_ACCESS_KEY;
request.put({
url: ['https://saucelabs.com/rest/v1', user, 'jobs', result.job_id].join('/'),
auth: { user: user, pass: pass },
json: { passed: result.passed }
}, function (error, response) {
if (error) {
callback(error);
} else if (response.statusCode !== 200) {
callback(new Error('Unexpected response status: '
+ response.statusCode + "\n "));
} else {
callback(null, result.passed);
}
});
};
var sauceBaseOptions = {
username: process.env.SAUCE_USERNAME,
key: process.env.SAUCE_ACCESS_KEY,
testname: "Mithril Tests " + new Date().toJSON(),
browsers: sauceBrowsers,
sauceConfig: {
"record-video": false,
"record-screenshots": false,
},
build: process.env.TRAVIS_JOB_ID,
onTestComplete: sauceOnTestComplete,
tunnelTimeout: 5,
};
var sauceCustomOptions = {
testname: "Mithril Custom Tests "+ new Date().toJSON(),
urls: ["http://127.0.0.1:8000/tests/index.html"],
};
_.assign(sauceCustomOptions, sauceBaseOptions);
var sauceQunitOptions = {
testname: "qUnit Tests "+ new Date().toJSON(),
urls: ["http://127.0.0.1:8000/tests/e2e/test.html"],
};
_.assign(sauceQunitOptions, sauceBaseOptions);
var currentVersionArchiveFolder = archiveFolder + "/v" + version;
grunt.initConfig({
// Keep this in sync with the .eslintignore
eslint: {
options: {
extensions: [".js"],
fix: true
},
all: [
"**/*.js",
"!node_modules/**",
"!**/*.min.js",
"!archive/**",
"!deploy/**",
"!mithril.closure-compiler-externs.js",
"!docs/layout/lib/**",
// TODO(impinball): Finish this.
"!mithril.js"
]
},
mocha_phantomjs: { // eslint-disable-line camelcase
test: {
src: ["test/index.html"],
options: {
reporter: "dot"
}
}
},
md2html: md2htmlTasks,
uglify: {
options: {banner: "/*\nMithril v" + version + "\nhttp://github.com/lhorie/mithril.js\n(c) Leo Horie\nLicense: MIT\n*/", sourceMap: true},
options: {
banner: [
"/*",
"Mithril v" + version,
"http://github.com/lhorie/mithril.js",
"(c) Leo Horie",
"License: MIT",
"*/"
].join("\n"),
sourceMap: true
},
mithril: {src: "mithril.js", dest: "mithril.min.js"}
},
concat: {
test: {src: ["mithril.js", "./tests/test.js", "./tests/mock.js", "./tests/mithril-tests.js"], dest: currentVersionArchiveFolder + "/mithril-tests.js"}
},
zip: {
distribution: {
cwd: currentVersionArchiveFolder + "/",
src: [currentVersionArchiveFolder + "/mithril.min.js", currentVersionArchiveFolder + "/mithril.min.js.map", currentVersionArchiveFolder + "/mithril.js"],
src: [
currentVersionArchiveFolder + "/mithril.min.js",
currentVersionArchiveFolder + "/mithril.min.js.map",
currentVersionArchiveFolder + "/mithril.js"
],
dest: currentVersionArchiveFolder + "/mithril.min.zip"
}
},
replace: {
options: {force: true, patterns: [{match: /\.md/g, replacement: ".html"}, {match: /\$version/g, replacement: version}]},
links: {expand: true, flatten: true, src: [tempFolder + "/**/*.html"], dest: currentVersionArchiveFolder + "/"},
index: {src: inputFolder + "/layout/index.html", dest: currentVersionArchiveFolder + "/index.html"},
commonjs: {expand: true, flatten: true, src: [inputFolder + "/layout/*.json"], dest: currentVersionArchiveFolder},
cdnjs: {src: "deploy/cdnjs-package.json", dest: "../cdnjs/ajax/libs/mithril/package.json"}
options: {
force: true,
patterns: [
{match: /\.md/g, replacement: ".html"},
{match: /\$version/g, replacement: version}
]
},
links: {
expand: true,
flatten: true,
src: [tempFolder + "/**/*.html"],
dest: currentVersionArchiveFolder + "/"
},
index: {
src: inputFolder + "/layout/index.html",
dest: currentVersionArchiveFolder + "/index.html"
},
commonjs: {
expand: true,
flatten: true,
src: [inputFolder + "/layout/*.json"],
dest: currentVersionArchiveFolder
},
cdnjs: {
src: "deploy/cdnjs-package.json",
dest: "../cdnjs/ajax/libs/mithril/package.json"
}
},
copy: {
style: {src: inputFolder + "/layout/style.css", dest: currentVersionArchiveFolder + "/style.css"},
pages: {src: inputFolder + "/layout/pages.json", dest: currentVersionArchiveFolder + "/pages.json"},
lib: {expand: true, cwd: inputFolder + "/layout/lib/", src: "./**", dest: currentVersionArchiveFolder + "/lib/"},
tools: {expand: true, cwd: inputFolder + "/layout/tools/", src: "./**", dest: currentVersionArchiveFolder + "/tools/"},
comparisons: {expand: true, cwd: inputFolder + "/layout/comparisons/", src: "./**", dest: currentVersionArchiveFolder + "/comparisons/"},
unminified: {src: "mithril.js", dest: currentVersionArchiveFolder + "/mithril.js"},
minified: {src: "mithril.min.js", dest: currentVersionArchiveFolder + "/mithril.min.js"},
readme: {src: "README.md", dest: currentVersionArchiveFolder + "/README.md"},
map: {src: "mithril.min.js.map", dest: currentVersionArchiveFolder + "/mithril.min.js.map"},
typescript: {src: "mithril.d.ts", dest: currentVersionArchiveFolder + "/mithril.d.ts"},
publish: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder},
archive: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder + "/archive/v" + version},
},
execute: {
tests: {src: [currentVersionArchiveFolder + "/mithril-tests.js"]}
},
qunit: {
all: ['tests/e2e/**/*.html']
},
"saucelabs-custom": {
all:{
options: sauceCustomOptions
style: {
src: inputFolder + "/layout/style.css",
dest: currentVersionArchiveFolder + "/style.css"
},
pages: {
src: inputFolder + "/layout/pages.json",
dest: currentVersionArchiveFolder + "/pages.json"
},
lib: {
expand: true,
cwd: inputFolder + "/layout/lib/",
src: "./**",
dest: currentVersionArchiveFolder + "/lib/"
},
tools: {
expand: true,
cwd: inputFolder + "/layout/tools/",
src: "./**",
dest: currentVersionArchiveFolder + "/tools/"
},
comparisons: {
expand: true,
cwd: inputFolder + "/layout/comparisons/",
src: "./**",
dest: currentVersionArchiveFolder + "/comparisons/"
},
unminified: {
src: "mithril.js",
dest: currentVersionArchiveFolder + "/mithril.js"
},
minified: {
src: "mithril.min.js",
dest: currentVersionArchiveFolder + "/mithril.min.js"
},
readme: {
src: "README.md",
dest: currentVersionArchiveFolder + "/README.md"
},
map: {
src: "mithril.min.js.map",
dest: currentVersionArchiveFolder + "/mithril.min.js.map"
},
typescript: {
src: "mithril.d.ts",
dest: currentVersionArchiveFolder + "/mithril.d.ts"
},
publish: {
expand: true,
cwd: currentVersionArchiveFolder,
src: "./**",
dest: outputFolder
},
archive: {
expand: true,
cwd: currentVersionArchiveFolder,
src: "./**",
dest: outputFolder + "/archive/v" + version
}
},
"saucelabs-qunit": {
all:{
options: sauceQunitOptions
"saucelabs-browsers": {
firefox: {
filter: function (browsers) {
return browsers.filter(function (browser) {
if (browser.browserName !== "firefox") return false
var version = browser.version
return version === "dev" || version === "beta" ||
+version >= 38 // The latest ESR version
})
}
},
chrome: {
filter: function (browsers) {
return browsers.filter(function (browser) {
if (browser.browserName !== "chrome") return false
var version = browser.version
return version === "dev" || version === "beta" ||
+version >= 41
})
}
},
ie: {
filter: function (browsers) {
return browsers.filter(function (browser) {
return browser.browserName === "internet explorer" &&
!/2003/.test(browser.platform)
})
}
},
edge: {
filter: function (browsers) {
return browsers.filter(function (browser) {
return browser.browserName === "microsoftedge"
})
}
},
safari: {
filter: function (browsers) {
return browsers.filter(function (browser) {
return browser.browserName === "safari"
})
}
},
opera: {
filter: function (browsers) {
return browsers.filter(function (browser) {
return browser.browserName === "opera"
})
}
}
},
saucelabs: {
all: {
options: {
username: process.env.SAUCE_USERNAME,
key: process.env.SAUCE_ACCESS_KEY,
testname: "Mithril Tests " + new Date().toJSON(),
browsers: "<%= saucelabs.browsers %>",
urls: ["http://localhost:8000/test/index.html"],
sauceConfig: {
"record-video": false,
"record-screenshots": false
},
build: process.env.TRAVIS_JOB_ID,
onTestComplete: function (result, callback) {
var user = process.env.SAUCE_USERNAME
var pass = process.env.SAUCE_ACCESS_KEY
var url = [
"https://saucelabs.com/rest/v1", user, "jobs",
result.job_id
].join("/")
require("request").put({
url: url,
auth: {user: user, pass: pass},
json: {passed: result.passed}
}, function (error, response) {
if (error) {
return callback(error)
} else if (response.statusCode !== 200) {
return callback(new Error(
"Unexpected response status: " +
response.statusCode + "\n "))
} else {
return callback(null, result.passed)
}
})
},
tunnelTimeout: 5
}
}
},
watch: {},
connect: {
server: {
options: {
port: 8888,
base: '.'
base: "."
}
}
},
clean: {
options: {force: true},
generated: [tempFolder]
},
jsfmt: {
default: {
files: [{
expand: true,
src: ['mithril.js'],
cwd: '.',
dest: '.'
}]
}
}
});
})
grunt.loadNpmTasks("grunt-contrib-clean");
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks("grunt-contrib-copy");
grunt.loadNpmTasks("grunt-contrib-uglify");
grunt.loadNpmTasks('grunt-execute');
grunt.loadNpmTasks("grunt-md2html");
grunt.loadNpmTasks("grunt-replace");
grunt.loadNpmTasks('grunt-zip');
grunt.loadNpmTasks('grunt-contrib-qunit');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-saucelabs');
grunt.loadNpmTasks('grunt-jsfmt');
grunt.loadNpmTasks("grunt-saucelabs-browsers")
grunt.loadNpmTasks("grunt-contrib-clean")
grunt.loadNpmTasks("grunt-contrib-copy")
grunt.loadNpmTasks("grunt-contrib-uglify")
grunt.loadNpmTasks("grunt-md2html")
grunt.loadNpmTasks("grunt-replace")
grunt.loadNpmTasks("grunt-zip")
grunt.loadNpmTasks("grunt-contrib-connect")
grunt.loadNpmTasks("grunt-saucelabs")
grunt.loadNpmTasks("grunt-eslint")
grunt.loadNpmTasks("grunt-mocha-phantomjs")
grunt.registerTask("build", ["test", "uglify", "zip", "md2html", "replace", "copy", "clean"]);
grunt.registerTask("testall", ["test", "teste2e"]);
grunt.registerTask("test", ["concat", "execute"]);
grunt.registerTask('teste2e', ['connect', 'qunit']);
grunt.registerTask("default", ["build"]);
grunt.registerTask("build", [
"test",
"uglify",
"zip",
"md2html",
"replace",
"copy",
"clean"
])
grunt.registerTask("sauce-qunit", ["connect", "saucelabs-qunit"]);
grunt.registerTask("sauce-custom", ["connect", "saucelabs-custom"]);
grunt.registerTask("sauce-all", ["connect", "saucelabs-qunit", "saucelabs-custom"]);
};
grunt.registerTask("test", ["eslint:all", "mocha_phantomjs"])
grunt.registerTask("default", ["build"])
grunt.registerTask("sauce", [
"saucelabs-browsers:all",
"connect",
"saucelabs"
])
}

View file

@ -3,4 +3,4 @@
{"title": "Documentation", "url": "mithril.html"},
{"title": "Mithril Blog", "url": "http://lhorie.github.io/mithril-blog/"},
{"title": "Mailing List", "url": "https://groups.google.com/forum/#!forum/mithriljs"}
]
]

View file

@ -1,9 +1,17 @@
<p>If you already have your HTML written and want to convert it into a Mithril template, paste the HTML below and press the "Convert" button.</p>
<!doctype html>
<title>HTML to Mithril Template Converter</title>
<h1>HTML to Mithril Template Converter</h1>
<p>
If you have HTML you want to convert into a Mithril template, paste it below
and press the "Convert" button. In case you're wondering, this itself is a
Mithril app.
</p>
<div id="converter"></div>
<script src="../mithril.min.js"></script>
<script src="template-converter.js"></script>
<script>
m.mount(document.getElementById("converter"), templateConverter);
</script>
m.mount(document.getElementById("converter"), templateConverter)
</script>

View file

@ -1,90 +1,145 @@
var templateConverter = {};
/* global m: false */
// TODO: ensure this targets the current API.
window.templateConverter = (function () {
"use strict"
templateConverter.DOMFragment = function(markup) {
if (markup.indexOf("<!doctype") > -1) return [new DOMParser().parseFromString(markup, "text/html").childNodes[1]]
var container = document.createElement("div");
container.insertAdjacentHTML("beforeend", markup);
return container.childNodes;
}
templateConverter.VirtualFragment = function recurse(domFragment) {
var virtualFragment = [];
for (var i = 0, el; el = domFragment[i]; i++) {
if (el.nodeType == 3) {
virtualFragment.push(el.nodeValue);
}
else if (el.nodeType == 1) {
var attrs = {};
for (var j = 0, attr; attr = el.attributes[j]; j++) {
attrs[attr.name] = attr.value;
}
virtualFragment.push({tag: el.nodeName.toLowerCase(), attrs: attrs, children: recurse(el.childNodes)});
function each(list, f) {
for (var i = 0; i < list.length; i++) {
f(list[i], i)
}
}
return virtualFragment;
}
templateConverter.Template = function recurse() {
if (Object.prototype.toString.call(arguments[0]) == "[object String]") {
return new recurse(new templateConverter.VirtualFragment(new templateConverter.DOMFragment(arguments[0])));
}
var virtualFragment = arguments[0], level = arguments[1]
if (!level) level = 1;
var tab = "\n" + new Array(level + 1).join("\t");
var virtuals = [];
for (var i = 0, el; el = virtualFragment[i]; i++) {
if (typeof el == "string") {
if (el.match(/\t| {2,}/g) && el.trim().length == 0) virtuals.indented = true;
else virtuals.push('"' + el.replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n") + '"');
function createFragment(markup) {
if (markup.indexOf("<!doctype") >= 0) {
return [
new DOMParser()
.parseFromString(markup, "text/html")
.childNodes[1]
]
}
else {
var virtual = "";
if (el.tag != "div") virtual += el.tag;
if (el.attrs["class"]) {
virtual += "." + el.attrs["class"].replace(/\t+/g, " ").split(" ").join(".");
delete el.attrs["class"];
var container = document.createElement("div")
container.insertAdjacentHTML("beforeend", markup)
return container.childNodes
}
function createVirtual(fragment) {
var list = []
each(fragment, function (el) {
if (el.nodeType === 3) {
list.push(el.nodeValue)
} else if (el.nodeType === 1) {
var attrs = {}
each(el.attributes, function (attr) {
attrs[attr.name] = attr.value
})
list.push({
tag: el.nodeName.toLowerCase(),
attrs: attrs,
children: createVirtual(el.childNodes)
})
}
var attrNames = Object.keys(el.attrs).sort()
for (var j = 0, attrName; attrName = attrNames[j]; j++) {
if (attrName != "style") virtual += "[" + attrName + "='" + el.attrs[attrName].replace(/'/g, "\\'") + "']";
})
return list
}
function TemplateBuilder(virtual, level) {
this.virtual = virtual
this.level = level
this.virtuals = []
this.indented = false
}
TemplateBuilder.prototype = {
addVirtualString: function (el) {
if (/\t| {2,}/.test(el) && /^\s*/.test(el)) {
this.indented = true
} else {
this.virtuals.push('"' + el.replace(/(["\r\n])/g, "\\$1") + '"')
}
if (virtual == "") virtual = "div"
virtual = '"' + virtual + '"';
var style = ""
},
addVirtualAttrs: function (el) {
var virtual = el.tag === "div" ? "" : el.tag
if (el.attrs.class) {
virtual += "." + el.attrs.class.replace(/\s+/g, ".")
el.attrs.class = undefined
}
each(Object.keys(el.attrs).sort(), function (attrName) {
if (attrName === "style") return
virtual += "[" + attrName + "='"
virtual += el.attrs[attrName].replace(/'/g, "\\'") + "']"
})
if (virtual === "") virtual = "div"
virtual = '"' + virtual + '"'
if (el.attrs.style) {
virtual += ", {style: " + ("{\"" + el.attrs.style.replace(/:/g, "\": \"").replace(/;/g, "\", \"") + "}").replace(/, "}|"}/, "}") + "}"
var style = "{\"" + el.attrs.style
.replace(/:/g, "\": \"")
.replace(/;/g, "\", \"") + "}"
virtual += ", {style: " + style.replace(/(, )"}/, "}") + "}"
}
if (el.children.length > 0) {
virtual += ", " + recurse(el.children, level + 1);
if (el.children.length !== 0) {
var builder = new TemplateBuilder(el.children, this.level + 1)
virtual += ", " + builder.complete()
}
this.virtuals.push("m(" + virtual + ")")
},
complete: function () {
var tab = "\n"
for (var i = 0; i <= this.level; i++) tab += "\t"
each(this.virtual, function (el) {
if (typeof el === "string") {
this.addVirtualString(el)
} else {
this.addVirtualAttrs(el)
}
}.bind(this))
if (!this.indented) tab = ""
if (this.virtuals.length === 1 && this.virtuals[0][0] === "\"") {
return this.virtuals.join(", ")
} else {
var body = this.virtuals.join("," + tab)
return "[" + tab + body + tab.slice(0, -1) + "]"
}
virtual = "m(" + virtual + ")";
virtuals.push(virtual);
}
}
if (!virtuals.indented) tab = "";
var isInline = virtuals.length == 1 && virtuals[0].charAt(0) == '"';
var template = isInline ? virtuals.join(", ") : "[" + tab + virtuals.join("," + tab) + tab.slice(0, -1) + "]";
return new String(template);
}
templateConverter.controller = function() {
this.source = m.prop("");
this.output = m.prop("");
this.convert = function() {
return this.output(new templateConverter.Template(this.source()));
};
};
return {
controller: function () {
this.source = m.prop("")
this.output = m.prop("")
templateConverter.view = function(ctrl) {
return m("div", [
m("textarea", {autofocus: true, style: {width:"100%", height: "40%"}, onchange: m.withAttr("value", ctrl.source)}, ctrl.source()),
m("button", {onclick: ctrl.convert.bind(ctrl)}, "Convert"),
m("textarea", {style: {width:"100%", height: "40%"}}, ctrl.output())
]);
};
this.convert = function () {
var source = createVirtual(createFragment(this.source()))
return this.output(new TemplateBuilder(source, 1).complete())
}.bind(this)
},
view: function (ctrl) {
return m("div", [
m("textarea", {
autofocus: true,
style: {width: "100%", height: "40%"},
onchange: m.withAttr("value", ctrl.source)
}, ctrl.source()),
m("button", {onclick: ctrl.convert}, "Convert"),
m("textarea", {style: {width: "100%", height: "40%"}},
ctrl.output())
])
}
}
})()

46
mithril.d.ts vendored
View file

@ -20,13 +20,13 @@ declare module _mithril {
* @see m.mount
* @see m.component
*/
<T extends MithrilController>(
(
selector: string,
attributes: MithrilAttributes,
...children: Array<string |
MithrilVirtualElement<T> |
MithrilComponent<T>>
): MithrilVirtualElement<T>;
MithrilVirtualElement |
MithrilComponent<MithrilController>>
): MithrilVirtualElement;
/**
* Creates a virtual element for use with m.render, m.mount, etc.
@ -40,12 +40,12 @@ declare module _mithril {
* @see m.mount
* @see m.component
*/
<T extends MithrilController>(
(
selector: string,
...children: Array<string |
MithrilVirtualElement<T> |
MithrilComponent<T>>
): MithrilVirtualElement<T>;
MithrilVirtualElement |
MithrilComponent<MithrilController>>
): MithrilVirtualElement;
/**
* Initializes a component for use with m.render, m.mount, etc.
@ -364,9 +364,9 @@ declare module _mithril {
* @param forceRecreation If true, overwrite the entire tree without
* diffing against it.
*/
render<T extends MithrilController>(
render(
rootElement: Element,
children: MithrilVirtualElement<T>|MithrilVirtualElement<T>[],
children: MithrilVirtualElement|MithrilVirtualElement[],
forceRecreation?: boolean
): void;
@ -431,10 +431,10 @@ declare module _mithril {
* @param defaultRoute The route to start with.
* @param routes A key-value mapping of pathname to controller.
*/
<T extends MithrilController>(
(
rootElement: Element,
defaultRoute: string,
routes: MithrilRoutes<T>
routes: MithrilRoutes
): void;
/**
@ -447,11 +447,11 @@ declare module _mithril {
* m("a[href='/dashboard/alicesmith']", {config: m.route});
* ```
*/
<T extends MithrilController>(
(
element: Element,
isInitialized: boolean,
context?: MithrilContext,
vdom?: MithrilVirtualElement<T>
vdom?: MithrilVirtualElement
): void;
/**
@ -615,7 +615,7 @@ declare module _mithril {
*
* @see m
*/
interface MithrilVirtualElement<T extends MithrilController> {
interface MithrilVirtualElement {
/**
* A key to optionally associate with this element.
*/
@ -634,7 +634,7 @@ declare module _mithril {
/**
* The children of this element.
*/
children?: Array<string|MithrilVirtualElement<T>|MithrilComponent<T>>;
children?: Array<string|MithrilVirtualElement|MithrilComponent<MithrilController>>;
}
/**
@ -686,11 +686,11 @@ declare module _mithril {
* @param context The associated context for this element.
* @param vdom The associated virtual element.
*/
<T extends MithrilController>(
(
element: Element,
isInitialized: boolean,
context: MithrilContext,
vdom: MithrilVirtualElement<T>
vdom: MithrilVirtualElement
): void;
}
@ -758,7 +758,7 @@ declare module _mithril {
/**
* Creates a view out of virtual elements.
*/
(ctrl: T): MithrilVirtualElement<T>;
(ctrl: T): MithrilVirtualElement;
}
/**
@ -781,7 +781,7 @@ declare module _mithril {
*
* @see m.component
*/
view(ctrl: T): MithrilVirtualElement<T>;
view(ctrl: T): MithrilVirtualElement;
}
/**
@ -805,7 +805,7 @@ declare module _mithril {
*
* @see m.component
*/
view(ctrl: TController, arg1: T1, arg2: T2, arg3: T3, arg4: T4, ...args:any[]): MithrilVirtualElement<TController>;
view(ctrl: TController, arg1: T1, arg2: T2, arg3: T3, arg4: T4, ...args:any[]): MithrilVirtualElement;
}
/**
@ -876,12 +876,12 @@ declare module _mithril {
/**
* This represents a key-value mapping linking routes to components.
*/
interface MithrilRoutes<T extends MithrilController> {
interface MithrilRoutes {
/**
* The key represents the route. The value represents the corresponding
* component.
*/
[key: string]: MithrilComponent<T>;
[key: string]: MithrilComponent<MithrilController>;
}
/**

View file

@ -1,46 +1,48 @@
{
"name": "mithril",
"description": "Mithril.js beta build - use this to help us test the releases before they are released",
"version": "0.2.1",
"repository": {
"type": "git",
"url": "git@github.com:lhorie/mithril.js.git"
},
"scripts": {
"test": "grunt test"
},
"main": "mithril.js",
"devDependencies": {
"grunt": "*",
"grunt-cli": "*",
"grunt-contrib-copy": "*",
"grunt-contrib-uglify": "*",
"grunt-contrib-clean": "*",
"grunt-contrib-concat": "*",
"grunt-execute": "*",
"grunt-md2html": "*",
"grunt-replace": "*",
"grunt-contrib-qunit": "*",
"grunt-zip": "*",
"grunt-jsfmt": "git://github.com/ysimonson/grunt-jsfmt",
"grunt-contrib-connect": "~0.7.1",
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-watch": "~0.6.1",
"grunt-jscs": "^1.1.0",
"grunt-sauce-tunnel": "^0.2.1",
"load-grunt-config": "^0.9.2",
"merge": "^1.1.3",
"publish": "~0.3.2",
"grunt-saucelabs": "*",
"request": "~2.35.0",
"q": "~1.0.0",
"saucelabs": "~0.1.1",
"sauce-tunnel": "~2.0.6",
"colors": "~0.6.2",
"lodash": "~2.4.1"
},
"main": "mithril.js",
"licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}],
"files": ["mithril.min.js", "mithril.min.js.map", "mithril.js", "README.*"]
"name": "mithril",
"description": "Mithril.js beta build - use this to help us test the releases before they are released",
"version": "0.2.1",
"repository": {
"type": "git",
"url": "git@github.com:lhorie/mithril.js.git"
},
"scripts": {
"test": "grunt test"
},
"main": "mithril.js",
"devDependencies": {
"chai": "^3.4.0",
"eslint": "^1.7.3",
"grunt": "*",
"grunt-cli": "*",
"grunt-contrib-clean": "*",
"grunt-contrib-connect": "~0.7.1",
"grunt-contrib-copy": "*",
"grunt-contrib-uglify": "*",
"grunt-eslint": "^17.3.1",
"grunt-md2html": "*",
"grunt-mocha-phantomjs": "^2.0.0",
"grunt-replace": "*",
"grunt-saucelabs": "*",
"grunt-saucelabs-browsers": "^0.2.0",
"grunt-zip": "*",
"load-grunt-config": "^0.9.2",
"mocha": "^2.3.3",
"request": "~2.35.0",
"saucelabs": "~0.1.1",
"sinon": "^1.17.2",
"sinon-chai": "^2.8.0"
},
"licenses": [
{
"type": "MIT",
"url": "http://opensource.org/licenses/MIT"
}
],
"files": [
"mithril.min.js",
"mithril.min.js.map",
"mithril.js",
"README.*"
]
}

16
test-deps/dom.js Normal file
View file

@ -0,0 +1,16 @@
/* eslint-env browser, mocha */
/* global m, mock */
this.dom = function (cb) {
"use strict"
context("(requires real DOM)", function () {
before(function () {
m.deps(window)
})
after(function () {
m.deps(mock)
})
cb()
})
}

258
test-deps/mock.js Normal file
View file

@ -0,0 +1,258 @@
;(function () { // eslint-disable-line no-extra-semi
"use strict"
/* eslint-disable no-extend-native */
Array.prototype.forEach = Array.prototype.forEach || function (callback) {
for (var i = 0; i < this.length; i++) {
callback(this[i], i, this)
}
}
Array.prototype.indexOf = Array.prototype.indexOf || function (item) {
for (var i = 0; i < this.length; i++) {
if (this[i] === item) return i
}
return -1
}
Array.prototype.map = Array.prototype.map || function (callback) {
var results = []
this.forEach(function (value, i, array) {
results.push(callback(value, i, array))
})
return results
}
Array.prototype.filter = Array.prototype.filter || function (callback) {
var results = []
this.forEach(function (value, i, array) {
if (callback(value, i, array)) results.push(value)
})
return results
}
Object.keys = Object.keys || function (obj) {
var keys = []
for (var i in obj) if ({}.hasOwnProperty.call(obj, i)) {
keys.push(i)
}
return keys
}
/* eslint-enable no-extend-native */
})()
window.mock = (function () {
"use strict"
var window = {}
var document = window.document = {
// FIXME: add document.createRange().createContextualFragment()
childNodes: [],
createElement: function (tag) {
return {
style: {},
childNodes: [],
nodeType: 1,
nodeName: tag.toUpperCase(),
appendChild: document.appendChild,
removeChild: document.removeChild,
replaceChild: document.replaceChild,
insertBefore: function (node, reference) {
node.parentNode = this
var referenceIndex = this.childNodes.indexOf(reference)
var index = this.childNodes.indexOf(node)
if (index > -1) this.childNodes.splice(index, 1)
if (referenceIndex < 0) this.childNodes.push(node)
else this.childNodes.splice(referenceIndex, 0, node)
},
insertAdjacentHTML: function (position, html) {
// TODO: accept markup
if (position === "beforebegin") {
this.parentNode.insertBefore(
document.createTextNode(html),
this)
} else if (position === "beforeend") {
this.appendChild(document.createTextNode(html))
}
},
setAttribute: function (name, value) {
this[name] = value.toString()
},
setAttributeNS: function (namespace, name, value) {
this.namespaceURI = namespace
this[name] = value.toString()
},
getAttribute: function (name) {
return this[name]
},
addEventListener: function () {},
removeEventListener: function () {}
}
},
createElementNS: function (namespace, tag) {
var element = document.createElement(tag)
element.namespaceURI = namespace
return element
},
createTextNode: function (text) {
return {nodeValue: text.toString()}
},
replaceChild: function (newChild, oldChild) {
var index = this.childNodes.indexOf(oldChild)
if (index > -1) this.childNodes.splice(index, 1, newChild)
else this.childNodes.push(newChild)
newChild.parentNode = this
oldChild.parentNode = null
},
appendChild: function (child) {
var index = this.childNodes.indexOf(child)
if (index > -1) this.childNodes.splice(index, 1)
this.childNodes.push(child)
child.parentNode = this
},
removeChild: function (child) {
var index = this.childNodes.indexOf(child)
this.childNodes.splice(index, 1)
child.parentNode = null
},
// getElementsByTagName is only used by JSONP tests, it's not required
// by Mithril
getElementsByTagName: function (name) {
name = name.toLowerCase()
var out = []
function traverse(node){
if (node.childNodes && node.childNodes.length > 0) {
node.childNodes.forEach(function (curr) {
if (curr.nodeName.toLowerCase() === name) {
out.push(curr)
}
traverse(curr)
})
}
}
traverse(document)
return out
}
}
document.documentElement = document.createElement("html")
window.scrollTo = function () {}
;(function (window) {
// 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
// browser.
// https://html.spec.whatwg.org/multipage/#animation-frames
//
// Adding and removing callbacks run in constant time. Please don't
// modify this unless it actually has an edge case bug. It will break
// other tests
var callbacks = []
var id = 0
var indices = {}
function requestAnimationFrame(callback) {
id++
indices[id] = callbacks.length
callbacks.push({
callback: callback,
id: id
})
return id
}
window.cancelAnimationFrame = function (id) {
var index = indices[id]
if (index !== 0) {
indices[id] = 0
callbacks[index] = undefined
}
}
var nanotime = typeof process === "object" ? function () {
var time = process.hrtime() // eslint-disable-line no-undef
return time[0] * 1e9 + time[1]
} : typeof performance === "object" ? function () {
return performance.now()
} : function () {
// PhantomJS 1 doesn't have the Performance API implemented.
return +new Date()
}
requestAnimationFrame.$resolve = function () {
var list = callbacks
callbacks = []
indices = {}
for (var i = 0; i < list.length; i++) {
var data = list[i]
if (data !== undefined) {
data.callback.call(data.id, nanotime())
}
}
}
window.requestAnimationFrame = requestAnimationFrame
})(window)
window.XMLHttpRequest = (function () {
function Request() {
this.$headers = {}
this.setRequestHeader = function (key, value) {
this.$headers[key] = value
}
this.open = function (method, url) {
this.method = method
this.url = url
}
this.send = function () {
this.responseText = JSON.stringify(this)
this.readyState = 4
this.status = 200
Request.$instances.push(this)
}
}
Request.$instances = []
return Request
})()
var location = window.location = {search: "", pathname: "", hash: ""}
window.history = {
$$length: 0,
pushState: function (data, title, url) {
window.history.$$length++
location.pathname = location.search = location.hash = url
},
replaceState: function (data, title, url) {
location.pathname = location.search = location.hash = url
}
}
return window
})()

18
test/.eslintrc Normal file
View file

@ -0,0 +1,18 @@
{
"extends": "../.eslintrc",
"env": {
"mocha": true
},
"globals": {
"sinon": false,
"expect": false,
"mock": false,
"dom": false,
"m": false
},
"rules": {
"max-statements": 0,
"no-unused-expressions": 0,
"no-warning-comments": 0
}
}

73
test/index.html Normal file
View file

@ -0,0 +1,73 @@
<!doctype html>
<meta charset="utf-8">
<title>Mithril test suite</title>
<div id="mocha"></div>
<!-- Dependencies -->
<script src="../node_modules/chai/chai.js"></script>
<script src="../node_modules/sinon/pkg/sinon.js" charset="utf-8"></script>
<script src="../node_modules/sinon-chai/lib/sinon-chai.js" charset="utf-8"></script>
<link rel="stylesheet" href="../node_modules/mocha/mocha.css">
<script src="../node_modules/mocha/mocha.js"></script>
<script src="../test-deps/dom.js"></script>
<script src="../test-deps/mock.js"></script>
<script src="../mithril.js"></script>
<script>
mocha.setup("bdd")
var expect = chai.expect
// Temporary workaround for https://github.com/mochajs/mocha/issues/1348
chai.config.truncateThreshold = 0
</script>
<!-- Tests go here -->
<script src="./mithril.js"></script>
<script src="./mithril.mount.js"></script>
<script src="./mithril.render.js"></script>
<script src="./mithril.withAttr.js"></script>
<script src="./mithril.trust.js"></script>
<script src="./mithril.redraw.js"></script>
<script src="./mithril.route.js"></script>
<script src="./mithril.route.parseQueryString.js"></script>
<script src="./mithril.route.buildQueryString.js"></script>
<script src="./mithril.prop.js"></script>
<script src="./mithril.request.js"></script>
<script src="./mithril.deferred.js"></script>
<script src="./mithril.sync.js"></script>
<script src="./mithril.startComputation.js"></script>
<!-- Set options and run this thing -->
<script>
m.deps(mock)
mocha.checkLeaks()
mocha.globals(["m", "mochaResults"])
// For Saucelabs reporting
var runner = mocha.run()
var failedTests = []
runner.on('end', function() {
window.mochaResults = runner.stats
window.mochaResults.reports = failedTests
})
runner.on('fail', function (test, err) {
function flattenTitles(test) {
var titles = []
while (test.parent.title) {
titles.push(test.parent.title)
test = test.parent
}
return titles.reverse()
}
failedTests.push({
name: test.title,
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test)
})
})
</script>

73
test/input-cursor.html Normal file
View file

@ -0,0 +1,73 @@
<!doctype html>
<title>Input cursor test</title>
<h2>Things to check:</h2>
<ul>
<li>
Typing in the fields below should not move the cursor to the end of the
input. Especially in Chrome.
</li>
<li>All inputs should update with the same value.</li>
<li>
Typing in an input should not prevent it from being updated by other
inputs.
</li>
</ul>
<div id="test"></div>
<script src="../mithril.js"></script>
<script>
function test(sel) {
return m("li", [
m("p", m("code", "m(" + JSON.stringify(sel) + ")")),
m.apply(null, arguments)
])
}
m.module(document.getElementById("test"), {
controller: function() {
this.title = m.prop("hello world");
},
view: function (ctrl) {
return m("body", [
m("h1", ["Title: ", ctrl.title()]),
m("ul", [
test("input[list=data]", {
onkeyup: m.withAttr("value", ctrl.title),
value: ctrl.title()
}),
test("datalist#data", [
m("option", "John"),
m("option", "Bob"),
m("option", "Mary")
]),
test("textarea", {
onkeyup: m.withAttr("value", ctrl.title),
value: ctrl.title()
}),
m("li", [
m("p", "untrusted ",
m("code", "m(\"div[contenteditable]\")")),
m("div[contenteditable]", {
style: {border: "1px solid #888"},
onkeyup: m.withAttr("innerHTML", ctrl.title)
}, ctrl.title()),
]),
m("li", [
m("p", "trusted ",
m("code", "m(\"div[contenteditable]\")")),
m("div[contenteditable]", {
style: {border: "1px solid #888"},
onkeyup: m.withAttr("innerHTML", ctrl.title)
}, m.trust(ctrl.title())),
])
])
]);
}
});
</script>

289
test/mithril.deferred.js Normal file
View file

@ -0,0 +1,289 @@
describe("m.deferred()", function () {
"use strict"
// Let unchecked exceptions bubble up in order to allow meaningful error
// messages in common cases like null reference exceptions due to typos.
// An unchecked exception is defined as an object that is a subclass of
// Error (but not a direct instance of Error itself) - basically anything
// that can be thrown without an explicit `throw` keyword and that we'd
// never want to programmatically manipulate. In other words, an unchecked
// error is one where we only care about its line number and where the only
// reasonable way to deal with it is to change the buggy source code that
// caused the error to be thrown in the first place.
//
// By contrast, a checked exception is defined as anything that is
// explicitly thrown via the `throw` keyword and that can be
// programmatically handled, for example to display a validation error
// message on the UI. If an exception is a subclass of Error for whatever
// reason, but it is meant to be handled as a checked exception (i.e.
// follow the rejection rules for A+), it can be rethrown as an instance
// of Error.
//
// This implementation deviates from the Promises/A+ spec in two ways:
//
// 1) A+ requires the `then` callback to be called asynchronously (this
// requires a setImmediate polyfill, which cannot be implemented in a
// reasonable way for Mithril's purpose - the possible polyfills are
// either too big or too slow). This implementation calls the `then`
// callback synchronously.
// 2) A+ swallows exceptions in a unrethrowable way, i.e. it's not possible
// to see default error messages on the console for runtime errors thrown
// from within a promise chain. This throws such checked exceptions.
it("exists", function () {
expect(m.deferred).to.be.a("function")
})
it("resolves values", function () {
var value = m.prop()
var deferred = m.deferred()
deferred.promise.then(value)
deferred.resolve("test")
expect(value()).to.equal("test")
})
it("resolves values returned in `then` method", function () {
var value = m.prop()
var deferred = m.deferred()
deferred.promise
.then(function () { return "foo" })
.then(value)
deferred.resolve("test")
expect(value()).to.equal("foo")
})
it("passes rejections through second `then` handler", function () {
var obj = {}
var value1 = m.prop(obj)
var value2 = m.prop(obj)
var deferred = m.deferred()
deferred.promise.then(value1, value2)
deferred.reject("test")
expect(value1()).to.equal(obj)
expect(value2()).to.equal("test")
})
it("passes rejections through `catch`", function () {
var value = m.prop()
var deferred = m.deferred()
deferred.promise.catch(value)
deferred.reject("test")
expect(value()).to.equal("test")
})
it("can resolve from a `then` rejection handler", function () {
var value = m.prop()
var deferred = m.deferred()
deferred.promise
.then(null, function () { return "foo" })
.then(value)
deferred.reject("test")
expect(value()).to.equal("foo")
})
it("can resolve from a `catch`", function () {
var value = m.prop()
var deferred = m.deferred()
deferred.promise
.catch(function () { return "foo" })
.then(value)
deferred.reject("test")
expect(value()).to.equal("foo")
})
it("can reject by throwing an `Error`", function () {
var value1 = m.prop()
var value2 = m.prop()
var deferred = m.deferred()
deferred.promise
.then(function () { throw new Error() })
.then(value1, value2)
deferred.resolve("test")
expect(value1()).to.not.exist
expect(value2()).to.be.an("error")
})
// FIXME: this is a bug.
xit("synchronously throws subclasses of Errors on creation", function () {
expect(function () {
m.deferred().reject(new TypeError())
}).to.throw()
})
it("synchronously throws subclasses of Errors thrown from its `then` fufill handler", function () { // eslint-disable-line
expect(function () {
var deferred = m.deferred()
deferred.promise.then(function () { throw new TypeError() })
deferred.resolve()
}).to.throw()
})
it("synchronously throws subclasses of Errors thrown from its `then` rejection handler", function () { // eslint-disable-line
expect(function () {
var deferred = m.deferred()
deferred.promise.then(null, function () { throw new TypeError() })
deferred.reject("test")
}).to.throw()
})
it("synchronously throws subclasses of Errors thrown from its `catch` method", function () { // eslint-disable-line
expect(function () {
var deferred = m.deferred()
deferred.promise.catch(function () { throw new TypeError() })
deferred.reject("test")
}).to.throw()
})
it("unwraps other thenables, and returns the correct values in the chain", function () { // eslint-disable-line
var deferred1 = m.deferred()
var deferred2 = m.deferred()
var value1, value2
deferred1.promise.then(function (data) {
value1 = data
return deferred2.promise
}).then(function (data) {
value2 = data
})
deferred1.resolve(1)
deferred2.resolve(2)
expect(value1).to.equal(1)
expect(value2).to.equal(2)
})
// https://github.com/lhorie/mithril.js/issues/80
it("propogates returns with `then` after being resolved", function () {
var deferred = m.deferred()
var value = m.prop()
deferred.resolve(1)
deferred.promise.then(value)
expect(value()).to.equal(1)
})
// https://github.com/lhorie/mithril.js/issues/80
it("propogates errors with `then` after being rejected", function () {
var deferred = m.deferred()
var value = m.prop()
deferred.reject(1)
deferred.promise.then(null, value)
expect(value()).to.equal(1)
})
// https://github.com/lhorie/mithril.js/issues/80
it("can only be resolved once before being chained", function () {
var deferred = m.deferred()
var value = m.prop()
deferred.resolve(1)
deferred.resolve(2)
deferred.promise.then(value)
expect(value()).to.equal(1)
})
// https://github.com/lhorie/mithril.js/issues/80
it("can only be resolved once after being chained", function () {
var deferred = m.deferred()
var value = m.prop()
deferred.promise.then(value)
deferred.resolve(1)
deferred.resolve(2)
expect(value()).to.equal(1)
})
// https://github.com/lhorie/mithril.js/issues/80
it("can't be rejected after being resolved", function () {
var deferred = m.deferred()
var value1 = m.prop()
var value2 = m.prop()
deferred.promise.then(value1, value2)
deferred.resolve(1)
deferred.reject(2)
expect(value1()).to.equal(1)
expect(value2()).to.not.exist
})
// https://github.com/lhorie/mithril.js/issues/80
it("can't be resolved after being rejected", function () {
var deferred = m.deferred()
var value1 = m.prop()
var value2 = m.prop()
deferred.promise.then(value1, value2)
deferred.reject(1)
deferred.resolve(2)
expect(value1()).to.not.exist
expect(value2()).to.equal(1)
})
// https://github.com/lhorie/mithril.js/issues/80
it("can only be rejected once before being chained", function () {
var deferred = m.deferred()
var value = m.prop()
deferred.reject(1)
deferred.reject(2)
deferred.promise.then(null, value)
expect(value()).to.equal(1)
})
// https://github.com/lhorie/mithril.js/issues/80
it("can only be rejected once after being chained", function () {
var deferred = m.deferred()
var value = m.prop()
deferred.promise.then(null, value)
deferred.reject(1)
deferred.reject(2)
expect(value()).to.equal(1)
})
// https://github.com/lhorie/mithril.js/issues/85
it("calls resolution handler when resolved with `undefined`", function () {
var deferred = m.deferred()
var value
deferred.resolve()
deferred.promise.then(function () {
value = 1
})
expect(value).to.equal(1)
})
// https://github.com/lhorie/mithril.js/issues/85
it("calls rejection handler when rejected with `undefined`", function () {
var deferred = m.deferred()
var value
deferred.reject()
deferred.promise.then(null, function () {
value = 1
})
expect(value).to.equal(1)
})
it("immediately resolves promise with `resolve` method", function () {
var deferred = m.deferred()
deferred.resolve(1)
expect(deferred.promise()).to.equal(1)
})
it("gets chained promise value when called", function () {
var deferred = m.deferred()
var promise = deferred.promise.then(function (data) { return data + 1 })
deferred.resolve(1)
expect(promise()).to.equal(2)
})
it("returns `undefined` from call if it's rejected", function () {
var deferred = m.deferred()
deferred.reject(1)
expect(deferred.promise()).to.be.undefined
})
})

216
test/mithril.js Normal file
View file

@ -0,0 +1,216 @@
describe("m.version()", function () {
"use strict"
it("exists", function () {
expect(m.version).to.be.a("function")
})
it("is a string", function () {
expect(m.version()).to.be.a("string")
})
})
describe("m()", function () {
"use strict"
it("exists", function () {
expect(m).to.be.a("function")
})
it("sets correct tag name", function () {
expect(m("div")).to.have.property("tag", "div")
})
it("sets correct tag name with only a class", function () {
expect(m(".foo")).to.have.property("tag", "div")
})
it("sets correct class name", function () {
expect(m(".foo")).to.have.deep.property("attrs.className", "foo")
})
it("sets correct tag name with only an attr", function () {
expect(m("[title=bar]")).to.have.property("tag", "div")
})
it("sets correct unquoted attr", function () {
expect(m("[title=bar]")).to.have.deep.property("attrs.title", "bar")
})
it("sets correct single quoted attr", function () {
expect(m("[title=\'bar\']")).to.have.deep.property("attrs.title", "bar")
})
it("sets correct double quoted attr", function () {
expect(m("[title=\"bar\"]")).to.have.deep.property("attrs.title", "bar")
})
it("sets correct children with 1 string arg", function () {
expect(m("div", "test"))
.to.have.property("children")
.that.eqls(["test"])
})
it("sets correct children with multiple string args", function () {
expect(m("div", "test", "test2"))
.to.have.property("children")
.that.eqls(["test", "test2"])
})
it("sets correct children with string array", function () {
expect(m("div", ["test"]))
.to.have.property("children")
.that.eqls(["test"])
})
it("sets correct attrs with object", function () {
expect(m("div", {title: "bar"}, "test"))
.to.have.deep.property("attrs.title", "bar")
})
it("sets correct children with attrs object", function () {
expect(m("div", {title: "bar"}, "test"))
.to.have.property("children")
.that.eqls(["test"])
})
it("sets correct children with nested node", function () {
expect(m("div", {title: "bar"}, m("div")))
.to.have.property("children")
.that.eqls([m("div")])
})
it("sets correct children with string rest arg", function () {
expect(m("div", {title: "bar"}, "test0", "test1", "test2", "test3"))
.to.have.property("children")
.that.eqls(["test0", "test1", "test2", "test3"])
})
it("sets correct children with node rest arg", function () {
expect(m("div", {title: "bar"}, m("div"), m("i"), m("span")))
.to.have.property("children")
.that.eqls([m("div"), m("i"), m("span")])
})
it("sets correct children with string array & no attrs", function () {
expect(m("div", ["a", "b"]))
.to.have.property("children")
.that.eqls(["a", "b"])
})
it("sets correct children with node array & no attrs", function () {
expect(m("div", [m("div"), m("i")]))
.to.have.property("children")
.that.eqls([m("div"), m("i")])
})
it("sets correct children with 2nd arg as node", function () {
expect(m("div", m("div"))).to.have.property("children")
.that.eqls([m("div")])
})
it("sets correct tag with undefined array entry", function () {
expect(m("div", [undefined])).to.have.property("tag", "div")
})
it("loosely accepts invalid objects", function () {
expect(function () { m("div", [{foo: "bar"}]) }).to.not.throw()
})
it("accepts svg nodes", function () {
expect(m("svg", [m("g")]))
.to.have.property("children")
.that.eqls([m("g")])
})
it("accepts HTML children in svg element", function () {
expect(m("svg", [m("a[href='http://google.com']")]))
.to.have.property("children")
.that.eqls([m("a[href='http://google.com']")])
})
it("uses className if given", function () {
expect(m(".foo", {className: ""}))
.to.have.deep.property("attrs.className", "foo")
})
it("accepts a class and class attr", function () {
var node = m(".foo", {class: "bar"})
expect(node).to.have.deep.property("attrs.class")
expect(node.attrs.class).to.include("foo").and.include("bar")
})
it("accepts a class and className attr", function () {
var node = m(".foo", {className: "bar"})
expect(node).to.have.deep.property("attrs.className")
expect(node.attrs.className).to.include("foo").and.include("bar")
})
// https://github.com/lhorie/mithril.js/issues/382 and 512
it("sets an empty className attr if it's an empty string", function () {
expect(m("div", {className: ""}))
.to.have.deep.property("attrs.className", "")
})
it("does not set className attr if class is given", function () {
expect(m("div", {class: ""})).to.not.have.property("attrs.className")
})
it("does not set class attr if className is given", function () {
expect(m("div", {className: ""})).to.not.have.property("attrs.class")
})
it("sets an empty class attr if it's an empty string", function () {
expect(m("div", {class: ""})).to.have.deep.property("attrs.class", "")
})
it("does not flatten 1 nested array", function () {
expect(m("div", [1, 2, 3], 4))
.to.have.property("children")
.that.eqls([[1, 2, 3], 4])
})
it("does not flatten 2 nested arrays", function () {
expect(m("div", [1, 2, 3], [4, 5, 6, 7]))
.to.have.property("children")
.that.eqls([[1, 2, 3], [4, 5, 6, 7]])
})
it("does not flatten 3 nested arrays", function () {
expect(m("div", [1], [2], [3]))
.to.have.property("children")
.that.eqls([[1], [2], [3]])
})
it("doesn't recreate the DOM when classes are different", function () {
var v1 = m(".foo", {class: "", onclick: function () {}})
var v2 = m(".foo", {class: "bar", onclick: function () {}})
expect(v1)
.to.have.property("attrs")
.that.contains.all.keys("class", "onclick")
expect(v2)
.to.have.property("attrs")
.that.contains.all.keys("class", "onclick")
})
it("proxies an object first arg to m.component()", function () {
var spy = sinon.spy()
var component = {
controller: spy,
view: function () {
return m("div", "testing")
}
}
var args = {age: 12}
m(component, args).controller()
expect(spy.firstCall).to.have.been.calledWith(args)
m.component(component, args).controller()
expect(spy.secondCall).to.have.been.calledWith(args)
})
})

802
test/mithril.mount.js Normal file
View file

@ -0,0 +1,802 @@
describe("m.mount()", function () {
"use strict"
// This is a frequent idiom
function refresh(force) {
m.redraw(!!force)
mock.requestAnimationFrame.$resolve()
}
function clear(root) {
m.mount(root, null)
mock.requestAnimationFrame.$resolve()
}
function mount(root, mod) {
var res = m.mount(root, mod)
mock.requestAnimationFrame.$resolve()
return res
}
// This is extremely frequent in the tests
function pure(view) {
return {
controller: function () {},
view: view
}
}
it("exists", function () {
expect(m.mount).to.be.a("function")
})
it("mounts onto the root", function () {
var root = mock.document.createElement("div")
var whatever = 1
var app = pure(function () {
return [
whatever % 2 ? m("span", "% 2") : undefined,
m("div", "bugs"),
m("a")
]
})
mount(root, app)
whatever++
refresh()
whatever++
refresh()
expect(root.childNodes).to.have.length.above(0)
})
it("reloads components correctly", function () {
mock.requestAnimationFrame.$resolve()
var root1 = mock.document.createElement("div")
var controller1 = sinon.spy(function () { this.value = "test1" }) // eslint-disable-line
var view1 = sinon.stub().returns("test1")
var mod1 = m.mount(root1, {
controller: controller1,
view: view1
})
var controller2 = sinon.spy(function () { this.value = "test2" }) // eslint-disable-line
var view2 = sinon.stub().returns("test2")
var root2 = mock.document.createElement("div")
var mod2 = mount(root2, {
controller: controller2,
view: view2
})
expect(controller1).to.have.been.called
expect(view1).to.have.been.called
expect(controller2).to.have.been.called
expect(view2).to.have.been.called
expect(root1.childNodes[0].nodeValue).to.equal("test1")
expect(root2.childNodes[0].nodeValue).to.equal("test2")
expect(mod1).to.have.property("value", "test1")
expect(mod2).to.have.property("value", "test2")
})
it("triggers an unload when the element is removed", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var spy = sinon.spy()
mount(root, {
controller: function () {
this.onunload = spy
},
view: function () {}
})
clear(root)
expect(spy).to.have.been.called
})
it("passes the args to both component & view", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var ctrlSpy = sinon.spy()
var viewSpy = sinon.stub().returns(m("div"))
var component = {
controller: ctrlSpy,
view: viewSpy
}
var arg = {}
mount(root, m.component(component, arg))
expect(ctrlSpy).to.have.been.calledWith(arg)
expect(viewSpy.firstCall.args[1]).to.equal(arg)
})
it("mounts a component without a controller", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var arg = {}
var spy = sinon.spy()
var component = pure(spy)
mount(root, m.component(component, arg))
expect(spy.firstCall.args[1]).to.equal(arg)
})
it("only runs a component controller once", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var ctrlSpy = sinon.spy()
var viewSpy = sinon.stub().returns(m("div"))
var sub = {
controller: ctrlSpy,
view: viewSpy
}
mount(root, pure(function () { return sub }))
refresh(true)
expect(ctrlSpy).to.have.been.calledOnce
expect(viewSpy).to.have.been.calledTwice
})
it("only runs a subcomponent controller once", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var ctrl1 = sinon.spy()
var view1 = sinon.stub().returns(m("div"))
var subsub = {
controller: ctrl1,
view: view1
}
var ctrl2 = sinon.spy()
var view2 = sinon.stub().returns(subsub)
var sub = {
controller: ctrl2,
view: view2
}
mount(root, pure(function () { return sub }))
refresh(true)
expect(ctrl1).to.have.been.calledOnce
expect(ctrl2).to.have.been.calledOnce
expect(view1).to.have.been.calledTwice
expect(view2).to.have.been.calledTwice
})
it("addresses keys in components", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var list = [1, 2, 3]
var sub = pure(function () { return m("div") })
m.mount(root, pure(function () {
return list.map(function (i) {
return m.component(sub, {key: i})
})
}))
var firstBefore = root.childNodes[0]
mock.requestAnimationFrame.$resolve()
list.reverse()
refresh(true)
expect(root.childNodes[2]).to.equal(firstBefore)
})
it("addresses keys in subcomponents correctly", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var list = [1, 2, 3]
var subsub = pure(function () { return m("div") })
var sub = pure(function () { return subsub })
m.mount(root, pure(function () {
return list.map(function (i) {
return m.component(sub, {key: i})
})
}))
var firstBefore = root.childNodes[0]
mock.requestAnimationFrame.$resolve()
list.reverse()
refresh(true)
expect(root.childNodes[2]).to.equal(firstBefore)
})
it("is error resistant with keys in components", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var list = [1, 2, 3]
var sub = pure(function () { return m("div", {key: 1}) })
m.mount(root, pure(function () {
return list.map(function (i) {
return m.component(sub, {key: i})
})
}))
var firstBefore = root.childNodes[0]
mock.requestAnimationFrame.$resolve()
list.reverse()
refresh(true)
expect(root.childNodes[2]).to.equal(firstBefore)
})
it("is error resistant with keys in subcomponents", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var list = [1, 2, 3]
var subsub = pure(function () { return m("div", {key: 1}) })
var sub = pure(function () { return subsub })
m.mount(root, pure(function () {
return list.map(function (i) {
return m.component(sub, {key: i})
})
}))
var firstBefore = root.childNodes[0]
mock.requestAnimationFrame.$resolve()
list.reverse()
refresh(true)
expect(root.childNodes[2]).to.equal(firstBefore)
})
it("retains subcomponent identity if child of keyed element", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var list = [1, 2, 3]
var sub = pure(function () { return m("div") })
m.mount(root, pure(function () {
return list.map(function (i) {
return m("div", {key: i}, sub)
})
}))
var firstBefore = root.childNodes[0].childNodes[0]
mock.requestAnimationFrame.$resolve()
list.reverse()
refresh(true)
expect(root.childNodes[2].childNodes[0]).to.equal(firstBefore)
})
it("calls component onunload when removed from template", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var list = [1, 2, 3]
var spies = []
var sub = {
controller: function (opts) {
this.onunload = spies[opts.key] = sinon.spy()
},
view: function () {
return m("div")
}
}
mount(root, pure(function () {
return list.map(function (i) {
return m.component(sub, {key: i})
})
}))
list.pop()
refresh(true)
// TODO: These fail.
// expect(spies[1]).to.have.been.called
// expect(spies[2]).to.have.been.called
expect(spies[3]).to.have.been.called
})
it("calls subcomponent onunload when removed from template", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var list = [1, 2, 3]
var spies1 = []
var spies2 = []
var subsub = {
controller: function (opts) {
this.onunload = spies1[opts.key] = sinon.spy()
},
view: function () {
return m("div")
}
}
var sub = {
controller: function (opts) {
this.onunload = spies2[opts.key] = sinon.spy()
},
view: function (ctrl, opts) {
return m.component(subsub, {key: opts.key})
}
}
mount(root, pure(function () {
return list.map(function (i) {
return m.component(sub, {key: i})
})
}))
list.pop()
refresh(true)
// TODO: These fail.
// expect(spies1[1]).to.have.been.called
// expect(spies1[2]).to.have.been.called
expect(spies1[3]).to.have.been.called
// TODO: These fail.
// expect(spies2[1]).to.have.been.called
// expect(spies2[2]).to.have.been.called
expect(spies2[3]).to.have.been.called
})
it("doesn't redraw if m.render() is called by controller constructor", function () { // eslint-disable-line
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var spy = sinon.stub().returns(m("div"))
var sub = {
controller: function () {
m.redraw()
},
view: spy
}
mount(root, pure(function () { return sub }))
expect(spy).to.have.been.called
})
it("doesn't redraw if m.render() is called by subcomponent controller constructor", function () { // eslint-disable-line
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var spy = sinon.stub().returns(m("div"))
var subsub = {
controller: function () {
m.redraw()
},
view: spy
}
var sub = pure(function () { return subsub })
mount(root, pure(function () { return sub }))
expect(spy).to.have.been.called
})
it("renders nested components under keyed components", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var spy = sinon.stub().returns(m(".reply"))
var Reply = pure(spy)
var CommentList = pure(function (ctrl, props) {
return m(".list", props.list.map(function (i) {
return m(".comment", [
m.component(Reply, {key: i})
])
}))
})
mount(root, pure(function () {
return m(".outer", [
m(".inner", m.component(CommentList, {list: [1, 2, 3]}))
])
}))
expect(spy).to.have.been.calledThrice
})
it("calls unload when the component is replaced with another component", function () { // eslint-disable-line
var root = mock.document.createElement("div")
var spy = sinon.spy()
m.mount(root, {
controller: function () {
this.onunload = spy
},
view: function () {}
})
m.mount(root, pure(function () {}))
expect(spy).to.have.been.called
})
it("calls config with truthy init only once", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var count = 0
mount(root, pure(function () {
return m("div", {
config: function (el, init) {
if (init) count += 1
}
})
}))
refresh()
expect(count).to.equal(1)
})
it("doesn't recreate node that modifies DOM in config, but stays same between redraws", function () { // eslint-disable-line
var root = mock.document.createElement("div")
var child = mock.document.createElement("div")
var show = true
function test(el, init) {
if (!init) {
root.appendChild(child)
}
}
mount(root, pure(function () {
return [
m(".foo", {
key: 1,
config: test,
onclick: function () { show = !show }
}),
show ? m(".bar", {key: 2}) : null
]
}))
show = false
refresh()
show = true
refresh()
expect(root.childNodes).to.have.length(3)
})
it("correctly replaces nodes", function () {
var root = mock.document.createElement("div")
var show = true
var sub = pure(function () { return m("div", "component") })
mount(root, pure(function () {
return show ? [
m("h1", "1"),
sub
] : [
m("h1", "2")
]
}))
show = false
refresh()
show = true
refresh()
expect(root.childNodes).to.have.length(2)
})
// https://github.com/lhorie/mithril.js/issues/551
it("only redraws a component when clicked", function () {
var root = mock.document.createElement("div")
var a = false
var found = {}
var onunload = sinon.spy()
var view = sinon.spy(function () {
return m("div", {config: Comp.config}, [ // eslint-disable-line
m("div", {
onclick: function () {
a = !a
m.redraw(true)
found = root.childNodes[0].childNodes[1]
}
}, "asd"),
a ? m("#a", "aaa") : null,
"test"
])
})
var Comp = {
view: view,
config: function (el, init, ctx) {
if (!init) ctx.onunload = onunload
}
}
m.mount(root, pure(function () { return Comp }))
var target = root.childNodes[0].childNodes[0]
target.onclick({currentTarget: target})
mock.requestAnimationFrame.$resolve()
expect(onunload).to.not.be.called
expect(found).to.have.property("id", "a")
expect(view).to.have.been.calledThrice
})
// https://github.com/lhorie/mithril.js/issues/551
it("only redraws a component when clicked if the strategy is `none`", function () { // eslint-disable-line
var root = mock.document.createElement("div")
var a = false
var found = {}
var onunload = sinon.spy()
var view = sinon.spy(function () {
return m("div", {config: Comp.config}, [ // eslint-disable-line
m("div", {
onclick: function () {
a = !a
m.redraw(true)
found = root.childNodes[0].childNodes[1]
m.redraw.strategy("none")
}
}, "asd"),
a ? m("#a", "aaa") : null,
"test"
])
})
var Comp = {
view: view,
config: function (el, init, ctx) {
if (!init) ctx.onunload = onunload
}
}
m.mount(root, pure(function () { return Comp }))
var target = root.childNodes[0].childNodes[0]
target.onclick({currentTarget: target})
mock.requestAnimationFrame.$resolve()
expect(onunload).to.not.be.called
expect(found).to.have.property("id", "a")
expect(view).to.have.been.calledTwice
})
it("redraws when clicked and click handler forces redraw", function () {
var root = mock.document.createElement("div")
var view = sinon.stub().returns(m("div", {
onclick: function () { m.redraw(true) }
}))
m.mount(root, pure(view))
var target = root.childNodes[0]
target.onclick({currentTarget: target})
mock.requestAnimationFrame.$resolve()
expect(view).to.be.calledThrice
})
function resolveXhr() {
mock.XMLHttpRequest.$instances.pop().onreadystatechange()
mock.requestAnimationFrame.$resolve()
}
it("doesn't redraw on a single synchronous request", function () {
var root = mock.document.createElement("div")
var data
var view = sinon.spy(function (ctrl) {
data = ctrl.foo()
return m("div")
})
var Comp = {
controller: function () {
this.foo = m.request({method: "GET", url: "/foo"})
},
view: view
}
mount(root, pure(function () { return Comp }))
resolveXhr()
clear(root)
expect(view).to.be.calledOnce
expect(data).to.have.property("url", "/foo")
})
it("doesn't redraw on multiple synchronous requests", function () {
mock.requestAnimationFrame.$resolve()
mock.location.search = "?"
var root = mock.document.createElement("div")
var view1 = sinon.stub().returns(m("div"))
var view2 = sinon.stub().returns(m("div"))
var Comp1 = {
controller: function () {
this.foo = m.request({method: "GET", url: "/foo"})
},
view: view1
}
var Comp2 = {
controller: function () {
this.bar = m.request({method: "GET", url: "/bar"})
},
view: view2
}
mount(root, pure(function () {
return m("div", [
Comp1,
Comp2
])
}))
resolveXhr()
resolveXhr()
clear(root)
expect(view1).to.be.calledOnce
expect(view2).to.be.calledOnce
})
it("instantiates different controllers for components without controller constructors", function () { // eslint-disable-line
var root = mock.document.createElement("div")
var cond = true
var controller1, controller2
var Comp1 = pure(function (ctrl) {
controller1 = ctrl
return m("div")
})
var Comp2 = pure(function (ctrl) {
controller2 = ctrl
return m("div")
})
mount(root, pure(function () { return cond ? Comp1 : Comp2 }))
cond = false
refresh(true)
expect(controller1).to.not.equal(controller2)
})
it("unloads removed components", function () {
var root = mock.document.createElement("div")
var onunload = sinon.spy()
var cond = true
var Comp1 = pure(function () {
return m("div", {
config: function (el, init, ctx) {
ctx.onunload = onunload
}
})
})
var Comp2 = pure(function () { return m("div") })
mount(root, pure(function () { return cond ? Comp1 : Comp2 }))
cond = false
refresh(true)
expect(onunload).to.be.called
})
it("calls config with its second argument false first", function () {
var root = mock.document.createElement("div")
var cond = true
var config = sinon.spy()
var Comp1 = pure(function () { return m("div") })
var Comp2 = pure(function () { return m("div", {config: config}) })
mount(root, pure(function () { return cond ? Comp1 : Comp2 }))
cond = false
refresh(true)
expect(config.firstCall.args[1]).to.be.false
})
it("refreshes the component when it's redrawn in a handler", function () {
var root = mock.document.createElement("div")
var sub = pure(function () { return m("#bar", "test") })
var el
m.mount(root, pure(function (ctrl) {
return m("div", [
m("button", {
onclick: function () {
ctrl.bar = true
m.redraw(true)
el = root.childNodes[0].childNodes[1]
}
}, "click me"),
ctrl.bar ? m.component(sub) : ""
])
}))
root.childNodes[0].childNodes[0].onclick({})
expect(el).to.have.property("id", "bar")
})
})

64
test/mithril.prop.js Normal file
View file

@ -0,0 +1,64 @@
describe("m.prop()", function () {
"use strict"
it("reads correct value", function () {
var prop = m.prop("test")
expect(prop()).to.equal("test")
})
it("defaults to `undefined`", function () {
var prop = m.prop()
expect(prop()).to.be.undefined
})
it("sets the correct value", function () {
var prop = m.prop("test")
prop("foo")
expect(prop()).to.equal("foo")
})
it("sets `null`", function () {
var prop = m.prop(null)
expect(prop()).to.be.null
})
it("sets `undefined`", function () {
var prop = m.prop(undefined)
expect(prop()).to.be.undefined
})
it("returns the new value when set", function () {
var prop = m.prop()
expect(prop("foo")).to.equal("foo")
})
it("correctly stringifies to the correct value", function () {
var prop = m.prop("test")
expect(JSON.stringify(prop)).to.equal('"test"')
})
it("correctly stringifies to the correct value as a child", function () {
var obj = {prop: m.prop("test")}
expect(JSON.stringify(obj)).to.equal('{"prop":"test"}')
})
it("correctly wraps Mithril promises", function () {
var defer = m.deferred()
var prop = m.prop(defer.promise)
defer.resolve("test")
expect(prop()).to.equal("test")
})
it("returns a thenable when wrapping a Mithril promise", function () {
var defer = m.deferred()
var prop = m.prop(defer.promise).then(function () {
return "test2"
})
defer.resolve("test")
expect(prop()).to.equal("test2")
})
})

220
test/mithril.redraw.js Normal file
View file

@ -0,0 +1,220 @@
describe("m.redraw()", function () {
"use strict"
beforeEach(function () {
mock.requestAnimationFrame.$resolve()
})
it("exists", function () {
expect(m.redraw).to.be.a("function")
})
it("correctly renders a property if the controller value changes", function () { // eslint-disable-line
var ctx
var root = mock.document.createElement("div")
m.mount(root, {
controller: function () { ctx = this }, // eslint-disable-line
view: function (ctrl) { return ctrl.value }
})
mock.requestAnimationFrame.$resolve()
var valueBefore = root.childNodes[0].nodeValue
ctx.value = "foo"
m.redraw()
mock.requestAnimationFrame.$resolve()
expect(valueBefore).to.equal("")
expect(root.childNodes[0].nodeValue).to.equal("foo")
})
it("runs unnecessary redraws asynchronously", function () {
var root = mock.document.createElement("div")
var view = sinon.spy()
m.mount(root, {
controller: function () {},
view: view
})
mock.requestAnimationFrame.$resolve() // teardown
m.redraw()
// These should run asynchronously
m.redraw()
m.redraw()
m.redraw()
mock.requestAnimationFrame.$resolve() // teardown
expect(view).to.be.calledThrice
})
it("runs unnecessary forced redraws asynchronously", function () {
var root = mock.document.createElement("div")
var view = sinon.spy()
m.mount(root, {
controller: function () {},
view: view
})
mock.requestAnimationFrame.$resolve() // teardown
m.redraw(true)
// These should run asynchronously
m.redraw(true)
m.redraw(true)
m.redraw(true)
mock.requestAnimationFrame.$resolve() // teardown
expect(view).to.have.callCount(5)
})
context("m.redraw.strategy()", function () {
// Use this instead of m.route() unless you have to call m.route and do
// something else in the same frame.
function route() {
var res = m.route.apply(null, arguments)
mock.requestAnimationFrame.$resolve()
return res
}
// Little helper utility
function noop() {}
// Use this if all you need to do is render a view (i.e. a pure
// component).
function pure(view) {
return {
controller: noop,
view: view
}
}
// Use these instead of `it` and `xit` in this set of tests if you need
// a root element.
var dit = makeIt(it)
// Wraps the `it` function for dependency injection that doesn't require
// `this`
/* eslint-disable no-invalid-this */
function makeIt(it) {
return function (name, callback) {
return it(name, function () {
var args = [this.root]
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i])
}
callback.apply(null, args)
})
}
}
beforeEach(function () {
mock.requestAnimationFrame.$resolve()
mock.location.search = "?"
m.route.mode = "search"
this.root = mock.document.createElement("div")
})
afterEach(function () {
m.mount(this.root, null)
})
/* eslint-enable no-invalid-this */
it("exists", function () {
expect(m.redraw.strategy).to.be.a("function")
})
dit("works with \"all\"", function (root) {
var strategy
route(root, "/foo1", {
"/foo1": {
controller: function () {
strategy = m.redraw.strategy()
m.redraw.strategy("none")
},
view: function () {
return m("div")
}
}
})
expect(strategy).to.equal("all")
expect(root.childNodes).to.be.empty
})
dit("works with \"redraw\"", function (root) {
var count = 0
var strategy
function config(el, init) {
if (!init) count++
}
route(root, "/foo1", {
"/foo1": pure(function () {
return m("div", {config: config})
}),
"/bar1": {
controller: function () {
strategy = m.redraw.strategy()
m.redraw.strategy("redraw")
},
view: function () {
return m("div", {config: config})
}
}
})
route("/bar1")
expect(strategy).to.equal("all")
expect(count).to.equal(1)
})
dit("works with \"diff\"", function (root) {
var strategy
m.route(root, "/foo1", {
"/foo1": {
controller: function () { this.number = 1 },
view: function (ctrl) {
return m("div", {
onclick: function () {
strategy = m.redraw.strategy()
ctrl.number++
m.redraw.strategy("none")
}
}, ctrl.number)
}
}
})
root.childNodes[0].onclick({})
mock.requestAnimationFrame.$resolve()
expect(strategy).to.equal("diff")
expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("1")
})
dit("recreates the component when \"all\"", function (root) {
var count = 0
function config(el, init) {
if (!init) count++
}
m.route(root, "/foo1", {
"/foo1": pure(function () {
return m("div", {
config: config,
onclick: function () {
m.redraw.strategy("all")
}
})
})
})
root.childNodes[0].onclick({})
mock.requestAnimationFrame.$resolve()
expect(count).to.equal(2)
})
})
})

1554
test/mithril.render.js Normal file

File diff suppressed because it is too large Load diff

348
test/mithril.request.js Normal file
View file

@ -0,0 +1,348 @@
describe("m.request()", function () {
"use strict"
// Much easier to read
function resolve() {
var xhr = mock.XMLHttpRequest.$instances.pop()
xhr.onreadystatechange()
return xhr
}
// Common abstraction: request(opts, ...callbacks)
function request(opts) {
var ret = m.request(opts)
for (var i = 0; i < arguments.length; i++) {
ret = ret.then(arguments[i])
}
resolve()
return ret
}
it("sets the correct properties on `GET`", function () {
var prop = request({
method: "GET",
url: "test"
})
expect(prop()).to.contain.keys({
method: "GET",
url: "test"
})
})
it("returns a Mithril promise (1)", function () {
var prop = request(
{method: "GET", url: "test"},
function () { return "foo" })
expect(prop()).to.equal("foo")
})
it("returns a Mithril promise (2)", function () {
var prop = request({method: "GET", url: "test"})
var result = prop()
expect(prop.then(function (value) { return value })()).to.equal(result)
})
it("sets the correct properties on `POST`", function () {
var prop = request({
method: "POST",
url: "http://domain.com:80",
data: {}
})
expect(prop()).to.contain.keys({
method: "POST",
url: "http://domain.com:80"
})
})
it("sets the correct arguments", function () {
expect(request({
method: "POST",
url: "http://domain.com:80/:test1",
data: {test1: "foo"}
})().url).to.equal("http://domain.com:80/foo")
})
it("propogates errors through the promise (1)", function () {
var error = m.prop()
var prop = m.request({
method: "GET",
url: "test",
deserialize: function () { throw new Error("error occurred") }
}).then(null, error)
resolve()
expect(prop().message).to.equal("error occurred")
expect(error().message).to.equal("error occurred")
})
it("propogates errors through the promise (2)", function () {
var error = m.prop()
var prop = m.request({
method: "GET",
url: "test",
deserialize: function () { throw new Error("error occurred") }
}).catch(error)
resolve()
expect(prop().message).to.equal("error occurred")
expect(error().message).to.equal("error occurred")
})
it("does not propogate results to `finally`", function () {
// Data returned by then() functions do *not* propagate to finally().
var data = m.prop()
var prop = m.request({
method: "GET",
url: "test"
})
.then(function () { return "foo" })
.finally(data)
resolve()
expect(prop()).to.equal("foo")
expect(data()).to.not.exist
})
it("does not propogate `finally` results to the next promise", function () {
var data = m.prop()
var prop = m.request({method: "GET", url: "test"})
.then(function () { return "foo" })
.finally(function () { return "bar" })
.then(data)
resolve()
expect(prop()).to.equal("foo")
expect(data()).to.equal("foo")
})
it("propogates `finally` errors", function () {
var error = m.prop()
var prop = m.request({method: "GET", url: "test"})
.then(function () { return "foo" })
.finally(function () { throw new Error("error occurred") })
.catch(error)
resolve()
expect(prop().message).to.equal("error occurred")
expect(error().message).to.equal("error occurred")
})
it("runs successive `finally` after `catch`", function () {
var error = m.prop()
var prop = m.request({
method: "GET",
url: "test",
deserialize: function () { throw new Error("error occurred") }
})
.catch(error)
.finally(function () { error("finally") })
resolve()
expect(prop().message).to.equal("error occurred")
expect(error()).to.equal("finally")
})
it("synchronously throws TypeErrors", function () {
var error = m.prop()
var exception
var prop = m.request({
method: "GET",
url: "test",
deserialize: function () { throw new TypeError("error occurred") }
}).then(null, error)
try {
resolve()
} catch (e) {
exception = e
}
expect(prop()).to.not.exist
expect(error()).to.not.exist
expect(exception.message).to.equal("error occurred")
})
it("sets correct Content-Type when given data", function () {
var error = m.prop()
m.request({
method: "POST",
url: "test",
data: {foo: 1}
}).then(null, error)
var xhr = mock.XMLHttpRequest.$instances.pop()
xhr.onreadystatechange()
expect(xhr.$headers).to.have.property(
"Content-Type",
"application/json; charset=utf-8")
})
it("doesn't set Content-Type when it doesn't have data", function () {
var error = m.prop()
m.request({
method: "POST",
url: "test"
}).then(null, error)
var xhr = mock.XMLHttpRequest.$instances.pop()
xhr.onreadystatechange()
expect(xhr.$headers).to.not.have.property("Content-Type")
})
it("correctly sets initial value", function () {
var prop = m.request({
method: "POST",
url: "test",
initialValue: "foo"
})
var initialValue = prop()
resolve()
expect(initialValue).to.equal("foo")
})
it("correctly propogates initial value when not completed", function () {
var prop = m.request({
method: "POST",
url: "test",
initialValue: "foo"
}).then(function (value) { return value })
var initialValue = prop()
resolve()
expect(initialValue).to.equal("foo")
})
it("resolves `then` correctly with an initialValue", function () {
var prop = m.request({
method: "POST",
url: "test",
initialValue: "foo"
}).then(function () { return "bar" })
resolve()
expect(prop()).to.equal("bar")
})
it("appends query strings to `url` from `data` for `GET`", function () {
var prop = m.request({method: "GET", url: "/test", data: {foo: 1}})
resolve()
expect(prop().url).to.equal("/test?foo=1")
})
it("doesn't append query strings to `url` from `data` for `POST`", function () { // eslint-disable-line
var prop = m.request({method: "POST", url: "/test", data: {foo: 1}})
resolve()
expect(prop().url).to.equal("/test")
})
it("appends children in query strings to `url` from `data` for `GET`", function () { // eslint-disable-line
var prop = m.request({method: "GET", url: "test", data: {foo: [1, 2]}})
resolve()
expect(prop().url).to.equal("test?foo=1&foo=2")
})
it("propogates initial value in call before request is completed", function () { // eslint-disable-line
var value
var prop1 = m.request({method: "GET", url: "test", initialValue: 123})
expect(prop1()).to.equal(123)
var prop2 = prop1.then(function () { return 1 })
expect(prop2()).to.equal(123)
var prop3 = prop1.then(function (v) { value = v })
expect(prop3()).to.equal(123)
resolve()
expect(value.method).to.equal("GET")
expect(value.url).to.equal("test")
})
context("over jsonp", function () {
/* eslint-disable no-invalid-this */
beforeEach(function () {
var body = this.body = mock.document.createElement("body")
mock.document.body = body
mock.document.appendChild(body)
})
afterEach(function () {
mock.document.removeChild(this.body)
})
/* eslint-enable no-invalid-this */
function request(data, callbackKey) {
return m.request({
url: "/test",
dataType: "jsonp",
data: data,
callbackKey: callbackKey
})
}
function find(list, item, prop) {
var res
for (var i = 0; i < list.length; i++) {
var entry = list[i]
if (prop != null) entry = entry[prop]
if (entry.indexOf(item) >= 0) res = entry
}
return res
}
function resolve(data) {
var callback = find(Object.keys(mock), "mithril_callback")
var url = find(mock.document.getElementsByTagName("script"),
callback, "src")
mock[callback](data)
return url
}
it("sets the `GET` url with the correct query parameters", function () {
request({foo: "bar"})
expect(resolve({foo: "bar"})).to.contain("foo=bar")
})
it("correctly gets the value, without appending the script on the document", function () { // eslint-disable-line
var data = m.prop()
request().then(data)
var url = resolve({foo: "bar"})
expect(url).to.contain("/test?callback=mithril_callback")
expect(data()).to.eql({foo: "bar"})
})
it("correctly gets the value with a custom `callbackKey`, without appending the script on the document", function () { // eslint-disable-line
var data = m.prop()
request(null, "jsonpCallback").then(data)
var url = resolve({foo: "bar1"})
expect(url).to.contain("/test?jsonpCallback=mithril_callback")
expect(data()).to.eql({foo: "bar1"})
})
it("correctly gets the value on calling the function", function () {
var req = request()
resolve({foo: "bar1"})
expect(req()).to.eql({foo: "bar1"})
})
})
})

View file

@ -0,0 +1,26 @@
describe("m.route.buildQueryString()", function () {
"use strict"
it("exists", function () {
expect(m.route.buildQueryString).to.be.a("function")
})
it("converts an empty object to an empty string", function () {
expect(m.route.buildQueryString({})).to.equal("")
})
it("converts an object into a correct query string", function () {
expect(
m.route.buildQueryString({
foo: "bar",
hello: ["world", "mars", "mars"],
world: {
test: 3
},
bam: "",
yup: null,
removed: undefined
})
).to.equal("foo=bar&hello=world&hello=mars&world%5Btest%5D=3&bam=&yup")
})
})

1240
test/mithril.route.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,34 @@
describe("m.route.parseQueryString()", function () {
"use strict"
it("exists", function () {
expect(m.route.parseQueryString).to.be.a("function")
})
it("parses an empty string as an empty object", function () {
var args = m.route.parseQueryString("")
expect(args).to.eql({})
})
it("parses multiple parameters correctly", function () {
var args = m.route.parseQueryString("foo=bar&hello=world&hello=mars" +
"&bam=&yup")
expect(args).to.eql({
foo: "bar",
hello: ["world", "mars"],
bam: "",
yup: null
})
})
it("parses escapes correctly", function () {
var args = m.route.parseQueryString("foo=bar&hello%5B%5D=world&" +
"hello%5B%5D=mars&hello%5B%5D=pluto")
expect(args).to.eql({
foo: "bar",
"hello[]": ["world", "mars", "pluto"]
})
})
})

View file

@ -0,0 +1,28 @@
describe("m.startComputation(), m.endComputation()", function () {
"use strict"
it("exists", function () {
expect(m.startComputation).to.be.a("function")
expect(m.endComputation).to.be.a("function")
})
it("blocks automatic rendering", function () {
mock.requestAnimationFrame.$resolve()
var root = mock.document.createElement("div")
var controller = m.mount(root, {
controller: function () {},
view: function (ctrl) { return ctrl.value }
})
mock.requestAnimationFrame.$resolve()
m.startComputation()
controller.value = "foo"
m.endComputation()
mock.requestAnimationFrame.$resolve()
expect(root.childNodes[0].nodeValue).to.equal("foo")
})
// FIXME: this needs to be better tested
})

56
test/mithril.sync.js Normal file
View file

@ -0,0 +1,56 @@
describe("m.sync()", function () {
"use strict"
it("exists", function () {
expect(m.sync).to.be.a("function")
})
it("joins multiple promises in order to an array", function () {
var value
var deferred1 = m.deferred()
var deferred2 = m.deferred()
m.sync([deferred1.promise, deferred2.promise])
.then(function (data) { value = data })
deferred1.resolve("test")
deferred2.resolve("foo")
expect(value).to.eql(["test", "foo"])
})
it("joins multiple promises out of order to an array", function () {
var value
var deferred1 = m.deferred()
var deferred2 = m.deferred()
m.sync([deferred1.promise, deferred2.promise])
.then(function (data) { value = data })
deferred2.resolve("foo")
deferred1.resolve("test")
expect(value).to.eql(["test", "foo"])
})
// FIXME: bad behavior?
it("rejects to an array if one promise rejects", function () {
var value
var deferred = m.deferred()
m.sync([deferred.promise]).catch(function (data) { value = data })
deferred.reject("fail")
expect(value).to.eql(["fail"])
})
it("resolves immediately if given an empty array", function () {
var value = 1
m.sync([]).then(function () { value = 2 })
expect(value).to.equal(2)
})
it("resolves to an empty array if given an empty array", function () {
var value
m.sync([]).then(function (data) { value = data })
expect(value).to.eql([])
})
})

58
test/mithril.trust.js Normal file
View file

@ -0,0 +1,58 @@
describe("m.trust()", function () {
"use strict"
it("exists", function () {
expect(m.trust).to.be.a("function")
})
it("returns an instance of String", function () {
expect(m.trust("foo")).to.be.an.instanceof(String)
})
it("does not modify the string", function () {
expect(m.trust("foo").valueOf()).to.equal("foo")
})
it("is not identical to the string", function () {
expect(m.trust("foo")).to.not.equal("foo")
})
// FIXME: implement document.createRange().createContextualFragment() in the
// mock window for these tests
dom(function () {
it("isn't escaped in m.render()", function () {
var root = document.createElement("div")
m.render(root, m("div", "a", m.trust("&amp;"), "b"))
expect(root.childNodes[0].innerHTML).to.equal("a&amp;b")
})
it("works with mixed trusted content in div", function () {
var root = document.createElement("div")
m.render(root, [m.trust("<p>1</p><p>2</p>"), m("i", "foo")])
expect(root.childNodes[2].tagName).to.equal("I")
})
it("works with mixed trusted content in text nodes", function () {
var root = document.createElement("div")
m.render(root, [
m.trust("<p>1</p>123<p>2</p>"),
m("i", "foo")
])
expect(root.childNodes[3].tagName).to.equal("I")
})
// FIXME: this is a bug (trusted string's contents rendered as just
// textual contents)
xit("works with mixed trusted content in td", function () {
var root = document.createElement("table")
root.appendChild(root = document.createElement("tr"))
m.render(root, [
m.trust("<td>1</td><td>2</td>"),
m("td", "foo")
])
expect(root.childNodes[2].tagName).to.equal("td")
})
})
})

17
test/mithril.withAttr.js Normal file
View file

@ -0,0 +1,17 @@
describe("m.withAttr()", function () {
"use strict"
it("calls the handler with the right value/context without callbackThis", function () { // eslint-disable-line
var spy = sinon.spy()
var object = {}
m.withAttr("test", spy).call(object, {currentTarget: {test: "foo"}})
expect(spy).to.be.calledOn(object).and.calledWith("foo")
})
it("calls the handler with the right value/context with callbackThis", function () { // eslint-disable-line
var spy = sinon.spy()
var object = {}
m.withAttr("test", spy, object)({currentTarget: {test: "foo"}})
expect(spy).to.be.calledOn(object).and.calledWith("foo")
})
})

214
test/svg.html Normal file
View file

@ -0,0 +1,214 @@
<!doctype html>
<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>
<p>
This page is for testing SVG support in Mithril. 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>
m.render(document.getElementById("test"), [
m("a[href='http://google.com'][target=_blank][title='HTML link']",
"HTML link"),
m("br"),
m("svg[width=180][height=200]", [
m("rect[title=Square]", {
x: 50,
y: 50,
height: 100,
width: 100,
transform: "translate(30) rotate(45 50 50)",
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[title='Line drawings']", {
"enable-background": "new 0 0 340 333",
height: "333px",
viewBox: "0 0 340 333",
width: "340px",
x: "0px",
y: "0px"
}, [
m("path.path", {
d: [
"M 66.039,133.545",
"c 0,0 -21 -57,18 -67",
"s 49 -4,65,8",
"s 30,41,53,27",
"s 66,4,58,32",
"s -5,44,18,57",
"s 22,46,0,45",
"s -54 -40 -68 -16",
"s -40,88 -83,48",
"s 11 -61 -11 -80",
"s -79 -7 -70 -41",
"C 46.039,146.545,53.039,128.545,66.039,133.545",
"z"
].join(" "),
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#hour", [
m("line", {
"stroke-width": 5,
y2: 75,
"stroke-linecap": "round",
stroke: "blue",
opacity: 0.5
}),
m("animateTransform[attributeName=transform]", {
type: "rotate",
repeatCount: "indefinite",
dur: "12h",
by: 360
}),
m("circle[r=7]")
]),
m("g#minute", [
m("line", {
"stroke-width": 4,
y2: 93,
"stroke-linecap": "round",
stroke: "green",
opacity: 0.9
}),
m("animateTransform[attributeName=transform]", {
type: "rotate",
repeatCount: "indefinite",
dur: "60min",
by: 360
}),
m("circle[r=6][fill=red]")
]),
m("g#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", "(" + function () {
"use strict"
function rotate(id, num) {
document.getElementById(id)
.setAttribute("transform", "rotate(" + num + ")")
}
var date = new Date()
var hours = date.getHours()
if (hours > 12) hours -= 12
var minutes = date.getMinutes()
var seconds = date.getSeconds()
rotate("hour", 30 * (hours + minutes / 60 + seconds / 3600))
rotate("minute", 6 * (minutes + seconds / 60))
rotate("second", 6 * seconds)
}.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</a>")
]))
])
])
</script>

View file

@ -1,244 +0,0 @@
/**
* QUnit v1.12.0 - A JavaScript Unit Testing Framework
*
* http://qunitjs.com
*
* Copyright 2012 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
line-height: 1em;
font-weight: normal;
border-radius: 5px 5px 0 0;
-moz-border-radius: 5px 5px 0 0;
-webkit-border-top-right-radius: 5px;
-webkit-border-top-left-radius: 5px;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-testrunner-toolbar label {
display: inline-block;
padding: 0 .5em 0 .1em;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
overflow: hidden;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
#qunit-modulefilter-container {
float: right;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests li .runtime {
float: right;
font-size: smaller;
}
.qunit-assert-list {
margin-top: 0.5em;
padding: 0.5em;
background-color: #fff;
border-radius: 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
}
.qunit-collapsed {
display: none;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
padding: 5px;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #3c510c;
background-color: #fff;
border-left: 10px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 10px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 5px 5px;
-moz-border-radius: 0 0 5px 5px;
-webkit-border-bottom-right-radius: 5px;
-webkit-border-bottom-left-radius: 5px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
#qunit-testresult .module-name {
font-weight: bold;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
width: 1000px;
height: 1000px;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Basic Test Suite</title>
<!-- Load local QUnit. -->
<link rel="stylesheet" href="libs/qunit.css" media="screen">
<script src="libs/qunit.js"></script>
<!-- Load local lib and tests. -->
<script src="libs/syn.js"></script>
<script src="../../mithril.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="dummy"></div>
</body>
<script src="tests.js"></script>
</html>

View file

@ -1,437 +0,0 @@
//saucelabs reporting; see https://github.com/axemclion/grunt-saucelabs#test-result-details-with-qunit
var log = []
QUnit.done(function (test_results) {
var tests = []
for (var i = 0, len = log.length; i < len; i++) {
var details = log[i]
tests.push({
name: details.name,
result: details.result,
expected: details.expected,
actual: details.actual,
source: details.source
})
}
test_results.tests = tests
window.global_test_results = test_results
})
QUnit.testStart(function (testDetails) {
QUnit.log(function (details) {
if (!details.result) {
details.name = testDetails.name
log.push(details)
}
})
})
//qunit doesn't support Function.prototype.bind...
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable")
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)))
}
fNOP.prototype = this.prototype
fBound.prototype = new fNOP()
return fBound
}
}
//tests
var dummyEl = document.getElementById('dummy')
test('Mithril accessible as window.m', function() {
expect(1)
ok(window.m)
})
test('m.trust w/ html entities', function() {
expect(1)
var view1 = m('div', "a", m.trust("&amp;"), "b")
m.render(dummyEl, view1)
equal(dummyEl.innerHTML, '<div>a&amp;b</div>', 'view1 rendered correctly')
})
test('m.trust w/ html entities 2', function() {
expect(1)
var view1 = m('div', "a", m.trust("&amp;"), "b", m.trust("&amp;"), "c")
m.render(dummyEl, view1)
equal(dummyEl.innerHTML, '<div>a&amp;b&amp;c</div>', 'view1 rendered correctly')
})
test('array item removal', function() {
expect(2)
var view1 = m('div', {}, [
m('div', {}, '0'),
m('div', {}, '1'),
m('div', {}, '2')
])
var view2 = m('div', {}, [
m('div', {}, '0')
])
m.render(dummyEl, view1)
equal(dummyEl.innerHTML, '<div><div>0</div><div>1</div><div>2</div></div>', 'view1 rendered correctly')
m.render(dummyEl, view2)
equal(dummyEl.innerHTML, '<div><div>0</div></div>', 'view2 should be rendered correctly')
})
test('issue99 regression', function() {
// see https://github.com/lhorie/mithril.js/issues/99
expect(2)
var view1 = m('div', {}, [
m('div', {}, '0'),
m('div', {}, '1'),
m('div', {}, '2')
])
var view2 = m('div', {}, [
m('span', {}, '0')
])
m.render(dummyEl, view1)
equal(dummyEl.innerHTML, '<div><div>0</div><div>1</div><div>2</div></div>', 'view1 rendered correctly')
m.render(dummyEl, view2)
equal(dummyEl.innerHTML, '<div><span>0</span></div>', 'view2 should be rendered correctly')
})
test('config handler context', function() {
expect(3)
var view = m('div', {config: function(evt, isInitialized, context) {
equal(context instanceof Object, true)
context.data = 1
}})
m.render(dummyEl, view)
view = m('div', {config: function(evt, isInitialized, context) {
equal(context instanceof Object, true)
equal(context.data, 1)
}})
m.render(dummyEl, view)
})
test('node identity remove firstChild', function() {
expect(2)
var view1 = m('div', {}, [
m('div', {key:1}, 'E1'),
m('div', {key:2}, 'E2')
])
m.render(dummyEl, view1)
var node2 = dummyEl.firstChild.lastChild
equal(node2.innerHTML, 'E2')
var view2 = m('div', {}, [
m('div', {key:2}, 'E2')
])
m.render(dummyEl, view2)
equal(dummyEl.firstChild.firstChild, node2)
})
test('node identity change order', function() {
expect(2)
var view1 = m('div', {}, [
m('div', {key:1}, 'E1'),
m('div', {key:2}, 'E2'),
m('div', {key:3}, 'E3')
])
m.render(dummyEl, view1)
var e2 = dummyEl.firstChild.firstChild.nextSibling
equal(e2.innerHTML, 'E2')
var view2 = m('div', {}, [
m('div', {key:2}, 'E2'),
m('div', {key:1}, 'E1'),
m('div', {key:3}, 'E3')
])
m.render(dummyEl, view2)
equal(dummyEl.firstChild.firstChild, e2)
})
test('node identity remove in the middle', function() {
expect(2)
var view1 = m('div', {}, [
m('div', {key:1}, 'E1'),
m('div', {key:2}, 'E2'),
m('div', {key:3}, 'E3')
])
m.render(dummyEl, view1)
var e3 = dummyEl.firstChild.lastChild
equal(e3.innerHTML, 'E3')
var view2 = m('div', {}, [
m('div', {key:1}, 'E1'),
m('div', {key:3}, 'E3')
])
m.render(dummyEl, view2)
equal(dummyEl.firstChild.firstChild.nextSibling, e3)
})
test('node identity remove last', function() {
expect(4)
var view1 = m('div', {}, [
m('div', {key:1}, 'E1'),
m('div', {key:2}, 'E2'),
m('div', {key:3}, 'E3')
])
m.render(dummyEl, view1)
var e1 = dummyEl.firstChild.firstChild
equal(e1.innerHTML, 'E1')
var e2 = dummyEl.firstChild.firstChild.nextSibling
equal(e2.innerHTML, 'E2')
var view2 = m('div', {}, [
m('div', {key:1}, 'E1'),
m('div', {key:2}, 'E2')
])
m.render(dummyEl, view2)
equal(dummyEl.firstChild.firstChild, e1)
equal(dummyEl.firstChild.firstChild.nextSibling, e2)
})
test('node identity shuffle and remove', function() {
expect(8)
var view1 = m('div', {}, [
m('div', {key:1}, 'E1'),
m('div', {key:2}, 'E2'),
m('div', {key:3}, 'E3'),
m('div', {key:4}, 'E4'),
m('div', {key:5}, 'E5')
])
m.render(dummyEl, view1)
var e1 = dummyEl.firstChild.firstChild
equal(e1.innerHTML, 'E1')
var e2 = e1.nextSibling
equal(e2.innerHTML, 'E2')
var e3 = e2.nextSibling
equal(e3.innerHTML, 'E3')
var e4 = e3.nextSibling
equal(e4.innerHTML, 'E4')
var e5 = e4.nextSibling
equal(e5.innerHTML, 'E5')
var view2 = m('div', {}, [
m('div', {key:4}, 'E4'),
m('div', {key:10}, 'E10'),
m('div', {key:1}, 'E1'),
m('div', {key:2}, 'E2')
])
m.render(dummyEl, view2)
equal(dummyEl.firstChild.firstChild, e4, 'e4 is first element')
equal(dummyEl.firstChild.firstChild.nextSibling.nextSibling, e1, 'e1 is third element')
equal(dummyEl.firstChild.firstChild.nextSibling.nextSibling.nextSibling, e2, 'e2 is fourth element')
})
asyncTest('issue214 regression', function() {
// see https://github.com/lhorie/mithril.js/issues/214
expect(2)
function controller() {
this.inputValue = m.prop('')
}
function view(ctrl) {
return m('input#testinput', {
value: ctrl.inputValue(),
onkeyup: m.withAttr('value', ctrl.inputValue)
})
}
var ctrl = m.module(dummyEl, { controller: controller, view: view })
Syn.click({}, 'testinput')
.type('0').delay(10)
.type('1').delay(10)
.type('2').delay(10)
.type('3').delay(10)
.type('4').delay(10)
.type('5').delay(10)
.type('6').delay(10)
.type('7').delay(10)
.type('8').delay(10)
.type('9').delay(10)
.type('a').delay(10)
.type('b').delay(10)
.type('c').delay(10)
.type('d').delay(10)
.type('e').delay(10)
.type('f').delay(10)
.type('0').delay(10)
.type('1').delay(10)
.type('2').delay(10)
.type('3').delay(10)
.type('4').delay(10)
.type('5').delay(10)
.type('6').delay(10)
.type('7').delay(10)
.type('8').delay(10)
.type('9').delay(10)
.type('a').delay(10)
.type('b').delay(10)
.type('c').delay(10)
.type('d').delay(10)
.type('e').delay(10)
.type('f').delay(10)
.type('0').delay(10)
.type('1').delay(10)
.type('2').delay(10)
.type('3').delay(10)
.type('4').delay(10)
.type('5').delay(10)
.type('6').delay(10)
.type('7').delay(10)
.type('8').delay(10)
.type('9').delay(10)
.type('a').delay(10)
.type('b').delay(10)
.type('c').delay(10)
.type('d').delay(10)
.type('e').delay(10)
.type('f').delay(10)
.type('0').delay(10)
.type('1').delay(10)
.type('2').delay(10)
.type('3').delay(10)
.type('4').delay(10)
.type('5').delay(10)
.type('6').delay(10)
.type('7').delay(10)
.type('8').delay(10)
.type('9').delay(10)
.type('a').delay(10)
.type('b').delay(10)
.type('c').delay(10)
.type('d').delay(10)
.type('e').delay(10)
.type('f', function() {
equal(ctrl.inputValue(), '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')
equal(document.getElementById('testinput').value, '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')
start()
})
})
asyncTest('issue288 regression', function() {
// see https://github.com/lhorie/mithril.js/issues/288
expect(2)
function controller() {
this.inputValue = m.prop('')
this.submit = function() {
if (this.inputValue()) {
this.inputValue('')
}
}.bind(this);
}
function view(ctrl) {
return m('form', { onsubmit: ctrl.submit }, [
m('input#testinput', {
onkeyup: m.withAttr('value', ctrl.inputValue),
value: ctrl.inputValue()
}),
m('button[type=submit]')
])
}
var ctrl = m.module(dummyEl, { controller: controller, view: view })
Syn.click({}, 'testinput')
.type('a').delay(10)
.type('b').delay(10)
.type('c').delay(10)
.type('d').delay(10)
.type('[enter]', function() {
equal(ctrl.inputValue(), '')
equal(document.getElementById('testinput').value, '')
start()
})
})
test('issue278 regression', function() {
// see https://github.com/lhorie/mithril.js/issues/278
expect(1)
var test = {
controller: function() {
this.values = [1, 2, 3, 4, 5]
this.value = m.prop([2, 3])
},
view: function(ctrl) {
return m('select#testselect', {
size: ctrl.values.length,
multiple: 'multiple'
}, [
ctrl.values.map(function(v){
var opts = {value: v}
if (ctrl.value().indexOf(v) !== -1) opts.selected = 'selected'
return m('option', opts, v)
})
])
}
}
m.render(dummyEl, test.view(new test.controller))
var select = document.getElementById('testselect')
for (var i = 0, selected = 0; i < select.options.length; i++) {
if (select.options[i].selected) selected++
}
equal(selected, 2)
})
test("mixing trusted content", function() {
m.render(dummyEl, [m.trust("<p>1</p><p>2</p>"), m("i", "foo")])
equal(dummyEl.childNodes[2].nodeName, "I")
})
test("mixing trusted content w/ text nodes", function() {
m.render(dummyEl, [m.trust("<p>1</p>123<p>2</p>"), m("i", "foo")])
equal(dummyEl.childNodes[3].nodeName, "I")
})
test("mixing trusted content w/ td", function() {
m.render(dummyEl, [m.trust("<td>1</td><td>2</td>"), m("i", "foo")])
equal(dummyEl.childNodes[1].nodeName, "I")
})
test("0 should not be treated as empty string", function() {
m.render(dummyEl, m("input", {value: ""}))
m.render(dummyEl, m("input", {value: 0}))
equal(dummyEl.childNodes[0].value, "0")
})
test("empty value in <option> should show as attribute", function() {
m.render(dummyEl, m("select", m("option", {value: ""}, "aaa")))
equal(dummyEl.childNodes[0].innerHTML, '<option value="">aaa</option>')
})

View file

@ -1,8 +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>
<script src="mithril-tests.js"></script>
<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>

File diff suppressed because it is too large Load diff

View file

@ -1,172 +0,0 @@
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
}
}
var mock = {}
mock.window = (function() {
var window = {}
window.document = {}
window.document.childNodes = []
window.document.createElement = function(tag) {
return {
style: {},
childNodes: [],
nodeType: 1,
nodeName: tag.toUpperCase(),
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)
var index = this.childNodes.indexOf(node)
if (index > -1) this.childNodes.splice(index, 1)
if (referenceIndex < 0) this.childNodes.push(node)
else this.childNodes.splice(referenceIndex, 0, node)
},
insertAdjacentHTML: function(position, html) {
// todo: accept markup
if (position === "beforebegin") {
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()
},
setAttributeNS: function(namespace, name, value) {
this.namespaceURI = namespace
this[name] = value.toString()
},
getAttribute: function(name, value) {
return this[name]
},
addEventListener: function () {},
removeEventListener: function () {}
}
}
window.document.createElementNS = function(namespace, tag) {
var element = window.document.createElement(tag)
element.namespaceURI = namespace
return element
}
window.document.createTextNode = function(text) {
return {nodeValue: text.toString()}
}
window.document.documentElement = window.document.createElement("html")
window.document.replaceChild = function(newChild, oldChild) {
var index = this.childNodes.indexOf(oldChild)
if (index > -1) this.childNodes.splice(index, 1, newChild)
else this.childNodes.push(newChild)
newChild.parentNode = this
oldChild.parentNode = null
}
window.document.appendChild = function(child) {
var index = this.childNodes.indexOf(child)
if (index > -1) this.childNodes.splice(index, 1)
this.childNodes.push(child)
child.parentNode = this
}
window.document.removeChild = function(child) {
var index = this.childNodes.indexOf(child)
this.childNodes.splice(index, 1)
child.parentNode = null
}
// 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){
if(node.childNodes && node.childNodes.length > 0){
node.childNodes.map(function(curr){
if (curr.nodeName.toLowerCase() === name) {
out.push(curr);
}
traverse(curr);
});
}
}
traverse(window.document);
return out;
}
window.scrollTo = function() {}
window.cancelAnimationFrame = function() {}
window.requestAnimationFrame = function(callback) {
window.requestAnimationFrame.$callback = callback
return window.requestAnimationFrame.$id++
}
window.requestAnimationFrame.$id = 1
window.requestAnimationFrame.$resolve = function() {
if (window.requestAnimationFrame.$callback) {
var callback = window.requestAnimationFrame.$callback
window.requestAnimationFrame.$callback = null
callback()
}
}
window.XMLHttpRequest = (function() {
var request = function() {
this.$headers = {}
this.setRequestHeader = function(key, value) {
this.$headers[key] = value
}
this.open = function(method, url) {
this.method = method
this.url = url
}
this.send = function() {
this.responseText = JSON.stringify(this)
this.readyState = 4
this.status = 200
request.$instances.push(this)
}
}
request.$instances = []
return request
}())
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
}
return window
}())

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 +0,0 @@
if (!this.console) {
var log = function(value) {document.write("<pre>" + value + "</pre>")}
this.console = {log: log, error: log}
}
function test(condition) {
test.total++
try {
if (!condition()) throw new Error("failed")
}
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")
}
}