Convert tests to Mocha/Chai/Sinon and lint them.
Details: 1. All tests now live in `test`. All test dependencies that aren't from npm live in `test-deps`. 2. The QUnit tests are gone, as well as their dependencies. Half of them duplicated existing tests, and some of them depended on the real DOM to properly test. 3. All tests are now using Mocha to run the tests, Chai for assertions, and Sinon and Sinon Chai for testing some callbacks. 4. Tests are run through mocha-phantomjs. If you want to run just the tests, run `grunt mocha_phantomjs` or fire up a server in the root and open `http://localhost:<port>/test/index.html`, e.g. `python3 -m http.server`. 5. The linter I chose is ESLint. It is relatively easy to configure, but with a lot of flexibility. The rules I chose mostly were in tune to the style the project was already using. I'm not including a style guide in this commit, but one will likely come. You can check out the `.eslintrc` in the root and in `test/` for the two configs. The `.eslintignore` includes a TODO for `mithril.js` itself targeted at me, in the root. Other info: - As a drive-by fix, I fixed line endings on a few of the files. - I also took care of a few other files and linted them as I went: - `Gruntfile.js` - `test/input-cursor.html` (was in `tests/`) - `test/svg.html` (was in `tests/`) - `docs/layout/tools/template-converter.html` - `docs/layout/tools/template-converter.js` I didn't test the template converter after linting it, because it needs further scrutiny to ensure it works with the latest version of Mithril. I know the API has changed a little, which is why I want to be sure. - I simplified the `.travis.yml` file because none of the tests are run directly through Node anymore. They are always run in a browser of some kind. Hopefully, this turned out all right...
This commit is contained in:
parent
8737c7e2c1
commit
12b8f044f1
39 changed files with 6210 additions and 10822 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
|
||||
|
|
|
|||
447
Gruntfile.js
447
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"}
|
||||
];
|
||||
|
||||
var sauceOnTestComplete = function(result, callback) {
|
||||
var request = require('request');
|
||||
|
||||
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);
|
||||
makeTasks("guide", guide)
|
||||
makeTasks("api", api)
|
||||
|
||||
var currentVersionArchiveFolder = archiveFolder + "/v" + version
|
||||
|
||||
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},
|
||||
style: {
|
||||
src: inputFolder + "/layout/style.css",
|
||||
dest: currentVersionArchiveFolder + "/style.css"
|
||||
},
|
||||
execute: {
|
||||
tests: {src: [currentVersionArchiveFolder + "/mithril-tests.js"]}
|
||||
|
||||
pages: {
|
||||
src: inputFolder + "/layout/pages.json",
|
||||
dest: currentVersionArchiveFolder + "/pages.json"
|
||||
},
|
||||
qunit: {
|
||||
all: ['tests/e2e/**/*.html']
|
||||
|
||||
lib: {
|
||||
expand: true,
|
||||
cwd: inputFolder + "/layout/lib/",
|
||||
src: "./**",
|
||||
dest: currentVersionArchiveFolder + "/lib/"
|
||||
},
|
||||
"saucelabs-custom": {
|
||||
all:{
|
||||
options: sauceCustomOptions
|
||||
|
||||
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"
|
||||
])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
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);
|
||||
function each(list, f) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
f(list[i], i)
|
||||
}
|
||||
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)});
|
||||
}
|
||||
}
|
||||
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])));
|
||||
function createFragment(markup) {
|
||||
if (markup.indexOf("<!doctype") >= 0) {
|
||||
return [
|
||||
new DOMParser()
|
||||
.parseFromString(markup, "text/html")
|
||||
.childNodes[1]
|
||||
]
|
||||
}
|
||||
|
||||
var virtualFragment = arguments[0], level = arguments[1]
|
||||
if (!level) level = 1;
|
||||
var container = document.createElement("div")
|
||||
container.insertAdjacentHTML("beforeend", markup)
|
||||
return container.childNodes
|
||||
}
|
||||
|
||||
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") + '"');
|
||||
}
|
||||
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 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, "\\'") + "']";
|
||||
}
|
||||
if (virtual == "") virtual = "div"
|
||||
virtual = '"' + virtual + '"';
|
||||
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)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
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") + '"')
|
||||
}
|
||||
},
|
||||
|
||||
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 + '"'
|
||||
|
||||
var style = ""
|
||||
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);
|
||||
}
|
||||
return {
|
||||
controller: function () {
|
||||
this.source = m.prop("")
|
||||
this.output = m.prop("")
|
||||
|
||||
templateConverter.controller = function() {
|
||||
this.source = m.prop("");
|
||||
this.output = m.prop("");
|
||||
this.convert = function () {
|
||||
var source = createVirtual(createFragment(this.source()))
|
||||
return this.output(new TemplateBuilder(source, 1).complete())
|
||||
}.bind(this)
|
||||
},
|
||||
|
||||
this.convert = function() {
|
||||
return this.output(new templateConverter.Template(this.source()));
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
templateConverter.view = function(ctrl) {
|
||||
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())
|
||||
]);
|
||||
};
|
||||
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
package.json
46
package.json
|
|
@ -11,36 +11,38 @@
|
|||
},
|
||||
"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-contrib-clean": "*",
|
||||
"grunt-contrib-concat": "*",
|
||||
"grunt-execute": "*",
|
||||
"grunt-eslint": "^17.3.1",
|
||||
"grunt-md2html": "*",
|
||||
"grunt-mocha-phantomjs": "^2.0.0",
|
||||
"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": "*",
|
||||
"grunt-saucelabs-browsers": "^0.2.0",
|
||||
"grunt-zip": "*",
|
||||
"load-grunt-config": "^0.9.2",
|
||||
"mocha": "^2.3.3",
|
||||
"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"
|
||||
"sinon": "^1.17.2",
|
||||
"sinon-chai": "^2.8.0"
|
||||
},
|
||||
"main": "mithril.js",
|
||||
"licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}],
|
||||
"files": ["mithril.min.js", "mithril.min.js.map", "mithril.js", "README.*"]
|
||||
"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