Merge pull request #1 from lhorie/next
This commit is contained in:
commit
f53e783a57
40 changed files with 6233 additions and 10845 deletions
12
.eslintignore
Normal file
12
.eslintignore
Normal 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
93
.eslintrc
Normal 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]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- "4.1"
|
||||
|
||||
script:
|
||||
- grunt test
|
||||
- grunt teste2e
|
||||
- stable
|
||||
|
||||
sudo: false
|
||||
|
|
|
|||
453
Gruntfile.js
453
Gruntfile.js
|
|
@ -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"
|
||||
])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
46
mithril.d.ts
vendored
|
|
@ -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>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
90
package.json
90
package.json
|
|
@ -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
16
test-deps/dom.js
Normal 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
258
test-deps/mock.js
Normal 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
18
test/.eslintrc
Normal 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
73
test/index.html
Normal 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
73
test/input-cursor.html
Normal 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
289
test/mithril.deferred.js
Normal 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
216
test/mithril.js
Normal 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
802
test/mithril.mount.js
Normal 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
64
test/mithril.prop.js
Normal 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
220
test/mithril.redraw.js
Normal 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
1554
test/mithril.render.js
Normal file
File diff suppressed because it is too large
Load diff
348
test/mithril.request.js
Normal file
348
test/mithril.request.js
Normal 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"})
|
||||
})
|
||||
})
|
||||
})
|
||||
26
test/mithril.route.buildQueryString.js
Normal file
26
test/mithril.route.buildQueryString.js
Normal 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
1240
test/mithril.route.js
Normal file
File diff suppressed because it is too large
Load diff
34
test/mithril.route.parseQueryString.js
Normal file
34
test/mithril.route.parseQueryString.js
Normal 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"]
|
||||
})
|
||||
})
|
||||
})
|
||||
28
test/mithril.startComputation.js
Normal file
28
test/mithril.startComputation.js
Normal 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
56
test/mithril.sync.js
Normal 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
58
test/mithril.trust.js
Normal 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("&"), "b"))
|
||||
expect(root.childNodes[0].innerHTML).to.equal("a&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
17
test/mithril.withAttr.js
Normal 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
214
test/svg.html
Normal 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>
|
||||
|
|
@ -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
|
|
@ -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>
|
||||
|
|
@ -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("&"), "b")
|
||||
|
||||
m.render(dummyEl, view1)
|
||||
equal(dummyEl.innerHTML, '<div>a&b</div>', 'view1 rendered correctly')
|
||||
})
|
||||
|
||||
test('m.trust w/ html entities 2', function() {
|
||||
expect(1)
|
||||
var view1 = m('div', "a", m.trust("&"), "b", m.trust("&"), "c")
|
||||
|
||||
m.render(dummyEl, view1)
|
||||
equal(dummyEl.innerHTML, '<div>a&b&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>')
|
||||
})
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
172
tests/mock.js
172
tests/mock.js
|
|
@ -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
|
||||
}())
|
||||
|
|
@ -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>
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue