From 12b8f044f1a86ad901b83e0f52d70d3d8013c423 Mon Sep 17 00:00:00 2001 From: impinball Date: Sat, 31 Oct 2015 11:07:22 -0400 Subject: [PATCH] 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:/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... --- .eslintignore | 12 + .eslintrc | 93 + .travis.yml | 8 +- Gruntfile.js | 453 ++- docs/layout/pages.json | 2 +- docs/layout/tools/template-converter.html | 14 +- docs/layout/tools/template-converter.js | 209 +- package.json | 90 +- test-deps/dom.js | 16 + test-deps/mock.js | 258 ++ test/.eslintrc | 18 + test/index.html | 73 + test/input-cursor.html | 73 + test/mithril.deferred.js | 289 ++ test/mithril.js | 216 + test/mithril.mount.js | 802 ++++ test/mithril.prop.js | 64 + test/mithril.redraw.js | 220 + test/mithril.render.js | 1554 ++++++++ test/mithril.request.js | 348 ++ test/mithril.route.buildQueryString.js | 26 + test/mithril.route.js | 1240 ++++++ test/mithril.route.parseQueryString.js | 34 + test/mithril.startComputation.js | 28 + test/mithril.sync.js | 56 + test/mithril.trust.js | 58 + test/mithril.withAttr.js | 17 + test/svg.html | 214 + tests/e2e/libs/qunit.css | 244 -- tests/e2e/libs/qunit.js | 2212 ---------- tests/e2e/libs/syn.js | 2858 ------------- tests/e2e/test.html | 18 - tests/e2e/tests.js | 437 -- tests/index.html | 8 - tests/input-cursor.html | 48 - tests/mithril-tests.js | 4425 --------------------- tests/mock.js | 172 - tests/svg.html | 97 - tests/test.js | 28 - 39 files changed, 6210 insertions(+), 10822 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 test-deps/dom.js create mode 100644 test-deps/mock.js create mode 100644 test/.eslintrc create mode 100644 test/index.html create mode 100644 test/input-cursor.html create mode 100644 test/mithril.deferred.js create mode 100644 test/mithril.js create mode 100644 test/mithril.mount.js create mode 100644 test/mithril.prop.js create mode 100644 test/mithril.redraw.js create mode 100644 test/mithril.render.js create mode 100644 test/mithril.request.js create mode 100644 test/mithril.route.buildQueryString.js create mode 100644 test/mithril.route.js create mode 100644 test/mithril.route.parseQueryString.js create mode 100644 test/mithril.startComputation.js create mode 100644 test/mithril.sync.js create mode 100644 test/mithril.trust.js create mode 100644 test/mithril.withAttr.js create mode 100644 test/svg.html delete mode 100644 tests/e2e/libs/qunit.css delete mode 100644 tests/e2e/libs/qunit.js delete mode 100644 tests/e2e/libs/syn.js delete mode 100644 tests/e2e/test.html delete mode 100644 tests/e2e/tests.js delete mode 100644 tests/index.html delete mode 100644 tests/input-cursor.html delete mode 100644 tests/mithril-tests.js delete mode 100644 tests/mock.js delete mode 100644 tests/svg.html delete mode 100644 tests/test.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..ff583c3f --- /dev/null +++ b/.eslintignore @@ -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 diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..15bd44a7 --- /dev/null +++ b/.eslintrc @@ -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] + } +} diff --git a/.travis.yml b/.travis.yml index 62c2fed2..2b42d205 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,5 @@ language: node_js node_js: - - "0.10" - - "0.12" - - "4.1" - -script: - - grunt test - - grunt teste2e + - stable sudo: false diff --git a/Gruntfile.js b/Gruntfile.js index b4dda36a..39f8fe60 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,11 +1,11 @@ -module.exports = function(grunt) { - var _ = require("lodash"); - var version = "0.2.1"; +/* eslint-env node */ +module.exports = function (grunt) { // eslint-disable-line + var version = "0.2.1" - var inputFolder = "./docs"; - var tempFolder = "./temp"; - var archiveFolder = "./archive"; - var outputFolder = "../mithril"; + var inputFolder = "./docs" + var tempFolder = "./temp" + var archiveFolder = "./archive" + var outputFolder = "../mithril" var guide = [ "auto-redrawing", @@ -22,7 +22,8 @@ module.exports = function(grunt) { "routing", "tools", "web-services" - ]; + ] + var api = [ "change-log", "roadmap", @@ -42,178 +43,344 @@ module.exports = function(grunt) { "mithril.trust", "mithril.withAttr", "mithril.xhr" - ]; + ] + + var md2htmlTasks = {} + function makeTasks(layout, pages) { + pages.forEach(function (name) { + var src = inputFolder + "/" + name + ".md" + var title = "" + + if (grunt.file.exists(src)) { + title = grunt.file.read(src).split(/\n/)[0].substring(3) + + " - " + } - var md2htmlTasks = {}; - var makeTasks = function(layout, pages) { - pages.map(function(name) { - var src = inputFolder + "/" + name + ".md"; - var title = (grunt.file.exists(src)) ? grunt.file.read(src).split(/\n/)[0].substring(3) + ' - ' : ''; md2htmlTasks[name] = { - options: {layout: inputFolder + "/layout/" + layout + ".html", templateData: { "topic": title }}, - files: [{src: [src], dest: tempFolder + "/" + name + ".html"}] + options: { + layout: inputFolder + "/layout/" + layout + ".html", + templateData: {topic: title} + }, + files: [{ + src: [src], + dest: tempFolder + "/" + name + ".html" + }] } }) - }; - makeTasks("guide", guide); - makeTasks("api", api); + } - var sauceBrowsers =[ - { browserName: 'firefox', version: '19', platform: 'XP' }, - { browserName: "internet explorer", platform: "XP", version: "6"}, - { browserName: "safari", platform: "OS X 10.9", version: "7"}, - { browserName: "iPad", platform: "OS X 10.9", version: "7.1"}, - { browserName: "opera", platform: "Linux", version: "12"}, - { browserName: "chrome", platform: "XP", version: "26"}, - { browserName: "chrome", platform: "Windows 8", version: "26"} - ]; + makeTasks("guide", guide) + makeTasks("api", api) - var sauceOnTestComplete = function(result, callback) { - var request = require('request'); + var currentVersionArchiveFolder = archiveFolder + "/v" + version - var user = process.env.SAUCE_USERNAME; - var pass = process.env.SAUCE_ACCESS_KEY; - - request.put({ - url: ['https://saucelabs.com/rest/v1', user, 'jobs', result.job_id].join('/'), - auth: { user: user, pass: pass }, - json: { passed: result.passed } - }, function (error, response) { - if (error) { - callback(error); - } else if (response.statusCode !== 200) { - callback(new Error('Unexpected response status: ' - + response.statusCode + "\n ")); - } else { - callback(null, result.passed); - } - }); - }; - - var sauceBaseOptions = { - username: process.env.SAUCE_USERNAME, - key: process.env.SAUCE_ACCESS_KEY, - testname: "Mithril Tests " + new Date().toJSON(), - browsers: sauceBrowsers, - sauceConfig: { - "record-video": false, - "record-screenshots": false, - }, - build: process.env.TRAVIS_JOB_ID, - onTestComplete: sauceOnTestComplete, - tunnelTimeout: 5, - }; - var sauceCustomOptions = { - testname: "Mithril Custom Tests "+ new Date().toJSON(), - urls: ["http://127.0.0.1:8000/tests/index.html"], - }; - _.assign(sauceCustomOptions, sauceBaseOptions); - var sauceQunitOptions = { - testname: "qUnit Tests "+ new Date().toJSON(), - urls: ["http://127.0.0.1:8000/tests/e2e/test.html"], - }; - _.assign(sauceQunitOptions, sauceBaseOptions); - - var currentVersionArchiveFolder = archiveFolder + "/v" + version; grunt.initConfig({ + // Keep this in sync with the .eslintignore + eslint: { + options: { + extensions: [".js"], + fix: true + }, + all: [ + "**/*.js", + "!node_modules/**", + "!**/*.min.js", + "!archive/**", + "!deploy/**", + "!mithril.closure-compiler-externs.js", + "!docs/layout/lib/**", + // TODO(impinball): Finish this. + "!mithril.js" + ] + }, + + mocha_phantomjs: { // eslint-disable-line camelcase + test: { + src: ["test/index.html"], + options: { + reporter: "dot" + } + } + }, + md2html: md2htmlTasks, + uglify: { - options: {banner: "/*\nMithril v" + version + "\nhttp://github.com/lhorie/mithril.js\n(c) Leo Horie\nLicense: MIT\n*/", sourceMap: true}, + options: { + banner: [ + "/*", + "Mithril v" + version, + "http://github.com/lhorie/mithril.js", + "(c) Leo Horie", + "License: MIT", + "*/" + ].join("\n"), + sourceMap: true + }, mithril: {src: "mithril.js", dest: "mithril.min.js"} }, - concat: { - test: {src: ["mithril.js", "./tests/test.js", "./tests/mock.js", "./tests/mithril-tests.js"], dest: currentVersionArchiveFolder + "/mithril-tests.js"} - }, + zip: { distribution: { cwd: currentVersionArchiveFolder + "/", - src: [currentVersionArchiveFolder + "/mithril.min.js", currentVersionArchiveFolder + "/mithril.min.js.map", currentVersionArchiveFolder + "/mithril.js"], + src: [ + currentVersionArchiveFolder + "/mithril.min.js", + currentVersionArchiveFolder + "/mithril.min.js.map", + currentVersionArchiveFolder + "/mithril.js" + ], dest: currentVersionArchiveFolder + "/mithril.min.zip" } }, + replace: { - options: {force: true, patterns: [{match: /\.md/g, replacement: ".html"}, {match: /\$version/g, replacement: version}]}, - links: {expand: true, flatten: true, src: [tempFolder + "/**/*.html"], dest: currentVersionArchiveFolder + "/"}, - index: {src: inputFolder + "/layout/index.html", dest: currentVersionArchiveFolder + "/index.html"}, - commonjs: {expand: true, flatten: true, src: [inputFolder + "/layout/*.json"], dest: currentVersionArchiveFolder}, - cdnjs: {src: "deploy/cdnjs-package.json", dest: "../cdnjs/ajax/libs/mithril/package.json"} + options: { + force: true, + patterns: [ + {match: /\.md/g, replacement: ".html"}, + {match: /\$version/g, replacement: version} + ] + }, + + links: { + expand: true, + flatten: true, + src: [tempFolder + "/**/*.html"], + dest: currentVersionArchiveFolder + "/" + }, + + index: { + src: inputFolder + "/layout/index.html", + dest: currentVersionArchiveFolder + "/index.html" + }, + + commonjs: { + expand: true, + flatten: true, + src: [inputFolder + "/layout/*.json"], + dest: currentVersionArchiveFolder + }, + + cdnjs: { + src: "deploy/cdnjs-package.json", + dest: "../cdnjs/ajax/libs/mithril/package.json" + } }, + copy: { - style: {src: inputFolder + "/layout/style.css", dest: currentVersionArchiveFolder + "/style.css"}, - pages: {src: inputFolder + "/layout/pages.json", dest: currentVersionArchiveFolder + "/pages.json"}, - lib: {expand: true, cwd: inputFolder + "/layout/lib/", src: "./**", dest: currentVersionArchiveFolder + "/lib/"}, - tools: {expand: true, cwd: inputFolder + "/layout/tools/", src: "./**", dest: currentVersionArchiveFolder + "/tools/"}, - comparisons: {expand: true, cwd: inputFolder + "/layout/comparisons/", src: "./**", dest: currentVersionArchiveFolder + "/comparisons/"}, - unminified: {src: "mithril.js", dest: currentVersionArchiveFolder + "/mithril.js"}, - minified: {src: "mithril.min.js", dest: currentVersionArchiveFolder + "/mithril.min.js"}, - readme: {src: "README.md", dest: currentVersionArchiveFolder + "/README.md"}, - map: {src: "mithril.min.js.map", dest: currentVersionArchiveFolder + "/mithril.min.js.map"}, - typescript: {src: "mithril.d.ts", dest: currentVersionArchiveFolder + "/mithril.d.ts"}, - publish: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder}, - archive: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder + "/archive/v" + version}, - }, - execute: { - tests: {src: [currentVersionArchiveFolder + "/mithril-tests.js"]} - }, - qunit: { - all: ['tests/e2e/**/*.html'] - }, - "saucelabs-custom": { - all:{ - options: sauceCustomOptions + style: { + src: inputFolder + "/layout/style.css", + dest: currentVersionArchiveFolder + "/style.css" + }, + + pages: { + src: inputFolder + "/layout/pages.json", + dest: currentVersionArchiveFolder + "/pages.json" + }, + + lib: { + expand: true, + cwd: inputFolder + "/layout/lib/", + src: "./**", + dest: currentVersionArchiveFolder + "/lib/" + }, + + tools: { + expand: true, + cwd: inputFolder + "/layout/tools/", + src: "./**", + dest: currentVersionArchiveFolder + "/tools/" + }, + + comparisons: { + expand: true, + cwd: inputFolder + "/layout/comparisons/", + src: "./**", + dest: currentVersionArchiveFolder + "/comparisons/" + }, + + unminified: { + src: "mithril.js", + dest: currentVersionArchiveFolder + "/mithril.js" + }, + + minified: { + src: "mithril.min.js", + dest: currentVersionArchiveFolder + "/mithril.min.js" + }, + + readme: { + src: "README.md", + dest: currentVersionArchiveFolder + "/README.md" + }, + + map: { + src: "mithril.min.js.map", + dest: currentVersionArchiveFolder + "/mithril.min.js.map" + }, + + typescript: { + src: "mithril.d.ts", + dest: currentVersionArchiveFolder + "/mithril.d.ts" + }, + + publish: { + expand: true, + cwd: currentVersionArchiveFolder, + src: "./**", + dest: outputFolder + }, + + archive: { + expand: true, + cwd: currentVersionArchiveFolder, + src: "./**", + dest: outputFolder + "/archive/v" + version } }, - "saucelabs-qunit": { - all:{ - options: sauceQunitOptions + + "saucelabs-browsers": { + firefox: { + filter: function (browsers) { + return browsers.filter(function (browser) { + if (browser.browserName !== "firefox") return false + var version = browser.version + return version === "dev" || version === "beta" || + +version >= 38 // The latest ESR version + }) + } + }, + + chrome: { + filter: function (browsers) { + return browsers.filter(function (browser) { + if (browser.browserName !== "chrome") return false + var version = browser.version + return version === "dev" || version === "beta" || + +version >= 41 + }) + } + }, + + ie: { + filter: function (browsers) { + return browsers.filter(function (browser) { + return browser.browserName === "internet explorer" && + !/2003/.test(browser.platform) + }) + } + }, + + edge: { + filter: function (browsers) { + return browsers.filter(function (browser) { + return browser.browserName === "microsoftedge" + }) + } + }, + + safari: { + filter: function (browsers) { + return browsers.filter(function (browser) { + return browser.browserName === "safari" + }) + } + }, + + opera: { + filter: function (browsers) { + return browsers.filter(function (browser) { + return browser.browserName === "opera" + }) + } + } + }, + + saucelabs: { + all: { + options: { + username: process.env.SAUCE_USERNAME, + key: process.env.SAUCE_ACCESS_KEY, + testname: "Mithril Tests " + new Date().toJSON(), + browsers: "<%= saucelabs.browsers %>", + urls: ["http://localhost:8000/test/index.html"], + sauceConfig: { + "record-video": false, + "record-screenshots": false + }, + build: process.env.TRAVIS_JOB_ID, + onTestComplete: function (result, callback) { + var user = process.env.SAUCE_USERNAME + var pass = process.env.SAUCE_ACCESS_KEY + + var url = [ + "https://saucelabs.com/rest/v1", user, "jobs", + result.job_id + ].join("/") + + require("request").put({ + url: url, + auth: {user: user, pass: pass}, + json: {passed: result.passed} + }, function (error, response) { + if (error) { + return callback(error) + } else if (response.statusCode !== 200) { + return callback(new Error( + "Unexpected response status: " + + response.statusCode + "\n ")) + } else { + return callback(null, result.passed) + } + }) + }, + tunnelTimeout: 5 + } } }, - watch: {}, connect: { server: { options: { port: 8888, - base: '.' + base: "." } } }, + clean: { options: {force: true}, generated: [tempFolder] - }, - jsfmt: { - default: { - files: [{ - expand: true, - src: ['mithril.js'], - cwd: '.', - dest: '.' - }] - } } - }); + }) - grunt.loadNpmTasks("grunt-contrib-clean"); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks("grunt-contrib-copy"); - grunt.loadNpmTasks("grunt-contrib-uglify"); - grunt.loadNpmTasks('grunt-execute'); - grunt.loadNpmTasks("grunt-md2html"); - grunt.loadNpmTasks("grunt-replace"); - grunt.loadNpmTasks('grunt-zip'); - grunt.loadNpmTasks('grunt-contrib-qunit'); - grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-saucelabs'); - grunt.loadNpmTasks('grunt-jsfmt'); + grunt.loadNpmTasks("grunt-saucelabs-browsers") + grunt.loadNpmTasks("grunt-contrib-clean") + grunt.loadNpmTasks("grunt-contrib-copy") + grunt.loadNpmTasks("grunt-contrib-uglify") + grunt.loadNpmTasks("grunt-md2html") + grunt.loadNpmTasks("grunt-replace") + grunt.loadNpmTasks("grunt-zip") + grunt.loadNpmTasks("grunt-contrib-connect") + grunt.loadNpmTasks("grunt-saucelabs") + grunt.loadNpmTasks("grunt-eslint") + grunt.loadNpmTasks("grunt-mocha-phantomjs") - grunt.registerTask("build", ["test", "uglify", "zip", "md2html", "replace", "copy", "clean"]); - grunt.registerTask("testall", ["test", "teste2e"]); - grunt.registerTask("test", ["concat", "execute"]); - grunt.registerTask('teste2e', ['connect', 'qunit']); - grunt.registerTask("default", ["build"]); + grunt.registerTask("build", [ + "test", + "uglify", + "zip", + "md2html", + "replace", + "copy", + "clean" + ]) - grunt.registerTask("sauce-qunit", ["connect", "saucelabs-qunit"]); - grunt.registerTask("sauce-custom", ["connect", "saucelabs-custom"]); - grunt.registerTask("sauce-all", ["connect", "saucelabs-qunit", "saucelabs-custom"]); -}; + grunt.registerTask("test", ["eslint:all", "mocha_phantomjs"]) + grunt.registerTask("default", ["build"]) + + grunt.registerTask("sauce", [ + "saucelabs-browsers:all", + "connect", + "saucelabs" + ]) +} diff --git a/docs/layout/pages.json b/docs/layout/pages.json index e915b6ac..bfd52f9a 100644 --- a/docs/layout/pages.json +++ b/docs/layout/pages.json @@ -3,4 +3,4 @@ {"title": "Documentation", "url": "mithril.html"}, {"title": "Mithril Blog", "url": "http://lhorie.github.io/mithril-blog/"}, {"title": "Mailing List", "url": "https://groups.google.com/forum/#!forum/mithriljs"} -] \ No newline at end of file +] diff --git a/docs/layout/tools/template-converter.html b/docs/layout/tools/template-converter.html index b8c077cf..fb5d2aa0 100644 --- a/docs/layout/tools/template-converter.html +++ b/docs/layout/tools/template-converter.html @@ -1,9 +1,17 @@ -

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.

+ +HTML to Mithril Template Converter + +

HTML to Mithril Template Converter

+

+ 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. +

\ No newline at end of file +m.mount(document.getElementById("converter"), templateConverter) + diff --git a/docs/layout/tools/template-converter.js b/docs/layout/tools/template-converter.js index b8cd01cf..55217ba3 100644 --- a/docs/layout/tools/template-converter.js +++ b/docs/layout/tools/template-converter.js @@ -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(" -1) return [new DOMParser().parseFromString(markup, "text/html").childNodes[1]] - var container = document.createElement("div"); - container.insertAdjacentHTML("beforeend", markup); - return container.childNodes; -} -templateConverter.VirtualFragment = function recurse(domFragment) { - var virtualFragment = []; - for (var i = 0, el; el = domFragment[i]; i++) { - if (el.nodeType == 3) { - virtualFragment.push(el.nodeValue); - } - else if (el.nodeType == 1) { - var attrs = {}; - for (var j = 0, attr; attr = el.attributes[j]; j++) { - attrs[attr.name] = attr.value; - } - - virtualFragment.push({tag: el.nodeName.toLowerCase(), attrs: attrs, children: recurse(el.childNodes)}); + function each(list, f) { + for (var i = 0; i < list.length; i++) { + f(list[i], i) } } - return virtualFragment; -} -templateConverter.Template = function recurse() { - if (Object.prototype.toString.call(arguments[0]) == "[object String]") { - return new recurse(new templateConverter.VirtualFragment(new templateConverter.DOMFragment(arguments[0]))); - } - - var virtualFragment = arguments[0], level = arguments[1] - if (!level) level = 1; - - var tab = "\n" + new Array(level + 1).join("\t"); - var virtuals = []; - for (var i = 0, el; el = virtualFragment[i]; i++) { - if (typeof el == "string") { - if (el.match(/\t| {2,}/g) && el.trim().length == 0) virtuals.indented = true; - else virtuals.push('"' + el.replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n") + '"'); + + function createFragment(markup) { + if (markup.indexOf("= 0) { + return [ + new DOMParser() + .parseFromString(markup, "text/html") + .childNodes[1] + ] } - else { - var virtual = ""; - if (el.tag != "div") virtual += el.tag; - if (el.attrs["class"]) { - virtual += "." + el.attrs["class"].replace(/\t+/g, " ").split(" ").join("."); - delete el.attrs["class"]; + + var container = document.createElement("div") + container.insertAdjacentHTML("beforeend", markup) + return container.childNodes + } + + function createVirtual(fragment) { + var list = [] + + each(fragment, function (el) { + if (el.nodeType === 3) { + list.push(el.nodeValue) + } else if (el.nodeType === 1) { + var attrs = {} + + each(el.attributes, function (attr) { + attrs[attr.name] = attr.value + }) + + list.push({ + tag: el.nodeName.toLowerCase(), + attrs: attrs, + children: createVirtual(el.childNodes) + }) } - var attrNames = Object.keys(el.attrs).sort() - for (var j = 0, attrName; attrName = attrNames[j]; j++) { - if (attrName != "style") virtual += "[" + attrName + "='" + el.attrs[attrName].replace(/'/g, "\\'") + "']"; + }) + + return list + } + + function TemplateBuilder(virtual, level) { + this.virtual = virtual + this.level = level + this.virtuals = [] + this.indented = false + } + + TemplateBuilder.prototype = { + addVirtualString: function (el) { + if (/\t| {2,}/.test(el) && /^\s*/.test(el)) { + this.indented = true + } else { + this.virtuals.push('"' + el.replace(/(["\r\n])/g, "\\$1") + '"') } - if (virtual == "") virtual = "div" - virtual = '"' + virtual + '"'; - - var style = "" + }, + + addVirtualAttrs: function (el) { + var virtual = el.tag === "div" ? "" : el.tag + + if (el.attrs.class) { + virtual += "." + el.attrs.class.replace(/\s+/g, ".") + el.attrs.class = undefined + } + + each(Object.keys(el.attrs).sort(), function (attrName) { + if (attrName === "style") return + virtual += "[" + attrName + "='" + virtual += el.attrs[attrName].replace(/'/g, "\\'") + "']" + }) + + if (virtual === "") virtual = "div" + virtual = '"' + virtual + '"' + if (el.attrs.style) { - virtual += ", {style: " + ("{\"" + el.attrs.style.replace(/:/g, "\": \"").replace(/;/g, "\", \"") + "}").replace(/, "}|"}/, "}") + "}" + var style = "{\"" + el.attrs.style + .replace(/:/g, "\": \"") + .replace(/;/g, "\", \"") + "}" + virtual += ", {style: " + style.replace(/(, )"}/, "}") + "}" } - - if (el.children.length > 0) { - virtual += ", " + recurse(el.children, level + 1); + + if (el.children.length !== 0) { + var builder = new TemplateBuilder(el.children, this.level + 1) + virtual += ", " + builder.complete() + } + + this.virtuals.push("m(" + virtual + ")") + }, + + complete: function () { + var tab = "\n" + for (var i = 0; i <= this.level; i++) tab += "\t" + + each(this.virtual, function (el) { + if (typeof el === "string") { + this.addVirtualString(el) + } else { + this.addVirtualAttrs(el) + } + }.bind(this)) + + if (!this.indented) tab = "" + + if (this.virtuals.length === 1 && this.virtuals[0][0] === "\"") { + return this.virtuals.join(", ") + } else { + var body = this.virtuals.join("," + tab) + return "[" + tab + body + tab.slice(0, -1) + "]" } - virtual = "m(" + virtual + ")"; - virtuals.push(virtual); } } - if (!virtuals.indented) tab = ""; - - var isInline = virtuals.length == 1 && virtuals[0].charAt(0) == '"'; - var template = isInline ? virtuals.join(", ") : "[" + tab + virtuals.join("," + tab) + tab.slice(0, -1) + "]"; - return new String(template); -} -templateConverter.controller = function() { - this.source = m.prop(""); - this.output = m.prop(""); - - this.convert = function() { - return this.output(new templateConverter.Template(this.source())); - }; - -}; + return { + controller: function () { + this.source = m.prop("") + this.output = m.prop("") -templateConverter.view = function(ctrl) { - return m("div", [ - m("textarea", {autofocus: true, style: {width:"100%", height: "40%"}, onchange: m.withAttr("value", ctrl.source)}, ctrl.source()), - m("button", {onclick: ctrl.convert.bind(ctrl)}, "Convert"), - m("textarea", {style: {width:"100%", height: "40%"}}, ctrl.output()) - ]); -}; \ No newline at end of file + this.convert = function () { + var source = createVirtual(createFragment(this.source())) + return this.output(new TemplateBuilder(source, 1).complete()) + }.bind(this) + }, + + view: function (ctrl) { + return m("div", [ + m("textarea", { + autofocus: true, + style: {width: "100%", height: "40%"}, + onchange: m.withAttr("value", ctrl.source) + }, ctrl.source()), + m("button", {onclick: ctrl.convert}, "Convert"), + m("textarea", {style: {width: "100%", height: "40%"}}, + ctrl.output()) + ]) + } + } +})() diff --git a/package.json b/package.json index ffd7ac2e..c84013ae 100644 --- a/package.json +++ b/package.json @@ -1,46 +1,48 @@ { - "name": "mithril", - "description": "Mithril.js beta build - use this to help us test the releases before they are released", - "version": "0.2.1", - "repository": { - "type": "git", - "url": "git@github.com:lhorie/mithril.js.git" - }, - "scripts": { - "test": "grunt test" - }, - "main": "mithril.js", - "devDependencies": { - "grunt": "*", - "grunt-cli": "*", - "grunt-contrib-copy": "*", - "grunt-contrib-uglify": "*", - "grunt-contrib-clean": "*", - "grunt-contrib-concat": "*", - "grunt-execute": "*", - "grunt-md2html": "*", - "grunt-replace": "*", - "grunt-contrib-qunit": "*", - "grunt-zip": "*", - "grunt-jsfmt": "git://github.com/ysimonson/grunt-jsfmt", - - "grunt-contrib-connect": "~0.7.1", - "grunt-contrib-jshint": "~0.10.0", - "grunt-contrib-watch": "~0.6.1", - "grunt-jscs": "^1.1.0", - "grunt-sauce-tunnel": "^0.2.1", - "load-grunt-config": "^0.9.2", - "merge": "^1.1.3", - "publish": "~0.3.2", - "grunt-saucelabs": "*", - "request": "~2.35.0", - "q": "~1.0.0", - "saucelabs": "~0.1.1", - "sauce-tunnel": "~2.0.6", - "colors": "~0.6.2", - "lodash": "~2.4.1" - }, - "main": "mithril.js", - "licenses": [{"type": "MIT", "url": "http://opensource.org/licenses/MIT"}], - "files": ["mithril.min.js", "mithril.min.js.map", "mithril.js", "README.*"] + "name": "mithril", + "description": "Mithril.js beta build - use this to help us test the releases before they are released", + "version": "0.2.1", + "repository": { + "type": "git", + "url": "git@github.com:lhorie/mithril.js.git" + }, + "scripts": { + "test": "grunt test" + }, + "main": "mithril.js", + "devDependencies": { + "chai": "^3.4.0", + "eslint": "^1.7.3", + "grunt": "*", + "grunt-cli": "*", + "grunt-contrib-clean": "*", + "grunt-contrib-connect": "~0.7.1", + "grunt-contrib-copy": "*", + "grunt-contrib-uglify": "*", + "grunt-eslint": "^17.3.1", + "grunt-md2html": "*", + "grunt-mocha-phantomjs": "^2.0.0", + "grunt-replace": "*", + "grunt-saucelabs": "*", + "grunt-saucelabs-browsers": "^0.2.0", + "grunt-zip": "*", + "load-grunt-config": "^0.9.2", + "mocha": "^2.3.3", + "request": "~2.35.0", + "saucelabs": "~0.1.1", + "sinon": "^1.17.2", + "sinon-chai": "^2.8.0" + }, + "licenses": [ + { + "type": "MIT", + "url": "http://opensource.org/licenses/MIT" + } + ], + "files": [ + "mithril.min.js", + "mithril.min.js.map", + "mithril.js", + "README.*" + ] } diff --git a/test-deps/dom.js b/test-deps/dom.js new file mode 100644 index 00000000..e90499dc --- /dev/null +++ b/test-deps/dom.js @@ -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() + }) +} diff --git a/test-deps/mock.js b/test-deps/mock.js new file mode 100644 index 00000000..5bf78e80 --- /dev/null +++ b/test-deps/mock.js @@ -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 +})() diff --git a/test/.eslintrc b/test/.eslintrc new file mode 100644 index 00000000..205b7860 --- /dev/null +++ b/test/.eslintrc @@ -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 + } +} diff --git a/test/index.html b/test/index.html new file mode 100644 index 00000000..6880ccfe --- /dev/null +++ b/test/index.html @@ -0,0 +1,73 @@ + + +Mithril test suite +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/input-cursor.html b/test/input-cursor.html new file mode 100644 index 00000000..8ee57964 --- /dev/null +++ b/test/input-cursor.html @@ -0,0 +1,73 @@ + +Input cursor test +

Things to check:

+ + +
+ + + diff --git a/test/mithril.deferred.js b/test/mithril.deferred.js new file mode 100644 index 00000000..1f31eb1c --- /dev/null +++ b/test/mithril.deferred.js @@ -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 + }) +}) diff --git a/test/mithril.js b/test/mithril.js new file mode 100644 index 00000000..bf86f972 --- /dev/null +++ b/test/mithril.js @@ -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) + }) +}) diff --git a/test/mithril.mount.js b/test/mithril.mount.js new file mode 100644 index 00000000..9b2f268d --- /dev/null +++ b/test/mithril.mount.js @@ -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") + }) +}) diff --git a/test/mithril.prop.js b/test/mithril.prop.js new file mode 100644 index 00000000..4be4b1b9 --- /dev/null +++ b/test/mithril.prop.js @@ -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") + }) +}) diff --git a/test/mithril.redraw.js b/test/mithril.redraw.js new file mode 100644 index 00000000..22e62cc0 --- /dev/null +++ b/test/mithril.redraw.js @@ -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) + }) + }) +}) diff --git a/test/mithril.render.js b/test/mithril.render.js new file mode 100644 index 00000000..34faa178 --- /dev/null +++ b/test/mithril.render.js @@ -0,0 +1,1554 @@ +describe("m.render()", function () { + "use strict" + + it("exists", function () { + expect(m.render).to.be.a("function") + }) + + it("renders a string", function () { + var root = mock.document.createElement("div") + m.render(root, "test") + expect(root.childNodes[0].nodeValue).to.equal("test") + }) + + it("does not replace nodes differing in only class attr", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", {class: "a"})) + var elementBefore = root.childNodes[0] + m.render(root, m("div", {class: "b"})) + expect(root.childNodes[0]).to.equal(elementBefore) + }) + + it("does not replace nodes differing in only class syntax", function () { + var root = mock.document.createElement("div") + m.render(root, m(".a")) + var elementBefore = root.childNodes[0] + m.render(root, m(".b")) + expect(root.childNodes[0]).to.equal(elementBefore) + }) + + it("replaces nodes differing in id attr", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", {id: "a"})) + var elementBefore = root.childNodes[0] + m.render(root, m("div", {title: "b"})) + expect(root.childNodes[0]).to.not.equal(elementBefore) + }) + + it("replaces nodes differing in id syntax", function () { + var root = mock.document.createElement("div") + m.render(root, m("#a")) + var elementBefore = root.childNodes[0] + m.render(root, m("[title=b]")) + expect(root.childNodes[0]).to.not.equal(elementBefore) + }) + + it("replaces id node with string node", function () { + var root = mock.document.createElement("div") + m.render(root, m("#a")) + var elementBefore = root.childNodes[0] + m.render(root, "test") + expect(root.childNodes[0]).to.not.equal(elementBefore) + }) + + it("renders `undefined` body to empty string", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", [undefined])) + expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("") + }) + + it("uses the W3C URI as default namespace for SVG children", function () { + var root = mock.document.createElement("div") + m.render(root, m("svg", [m("g")])) + expect(root.childNodes[0].childNodes[0]).to.contain.all.keys({ + nodeName: "G", + namespaceURI: "http://www.w3.org/2000/svg" + }) + }) + + it("renders HTML elements contained in SVG elements", function () { + var root = mock.document.createElement("div") + m.render(root, m("svg", [m("a[href='http://google.com']")])) + expect(root.childNodes[0].childNodes[0].nodeName).to.equal("A") + }) + + it("does not append rerendered items", function () { + var root = mock.document.createElement("div") + m.render(root, m("div.classname", [m("a", {href: "/first"})])) + m.render(root, m("div", [m("a", {href: "/second"})])) + expect(root.childNodes[0].childNodes).to.have.length(1) + }) + + it("renders an added `undefined` to an empty string", function () { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [m("li"), undefined])) + expect(root.childNodes[0].childNodes[1].nodeValue).to.equal("") + }) + + it("renders a node replaced with `undefined` to an empty string", function () { // eslint-disable-line + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li"), m("li")])) + m.render(root, m("ul", [m("li"), undefined])) + expect(root.childNodes[0].childNodes[1].nodeValue).to.equal("") + }) + + it("renders a replaced first `undefined` to an empty string", function () { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [undefined])) + expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("") + }) + + it("does not render something replaced with empty object", function () { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [{}])) + expect(root.childNodes[0].childNodes).to.be.empty + }) + + it("renders an incomplete tag with primitive tag type", function () { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [{tag: "b", attrs: {}}])) + expect(root.childNodes[0].childNodes[0].nodeName).to.equal("B") + }) + + it("renders an incomplete tag with String object tag type", function () { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + /* eslint-disable no-new-wrappers */ + m.render(root, m("ul", [{tag: new String("b"), attrs: {}}])) + /* eslint-enable no-new-wrappers */ + expect(root.childNodes[0].childNodes[0].nodeName).to.equal("B") + }) + + it("renders the last tag when `subtree: \"retain\"`", function () { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li", [m("a")])])) + m.render(root, m("ul", [{subtree: "retain"}])) + expect(root.childNodes[0].childNodes[0].childNodes[0].nodeName) + .to.equal("A") + }) + + // https://github.com/lhorie/mithril.js/issues/43 + it("rerenders anchors correctly with mode abstraction (`config: m.route`)", function () { // eslint-disable-line + var root = mock.document.createElement("div") + m.render(root, m("a", {config: m.route}, "test")) + m.render(root, m("a", {config: m.route}, "test")) + expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("test") + }) + + // https://github.com/lhorie/mithril.js/issues/45 + it("replaces initial null with string", function () { + var root = mock.document.createElement("div") + m.render(root, m("#foo", [null, m("#bar")])) + m.render(root, m("#foo", ["test", m("#bar")])) + expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("test") + }) + + // https://github.com/lhorie/mithril.js/issues/45 + it("replaces initial null with node", function () { + var root = mock.document.createElement("div") + m.render(root, m("#foo", [null, m("#bar")])) + m.render(root, m("#foo", [m("div"), m("#bar")])) + expect(root.childNodes[0].childNodes[0].nodeName).to.equal("DIV") + }) + + // https://github.com/lhorie/mithril.js/issues/45 + it("replaces initial string with node", function () { + var root = mock.document.createElement("div") + m.render(root, m("#foo", ["test", m("#bar")])) + m.render(root, m("#foo", [m("div"), m("#bar")])) + expect(root.childNodes[0].childNodes[0].nodeName).to.equal("DIV") + }) + + // https://github.com/lhorie/mithril.js/issues/45 + it("replaces initial node with string", function () { + var root = mock.document.createElement("div") + m.render(root, m("#foo", [m("div"), m("#bar")])) + m.render(root, m("#foo", ["test", m("#bar")])) + expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("test") + }) + + // https://github.com/lhorie/mithril.js/issues/45 + it("adds new duplicate node", function () { + var root = mock.document.createElement("div") + m.render(root, m("#foo", [m("#bar")])) + m.render(root, m("#foo", [m("#bar"), [m("#baz")]])) + expect(root.childNodes[0].childNodes[1].id).to.equal("baz") + }) + + // https://github.com/lhorie/mithril.js/issues/48 + it("renders from html when base is document", function () { + var root = mock.document + m.render(root, m("html", [m("#foo")])) + var result = root.childNodes[0].childNodes[0].id + // Have to clean up before assertion, or this will break other tests + root.childNodes = [mock.document.createElement("html")] + expect(result).to.equal("foo") + }) + + // https://github.com/lhorie/mithril.js/issues/49 + it("reattaches cached text nodes to original parent (1)", function () { + var root = mock.document.createElement("div") + m.render(root, m("a", "test")) + m.render(root, m("a.foo", "test")) + expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("test") + }) + + // https://github.com/lhorie/mithril.js/issues/49 + it("reattaches cached text nodes to original parent (2)", function () { + var root = mock.document.createElement("div") + m.render(root, m("a.foo", "test")) + m.render(root, m("a", "test")) + expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("test") + }) + + // https://github.com/lhorie/mithril.js/issues/49 + it("reattaches cached text nodes to original parent (3)", function () { + var root = mock.document.createElement("div") + m.render(root, m("a.foo", "test")) + m.render(root, m("a", "test1")) + expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("test1") + }) + + // https://github.com/lhorie/mithril.js/issues/49 + it("reattaches cached text nodes to original parent (4)", function () { + var root = mock.document.createElement("div") + m.render(root, m("a", "test")) + m.render(root, m("a", "test1")) + expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("test1") + }) + + // https://github.com/lhorie/mithril.js/issues/50 + it("renders nested arrays correctly (1)", function () { + var root = mock.document.createElement("div") + m.render(root, m("#foo", [[m("div", "a"), m("div", "b")], m("#bar")])) + + expect(root.childNodes[0].childNodes[1].childNodes[0].nodeValue) + .to.equal("b") + }) + + // https://github.com/lhorie/mithril.js/issues/50 + it("renders nested arrays correctly (2)", function () { + var root = mock.document.createElement("div") + + m.render(root, m("#foo", [ + [m("div", "a"), m("div", "b")], + [m("div", "c"), m("div", "d")], + m("#bar") + ])) + + expect(root.childNodes[0].childNodes[3].childNodes[0].nodeValue) + .to.equal("d") + expect(root.childNodes[0].childNodes[4].id).to.equal("bar") + }) + + // https://github.com/lhorie/mithril.js/issues/50 + it("renders nested arrays correctly (3)", function () { + var root = mock.document.createElement("div") + m.render(root, m("#foo", [[m("div", "a"), m("div", "b")], "test"])) + expect(root.childNodes[0].childNodes[1].childNodes[0].nodeValue) + .to.equal("b") + expect(root.childNodes[0].childNodes[2].nodeValue).to.equal("test") + }) + + // https://github.com/lhorie/mithril.js/issues/50 + it("renders nested arrays correctly (4)", function () { + var root = mock.document.createElement("div") + m.render(root, m("#foo", [["a", "b"], "test"])) + expect(root.childNodes[0].childNodes[1].nodeValue).to.equal("b") + expect(root.childNodes[0].childNodes[2].nodeValue).to.equal("test") + }) + + // https://github.com/lhorie/mithril.js/issues/156 + it("renders nested arrays correctly (5)", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", [ + ["a", "b", "c", "d"].map(function () { + return [m("div"), " "] + }), + m("span") + ])) + expect(root.childNodes[0].childNodes[8].nodeName).to.equal("SPAN") + }) + + // https://github.com/lhorie/mithril.js/issues/50 + it("reconciles nested list differences correctly", function () { + var root = mock.document.createElement("div") + + m.render(root, m("#foo", [[m("div", "a"), m("div", "b")], m("#bar")])) + + m.render(root, m("#foo", [ + [m("div", "a"), m("div", "b"), m("div", "c")], + m("#bar") + ])) + + expect(root.childNodes[0].childNodes[2].childNodes[0].nodeValue) + .to.equal("c") + }) + + // https://github.com/lhorie/mithril.js/issues/51 + it("reconciles nested node differences correctly (1)", function () { + var root = mock.document.createElement("div") + + m.render(root, m("main", [ + m("button"), + m("article", [m("section"), m("nav")]) + ])) + + m.render(root, m("main", [ + m("button"), + m("article", [m("span"), m("nav")]) + ])) + + expect(root.childNodes[0].childNodes[1].childNodes[0].nodeName) + .to.equal("SPAN") + }) + + // https://github.com/lhorie/mithril.js/issues/51 + it("reconciles nested node differences correctly (2)", function () { + var root = mock.document.createElement("div") + + m.render(root, m("main", [ + m("button"), + m("article", [m("section"), m("nav")]) + ])) + + m.render(root, m("main", [ + m("button"), + m("article", ["test", m("nav")]) + ])) + + expect(root.childNodes[0].childNodes[1].childNodes[0].nodeValue) + .to.equal("test") + }) + + // https://github.com/lhorie/mithril.js/issues/51 + it("reconciles nested node differences correctly (3)", function () { + var root = mock.document.createElement("div") + + m.render(root, m("main", [ + m("button"), + m("article", [m("section"), m("nav")]) + ])) + + m.render(root, m("main", [ + m("button"), + m("article", [m.trust("test"), m("nav")]) + ])) + + expect(root.childNodes[0].childNodes[1].childNodes[0].nodeValue) + .to.equal("test") + }) + + // https://github.com/lhorie/mithril.js/issues/55 + it("redraws when id attrs are different", function () { + var root = mock.document.createElement("div") + m.render(root, m("#a")) + var elementBefore = root.childNodes[0] + m.render(root, m("#b")) + expect(root.childNodes[0]).to.not.equal(elementBefore) + }) + + // https://github.com/lhorie/mithril.js/issues/56 + it("doesn't duplicate with a preceding null element", function () { + var root = mock.document.createElement("div") + m.render(root, [null, "foo"]) + m.render(root, ["bar"]) + expect(root.childNodes).to.have.length(1) + }) + + // https://github.com/lhorie/mithril.js/issues/56 + it("doesn't duplicate with a preceding element with same tag name", function () { // eslint-disable-line + var root = mock.document.createElement("div") + m.render(root, m("div", "foo")) + expect(root.childNodes).to.have.length(1) + }) + + it("removes single `undefined` child node in place", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", [m("button"), m("ul")])) + expect(root.childNodes[0].childNodes[0]) + .to.have.property("nodeName", "BUTTON") + m.render(root, m("div", [undefined, m("ul")])) + expect(root.childNodes[0].childNodes[0]) + .to.have.property("nodeValue", "") + }) + + it("removes multiple `undefined` nodes in place", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", [m("ul"), undefined])) + + expect(root.childNodes[0].childNodes[0]) + .to.have.property("nodeName", "UL") + + expect(root.childNodes[0].childNodes[1]) + .to.have.property("nodeValue", "") + + m.render(root, m("div", [undefined, m("ul")])) + + expect(root.childNodes[0].childNodes[0]) + .to.have.property("nodeValue", "") + + expect(root.childNodes[0].childNodes[1]) + .to.have.property("nodeName", "UL") + }) + + // https://github.com/lhorie/mithril.js/issues/79 + it("changes the style when specified in the node", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", {style: {background: "red"}})) + expect(root.childNodes[0].style).to.have.property("background", "red") + m.render(root, m("div", {style: {}})) + expect(root.childNodes[0].style).to.have.property("background", "") + }) + + it("reads styles from syntax", function () { + var root = mock.document.createElement("div") + m.render(root, m("div[style='background:red']")) + expect(root.childNodes[0].style).to.equal("background:red") + }) + + it("removes styles when not passed", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", {style: {background: "red"}})) + expect(root.childNodes[0].style.background).to.equal("red") + m.render(root, m("div", {})) + expect(root.childNodes[0].style.background).to.not.exist + }) + + // https://github.com/lhorie/mithril.js/issues/87 + it("removes correct number of elements from nested lists (1)", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", [[m("a"), m("a")], m("button")])) + m.render(root, m("div", [[m("a")], m("button")])) + + expect(root.childNodes[0].childNodes).to.have.length(2) + + expect(root.childNodes[0].childNodes[1]) + .to.have.property("nodeName", "BUTTON") + }) + + // https://github.com/lhorie/mithril.js/issues/87 + it("removes correct number of elements from nested lists (2)", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", [m("a"), m("b"), m("button")])) + m.render(root, m("div", [m("a"), m("button")])) + + expect(root.childNodes[0].childNodes).to.have.length(2) + + expect(root.childNodes[0].childNodes[1]) + .to.have.property("nodeName", "BUTTON") + }) + + // https://github.com/lhorie/mithril.js/issues/99 + it("removes correct number of elements from nested lists (3)", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", [m("img"), m("h1")])) + m.render(root, m("div", [m("a")])) + + expect(root.childNodes[0].childNodes).to.have.length(1) + + expect(root.childNodes[0].childNodes[0]) + .to.have.property("nodeName", "A") + }) + + // https://github.com/lhorie/mithril.js/issues/120 + it("avoids duplication in nested arrays (1)", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", ["a", "b", "c", "d"])) + m.render(root, m("div", [["d", "e"]])) + + var children = root.childNodes[0].childNodes + + expect(children).to.have.length(2) + expect(children[0]).to.have.property("nodeValue", "d") + expect(children[1]).to.have.property("nodeValue", "e") + }) + + // https://github.com/lhorie/mithril.js/issues/120 + it("avoids duplication in nested arrays (2)", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", [["a", "b", "c", "d"]])) + m.render(root, m("div", ["d", "e"])) + + var children = root.childNodes[0].childNodes + + expect(children).to.have.length(2) + expect(children[0]).to.have.property("nodeValue", "d") + expect(children[1]).to.have.property("nodeValue", "e") + }) + + // https://github.com/lhorie/mithril.js/issues/120 + it("avoids duplication in nested arrays (3)", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", ["x", [["a"], "b", "c", "d"]])) + m.render(root, m("div", ["d", ["e"]])) + + var children = root.childNodes[0].childNodes + + expect(children).to.have.length(2) + expect(children[0]).to.have.property("nodeValue", "d") + expect(children[1]).to.have.property("nodeValue", "e") + }) + + // https://github.com/lhorie/mithril.js/issues/120 + it("avoids duplication in nested arrays (4)", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", ["b"])) + m.render(root, m("div", [["e"]])) + + var children = root.childNodes[0].childNodes + + expect(children).to.have.length(1) + expect(children[0]).to.have.property("nodeValue", "e") + }) + + // https://github.com/lhorie/mithril.js/issues/120 + it("avoids duplication in nested arrays (5)", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", ["a", ["b"]])) + m.render(root, m("div", ["d", [["e"]]])) + + var children = root.childNodes[0].childNodes + + expect(children).to.have.length(2) + expect(children[0]).to.have.property("nodeValue", "d") + expect(children[1]).to.have.property("nodeValue", "e") + }) + + // https://github.com/lhorie/mithril.js/issues/120 + it("avoids duplication in nested arrays (6)", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", ["a", [["b"]]])) + m.render(root, m("div", ["d", ["e"]])) + + var children = root.childNodes[0].childNodes + + expect(children).to.have.length(2) + expect(children[0]).to.have.property("nodeValue", "d") + expect(children[1]).to.have.property("nodeValue", "e") + }) + + // https://github.com/lhorie/mithril.js/issues/120 + it("avoids duplication in nested arrays (7)", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", ["a", [["b"], "c"]])) + m.render(root, m("div", ["d", [[["e"]], "x"]])) + + var children = root.childNodes[0].childNodes + + expect(children).to.have.length(3) + expect(children[0]).to.have.property("nodeValue", "d") + expect(children[1]).to.have.property("nodeValue", "e") + }) + + it("honors setting context properties in stateful config", function () { + var root = mock.document.createElement("div") + var config = sinon.spy() + + m.render(root, m("div", { + config: function (el, init, ctx) { ctx.data = 1 } + })) + + m.render(root, m("div", {config: config})) + + expect(config.firstCall.args[2]).to.have.property("data", 1) + }) + + it("calls configs in order, first to last", function () { + var root = mock.document.createElement("div") + var config = sinon.spy() + var index = 0 + + var node = m("div", { + config: function (el, init, ctx) { ctx.data = index++ } + }) + + m.render(root, [node, node]) + + node = m("div", {config: config}) + m.render(root, [node, node]) + + expect(config).to.have.been.called + config.args.forEach(function (args, i) { + expect(args[2]).to.have.property("data", i) + }) + }) + + it("passes the correct node as the element", function () { + var root = mock.document.createElement("div") + var spy = sinon.spy() + m.render(root, m("div", m("a", {config: spy}))) + expect(spy).to.have.been.calledWith(root.childNodes[0].childNodes[0]) + }) + + it("does not recursively call the config if a separate node is rendered to", function () { // eslint-disable-line + var root = mock.document.createElement("div") + var spy = sinon.spy(function () { + var island = mock.document.createElement("div") + m.render(island, m("div")) + }) + + m.render(root, m("div", m("a", {config: spy}))) + + expect(spy).to.be.calledOnce + }) + + // https://github.com/lhorie/mithril.js/issues/129 + it("does not throw replacing arrays with single entries", function () { + var root = mock.document.createElement("div") + expect(function () { + m.render(root, m("div", [ + ["foo", "bar"], + ["foo", "bar"], + ["foo", "bar"] + ])) + + m.render(root, m("div", ["asdf", "asdf2", "asdf3"])) + }).to.not.throw() + }) + + // https://github.com/lhorie/mithril.js/issues/98 + it("correctly keeps key association to nodes (1)", function () { + // insert at beginning + var root = mock.document.createElement("div") + + m.render(root, [ + m("a", {key: 1}, 1), + m("a", {key: 2}, 2), + m("a", {key: 3}, 3) + ]) + + var firstBefore = root.childNodes[0] + + m.render(root, [ + m("a", {key: 4}, 4), + m("a", {key: 1}, 1), + m("a", {key: 2}, 2), + m("a", {key: 3}, 3) + ]) + + var firstAfter = root.childNodes[1] + + expect(firstBefore).to.equal(firstAfter) + + expect(root.childNodes[0].childNodes[0]) + .to.have.property("nodeValue", "4") + + expect(root.childNodes).to.have.length(4) + }) + + // https://github.com/lhorie/mithril.js/issues/98 + it("correctly keeps key association to nodes (2)", function () { + var root = mock.document.createElement("div") + + m.render(root, [ + m("a", {key: 1}, 1), + m("a", {key: 2}, 2), + m("a", {key: 3}, 3) + ]) + + var firstBefore = root.childNodes[0] + + m.render(root, [ + m("a", {key: 4}, 4), + m("a", {key: 1}, 1), + m("a", {key: 2}, 2) + ]) + + var firstAfter = root.childNodes[1] + + expect(firstBefore).to.equal(firstAfter) + + expect(root.childNodes[0].childNodes[0]) + .to.have.property("nodeValue", "4") + + expect(root.childNodes).to.have.length(3) + }) + + // https://github.com/lhorie/mithril.js/issues/98 + it("correctly keeps key association to nodes (3)", function () { + var root = mock.document.createElement("div") + + m.render(root, [ + m("a", {key: 1}, 1), + m("a", {key: 2}, 2), + m("a", {key: 3}, 3) + ]) + + var firstBefore = root.childNodes[1] + + m.render(root, [ + m("a", {key: 2}, 2), + m("a", {key: 3}, 3), + m("a", {key: 4}, 4) + ]) + + var firstAfter = root.childNodes[0] + + expect(firstBefore).to.equal(firstAfter) + + expect(root.childNodes[0].childNodes[0]) + .to.have.property("nodeValue", "2") + + expect(root.childNodes).to.have.length(3) + }) + + // https://github.com/lhorie/mithril.js/issues/98 + it("correctly keeps key association to nodes (4)", function () { + var root = mock.document.createElement("div") + + m.render(root, [ + m("a", {key: 1}, 1), + m("a", {key: 2}, 2), + m("a", {key: 3}, 3), + m("a", {key: 4}, 4), + m("a", {key: 5}, 5) + ]) + + var firstBefore = root.childNodes[0] + var secondBefore = root.childNodes[1] + var fourthBefore = root.childNodes[3] + + m.render(root, [ + m("a", {key: 4}, 4), + m("a", {key: 10}, 10), + m("a", {key: 1}, 1), + m("a", {key: 2}, 2) + ]) + + var firstAfter = root.childNodes[2] + var secondAfter = root.childNodes[3] + var fourthAfter = root.childNodes[0] + + expect(firstBefore).to.equal(firstAfter) + expect(secondBefore).to.equal(secondAfter) + expect(fourthBefore).to.equal(fourthAfter) + expect(root.childNodes[1].childNodes[0].nodeValue).to.equal("10") + expect(root.childNodes).to.have.length(4) + }) + + // https://github.com/lhorie/mithril.js/issues/98 + it("correctly keeps key association to nodes (5)", function () { + var root = mock.document.createElement("div") + + m.render(root, [ + m("a", {key: 1}, 1), + m("a", {key: 2}, 2), + m("a", {key: 3}, 3), + m("a", {key: 4}, 4), + m("a", {key: 5}, 5) + ]) + + var firstBefore = root.childNodes[0] + var secondBefore = root.childNodes[1] + var fourthBefore = root.childNodes[3] + + m.render(root, [ + m("a", {key: 4}, 4), + m("a", {key: 10}, 10), + m("a", {key: 2}, 2), + m("a", {key: 1}, 1), + m("a", {key: 6}, 6), + m("a", {key: 7}, 7) + ]) + + var firstAfter = root.childNodes[3] + var secondAfter = root.childNodes[2] + var fourthAfter = root.childNodes[0] + + expect(firstBefore).to.equal(firstAfter) + expect(secondBefore).to.equal(secondAfter) + expect(fourthBefore).to.equal(fourthAfter) + + expect(root.childNodes[1].childNodes[0].nodeValue).to.equal("10") + expect(root.childNodes[4].childNodes[0].nodeValue).to.equal("6") + expect(root.childNodes[5].childNodes[0].nodeValue).to.equal("7") + + expect(root.childNodes).to.have.length(6) + }) + + // https://github.com/lhorie/mithril.js/issues/149 + it("correctly keeps key association to nodes (6)", function () { + var root = mock.document.createElement("div") + + m.render(root, [ + m("a", {key: 1}), + m("a", {key: 2}), + m("a"), + m("a", {key: 4}), + m("a", {key: 5}) + ]) + + var firstBefore = root.childNodes[0] + var secondBefore = root.childNodes[1] + var thirdBefore = root.childNodes[2] + var fourthBefore = root.childNodes[3] + var fifthBefore = root.childNodes[4] + + m.render(root, [ + m("a", {key: 4}), + m("a", {key: 5}), + m("a"), + m("a", {key: 1}), + m("a", {key: 2}) + ]) + + var firstAfter = root.childNodes[3] + var secondAfter = root.childNodes[4] + var thirdAfter = root.childNodes[2] + var fourthAfter = root.childNodes[0] + var fifthAfter = root.childNodes[1] + + expect(firstBefore).to.equal(firstAfter) + expect(secondBefore).to.equal(secondAfter) + expect(thirdBefore).to.equal(thirdAfter) + expect(fourthBefore).to.equal(fourthAfter) + expect(fifthBefore).to.equal(fifthAfter) + }) + + // https://github.com/lhorie/mithril.js/issues/246 + it("correctly renders non-keyed objects in the middle", function () { + var root = mock.document.createElement("div") + m.render(root, [m("a", {key: 1}, 1)]) + var firstBefore = root.childNodes[0] + m.render(root, [m("a", {key: 2}, 2), m("br"), m("a", {key: 1}, 1)]) + var firstAfter = root.childNodes[2] + expect(firstBefore).to.equal(firstAfter) + expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("2") + expect(root.childNodes.length).to.equal(3) + }) + + // https://github.com/lhorie/mithril.js/issues/134 + it("doesn't redraw when updating contenteditable", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", {contenteditable: true}, "test")) + mock.document.activeElement = root.childNodes[0] + m.render(root, m("div", {contenteditable: true}, "test1")) + m.render(root, m("div", {contenteditable: false}, "test2")) + expect(root.childNodes[0].childNodes[0].nodeValue).to.equal("test2") + }) + + // https://github.com/lhorie/mithril.js/issues/136 + it("redraws when a textarea updates its values", function () { + var root = mock.document.createElement("div") + m.render(root, m("textarea", ["test"])) + m.render(root, m("textarea", ["test1"])) + expect(root.childNodes[0].value).to.equal("test1") + }) + + it("doesn't call onunload when matching keys are given", function () { + var root = mock.document.createElement("div") + var spy = sinon.spy() + m.render(root, [ + m("div", { + key: 1, + config: function (el, init, ctx) { + ctx.onunload = spy + } + }) + ]) + m.render(root, [ + m("div", {key: 2}), + m("div", { + key: 1, + config: function (el, init, ctx) { + ctx.onunload = spy + } + }) + ]) + expect(spy).to.not.have.been.called + }) + + it("unloads the parent but not child, when parent changes and not child", function () { // eslint-disable-line + var root = mock.document.createElement("div") + var parentSpy = sinon.spy() + var childSpy = sinon.spy() + + function parent(el, init, ctx) { + ctx.onunload = parentSpy + } + + function child(el, init, ctx) { + ctx.onunload = childSpy + } + + m.render(root, m("div", {config: parent}, m("a", {config: child}))) + m.render(root, m("main", {config: parent}, m("a", {config: child}))) + + expect(parentSpy).to.be.calledOnce + expect(childSpy).to.not.have.been.called + }) + + it("unloads parent and child when both change", function () { + var root = mock.document.createElement("div") + var parentSpy = sinon.spy() + var childSpy = sinon.spy() + + function parent(el, init, ctx) { + ctx.onunload = parentSpy + } + + function child(el, init, ctx) { + ctx.onunload = childSpy + } + + m.render(root, m("div", {config: parent}, m("a", {config: child}))) + m.render(root, m("main", {config: parent}, m("b", {config: child}))) + expect(parentSpy).to.have.been.calledOnce + expect(childSpy).to.have.been.calledOnce + }) + + // https://github.com/lhorie/mithril.js/issues/150 + it("treats empty arrays similarly to `null` and `undefined`", function () { + var root = mock.document.createElement("div") + m.render(root, [m("a"), m("div")]) + m.render(root, [[], m("div")]) + expect(root.childNodes.length).to.equal(1) + expect(root.childNodes[0].nodeName).to.equal("DIV") + }) + + + // https://github.com/lhorie/mithril.js/issues/157 + it("renders nodes with new keys correctly", function () { + var root = mock.document.createElement("div") + m.render(root, m("ul", [ + m("li", {key: 0}, 0), + m("li", {key: 2}, 2), + m("li", {key: 4}, 4) + ])) + + m.render(root, m("ul", [ + m("li", {key: 0}, 0), + m("li", {key: 1}, 1), + m("li", {key: 2}, 2), + m("li", {key: 3}, 3), + m("li", {key: 4}, 4), + m("li", {key: 5}, 5) + ])) + + expect( + root.childNodes[0].childNodes.map(function (n) { + return n.childNodes[0].nodeValue + }) + ).to.eql(["0", "1", "2", "3", "4", "5"]) + }) + + // https://github.com/lhorie/mithril.js/issues/157 + it("doesn't render extra child nodes if none are given (1)", function () { + var root = mock.document.createElement("div") + m.render(root, m("input", {value: "a"})) + m.render(root, m("input", {value: "aa"})) + expect(root.childNodes[0].childNodes).to.be.empty + }) + + // https://github.com/lhorie/mithril.js/issues/157 + it("doesn't render extra child nodes if none are given (2)", function () { + var root = mock.document.createElement("div") + m.render(root, m("br", {class: "a"})) + m.render(root, m("br", {class: "aa"})) + expect(root.childNodes[0].childNodes).to.be.empty + }) + + // https://github.com/lhorie/mithril.js/issues/194 + it("removes removed contained keyed elements from DOM", function () { + var root = mock.document.createElement("div") + + m.render(root, m("ul", [ + m("li", {key: 0}, 0), + m("li", {key: 1}, 1), + m("li", {key: 2}, 2), + m("li", {key: 3}, 3), + m("li", {key: 4}, 4), + m("li", {key: 5}, 5) + ])) + + m.render(root, m("ul", [ + m("li", {key: 0}, 0), + m("li", {key: 1}, 1), + m("li", {key: 2}, 2), + m("li", {key: 4}, 4), + m("li", {key: 5}, 5) + ])) + + expect( + root.childNodes[0].childNodes.map(function (n) { + return n.childNodes[0].nodeValue + }) + ).to.eql(["0", "1", "2", "4", "5"]) + }) + + // https://github.com/lhorie/mithril.js/issues/194 + it("removes removed list of keyed elements from DOM", function () { + var root = mock.document.createElement("div") + + m.render(root, m("ul", [ + m("li", {key: 0}, 0), + m("li", {key: 1}, 1), + m("li", {key: 2}, 2), + m("li", {key: 3}, 3), + m("li", {key: 4}, 4), + m("li", {key: 5}, 5) + ])) + + m.render(root, m("ul", [ + m("li", {key: 1}, 1), + m("li", {key: 2}, 2), + m("li", {key: 3}, 3), + m("li", {key: 4}, 4), + m("li", {key: 5}, 5), + m("li", {key: 6}, 6) + ])) + + m.render(root, m("ul", [ + m("li", {key: 12}, 12), + m("li", {key: 13}, 13), + m("li", {key: 14}, 14), + m("li", {key: 15}, 15), + m("li", {key: 16}, 16), + m("li", {key: 17}, 17) + ])) + + expect( + root.childNodes[0].childNodes.map(function (n) { + return n.childNodes[0].nodeValue + }) + ).to.eql(["12", "13", "14", "15", "16", "17"]) + }) + + // https://github.com/lhorie/mithril.js/issues/206 + it("removes `undefined` children", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", undefined)) + m.render(root, m("div", [m("div")])) + expect(root.childNodes[0].childNodes).to.have.length(1) + }) + + // https://github.com/lhorie/mithril.js/issues/206 + it("removes `null` children", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", null)) + m.render(root, m("div", [m("div")])) + expect(root.childNodes[0].childNodes).to.have.length(1) + }) + + // https://github.com/lhorie/mithril.js/issues/200 + it("calls onunload when updating a rendered collection", function () { + var root = mock.document.createElement("div") + + var onunload1 = sinon.spy() + var onunload2 = sinon.spy() + + m.render(root, [m("div", { + config: function (el, init, ctx) { + ctx.onunload = onunload1 + } + })]) + + m.render(root, []) + + m.render(root, [m("div", { + config: function (el, init, ctx) { + ctx.onunload = onunload2 + } + })]) + + m.render(root, []) + + expect(onunload1).to.be.called + expect(onunload2).to.be.called + }) + + it("should prepend new DOM elements", function () { + var root = mock.document.createElement("div") + + m.render(root, [m("div.blue")]) + + m.render(root, [ + m("div.green", [m("div")]), + m("div.blue") + ]) + + expect(root.childNodes).to.have.length(2) + }) + + // https://github.com/lhorie/mithril.js/issues/277 + it("adds objects that look like virtual nodes", function () { + var root = mock.document.createElement("div") + function Field() { + this.tag = "div" + this.attrs = {} + this.children = "hello" + } + m.render(root, new Field()) + expect(root.childNodes).to.have.length(1) + }) + + it("doesn't add objects that don't look like virtual nodes", function () { + var root = mock.document.createElement("div") + m.render(root, {foo: 123}) + expect(root.childNodes).to.have.length(0) + }) + + // https://github.com/lhorie/mithril.js/issues/299 + it("retains key order in the presence of `null`s (1)", function () { + var root = mock.document.createElement("div") + + m.render(root, m("div", [ + m("div", {key: 1}, 1), + m("div", {key: 2}, 2), + m("div", {key: 3}, 3), + m("div", {key: 4}, 4), + m("div", {key: 5}, 5), + null, null, null, null, null, null, null, null, null, null + ])) + + m.render(root, m("div", [ + null, null, + m("div", {key: 3}, 3), + null, null, + m("div", {key: 6}, 6), + null, null, + m("div", {key: 9}, 9), + null, null, + m("div", {key: 12}, 12), + null, null, + m("div", {key: 15}, 15) + ])) + + m.render(root, m("div", [ + m("div", {key: 1}, 1), + m("div", {key: 2}, 2), + m("div", {key: 3}, 3), + m("div", {key: 4}, 4), + m("div", {key: 5}, 5), + null, null, null, null, null, null, null, null, null, null + ])) + + expect( + root.childNodes[0].childNodes.map(function (c) { + return c.childNodes ? c.childNodes[0].nodeValue : c.nodeValue + }).slice(0, 5) + ).to.eql(["1", "2", "3", "4", "5"]) + }) + + // https://github.com/lhorie/mithril.js/issues/299 + // https://github.com/lhorie/mithril.js/issues/377 + it("retains key order in the presence of `null`s (2)", function () { + var root = mock.document.createElement("div") + + m.render(root, m("div", [ + m("div", 1), + m("div", 2), + [ + m("div", {key: 3}, 3), + m("div", {key: 4}, 4), + m("div", {key: 5}, 5) + ], + [m("div", {key: 6}, 6)] + ])) + + m.render(root, m("div", [ + m("div", 1), + null, + [ + m("div", {key: 3}, 3), + m("div", {key: 4}, 4), + m("div", {key: 5}, 5) + ], + [m("div", {key: 6}, 6)] + ])) + + expect( + root.childNodes[0].childNodes.map(function (c) { + return c.childNodes ? c.childNodes[0].nodeValue : c.nodeValue + }) + ).to.eql(["1", "", "3", "4", "5", "6"]) + }) + + it("doesn't throw trying to render result of console.log()", function () { + var root = mock.document.createElement("div") + expect(function () { + /* eslint-disable no-console */ + m.render(root, m("div", [console.log()])) + /* eslint-enable no-console */ + }).to.not.throw() + }) + + it("retains key order for ids", function () { + var root = mock.document.createElement("div") + + m.render(root, [ + m("#div-1", {key: 1}), + m("#div-2", {key: 2}), + m("#div-3", {key: 3}) + ]) + + root.appendChild(root.childNodes[1]) + + m.render(root, [ + m("#div-1", {key: 1}), + m("#div-3", {key: 3}), + m("#div-2", {key: 2}) + ]) + + expect( + root.childNodes.map(function (node) { return node.id }) + ).to.eql(["div-1", "div-3", "div-2"]) + }) + + it("doesn't render functions as nodes", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", function () {})) + expect(root.childNodes[0].childNodes).to.have.length(0) + }) + + it("removes nodes that result in only a single text node", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", "foo", m("a"))) + m.render(root, m("div", "test")) + expect(root.childNodes[0].childNodes).to.have.length(1) + }) + + it("keeps identity if the element is preceded by conditional", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", [m("a"), m("input[autofocus]")])) + var before = root.childNodes[0].childNodes[1] + m.render(root, m("div", [undefined, m("input[autofocus]")])) + var after = root.childNodes[0].childNodes[1] + expect(before).to.equal(after) + }) + + it("keeps unkeyed identity if mixed with keyed elements and identity can be inferred", function () { // eslint-disable-line + var root = mock.document.createElement("div") + + m.render(root, m("div", [ + m("a", {key: 1}), + m("a", {key: 2}), + m("a", {key: 3}), + m("i") + ])) + var before = root.childNodes[0].childNodes[3] + + m.render(root, m("div", [ + m("b", {key: 3}), + m("b", {key: 4}), + m("i"), + m("b", {key: 1}) + ])) + var after = root.childNodes[0].childNodes[2] + + expect(before).to.equal(after) + }) + + it("keeps unkeyed identity if mixed with keyed/text elements and identity can be inferred", function () { // eslint-disable-line + var root = mock.document.createElement("div") + + m.render(root, m("div", [ + m("a", {key: 1}), + m("a", {key: 2}), + "foo", + m("a", {key: 3}), + m("i") + ])) + var before = root.childNodes[0].childNodes[4] + + m.render(root, m("div", [ + m("a", {key: 3}), + m("a", {key: 4}), + "bar", + m("i"), + m("a", {key: 1}) + ])) + var after = root.childNodes[0].childNodes[3] + + expect(before).to.equal(after) + }) + + it("keeps unkeyed identity if mixed with elements/nulls and identity can be inferred", function () { // eslint-disable-line + var root = mock.document.createElement("div") + + m.render(root, m("div", [ + m("a", {key: 1}), + m("a", {key: 2}), + null, + m("a", {key: 3}), + m("i") + ])) + var before = root.childNodes[0].childNodes[4] + + m.render(root, m("div", [ + m("a", {key: 3}), + m("a", {key: 4}), + null, + m("i"), + m("a", {key: 1}) + ])) + var after = root.childNodes[0].childNodes[3] + + expect(before).to.equal(after) + }) + + it("keeps unkeyed identity if mixed with elements/undefined and identity can be inferred", function () { // eslint-disable-line + var root = mock.document.createElement("div") + + m.render(root, m("div", [ + m("a", {key: 1}), + m("a", {key: 2}), + undefined, + m("a", {key: 3}), + m("i") + ])) + var before = root.childNodes[0].childNodes[4] + + m.render(root, m("div", [ + m("a", {key: 3}), + m("a", {key: 4}), + undefined, + m("i"), + m("a", {key: 1}) + ])) + var after = root.childNodes[0].childNodes[3] + + expect(before).to.equal(after) + }) + + // FIXME: implement document.createRange().createContextualFragment() in the + // mock document to fix this test + xit("keeps unkeyed identity if mixed with elements/trusted text and identity can be inferred", function () { // eslint-disable-line + var root = mock.document.createElement("div") + + m.render(root, m("div", [ + m("a", {key: 1}), + m("a", {key: 2}), + m.trust("a"), + m("a", {key: 3}), + m("i") + ])) + var before = root.childNodes[0].childNodes[4] + + m.render(root, m("div", [ + m("a", {key: 3}), + m("a", {key: 4}), + m.trust("a"), + m("i"), + m("a", {key: 1}) + ])) + var after = root.childNodes[0].childNodes[3] + + expect(before).to.equal(after) + }) + + it("uses the syntax class if it's given as `undefined` in attr", function () { // eslint-disable-line + var root = mock.document.createElement("div") + var vdom = m("div.a", {class: undefined}) + m.render(root, vdom) + expect(root.childNodes[0].class).to.equal("a") + }) + + it("updates div with syntax class and removed body", function () { + var root = mock.document.createElement("div") + m.render(root, m(".a", [1])) + m.render(root, m(".a", [])) + expect(root.childNodes[0].childNodes).to.have.length(0) + }) + + it("renders removed elements in div with empty attrs correctly", function () { // eslint-disable-line + var root = mock.document.createElement("div") + m.render(root, m("div", {}, [ + m("div", {}, "0"), + m("div", {}, "1"), + m("div", {}, "2") + ])) + + expect( + root.childNodes[0].childNodes.map(function (node) { + return node.childNodes[0].nodeValue + }) + ).to.eql(["0", "1", "2"]) + + m.render(root, m("div", {}, [ + m("div", {}, "0") + ])) + + expect( + root.childNodes[0].childNodes.map(function (node) { + return node.childNodes[0].nodeValue + }) + ).to.eql(["0"]) + }) + + it("renders removed elements in span with empty attrs correctly", function () { // eslint-disable-line + var root = mock.document.createElement("div") + m.render(root, m("span", {}, [ + m("div", {}, "0"), + m("div", {}, "1"), + m("div", {}, "2") + ])) + + expect( + root.childNodes[0].childNodes.map(function (node) { + return node.childNodes[0].nodeValue + }) + ).to.eql(["0", "1", "2"]) + + m.render(root, m("span", {}, [ + m("div", {}, "0") + ])) + + expect( + root.childNodes[0].childNodes.map(function (node) { + return node.childNodes[0].nodeValue + }) + ).to.eql(["0"]) + }) + + function emit(el, ev) { + el[ev]({currentTarget: el}) + } + + // https://github.com/lhorie/mithril.js/issues/214 + it("keeps all input events", function () { + var root = mock.document.createElement("div") + + var ctrl = m.mount(root, { + controller: function () { + this.inputValue = m.prop("") + }, + view: function (ctrl) { + return m("input", { + value: ctrl.inputValue(), + onkeyup: m.withAttr("value", ctrl.inputValue) + }) + } + }) + mock.requestAnimationFrame.$resolve() + + var input = mock.document.activeElement = root.childNodes[0] + var expected = "" + var keys = "0123456789abcdef" + + function writeKey(key) { + input.value += key[0] + emit(input, "onkeyup") + mock.requestAnimationFrame.$resolve() + } + + for (var i = 0; i < 4; i++) { + expected += keys + keys.split("").forEach(writeKey) + } + + expect(ctrl.inputValue()).to.equal(expected) + expect(input.value).to.equal(expected) + + mock.document.activeElement = null + }) + + // https://github.com/lhorie/mithril.js/issues/288 + it("doesn't reset if the input value is submitted with ", function () { // eslint-disable-line + var root = mock.document.createElement("div") + + var ctrl = m.mount(root, { + controller: function () { + this.inputValue = m.prop("") + + this.submit = function () { + if (this.inputValue()) { + this.inputValue("") + } + }.bind(this) + }, + + view: function (ctrl) { + return m("form", {onsubmit: ctrl.submit}, [ + m("input", { + onkeyup: m.withAttr("value", ctrl.inputValue), + value: ctrl.inputValue() + }), + m("button[type=submit]") + ]) + } + }) + + var form = root.childNodes[0] + var input = mock.document.activeElement = form.childNodes[0] + + function writeKey(key) { + if (key === "[enter]") { + emit(form, "onsubmit") + } else { + input.value += key[0] + emit(input, "onkeyup") + } + mock.requestAnimationFrame.$resolve() + } + + writeKey("a") + writeKey("b") + writeKey("c") + writeKey("d") + writeKey("[enter]") + + expect(ctrl.inputValue()).to.equal("") + expect(input.value).to.equal("") + + mock.document.activeElement = null + }) + + // https://github.com/lhorie/mithril.js/issues/278 + it("renders multiple select correctly", function () { + var root = mock.document.createElement("div") + + m.mount(root, { + controller: function () { + this.values = [1, 2, 3, 4, 5] + this.value = m.prop([2, 3]) + }, + + view: function (ctrl) { + return m("select", { + 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) + }) + ]) + } + }) + + mock.requestAnimationFrame.$resolve() + + var select = root.childNodes[0] + + expect(select.childNodes[0].selected).to.not.be.ok + expect(select.childNodes[1].selected).to.be.ok + expect(select.childNodes[2].selected).to.be.ok + expect(select.childNodes[3].selected).to.not.be.ok + expect(select.childNodes[4].selected).to.not.be.ok + }) + + it("doesn't treat 0 as an empty string", function () { + var root = mock.document.createElement("div") + m.render(root, m("div", {class: ""})) + m.render(root, m("div", {class: 0})) + expect(root.childNodes[0].class).to.equal("0") + }) + + dom(function () { + it("renders empty `value` in ') + }) + }) +}) diff --git a/test/mithril.request.js b/test/mithril.request.js new file mode 100644 index 00000000..fd8ab1f4 --- /dev/null +++ b/test/mithril.request.js @@ -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"}) + }) + }) +}) diff --git a/test/mithril.route.buildQueryString.js b/test/mithril.route.buildQueryString.js new file mode 100644 index 00000000..bd53ad5e --- /dev/null +++ b/test/mithril.route.buildQueryString.js @@ -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") + }) +}) diff --git a/test/mithril.route.js b/test/mithril.route.js new file mode 100644 index 00000000..7317d268 --- /dev/null +++ b/test/mithril.route.js @@ -0,0 +1,1240 @@ +describe("m.route()", function () { + "use strict" + + // 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 + } + + var mode = (function () { + var types = { + search: "?", + hash: "#", + pathname: "/" + } + + return function (type) { + if (!{}.hasOwnProperty.call(types, type)) { + throw new RangeError("bad mode type") + } + mock.location[type] = types[type] + m.route.mode = type + } + })() + + // 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) + var xdit = makeIt(xit) + + // 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() + this.root = mock.document.createElement("div") + }) + + afterEach(function () { + m.mount(this.root, null) + }) + /* eslint-enable no-invalid-this */ + + it("exists", function () { + expect(m.route).to.be.a("function") + }) + + dit("routes to the right location by default", function (root) { + mode("search") + + route(root, "/test1", { + "/test1": pure(function () { return "foo" }) + }) + + expect(mock.location.search).to.equal("?/test1") + expect(root.childNodes[0].nodeValue).to.equal("foo") + }) + + dit("gets the right right location when routed to it", function (root) { + mode("search") + + var route1, route2 + route(root, "/", { + "/": { + controller: function () { route1 = m.route() }, + view: noop + }, + "/test13": { + controller: function () { route2 = m.route() }, + view: noop + } + }) + + m.route("/test13") + + expect(route1).to.equal("/") + expect(route2).to.equal("/test13") + }) + + // FIXME: this causes others to fail + xdit("skips route change if component ctrl.onunload calls preventDefault", function (root) { // eslint-disable-line + mode("search") + var spy = sinon.spy() + + var sub = { + controller: function () { + this.onunload = function (e) { e.preventDefault() } + }, + view: function () { + return m("div") + } + } + + route(root, "/a", { + "/a": pure(function () { return sub }), + + "/b": { + controller: spy, + view: noop + } + }) + + route("/b") + + expect(spy).to.not.have.been.called + }) + + // FIXME: this causes others to fail + xdit("skips route change if subcomponent ctrl.onunload calls preventDefault", function (root) { // eslint-disable-line + mode("search") + + var spy = sinon.spy() + + var subsub = { + controller: function () { + this.onunload = function (e) { e.preventDefault() } + }, + view: function () { + return m("div") + } + } + + var sub = pure(function () { return subsub }) + + route(root, "/a", { + "/a": pure(function () { return sub }), + + "/b": { + controller: spy, + view: noop + } + }) + + route("/b") + + expect(spy).to.not.have.been.called + }) + + // FIXME: this causes others to fail + xdit("skips route change if non-curried component ctrl.onunload calls preventDefault", function (root) { // eslint-disable-line + mode("search") + + var spy = sinon.spy() + + var sub = { + controller: function () { + this.onunload = function (e) { e.preventDefault() } + }, + view: function () { + return m("div") + } + } + + route(root, "/a", { + "/a": pure(function () { return sub }), + + "/b": { + controller: spy, + view: noop + } + }) + + route("/b") + + expect(spy).to.not.have.been.called + }) + + // FIXME: this causes others to fail + xdit("skips route change if non-curried subcomponent ctrl.onunload calls preventDefault", function (root) { // eslint-disable-line + mode("search") + + var spy = sinon.spy() + + var subsub = { + controller: function () { + this.onunload = function (e) { e.preventDefault() } + }, + view: function () { + return m("div") + } + } + + var sub = pure(function () { return subsub }) + + route(root, "/a", { + "/a": pure(function () { return sub }), + + "/b": { + controller: spy, + view: noop + } + }) + + route("/b") + + expect(spy).to.not.have.been.called + }) + + dit("initializes a component's constructor on route change", function (root) { // eslint-disable-line + mode("search") + + var ctrl1 = sinon.spy() + var ctrl2 = sinon.spy() + + var sub1 = { + controller: ctrl1, + view: function () { return m("div") } + } + + var sub2 = { + controller: ctrl2, + view: function () { return m("div") } + } + + route(root, "/a", { + "/a": pure(function () { + return m(".page-a", [ + m("h1"), m.component(sub1, {x: 11}) + ]) + }), + + "/b": pure(function () { + return m(".page-b", [ + m("h2"), m.component(sub2, {y: 22}) + ]) + }) + }) + + route("/b") + route("/a") + + expect(ctrl1).to.have.been.calledTwice + expect(ctrl2).to.have.been.calledOnce + }) + + dit("doesn't require components to have a view", function (root) { + mode("search") + + var Component = pure(function () { return m(".comp") }) + + route(root, "/foo", { + "/foo": pure(function () { return [Component] }) + }) + + expect(root.childNodes[0].nodeName).to.equal("DIV") + }) + + // https://github.com/lhorie/mithril.js/issues/555 + dit("reinstantiates the controller when redraw strategy is `all`", function (root) { // eslint-disable-line + var MyComponent = { + controller: function (args) { + this.name = args.name + }, + + view: function (ctrl) { + return m("div", ctrl.name) + } + } + + route(root, "/", { + "/": pure(function () { + return m("div", [ + m("a[href=/]", {config: m.route}, "foo"), + m("a[href=/bar]", {config: m.route}, "bar"), + m.component(MyComponent, {name: "Jane"}) + ]) + }), + + "/bar": pure(function () { + return m("div", [ + m("a[href=/]", {config: m.route}, "foo"), + m("a[href=/bar]", {config: m.route}, "bar"), + m.component(MyComponent, {name: "Bob"}) + ]) + }) + }) + + route("/bar") + + expect(root.childNodes[0].childNodes[2].childNodes[0].nodeValue) + .to.equal("Bob") + }) + + dit("sets the correct href with config: m.route", function (root) { + mode("pathname") + + route(root, "/test2", { + "/test2": pure(function () { + return [ + "foo", + m("a", {href: "/test2", config: m.route}, "Test2") + ] + }) + }) + + expect(mock.location.pathname).to.equal("/test2") + expect(root.childNodes[0].nodeValue).to.equal("foo") + expect(root.childNodes[1].href).to.equal("/test2") + }) + + dit("can use a hash", function (root) { + mode("hash") + + route(root, "/test3", { + "/test3": pure(function () { return "foo" }) + }) + + expect(mock.location.hash).to.equal("#/test3") + expect(root.childNodes[0].nodeValue).to.equal("foo") + }) + + dit("can use a query", function (root) { + mode("search") + + route(root, "/test4/foo", { + "/test4/:test": pure(function () { return m.route.param("test") }) + }) + + expect(mock.location.search).to.equal("?/test4/foo") + expect(root.childNodes[0].nodeValue).to.equal("foo") + }) + + context("m.route.param()", function () { + it("exists", function () { + expect(m.route.param).to.be.a("function") + }) + + dit("can get params (1)", function (root) { + mode("search") + + var component = pure(function () { return m.route.param("test") }) + + m.route(root, "/test5/foo", { + "/": component, + "/test5/:test": component + }) + + var paramValueBefore = m.route.param("test") + + mock.requestAnimationFrame.$resolve() + m.route("/") + + var paramValueAfter = m.route.param("test") + + mock.requestAnimationFrame.$resolve() + + expect(mock.location.search).to.equal("?/") + expect(paramValueBefore).to.equal("foo") + expect(paramValueAfter).to.not.exist + }) + + dit("can deal with params (2)", function (root) { + mode("search") + + var component = pure(function () { return m.route.param("a1") }) + + m.route(root, "/test6/foo", { + "/": component, + "/test6/:a1": component + }) + + var paramValueBefore = m.route.param("a1") + + mock.requestAnimationFrame.$resolve() + m.route("/") + + var paramValueAfter = m.route.param("a1") + + mock.requestAnimationFrame.$resolve() + + expect(mock.location.search).to.equal("?/") + expect(paramValueBefore).to.equal("foo") + expect(paramValueAfter).to.not.exist + }) + + // https://github.com/lhorie/mithril.js/issues/61 + dit("can get the route via m.route()", function (root) { + mode("search") + + var component = pure(function () { return m.route.param("a1") }) + + m.route(root, "/test7/foo", { + "/": component, + "/test7/:a1": component + }) + + var routeValueBefore = m.route() + + mock.requestAnimationFrame.$resolve() + m.route("/") + + var routeValueAfter = m.route() + + mock.requestAnimationFrame.$resolve() + + expect(routeValueBefore).to.equal("/test7/foo") + expect(routeValueAfter).to.equal("/") + }) + + dit("can deal with rest paths at the end", function (root) { + mode("search") + + route(root, "/test8/foo/SEP/bar/baz", { + "/test8/:test/SEP/:path...": pure(function () { + return m.route.param("test") + "_" + m.route.param("path") + }) + }) + + expect(mock.location.search).to.equal("?/test8/foo/SEP/bar/baz") + expect(root.childNodes[0].nodeValue).to.equal("foo_bar/baz") + }) + + dit("can deal with rest paths in the middle", function (root) { + mode("search") + + route(root, "/test9/foo/bar/SEP/baz", { + "/test9/:test.../SEP/:path": pure(function () { + return m.route.param("test") + "_" + m.route.param("path") + }) + }) + + expect(mock.location.search).to.equal("?/test9/foo/bar/SEP/baz") + expect(root.childNodes[0].nodeValue).to.equal("foo/bar_baz") + }) + + dit("unescapes urls for m.route.param()", function (root) { + mode("search") + + route(root, "/test10/foo%20bar", { + "/test10/:test": pure(function () { + return m.route.param("test") + }) + }) + + expect(root.childNodes[0].nodeValue).to.equal("foo bar") + }) + + dit("renders the correct path", function (root) { + mode("search") + + route(root, "/", { + "/": pure(function () { return "foo" }), + "/test11": pure(function () { return "bar" }) + }) + + route("/test11/") + + expect(mock.location.search).to.equal("?/test11/") + expect(root.childNodes[0].nodeValue).to.equal("bar") + }) + + dit("reads params by parsing query string", function (root) { + mode("search") + + route(root, "/", { + "/": pure(noop), + "/test12": pure(noop) + }) + + route("/test12?a=foo&b=bar") + + expect(mock.location.search).to.equal("?/test12?a=foo&b=bar") + expect(m.route.param("a")).to.equal("foo") + expect(m.route.param("b")).to.equal("bar") + }) + + dit("prefers local params to global params", function (root) { + mode("search") + + route(root, "/", { + "/": pure(function () { return "bar" }), + "/test13/:test": pure(function () { + return m.route.param("test") + }) + }) + + route("/test13/foo?test=bar") + + expect(mock.location.search).to.equal("?/test13/foo?test=bar") + expect(root.childNodes[0].nodeValue).to.equal("foo") + }) + + dit("reads global params", function (root) { + mode("search") + + route(root, "/", { + "/": pure(function () { return "bar" }), + "/test14": pure(function () { return "foo" }) + }) + + route("/test14?test&test2=") + + expect(mock.location.search).to.equal("?/test14?test&test2=") + expect(m.route.param("test")).to.not.exist + expect(m.route.param("test2")).to.equal("") + }) + + dit("parses params when using m.route(path, params)", function (root) { + mode("search") + + route(root, "/", { + "/": pure(noop), + "/test12": pure(noop) + }) + + route("/test12", {a: "foo", b: "bar"}) + + expect(mock.location.search).to.equal("?/test12?a=foo&b=bar") + expect(m.route.param("a")).to.equal("foo") + expect(m.route.param("b")).to.equal("bar") + }) + + dit("gets params object by using m.route.param()", function (root) { + mode("search") + + route(root, "/", { + "/": pure(noop), + "/test12": pure(noop) + }) + + route("/test12", {a: "foo", b: "bar"}) + + var params = m.route.param() + + expect(params.a).to.equal("foo") + expect(params.b).to.equal("bar") + }) + }) + + dit("only calls onunload once when routed away (1)", function (root) { + mode("search") + + var onunload = sinon.spy() + + route(root, "/", { + "/": pure(function () { + return m("div", { + config: function (el, init, ctx) { + ctx.onunload = onunload + } + }) + }), + "/test14": pure(noop) + }) + + route("/test14") + + expect(onunload).to.be.calledOnce + }) + + dit("only calls onunload once when routed away (2)", function (root) { + mode("search") + + var onunload = sinon.spy() + + route(root, "/", { + "/": pure(function () { + return [ + m("div"), + m("div", { + config: function (el, init, ctx) { + ctx.onunload = onunload + } + }) + ] + }), + "/test15": pure(function () { return [m("div")] }) + }) + + route("/test15") + + expect(onunload).to.be.calledOnce + }) + + dit("only calls onunload once when routed away (3)", function (root) { + mode("search") + + var onunload = sinon.spy() + + route(root, "/", { + "/": pure(function () { + return m("div", { + config: function (el, init, ctx) { + ctx.onunload = onunload + } + }) + }), + "/test16": pure(function () { return m("a") }) + }) + + route("/test16") + + expect(onunload).to.be.calledOnce + }) + + dit("only calls onunload once when routed away (4)", function (root) { + mode("search") + + var onunload = sinon.spy() + + route(root, "/", { + "/": pure(function () { + return [ + m("div", { + config: function (el, init, ctx) { + ctx.onunload = onunload + } + }) + ] + }), + "/test17": pure(function () { return m("a") }) + }) + + route("/test17") + + expect(onunload).to.be.calledOnce + }) + + dit("only calls onunload once when routed away (5)", function (root) { + mode("search") + + var onunload = sinon.spy() + + route(root, "/", { + "/": pure(function () { + return m("div", { + config: function (el, init, ctx) { + ctx.onunload = onunload + } + }) + }), + "/test18": pure(function () { return [m("a")] }) + }) + + route("/test18") + + expect(onunload).to.be.calledOnce + }) + + dit("only calls onunload once when routed away (6)", function (root) { + mode("search") + + var onunload = sinon.spy() + + route(root, "/", { + "/": pure(function () { + return [ + m("div", { + key: 1, + config: function (el, init, ctx) { + ctx.onunload = onunload + } + }) + ] + }), + "/test20": pure(function () { + return [ + m("div", { + key: 2, + config: function (el, init, ctx) { + ctx.onunload = onunload + } + }) + ] + }) + }) + + route("/test20") + + expect(onunload).to.be.calledOnce + }) + + dit("only calls onunload once when routed away (7)", function (root) { + mode("search") + + var onunload = sinon.spy() + + route(root, "/", { + "/": pure(function () { + return [ + m("div", { + key: 1, + config: function (el, init, ctx) { + ctx.onunload = onunload + } + }) + ] + }), + "/test21": pure(function () { + return [ + m("div", { + config: function (el, init, ctx) { + ctx.onunload = onunload + } + }) + ] + }) + }) + + route("/test21") + + expect(onunload).to.be.calledOnce + }) + + dit("renders the right virtual node when routed to it", function (root) { + mode("search") + + route(root, "/foo", { + "/foo": pure(function () { return m("div", "foo") }), + "/bar": pure(function () { return m("div", "bar") }) + }) + var foo = root.childNodes[0].childNodes[0].nodeValue + + route("/bar") + var bar = root.childNodes[0].childNodes[0].nodeValue + + expect(foo).to.equal("foo") + expect(bar).to.equal("bar") + }) + + dit("keeps identity with unchanged nodes", function (root) { + mode("search") + + var onunload = sinon.spy() + function config(el, init, ctx) { + ctx.onunload = onunload + } + + route(root, "/foo1", { + "/foo1": pure(function () { + return m("div", m("a", {config: config}, "foo")) + }), + "/bar1": pure(function () { + return m("main", m("a", {config: config}, "foo")) + }) + }) + + route("/bar1") + + expect(onunload).to.be.calledOnce + }) + + dit("allows illegal URL characters in paths", function (root) { + mode("search") + + var value + m.route(root, "/foo+bar", { + "/:arg": { + controller: function () { value = m.route.param("arg") }, + view: function () { + return "" + } + } + }) + expect(value).to.equal("foo+bar") + }) + + dit("allows trailing slashes in paths", function (root) { + mode("search") + + route(root, "/", { + "/": pure(function () { return "foo" }), + "/test22": pure(function () { return "bar" }) + }) + + m.route("/test22/") + + expect(mock.location.search).to.equal("?/test22/") + expect(root.childNodes[0].nodeValue).to.equal("bar") + }) + + dit("reads non-primitive String objects in route changes", function (root) { + mode("search") + + route(root, "/", { + "/": pure(function () { return "foo" }), + "/test23": pure(function () { return "bar" }) + }) + + route(new String("/test23/")) // eslint-disable-line no-new-wrappers + + expect(mock.location.search).to.equal("?/test23/") + expect(root.childNodes[0].nodeValue).to.equal("bar") + }) + + dit("reads primitive Strings in default routes", function (root) { + mode("search") + + var value + m.route(root, "/foo+bar", { + "/:arg": { + controller: function () { value = m.route.param("arg") }, + view: function () { + return "" + } + } + }) + expect(value).to.equal("foo+bar") + }) + + dit("reads non-primitive Strings in default routes", function (root) { + mode("search") + + var value + m.route(root, new String("/foo+bar"), { // eslint-disable-line + "/:arg": { + controller: function () { value = m.route.param("arg") }, + view: function () { + return "" + } + } + }) + + expect(value).to.equal("foo+bar") + }) + + dit("can redirect to another route while loading default", function (root) { // eslint-disable-line + mode("search") + + route(root, "/a", { + "/a": { + controller: function () { m.route("/b") }, + view: function () { return "a" } + }, + + "/b": pure(function () { return "b" }) + }) + + expect(root.childNodes[0].nodeValue).to.equal("b") + }) + + dit("can redirect to another route with params while loading default", function (root) { // eslint-disable-line + mode("search") + + route(root, "/", { + "/": { + controller: function () { + m.route("/b?foo=1", {foo: 2}) + }, + view: function () { return "a" } + }, + "/b": pure(function () { return "b" }) + }) + + expect(mock.location.search).to.equal("?/b?foo=2") + }) + + dit("modifies history when changing route", function (root) { + mode("search") + mock.history.$$length = 0 + + route(root, "/a", { + "/a": pure(function () { return "a" }), + "/b": pure(function () { return "b" }) + }) + + route("/b") + + expect(mock.history.$$length).to.equal(1) + }) + + dit("doesn't modify history when redirecting to same route", function (root) { // eslint-disable-line + mode("search") + mock.history.$$length = 0 + + route(root, "/a", { + "/a": pure(function () { return "a" }), + "/b": pure(function () { return "b" }) + }) + + route("/a") + + expect(mock.history.$$length).to.equal(0) + }) + + context("m.route.strategy() === \"all\", identical views", function () { + context("parent nodes", function () { + dit("renders routes independently", function (root) { + mode("search") + var initCount = 0 + + var a = pure(function () { + return m("a", { + config: function (el, init) { + if (!init) initCount++ + } + }) + }) + + route(root, "/a", { + "/a": a, + "/b": pure(a.view) + }) + + route("/b") + + expect(initCount).to.equal(2) + }) + + dit("renders routes independently with `context.retain === false`", function (root) { // eslint-disable-line + mode("search") + var initCount = 0 + + var a = pure(function () { + return m("a", { + config: function (el, init, ctx) { + ctx.retain = false + if (!init) initCount++ + } + }) + }) + + route(root, "/a", { + "/a": a, + "/b": pure(a.view) + }) + + route("/b") + + expect(initCount).to.equal(2) + }) + + dit("renders routes independently with `context.retain === true`", function (root) { // eslint-disable-line + mode("search") + var initCount = 0 + + var a = pure(function () { + return m("a", { + config: function (el, init, ctx) { + ctx.retain = true + if (!init) initCount++ + } + }) + }) + + route(root, "/a", { + "/a": a, + "/b": pure(a.view) + }) + + route("/b") + + expect(initCount).to.equal(1) + }) + }) + + context("child nodes", function () { + dit("reinitialize unchanged child nodes without `context.retain`", function (root) { // eslint-disable-line + mode("search") + var initCount = 0 + + function config(el, init) { + if (!init) initCount++ + } + + route(root, "/a", { + "/a": pure(function () { + return m("div", m("a", {config: config})) + }), + "/b": pure(function () { + return m("section", m("a", {config: config})) + }) + }) + + route("/b") + + expect(initCount).to.equal(2) + }) + + dit("doesn't reinitialize unchanged child nodes with `context.retain === true`", function (root) { // eslint-disable-line + mode("search") + var initCount = 0 + function config(el, init, ctx) { + ctx.retain = true + if (!init) initCount++ + } + + route(root, "/a", { + "/a": pure(function () { + return m("div", m("a", {config: config})) + }), + "/b": pure(function () { + return m("section", m("a", {config: config})) + }) + }) + + route("/b") + + expect(initCount).to.equal(1) + }) + + dit("reinitializes unchanged child nodes with `context.retain === false`", function (root) { // eslint-disable-line + mode("search") + var initCount = 0 + function config(el, init, ctx) { + ctx.retain = false + if (!init) initCount++ + } + + route(root, "/a", { + "/a": pure(function () { + return m("div", m("a", {config: config})) + }), + "/b": pure(function () { + return m("section", m("a", {config: config})) + }) + }) + + route("/b") + + expect(initCount).to.equal(2) + }) + }) + }) + + context("m.route.strategy() === \"diff\"", function () { + function diff(view) { + return { + controller: function () { m.redraw.strategy("diff") }, + view: view + } + } + + context("parent nodes", function () { + dit("renders routes independently", function (root) { + mode("search") + var initCount = 0 + + var a = diff(function () { + return m("a", { + config: function (el, init) { + if (!init) initCount++ + } + }) + }) + + route(root, "/a", { + "/a": a, + "/b": diff(a.view) + }) + + route("/b") + + expect(initCount).to.equal(1) + }) + + dit("renders routes independently with `context.retain === false`", function (root) { // eslint-disable-line + mode("search") + var initCount = 0 + + var a = diff(function () { + return m("a", { + config: function (el, init, ctx) { + ctx.retain = true + if (!init) initCount++ + } + }) + }) + + route(root, "/a", { + "/a": a, + "/b": diff(a.view) + }) + + route("/b") + + expect(initCount).to.equal(1) + }) + + dit("renders routes independently with `context.retain === true`", function (root) { // eslint-disable-line + mode("search") + var initCount = 0 + + var a = diff(function () { + return m("a", { + config: function (el, init, ctx) { + ctx.retain = false + if (!init) initCount++ + } + }) + }) + + route(root, "/a", { + "/a": a, + "/b": diff(a.view) + }) + + route("/b") + + expect(initCount).to.equal(2) + }) + }) + + context("child nodes", function () { + dit("reinitialize unchanged child nodes without `context.retain`", function (root) { // eslint-disable-line + mode("search") + var initCount = 0 + + function config(el, init) { + if (!init) initCount++ + } + + route(root, "/a", { + "/a": diff(function () { + return m("div", m("a", {config: config})) + }), + "/b": diff(function () { + return m("section", m("a", {config: config})) + }) + }) + + route("/b") + + expect(initCount).to.equal(1) + }) + + dit("doesn't reinitialize unchanged child nodes with `context.retain === true`", function (root) { // eslint-disable-line + mode("search") + var initCount = 0 + + function config(el, init, ctx) { + ctx.retain = true + if (!init) initCount++ + } + + route(root, "/a", { + "/a": diff(function () { + return m("div", m("a", {config: config})) + }), + "/b": diff(function () { + return m("section", m("a", {config: config})) + }) + }) + + route("/b") + + expect(initCount).to.equal(1) + }) + + dit("reinitializes unchanged child nodes with `context.retain === false`", function (root) { // eslint-disable-line + mode("search") + var initCount = 0 + + function config(el, init, ctx) { + ctx.retain = false + if (!init) initCount++ + } + + route(root, "/a", { + "/a": diff(function () { + return m("div", m("a", {config: config})) + }), + "/b": diff(function () { + return m("section", m("a", {config: config})) + }) + }) + + m.route("/b") + + expect(initCount).to.equal(2) + }) + }) + }) + + dit("honors retain flag inside child components during route change", function (root) { // eslint-disable-line + mode("search") + var initCount = 0 + + function config(el, init, ctx) { + ctx.retain = true + if (!init) initCount++ + } + + var a = pure(function () { + return m("div", m("a", {config: config})) + }) + + var b = { + controller: function () { m.redraw.strategy("diff") }, + view: function () { + return m("section", m("a", {config: config})) + } + } + + route(root, "/a", { + "/a": pure(function () { return m("div", a) }), + "/b": pure(function () { return m("div", b) }) + }) + + route("/b") + + expect(initCount).to.equal(1) + }) + + // https://github.com/lhorie/mithril.js/pull/571 + dit("clears nodes with config on route change", function (root) { + mode("search") + + route(root, "/a", { + "/a": pure(function () { + return m("div", { + config: function (el) { + el.childNodes[0].modified = true + } + }, m("div")) + }), + "/b": pure(function () { return m("div", m("div")) }) + }) + + route("/b") + + expect(root.childNodes[0].childNodes[0]) + .to.not.have.property("modified") + }) +}) diff --git a/test/mithril.route.parseQueryString.js b/test/mithril.route.parseQueryString.js new file mode 100644 index 00000000..3acc68ba --- /dev/null +++ b/test/mithril.route.parseQueryString.js @@ -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"] + }) + }) +}) diff --git a/test/mithril.startComputation.js b/test/mithril.startComputation.js new file mode 100644 index 00000000..cfa58063 --- /dev/null +++ b/test/mithril.startComputation.js @@ -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 +}) diff --git a/test/mithril.sync.js b/test/mithril.sync.js new file mode 100644 index 00000000..48be4605 --- /dev/null +++ b/test/mithril.sync.js @@ -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([]) + }) +}) diff --git a/test/mithril.trust.js b/test/mithril.trust.js new file mode 100644 index 00000000..748fa75e --- /dev/null +++ b/test/mithril.trust.js @@ -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("

1

2

"), 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("

1

123

2

"), + 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("12"), + m("td", "foo") + ]) + + expect(root.childNodes[2].tagName).to.equal("td") + }) + }) +}) diff --git a/test/mithril.withAttr.js b/test/mithril.withAttr.js new file mode 100644 index 00000000..aae26656 --- /dev/null +++ b/test/mithril.withAttr.js @@ -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") + }) +}) diff --git a/test/svg.html b/test/svg.html new file mode 100644 index 00000000..322d1045 --- /dev/null +++ b/test/svg.html @@ -0,0 +1,214 @@ + +SVG test + + +

+ This page is for testing SVG support in Mithril. This page should contain: +

+
    +
  • an HTML link labeled "HTML link"
  • +
  • an SVG link labeled "SVG link"
  • +
  • a tilted blue square
  • +
  • a cat picture
  • +
  • an animated line drawing
  • +
  • a clock with the current time
  • +
+ +

+ The links should open in a new tab. All items should display title tooltips + when hovered over. +

+ +
+ + diff --git a/tests/e2e/libs/qunit.css b/tests/e2e/libs/qunit.css deleted file mode 100644 index 2a6a02bf..00000000 --- a/tests/e2e/libs/qunit.css +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/tests/e2e/libs/qunit.js b/tests/e2e/libs/qunit.js deleted file mode 100644 index ba99f1ab..00000000 --- a/tests/e2e/libs/qunit.js +++ /dev/null @@ -1,2212 +0,0 @@ -/** - * QUnit v1.12.0 - A JavaScript Unit Testing Framework - * - * http://qunitjs.com - * - * Copyright 2013 jQuery Foundation and other contributors - * Released under the MIT license. - * https://jquery.org/license/ - */ - -(function( window ) { - -var QUnit, - assert, - config, - onErrorFnPrev, - testId = 0, - fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - // Keep a local reference to Date (GH-283) - Date = window.Date, - setTimeout = window.setTimeout, - defined = { - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch( e ) { - return false; - } - }()) - }, - /** - * Provides a normalized error string, correcting an issue - * with IE 7 (and prior) where Error.prototype.toString is - * not properly implemented - * - * Based on http://es5.github.com/#x15.11.4.4 - * - * @param {String|Error} error - * @return {String} error message - */ - errorString = function( error ) { - var name, message, - errorString = error.toString(); - if ( errorString.substring( 0, 7 ) === "[object" ) { - name = error.name ? error.name.toString() : "Error"; - message = error.message ? error.message.toString() : ""; - if ( name && message ) { - return name + ": " + message; - } else if ( name ) { - return name; - } else if ( message ) { - return message; - } else { - return "Error"; - } - } else { - return errorString; - } - }, - /** - * Makes a clone of an object using only Array or Object as base, - * and copies over the own enumerable properties. - * - * @param {Object} obj - * @return {Object} New object with only the own properties (recursively). - */ - objectValues = function( obj ) { - // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. - /*jshint newcap: false */ - var key, val, - vals = QUnit.is( "array", obj ) ? [] : {}; - for ( key in obj ) { - if ( hasOwn.call( obj, key ) ) { - val = obj[key]; - vals[key] = val === Object(val) ? objectValues(val) : val; - } - } - return vals; - }; - -function Test( settings ) { - extend( this, settings ); - this.assertions = []; - this.testNumber = ++Test.count; -} - -Test.count = 0; - -Test.prototype = { - init: function() { - var a, b, li, - tests = id( "qunit-tests" ); - - if ( tests ) { - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml; - - // `a` initialized at top of scope - a = document.createElement( "a" ); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ testNumber: this.testNumber }); - - li = document.createElement( "li" ); - li.appendChild( b ); - li.appendChild( a ); - li.className = "running"; - li.id = this.id = "qunit-test-output" + testId++; - - tests.appendChild( li ); - } - }, - setup: function() { - if ( - // Emit moduleStart when we're switching from one module to another - this.module !== config.previousModule || - // They could be equal (both undefined) but if the previousModule property doesn't - // yet exist it means this is the first test in a suite that isn't wrapped in a - // module, in which case we'll just emit a moduleStart event for 'undefined'. - // Without this, reporters can get testStart before moduleStart which is a problem. - !hasOwn.call( config, "previousModule" ) - ) { - if ( hasOwn.call( config, "previousModule" ) ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - runLoggingCallbacks( "moduleStart", QUnit, { - name: this.module - }); - } - - config.current = this; - - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment ); - - this.started = +new Date(); - runLoggingCallbacks( "testStart", QUnit, { - name: this.testName, - module: this.module - }); - - /*jshint camelcase:false */ - - - /** - * Expose the current test environment. - * - * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. - */ - QUnit.current_testEnvironment = this.testEnvironment; - - /*jshint camelcase:true */ - - if ( !config.pollution ) { - saveGlobal(); - } - if ( config.notrycatch ) { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); - return; - } - try { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); - } - }, - run: function() { - config.current = this; - - var running = id( "qunit-testresult" ); - - if ( running ) { - running.innerHTML = "Running:
" + this.nameHtml; - } - - if ( this.async ) { - QUnit.stop(); - } - - this.callbackStarted = +new Date(); - - if ( config.notrycatch ) { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; - return; - } - - try { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; - } catch( e ) { - this.callbackRuntime = +new Date() - this.callbackStarted; - - QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - QUnit.start(); - } - } - }, - teardown: function() { - config.current = this; - if ( config.notrycatch ) { - if ( typeof this.callbackRuntime === "undefined" ) { - this.callbackRuntime = +new Date() - this.callbackStarted; - } - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); - return; - } else { - try { - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); - } - } - checkPollution(); - }, - finish: function() { - config.current = this; - if ( config.requireExpects && this.expected === null ) { - QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); - } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); - } else if ( this.expected === null && !this.assertions.length ) { - QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); - } - - var i, assertion, a, b, time, li, ol, - test = this, - good = 0, - bad = 0, - tests = id( "qunit-tests" ); - - this.runtime = +new Date() - this.started; - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; - - if ( tests ) { - ol = document.createElement( "ol" ); - ol.className = "qunit-assert-list"; - - for ( i = 0; i < this.assertions.length; i++ ) { - assertion = this.assertions[i]; - - li = document.createElement( "li" ); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - // store result when possible - if ( QUnit.config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); - } - } - - if ( bad === 0 ) { - addClass( ol, "qunit-collapsed" ); - } - - // `b` initialized at top of scope - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - addEvent(b, "click", function() { - var next = b.parentNode.lastChild, - collapsed = hasClass( next, "qunit-collapsed" ); - ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); - }); - - addEvent(b, "dblclick", function( e ) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ testNumber: test.testNumber }); - } - }); - - // `time` initialized at top of scope - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = this.runtime + " ms"; - - // `li` initialized at top of scope - li = id( this.id ); - li.className = bad ? "fail" : "pass"; - li.removeChild( li.firstChild ); - a = li.firstChild; - li.appendChild( b ); - li.appendChild( a ); - li.appendChild( time ); - li.appendChild( ol ); - - } else { - for ( i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - } - - runLoggingCallbacks( "testDone", QUnit, { - name: this.testName, - module: this.module, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length, - duration: this.runtime - }); - - QUnit.reset(); - - config.current = undefined; - }, - - queue: function() { - var bad, - test = this; - - synchronize(function() { - test.init(); - }); - function run() { - // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); - } - - // `bad` initialized at top of scope - // defer when previous test run passed, if storage is available - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); - - if ( bad ) { - run(); - } else { - synchronize( run, true ); - } - } -}; - -// Root QUnit object. -// `QUnit` initialized at top of scope -QUnit = { - - // call on start of module test to prepend name to all tests - module: function( name, testEnvironment ) { - config.currentModule = name; - config.currentModuleTestEnvironment = testEnvironment; - config.modules[name] = true; - }, - - asyncTest: function( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - QUnit.test( testName, expected, callback, true ); - }, - - test: function( testName, expected, callback, async ) { - var test, - nameHtml = "" + escapeText( testName ) + ""; - - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - if ( config.currentModule ) { - nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; - } - - test = new Test({ - nameHtml: nameHtml, - testName: testName, - expected: expected, - async: async, - callback: callback, - module: config.currentModule, - moduleTestEnvironment: config.currentModuleTestEnvironment, - stack: sourceFromStacktrace( 2 ) - }); - - if ( !validTest( test ) ) { - return; - } - - test.queue(); - }, - - // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. - expect: function( asserts ) { - if (arguments.length === 1) { - config.current.expected = asserts; - } else { - return config.current.expected; - } - }, - - start: function( count ) { - // QUnit hasn't been initialized yet. - // Note: RequireJS (et al) may delay onLoad - if ( config.semaphore === undefined ) { - QUnit.begin(function() { - // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first - setTimeout(function() { - QUnit.start( count ); - }); - }); - return; - } - - config.semaphore -= count || 1; - // don't start until equal number of stop-calls - if ( config.semaphore > 0 ) { - return; - } - // ignore if start is called more often then stop - if ( config.semaphore < 0 ) { - config.semaphore = 0; - QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); - return; - } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - setTimeout(function() { - if ( config.semaphore > 0 ) { - return; - } - if ( config.timeout ) { - clearTimeout( config.timeout ); - } - - config.blocking = false; - process( true ); - }, 13); - } else { - config.blocking = false; - process( true ); - } - }, - - stop: function( count ) { - config.semaphore += count || 1; - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - config.semaphore = 1; - QUnit.start(); - }, config.testTimeout ); - } - } -}; - -// `assert` initialized at top of scope -// Assert helpers -// All of these must either call QUnit.push() or manually do: -// - runLoggingCallbacks( "log", .. ); -// - config.current.assertions.push({ .. }); -// We attach it to the QUnit object *after* we expose the public API, -// otherwise `assert` will become a global variable in browsers (#341). -assert = { - /** - * Asserts rough true-ish result. - * @name ok - * @function - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function( result, msg ) { - if ( !config.current ) { - throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - result = !!result; - msg = msg || (result ? "okay" : "failed" ); - - var source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: msg - }; - - msg = "" + escapeText( msg ) + ""; - - if ( !result ) { - source = sourceFromStacktrace( 2 ); - if ( source ) { - details.source = source; - msg += "
Source:
" + escapeText( source ) + "
"; - } - } - runLoggingCallbacks( "log", QUnit, details ); - config.current.assertions.push({ - result: result, - message: msg - }); - }, - - /** - * Assert that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * @name equal - * @function - * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); - */ - equal: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - QUnit.push( expected == actual, actual, expected, message ); - }, - - /** - * @name notEqual - * @function - */ - notEqual: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - QUnit.push( expected != actual, actual, expected, message ); - }, - - /** - * @name propEqual - * @function - */ - propEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name notPropEqual - * @function - */ - notPropEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name deepEqual - * @function - */ - deepEqual: function( actual, expected, message ) { - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name notDeepEqual - * @function - */ - notDeepEqual: function( actual, expected, message ) { - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name strictEqual - * @function - */ - strictEqual: function( actual, expected, message ) { - QUnit.push( expected === actual, actual, expected, message ); - }, - - /** - * @name notStrictEqual - * @function - */ - notStrictEqual: function( actual, expected, message ) { - QUnit.push( expected !== actual, actual, expected, message ); - }, - - "throws": function( block, expected, message ) { - var actual, - expectedOutput = expected, - ok = false; - - // 'expected' is optional - if ( typeof expected === "string" ) { - message = expected; - expected = null; - } - - config.current.ignoreGlobalErrors = true; - try { - block.call( config.current.testEnvironment ); - } catch (e) { - actual = e; - } - config.current.ignoreGlobalErrors = false; - - if ( actual ) { - // we don't want to validate thrown error - if ( !expected ) { - ok = true; - expectedOutput = null; - // expected is a regexp - } else if ( QUnit.objectType( expected ) === "regexp" ) { - ok = expected.test( errorString( actual ) ); - // expected is a constructor - } else if ( actual instanceof expected ) { - ok = true; - // expected is a validation function which returns true is validation passed - } else if ( expected.call( {}, actual ) === true ) { - expectedOutput = null; - ok = true; - } - - QUnit.push( ok, actual, expectedOutput, message ); - } else { - QUnit.pushFailure( message, null, "No exception was thrown." ); - } - } -}; - -/** - * @deprecated since 1.8.0 - * Kept assertion helpers in root for backwards compatibility. - */ -extend( QUnit, assert ); - -/** - * @deprecated since 1.9.0 - * Kept root "raises()" for backwards compatibility. - * (Note that we don't introduce assert.raises). - */ -QUnit.raises = assert[ "throws" ]; - -/** - * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.equals = function() { - QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); -}; -QUnit.same = function() { - QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); -}; - -// We want access to the constructor's prototype -(function() { - function F() {} - F.prototype = QUnit; - QUnit = new F(); - // Make F QUnit's constructor so that we can add to the prototype later - QUnit.constructor = F; -}()); - -/** - * Config object: Maintain internal state - * Later exposed as QUnit.config - * `config` initialized at top of scope - */ -config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true, - - // when enabled, show only failing tests - // gets persisted through sessionStorage and can be changed in UI via checkbox - hidepassed: false, - - // by default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - // by default, modify document.title when suite is done - altertitle: true, - - // when enabled, all tests must call expect() - requireExpects: false, - - // add checkboxes that are persisted in the query-string - // when enabled, the id is set to `true` as a `QUnit.config` property - urlConfig: [ - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." - } - ], - - // Set of all modules. - modules: {}, - - // logging callback queues - begin: [], - done: [], - log: [], - testStart: [], - testDone: [], - moduleStart: [], - moduleDone: [] -}; - -// Export global variables, unless an 'exports' object exists, -// in that case we assume we're in CommonJS (dealt with on the bottom of the script) -if ( typeof exports === "undefined" ) { - extend( window, QUnit.constructor.prototype ); - - // Expose QUnit object - window.QUnit = QUnit; -} - -// Initialize more QUnit.config and QUnit.urlParams -(function() { - var i, - location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}, - current; - - if ( params[ 0 ] ) { - for ( i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - urlParams[ current[ 0 ] ] = current[ 1 ]; - } - } - - QUnit.urlParams = urlParams; - - // String search anywhere in moduleName+testName - config.filter = urlParams.filter; - - // Exact match of the module name - config.module = urlParams.module; - - config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = location.protocol === "file:"; -}()); - -// Extend QUnit object, -// these after set here because they should not be exposed as global functions -extend( QUnit, { - assert: assert, - - config: config, - - // Initialize the configuration options - init: function() { - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date(), - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - filter: "", - queue: [], - semaphore: 1 - }); - - var tests, banner, result, - qunit = id( "qunit" ); - - if ( qunit ) { - qunit.innerHTML = - "

" + escapeText( document.title ) + "

" + - "

" + - "
" + - "

" + - "
    "; - } - - tests = id( "qunit-tests" ); - banner = id( "qunit-banner" ); - result = id( "qunit-testresult" ); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
     "; - } - }, - - // Resets the test setup. Useful for tests that modify the DOM. - /* - DEPRECATED: Use multiple tests instead of resetting inside a test. - Use testStart or testDone for custom cleanup. - This method will throw an error in 2.0, and will be removed in 2.1 - */ - reset: function() { - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } - }, - - // Trigger an event on an element. - // @example triggerEvent( document.body, "click" ); - triggerEvent: function( elem, type, event ) { - if ( document.createEvent ) { - event = document.createEvent( "MouseEvents" ); - event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, - 0, 0, 0, 0, 0, false, false, false, false, 0, null); - - elem.dispatchEvent( event ); - } else if ( elem.fireEvent ) { - elem.fireEvent( "on" + type ); - } - }, - - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) === type; - }, - - objectType: function( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - // consider: typeof null === object - } - if ( obj === null ) { - return "null"; - } - - var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), - type = match && match[1] || ""; - - switch ( type ) { - case "Number": - if ( isNaN(obj) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Date": - case "RegExp": - case "Function": - return type.toLowerCase(); - } - if ( typeof obj === "object" ) { - return "object"; - } - return undefined; - }, - - push: function( result, actual, expected, message ) { - if ( !config.current ) { - throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); - } - - var output, source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: message, - actual: actual, - expected: expected - }; - - message = escapeText( message ) || ( result ? "okay" : "failed" ); - message = "" + message + ""; - output = message; - - if ( !result ) { - expected = escapeText( QUnit.jsDump.parse(expected) ); - actual = escapeText( QUnit.jsDump.parse(actual) ); - output += ""; - - if ( actual !== expected ) { - output += ""; - output += ""; - } - - source = sourceFromStacktrace(); - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeText( source ) + "
    "; - } - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: !!result, - message: output - }); - }, - - pushFailure: function( message, source, actual ) { - if ( !config.current ) { - throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - - var output, - details = { - module: config.current.module, - name: config.current.testName, - result: false, - message: message - }; - - message = escapeText( message ) || "error"; - message = "" + message + ""; - output = message; - - output += ""; - - if ( actual ) { - output += ""; - } - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
    Result:
    " + escapeText( actual ) + "
    Source:
    " + escapeText( source ) + "
    "; - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: false, - message: output - }); - }, - - url: function( params ) { - params = extend( extend( {}, QUnit.urlParams ), params ); - var key, - querystring = "?"; - - for ( key in params ) { - if ( hasOwn.call( params, key ) ) { - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; - } - } - return window.location.protocol + "//" + window.location.host + - window.location.pathname + querystring.slice( 0, -1 ); - }, - - extend: extend, - id: id, - addEvent: addEvent, - addClass: addClass, - hasClass: hasClass, - removeClass: removeClass - // load, equiv, jsDump, diff: Attached later -}); - -/** - * @deprecated: Created for backwards compatibility with test runner that set the hook function - * into QUnit.{hook}, instead of invoking it and passing the hook function. - * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. - * Doing this allows us to tell if the following methods have been overwritten on the actual - * QUnit object. - */ -extend( QUnit.constructor.prototype, { - - // Logging callbacks; all receive a single argument with the listed properties - // run test/logs.html for any related changes - begin: registerLoggingCallback( "begin" ), - - // done: { failed, passed, total, runtime } - done: registerLoggingCallback( "done" ), - - // log: { result, actual, expected, message } - log: registerLoggingCallback( "log" ), - - // testStart: { name } - testStart: registerLoggingCallback( "testStart" ), - - // testDone: { name, failed, passed, total, duration } - testDone: registerLoggingCallback( "testDone" ), - - // moduleStart: { name } - moduleStart: registerLoggingCallback( "moduleStart" ), - - // moduleDone: { name, failed, passed, total } - moduleDone: registerLoggingCallback( "moduleDone" ) -}); - -if ( typeof document === "undefined" || document.readyState === "complete" ) { - config.autorun = true; -} - -QUnit.load = function() { - runLoggingCallbacks( "begin", QUnit, {} ); - - // Initialize the config, saving the execution queue - var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, - urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter, - numModules = 0, - moduleNames = [], - moduleFilterHtml = "", - urlConfigHtml = "", - oldconfig = extend( {}, config ); - - QUnit.init(); - extend(config, oldconfig); - - config.blocking = false; - - len = config.urlConfig.length; - - for ( i = 0; i < len; i++ ) { - val = config.urlConfig[i]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val, - tooltip: "[no tooltip available]" - }; - } - config[ val.id ] = QUnit.urlParams[ val.id ]; - urlConfigHtml += ""; - } - for ( i in config.modules ) { - if ( config.modules.hasOwnProperty( i ) ) { - moduleNames.push(i); - } - } - numModules = moduleNames.length; - moduleNames.sort( function( a, b ) { - return a.localeCompare( b ); - }); - moduleFilterHtml += ""; - - // `userAgent` initialized at top of scope - userAgent = id( "qunit-userAgent" ); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } - - // `banner` initialized at top of scope - banner = id( "qunit-header" ); - if ( banner ) { - banner.innerHTML = "" + banner.innerHTML + " "; - } - - // `toolbar` initialized at top of scope - toolbar = id( "qunit-testrunner-toolbar" ); - if ( toolbar ) { - // `filter` initialized at top of scope - filter = document.createElement( "input" ); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - - addEvent( filter, "click", function() { - var tmp, - ol = document.getElementById( "qunit-tests" ); - - if ( filter.checked ) { - ol.className = ol.className + " hidepass"; - } else { - tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace( / hidepass /, " " ); - } - if ( defined.sessionStorage ) { - if (filter.checked) { - sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); - } else { - sessionStorage.removeItem( "qunit-filter-passed-tests" ); - } - } - }); - - if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { - filter.checked = true; - // `ol` initialized at top of scope - ol = document.getElementById( "qunit-tests" ); - ol.className = ol.className + " hidepass"; - } - toolbar.appendChild( filter ); - - // `label` initialized at top of scope - label = document.createElement( "label" ); - label.setAttribute( "for", "qunit-filter-pass" ); - label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); - label.innerHTML = "Hide passed tests"; - toolbar.appendChild( label ); - - urlConfigCheckboxesContainer = document.createElement("span"); - urlConfigCheckboxesContainer.innerHTML = urlConfigHtml; - urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input"); - // For oldIE support: - // * Add handlers to the individual elements instead of the container - // * Use "click" instead of "change" - // * Fallback from event.target to event.srcElement - addEvents( urlConfigCheckboxes, "click", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.checked ? true : undefined; - window.location = QUnit.url( params ); - }); - toolbar.appendChild( urlConfigCheckboxesContainer ); - - if (numModules > 1) { - moduleFilter = document.createElement( "span" ); - moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); - moduleFilter.innerHTML = moduleFilterHtml; - addEvent( moduleFilter.lastChild, "change", function() { - var selectBox = moduleFilter.getElementsByTagName("select")[0], - selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); - - window.location = QUnit.url({ - module: ( selectedModule === "" ) ? undefined : selectedModule, - // Remove any existing filters - filter: undefined, - testNumber: undefined - }); - }); - toolbar.appendChild(moduleFilter); - } - } - - // `main` initialized at top of scope - main = id( "qunit-fixture" ); - if ( main ) { - config.fixture = main.innerHTML; - } - - if ( config.autostart ) { - QUnit.start(); - } -}; - -addEvent( window, "load", QUnit.load ); - -// `onErrorFnPrev` initialized at top of scope -// Preserve other handlers -onErrorFnPrev = window.onerror; - -// Cover uncaught exceptions -// Returning true will suppress the default browser handler, -// returning false will let it run. -window.onerror = function ( error, filePath, linerNr ) { - var ret = false; - if ( onErrorFnPrev ) { - ret = onErrorFnPrev( error, filePath, linerNr ); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not suppressed. - if ( ret !== true ) { - if ( QUnit.config.current ) { - if ( QUnit.config.current.ignoreGlobalErrors ) { - return true; - } - QUnit.pushFailure( error, filePath + ":" + linerNr ); - } else { - QUnit.test( "global failure", extend( function() { - QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: validTest } ) ); - } - return false; - } - - return ret; -}; - -function done() { - config.autorun = true; - - // Log the last module results - if ( config.currentModule ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.currentModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - delete config.previousModule; - - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - runtime = +new Date() - config.started, - passed = config.stats.all - config.stats.bad, - html = [ - "Tests completed in ", - runtime, - " milliseconds.
    ", - "", - passed, - " assertions of ", - config.stats.all, - " passed, ", - config.stats.bad, - " failed." - ].join( "" ); - - if ( banner ) { - banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( config.altertitle && typeof document !== "undefined" && document.title ) { - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( config.stats.bad ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { - // `key` & `i` initialized at top of scope - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); - } - } - } - - // scroll back to top to show results - if ( window.scrollTo ) { - window.scrollTo(0, 0); - } - - runLoggingCallbacks( "done", QUnit, { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - }); -} - -/** @return Boolean: true if this test should be ran */ -function validTest( test ) { - var include, - filter = config.filter && config.filter.toLowerCase(), - module = config.module && config.module.toLowerCase(), - fullName = (test.module + ": " + test.testName).toLowerCase(); - - // Internally-generated tests are always valid - if ( test.callback && test.callback.validTest === validTest ) { - delete test.callback.validTest; - return true; - } - - if ( config.testNumber ) { - return test.testNumber === config.testNumber; - } - - if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { - return false; - } - - if ( !filter ) { - return true; - } - - include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.slice( 1 ); - } - - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } - - // Otherwise, do the opposite - return !include; -} - -// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) -// Later Safari and IE10 are supposed to support error.stack as well -// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack -function extractStacktrace( e, offset ) { - offset = offset === undefined ? 3 : offset; - - var stack, include, i; - - if ( e.stacktrace ) { - // Opera - return e.stacktrace.split( "\n" )[ offset + 3 ]; - } else if ( e.stack ) { - // Firefox, Chrome - stack = e.stack.split( "\n" ); - if (/^error$/i.test( stack[0] ) ) { - stack.shift(); - } - if ( fileName ) { - include = []; - for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) !== -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } else if ( e.sourceURL ) { - // Safari, PhantomJS - // hopefully one day Safari provides actual stacktraces - // exclude useless self-reference for generated Error objects - if ( /qunit.js$/.test( e.sourceURL ) ) { - return; - } - // for actual exceptions, this is useful - return e.sourceURL + ":" + e.line; - } -} -function sourceFromStacktrace( offset ) { - try { - throw new Error(); - } catch ( e ) { - return extractStacktrace( e, offset ); - } -} - -/** - * Escape text for attribute or text content. - */ -function escapeText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch( s ) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - } - }); -} - -function synchronize( callback, last ) { - config.queue.push( callback ); - - if ( config.autorun && !config.blocking ) { - process( last ); - } -} - -function process( last ) { - function next() { - process( last ); - } - var start = new Date().getTime(); - config.depth = config.depth ? config.depth + 1 : 1; - - while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { - config.queue.shift()(); - } else { - setTimeout( next, 13 ); - break; - } - } - config.depth--; - if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { - done(); - } -} - -function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( var key in window ) { - if ( hasOwn.call( window, key ) ) { - // in Opera sometimes DOM element ids show up here, ignore them - if ( /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); - } - } - } -} - -function checkPollution() { - var newGlobals, - deletedGlobals, - old = config.pollution; - - saveGlobal(); - - newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); - } - - deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); - } -} - -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var i, j, - result = a.slice(); - - for ( i = 0; i < result.length; i++ ) { - for ( j = 0; j < b.length; j++ ) { - if ( result[i] === b[j] ) { - result.splice( i, 1 ); - i--; - break; - } - } - } - return result; -} - -function extend( a, b ) { - for ( var prop in b ) { - if ( hasOwn.call( b, prop ) ) { - // Avoid "Member not found" error in IE8 caused by messing with window.constructor - if ( !( prop === "constructor" && a === window ) ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - } else { - a[ prop ] = b[ prop ]; - } - } - } - } - - return a; -} - -/** - * @param {HTMLElement} elem - * @param {string} type - * @param {Function} fn - */ -function addEvent( elem, type, fn ) { - // Standards-based browsers - if ( elem.addEventListener ) { - elem.addEventListener( type, fn, false ); - // IE - } else { - elem.attachEvent( "on" + type, fn ); - } -} - -/** - * @param {Array|NodeList} elems - * @param {string} type - * @param {Function} fn - */ -function addEvents( elems, type, fn ) { - var i = elems.length; - while ( i-- ) { - addEvent( elems[i], type, fn ); - } -} - -function hasClass( elem, name ) { - return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; -} - -function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += (elem.className ? " " : "") + name; - } -} - -function removeClass( elem, name ) { - var set = " " + elem.className + " "; - // Class name may appear multiple times - while ( set.indexOf(" " + name + " ") > -1 ) { - set = set.replace(" " + name + " " , " "); - } - // If possible, trim it for prettiness, but not necessarily - elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); -} - -function id( name ) { - return !!( typeof document !== "undefined" && document && document.getElementById ) && - document.getElementById( name ); -} - -function registerLoggingCallback( key ) { - return function( callback ) { - config[key].push( callback ); - }; -} - -// Supports deprecated method of completely overwriting logging callbacks -function runLoggingCallbacks( key, scope, args ) { - var i, callbacks; - if ( QUnit.hasOwnProperty( key ) ) { - QUnit[ key ].call(scope, args ); - } else { - callbacks = config[ key ]; - for ( i = 0; i < callbacks.length; i++ ) { - callbacks[ i ].call( scope, args ); - } - } -} - -// Test for equality any JavaScript type. -// Author: Philippe Rathé -QUnit.equiv = (function() { - - // Call the o related callback with the given arguments. - function bindCallbacks( o, callbacks, args ) { - var prop = QUnit.objectType( o ); - if ( prop ) { - if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { - return callbacks[ prop ].apply( callbacks, args ); - } else { - return callbacks[ prop ]; // or undefined - } - } - } - - // the real equiv function - var innerEquiv, - // stack to decide between skip/abort functions - callers = [], - // stack to avoiding loops from circular referencing - parents = [], - parentsB = [], - - getProto = Object.getPrototypeOf || function ( obj ) { - /*jshint camelcase:false */ - return obj.__proto__; - }, - callbacks = (function () { - - // for string, boolean, number and null - function useStrictEquality( b, a ) { - /*jshint eqeqeq:false */ - if ( b instanceof a.constructor || a instanceof b.constructor ) { - // to catch short annotation VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function( b ) { - return isNaN( b ); - }, - - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && - // the regex itself - a.source === b.source && - // and its modifiers - a.global === b.global && - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function() { - var caller = callers[callers.length - 1]; - return caller !== Object && typeof caller !== "undefined"; - }, - - "array": function( b, a ) { - var i, j, len, loop, aCircular, bCircular; - - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } - - len = a.length; - if ( len !== b.length ) { - // safe and faster - return false; - } - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - for ( i = 0; i < len; i++ ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; - if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { - loop = true; - } else { - parents.pop(); - parentsB.pop(); - return false; - } - } - } - if ( !loop && !innerEquiv(a[i], b[i]) ) { - parents.pop(); - parentsB.pop(); - return false; - } - } - parents.pop(); - parentsB.pop(); - return true; - }, - - "object": function( b, a ) { - /*jshint forin:false */ - var i, j, loop, aCircular, bCircular, - // Default to true - eq = true, - aProperties = [], - bProperties = []; - - // comparing constructors is more strict than using - // instanceof - if ( a.constructor !== b.constructor ) { - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || - ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { - return false; - } - } - - // stack constructor before traversing properties - callers.push( a.constructor ); - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - - // be strict: don't ensure hasOwnProperty and go deep - for ( i in a ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; - if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { - loop = true; - } else { - eq = false; - break; - } - } - } - aProperties.push(i); - if ( !loop && !innerEquiv(a[i], b[i]) ) { - eq = false; - break; - } - } - - parents.pop(); - parentsB.pop(); - callers.pop(); // unstack, we are done - - for ( i in b ) { - bProperties.push( i ); // collect b's properties - } - - // Ensures identical properties name - return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); - } - }; - }()); - - innerEquiv = function() { // can take multiple arguments - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { - return true; // end transition - } - - return (function( a, b ) { - if ( a === b ) { - return true; // catch the most you can - } else if ( a === null || b === null || typeof a === "undefined" || - typeof b === "undefined" || - QUnit.objectType(a) !== QUnit.objectType(b) ) { - return false; // don't lose time with error prone cases - } else { - return bindCallbacks(a, callbacks, [ b, a ]); - } - - // apply transition with (1..n) arguments - }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); - }; - - return innerEquiv; -}()); - -/** - * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | - * http://flesler.blogspot.com Licensed under BSD - * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 - * - * @projectDescription Advanced and extensible data dumping for Javascript. - * @version 1.0.0 - * @author Ariel Flesler - * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} - */ -QUnit.jsDump = (function() { - function quote( str ) { - return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; - } - function literal( o ) { - return o + ""; - } - function join( pre, arr, post ) { - var s = jsDump.separator(), - base = jsDump.indent(), - inner = jsDump.indent(1); - if ( arr.join ) { - arr = arr.join( "," + s + inner ); - } - if ( !arr ) { - return pre + post; - } - return [ pre, inner + arr, base + post ].join(s); - } - function array( arr, stack ) { - var i = arr.length, ret = new Array(i); - this.up(); - while ( i-- ) { - ret[i] = this.parse( arr[i] , undefined , stack); - } - this.down(); - return join( "[", ret, "]" ); - } - - var reName = /^function (\w+)/, - jsDump = { - // type is used mostly internally, you can fix a (custom)type in advance - parse: function( obj, type, stack ) { - stack = stack || [ ]; - var inStack, res, - parser = this.parsers[ type || this.typeOf(obj) ]; - - type = typeof parser; - inStack = inArray( obj, stack ); - - if ( inStack !== -1 ) { - return "recursion(" + (inStack - stack.length) + ")"; - } - if ( type === "function" ) { - stack.push( obj ); - res = parser.call( this, obj, stack ); - stack.pop(); - return res; - } - return ( type === "string" ) ? parser : this.parsers.error; - }, - typeOf: function( obj ) { - var type; - if ( obj === null ) { - type = "null"; - } else if ( typeof obj === "undefined" ) { - type = "undefined"; - } else if ( QUnit.is( "regexp", obj) ) { - type = "regexp"; - } else if ( QUnit.is( "date", obj) ) { - type = "date"; - } else if ( QUnit.is( "function", obj) ) { - type = "function"; - } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { - type = "window"; - } else if ( obj.nodeType === 9 ) { - type = "document"; - } else if ( obj.nodeType ) { - type = "node"; - } else if ( - // native arrays - toString.call( obj ) === "[object Array]" || - // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) - ) { - type = "array"; - } else if ( obj.constructor === Error.prototype.constructor ) { - type = "error"; - } else { - type = typeof obj; - } - return type; - }, - separator: function() { - return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; - }, - // extra can be a number, shortcut for increasing-calling-decreasing - indent: function( extra ) { - if ( !this.multiline ) { - return ""; - } - var chr = this.indentChar; - if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); - } - return new Array( this.depth + ( extra || 0 ) ).join(chr); - }, - up: function( a ) { - this.depth += a || 1; - }, - down: function( a ) { - this.depth -= a || 1; - }, - setParser: function( name, parser ) { - this.parsers[name] = parser; - }, - // The next 3 are exposed so you can use them - quote: quote, - literal: literal, - join: join, - // - depth: 1, - // This is the list of parsers, to modify them, use jsDump.setParser - parsers: { - window: "[Window]", - document: "[Document]", - error: function(error) { - return "Error(\"" + error.message + "\")"; - }, - unknown: "[Unknown]", - "null": "null", - "undefined": "undefined", - "function": function( fn ) { - var ret = "function", - // functions never have name in IE - name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; - - if ( name ) { - ret += " " + name; - } - ret += "( "; - - ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); - }, - array: array, - nodelist: array, - "arguments": array, - object: function( map, stack ) { - /*jshint forin:false */ - var ret = [ ], keys, key, val, i; - QUnit.jsDump.up(); - keys = []; - for ( key in map ) { - keys.push( key ); - } - keys.sort(); - for ( i = 0; i < keys.length; i++ ) { - key = keys[ i ]; - val = map[ key ]; - ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); - } - QUnit.jsDump.down(); - return join( "{", ret, "}" ); - }, - node: function( node ) { - var len, i, val, - open = QUnit.jsDump.HTML ? "<" : "<", - close = QUnit.jsDump.HTML ? ">" : ">", - tag = node.nodeName.toLowerCase(), - ret = open + tag, - attrs = node.attributes; - - if ( attrs ) { - for ( i = 0, len = attrs.length; i < len; i++ ) { - val = attrs[i].nodeValue; - // IE6 includes all attributes in .attributes, even ones not explicitly set. - // Those have values like undefined, null, 0, false, "" or "inherit". - if ( val && val !== "inherit" ) { - ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); - } - } - } - ret += close; - - // Show content of TextNode or CDATASection - if ( node.nodeType === 3 || node.nodeType === 4 ) { - ret += node.nodeValue; - } - - return ret + open + "/" + tag + close; - }, - // function calls it internally, it's the arguments part of the function - functionArgs: function( fn ) { - var args, - l = fn.length; - - if ( !l ) { - return ""; - } - - args = new Array(l); - while ( l-- ) { - // 97 is 'a' - args[l] = String.fromCharCode(97+l); - } - return " " + args.join( ", " ) + " "; - }, - // object calls it internally, the key part of an item in a map - key: quote, - // function calls it internally, it's the content of the function - functionCode: "[code]", - // node calls it internally, it's an html attribute value - attribute: quote, - string: quote, - date: quote, - regexp: literal, - number: literal, - "boolean": literal - }, - // if true, entities are escaped ( <, >, \t, space and \n ) - HTML: false, - // indentation unit - indentChar: " ", - // if true, items in a collection, are separated by a \n, else just a space. - multiline: true - }; - - return jsDump; -}()); - -// from jquery.js -function inArray( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; -} - -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" - */ -QUnit.diff = (function() { - /*jshint eqeqeq:false, eqnull:true */ - function diff( o, n ) { - var i, - ns = {}, - os = {}; - - for ( i = 0; i < n.length; i++ ) { - if ( !hasOwn.call( ns, n[i] ) ) { - ns[ n[i] ] = { - rows: [], - o: null - }; - } - ns[ n[i] ].rows.push( i ); - } - - for ( i = 0; i < o.length; i++ ) { - if ( !hasOwn.call( os, o[i] ) ) { - os[ o[i] ] = { - rows: [], - n: null - }; - } - os[ o[i] ].rows.push( i ); - } - - for ( i in ns ) { - if ( hasOwn.call( ns, i ) ) { - if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { - n[ ns[i].rows[0] ] = { - text: n[ ns[i].rows[0] ], - row: os[i].rows[0] - }; - o[ os[i].rows[0] ] = { - text: o[ os[i].rows[0] ], - row: ns[i].rows[0] - }; - } - } - } - - for ( i = 0; i < n.length - 1; i++ ) { - if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && - n[ i + 1 ] == o[ n[i].row + 1 ] ) { - - n[ i + 1 ] = { - text: n[ i + 1 ], - row: n[i].row + 1 - }; - o[ n[i].row + 1 ] = { - text: o[ n[i].row + 1 ], - row: i + 1 - }; - } - } - - for ( i = n.length - 1; i > 0; i-- ) { - if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && - n[ i - 1 ] == o[ n[i].row - 1 ]) { - - n[ i - 1 ] = { - text: n[ i - 1 ], - row: n[i].row - 1 - }; - o[ n[i].row - 1 ] = { - text: o[ n[i].row - 1 ], - row: i - 1 - }; - } - } - - return { - o: o, - n: n - }; - } - - return function( o, n ) { - o = o.replace( /\s+$/, "" ); - n = n.replace( /\s+$/, "" ); - - var i, pre, - str = "", - out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), - oSpace = o.match(/\s+/g), - nSpace = n.match(/\s+/g); - - if ( oSpace == null ) { - oSpace = [ " " ]; - } - else { - oSpace.push( " " ); - } - - if ( nSpace == null ) { - nSpace = [ " " ]; - } - else { - nSpace.push( " " ); - } - - if ( out.n.length === 0 ) { - for ( i = 0; i < out.o.length; i++ ) { - str += "" + out.o[i] + oSpace[i] + ""; - } - } - else { - if ( out.n[0].text == null ) { - for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { - str += "" + out.o[n] + oSpace[n] + ""; - } - } - - for ( i = 0; i < out.n.length; i++ ) { - if (out.n[i].text == null) { - str += "" + out.n[i] + nSpace[i] + ""; - } - else { - // `pre` initialized at top of scope - pre = ""; - - for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { - pre += "" + out.o[n] + oSpace[n] + ""; - } - str += " " + out.n[i].text + nSpace[i] + pre; - } - } - } - - return str; - }; -}()); - -// for CommonJS environments, export everything -if ( typeof exports !== "undefined" ) { - extend( exports, QUnit.constructor.prototype ); -} - -// get at whatever the global object is, like window in browsers -}( (function() {return this;}.call()) )); \ No newline at end of file diff --git a/tests/e2e/libs/syn.js b/tests/e2e/libs/syn.js deleted file mode 100644 index 640572fb..00000000 --- a/tests/e2e/libs/syn.js +++ /dev/null @@ -1,2858 +0,0 @@ -/** - * Syn - 0.0.2 - * - * @copyright 2014 Bitovi - * Mon, 30 Jun 2014 22:44:59 GMT - * @license MIT - */ - -!function(window) { - -// ## src/synthetic.js -var __m2 = (function () { - //allow for configuration of Syn - var opts = window.Syn ? window.Syn : {}; - - var extend = function (d, s) { - var p; - for (p in s) { - d[p] = s[p]; - } - return d; - }, - // only uses browser detection for key events - browser = { - msie: !! (window.attachEvent && !window.opera), - opera: !! window.opera, - webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - safari: navigator.userAgent.indexOf('AppleWebKit/') > -1 && navigator.userAgent.indexOf('Chrome/') === -1, - gecko: navigator.userAgent.indexOf('Gecko') > -1, - mobilesafari: !! navigator.userAgent.match(/Apple.*Mobile.*Safari/), - rhino: navigator.userAgent.match(/Rhino/) && true - }, - createEventObject = function (type, options, element) { - var event = element.ownerDocument.createEventObject(); - return extend(event, options); - }, - data = {}, - id = 1, - expando = "_synthetic" + new Date() - .getTime(), - bind, unbind, schedule, key = /keypress|keyup|keydown/, - page = /load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll/, - //this is maintained so we can click on html and blur the active element - activeElement, - - /** - * @class Syn - * @download funcunit/dist/syn.js - * @test funcunit/synthetic/qunit.html - * Syn is used to simulate user actions. It creates synthetic events and - * performs their default behaviors. - * - *

    Basic Use

    - * The following clicks an input element with id='description' - * and then types 'Hello World'. - * - @codestart - Syn.click({},'description') - .type("Hello World") - @codeend - *

    User Actions and Events

    - *

    Syn is typically used to simulate user actions as opposed to triggering events. Typing characters - * is an example of a user action. The keypress that represents an 'a' - * character being typed is an example of an event. - *

    - *

    - * While triggering events is supported, it's much more useful to simulate actual user behavior. The - * following actions are supported by Syn: - *

    - *
      - *
    • [Syn.prototype.click click] - a mousedown, focus, mouseup, and click.
    • - *
    • [Syn.prototype.dblclick dblclick] - two click! events followed by a dblclick.
    • - *
    • [Syn.prototype.key key] - types a single character (keydown, keypress, keyup).
    • - *
    • [Syn.prototype.type type] - types multiple characters into an element.
    • - *
    • [Syn.prototype.move move] - moves the mouse from one position to another (triggering mouseover / mouseouts).
    • - *
    • [Syn.prototype.drag drag] - a mousedown, followed by mousemoves, and a mouseup.
    • - *
    - * All actions run asynchronously. - * Click on the links above for more - * information on how to use the specific action. - *

    Asynchronous Callbacks

    - * Actions don't complete immediately. This is almost - * entirely because focus() - * doesn't run immediately in IE. - * If you provide a callback function to Syn, it will - * be called after the action is completed. - *
    The following checks that "Hello World" was entered correctly: - @codestart - Syn.click({},'description') - .type("Hello World", function(){ - - ok("Hello World" == document.getElementById('description').value) - }) - @codeend -

    Asynchronous Chaining

    -

    You might have noticed the [Syn.prototype.then then] method. It provides chaining - so you can do a sequence of events with a single (final) callback. -

    - If an element isn't provided to then, it uses the previous Syn's element. -

    - The following does a lot of stuff before checking the result: - @codestart - Syn.type('ice water','title') - .type('ice and water','description') - .click({},'create') - .drag({to: 'favorites'},'newRecipe', - function(){ - ok($('#newRecipe').parents('#favorites').length); - }) - @codeend - -

    jQuery Helper

    - If jQuery is present, Syn adds a triggerSyn helper you can use like: - @codestart - $("#description").triggerSyn("type","Hello World"); - @codeend - *

    Key Event Recording

    - *

    Every browser has very different rules for dispatching key events. - * As there is no way to feature detect how a browser handles key events, - * synthetic uses a description of how the browser behaves generated - * by a recording application.

    - *

    - * If you want to support a browser not currently supported, you can - * record that browser's key event description and add it to - * Syn.key.browsers by it's navigator agent. - *

    - @codestart - Syn.key.browsers["Envjs\ Resig/20070309 PilotFish/1.2.0.10\1.6"] = { - 'prevent': - {"keyup":[],"keydown":["char","keypress"],"keypress":["char"]}, - 'character': - { ... } - } - @codeend - *

    Limitations

    - * Syn fully supports IE 6+, FF 3+, Chrome, Safari, Opera 10+. - * With FF 1+, drag / move events are only partially supported. They will - * not trigger mouseover / mouseout events.
    - * Safari crashes when a mousedown is triggered on a select. Syn will not - * create this event. - *

    Contributing to Syn

    - * Have we missed something? We happily accept patches. The following are - * important objects and properties of Syn: - *
      - *
    • Syn.create - contains methods to setup, convert options, and create an event of a specific type.
    • - *
    • Syn.defaults - default behavior by event type (except for keys).
    • - *
    • Syn.key.defaults - default behavior by key.
    • - *
    • Syn.keycodes - supported keys you can type.
    • - *
    - *

    Roll Your Own Functional Test Framework

    - *

    Syn is really the foundation of JavaScriptMVC's functional testing framework - [FuncUnit]. - * But, we've purposely made Syn work without any dependencies in the hopes that other frameworks or - * testing solutions can use it as well. - *

    - * @constructor - * @signature `Syn(type, options, element, callback)` - * Creates a synthetic event on the element. - * @param {Object} type - * @param {Object} options - * @param {Object} element - * @param {Object} callback - * @return {Syn} returns the Syn object. - */ - Syn = function (type, options, element, callback) { - return (new Syn.init(type, options, element, callback)); - }; - - Syn.config = opts; - - // helper for supporting IE8 and below: - // focus will throw in some circumnstances, like element being invisible - Syn.__tryFocus = function tryFocus(element) { - try { - element.focus(); - } catch (e) {} - }; - - bind = function (el, ev, f) { - return el.addEventListener ? el.addEventListener(ev, f, false) : el.attachEvent("on" + ev, f); - }; - unbind = function (el, ev, f) { - return el.addEventListener ? el.removeEventListener(ev, f, false) : el.detachEvent("on" + ev, f); - }; - - schedule = Syn.config.schedule || function (fn, ms) { - setTimeout(fn, ms); - }; - /** - * @Static - */ - extend(Syn, { - /** - * Creates a new synthetic event instance - * @hide - * @param {Object} type - * @param {Object} options - * @param {Object} element - * @param {Object} callback - */ - init: function (type, options, element, callback) { - var args = Syn.args(options, element, callback), - self = this; - this.queue = []; - this.element = args.element; - - //run event - if (typeof this[type] === "function") { - this[type](args.options, args.element, function (defaults, el) { - if (args.callback) { - args.callback.apply(self, arguments); - } - self.done.apply(self, arguments); - }); - } else { - this.result = Syn.trigger(type, args.options, args.element); - if (args.callback) { - args.callback.call(this, args.element, this.result); - } - } - }, - jquery: function (el, fast) { - if (window.FuncUnit && window.FuncUnit.jQuery) { - return window.FuncUnit.jQuery; - } - if (el) { - return Syn.helpers.getWindow(el) - .jQuery || window.jQuery; - } else { - return window.jQuery; - } - }, - /** - * Returns an object with the args for a Syn. - * @hide - * @return {Object} - */ - args: function () { - var res = {}, - i = 0; - for (; i < arguments.length; i++) { - if (typeof arguments[i] === 'function') { - res.callback = arguments[i]; - } else if (arguments[i] && arguments[i].jquery) { - res.element = arguments[i][0]; - } else if (arguments[i] && arguments[i].nodeName) { - res.element = arguments[i]; - } else if (res.options && typeof arguments[i] === 'string') { //we can get by id - res.element = document.getElementById(arguments[i]); - } else if (arguments[i]) { - res.options = arguments[i]; - } - } - return res; - }, - click: function (options, element, callback) { - Syn('click!', options, element, callback); - }, - /** - * @hide - * @attribute defaults - * Default actions for events. Each default function is called with this as its - * element. It should return true if a timeout - * should happen after it. If it returns an element, a timeout will happen - * and the next event will happen on that element. - */ - defaults: { - focus: function focus() { - if (!Syn.support.focusChanges) { - var element = this, - nodeName = element.nodeName.toLowerCase(); - Syn.data(element, "syntheticvalue", element.value); - - //TODO, this should be textarea too - //and this might be for only text style inputs ... hmmmmm .... - if (nodeName === "input" || nodeName === "textarea") { - bind(element, "blur", function () { - if (Syn.data(element, "syntheticvalue") !== element.value) { - - Syn.trigger("change", {}, element); - } - unbind(element, "blur", focus); - }); - - } - } - }, - submit: function () { - Syn.onParents(this, function (el) { - if (el.nodeName.toLowerCase() === 'form') { - el.submit(); - return false; - } - }); - } - }, - changeOnBlur: function (element, prop, value) { - - bind(element, "blur", function onblur() { - if (value !== element[prop]) { - Syn.trigger("change", {}, element); - } - unbind(element, "blur", onblur); - }); - - }, - /** - * Returns the closest element of a particular type. - * @hide - * @param {Object} el - * @param {Object} type - */ - closest: function (el, type) { - while (el && el.nodeName.toLowerCase() !== type.toLowerCase()) { - el = el.parentNode; - } - return el; - }, - /** - * adds jQuery like data (adds an expando) and data exists FOREVER :) - * @hide - * @param {Object} el - * @param {Object} key - * @param {Object} value - */ - data: function (el, key, value) { - var d; - if (!el[expando]) { - el[expando] = id++; - } - if (!data[el[expando]]) { - data[el[expando]] = {}; - } - d = data[el[expando]]; - if (value) { - data[el[expando]][key] = value; - } else { - return data[el[expando]][key]; - } - }, - /** - * Calls a function on the element and all parents of the element until the function returns - * false. - * @hide - * @param {Object} el - * @param {Object} func - */ - onParents: function (el, func) { - var res; - while (el && res !== false) { - res = func(el); - el = el.parentNode; - } - return el; - }, - //regex to match focusable elements - focusable: /^(a|area|frame|iframe|label|input|select|textarea|button|html|object)$/i, - /** - * Returns if an element is focusable - * @hide - * @param {Object} elem - */ - isFocusable: function (elem) { - var attributeNode; - - // IE8 Standards doesn't like this on some elements - if (elem.getAttributeNode) { - attributeNode = elem.getAttributeNode("tabIndex"); - } - - return this.focusable.test(elem.nodeName) || - (attributeNode && attributeNode.specified) && - Syn.isVisible(elem); - }, - /** - * Returns if an element is visible or not - * @hide - * @param {Object} elem - */ - isVisible: function (elem) { - return (elem.offsetWidth && elem.offsetHeight) || (elem.clientWidth && elem.clientHeight); - }, - /** - * Gets the tabIndex as a number or null - * @hide - * @param {Object} elem - */ - tabIndex: function (elem) { - var attributeNode = elem.getAttributeNode("tabIndex"); - return attributeNode && attributeNode.specified && (parseInt(elem.getAttribute('tabIndex')) || 0); - }, - bind: bind, - unbind: unbind, - /** - * @function Syn.schedule schedule() - * @param {Function} fn Function to be ran - * @param {Number} ms Milliseconds to way before calling fn - * @signature `Syn.schedule(fn, ms)` - * @parent config - * - * Schedules a function to be ran later. - * Must be registered prior to Syn loading, otherwise `setTimeout` will be - * used as the scheduler. - * @codestart - * Syn = { - * schedule: function(fn, ms) { - * Platform.run.later(fn, ms); - * } - * }; - * @codeend - */ - schedule: schedule, - browser: browser, - //some generic helpers - helpers: { - createEventObject: createEventObject, - createBasicStandardEvent: function (type, defaults, doc) { - var event; - try { - event = doc.createEvent("Events"); - } catch (e2) { - event = doc.createEvent("UIEvents"); - } finally { - event.initEvent(type, true, true); - extend(event, defaults); - } - return event; - }, - inArray: function (item, array) { - var i = 0; - for (; i < array.length; i++) { - if (array[i] === item) { - return i; - } - } - return -1; - }, - getWindow: function (element) { - if (element.ownerDocument) { - return element.ownerDocument.defaultView || element.ownerDocument.parentWindow; - } - }, - extend: extend, - scrollOffset: function (win, set) { - var doc = win.document.documentElement, - body = win.document.body; - if (set) { - window.scrollTo(set.left, set.top); - - } else { - return { - left: (doc && doc.scrollLeft || body && body.scrollLeft || 0) + (doc.clientLeft || 0), - top: (doc && doc.scrollTop || body && body.scrollTop || 0) + (doc.clientTop || 0) - }; - } - - }, - scrollDimensions: function (win) { - var doc = win.document.documentElement, - body = win.document.body, - docWidth = doc.clientWidth, - docHeight = doc.clientHeight, - compat = win.document.compatMode === "CSS1Compat"; - - return { - height: compat && docHeight || - body.clientHeight || docHeight, - width: compat && docWidth || - body.clientWidth || docWidth - }; - }, - addOffset: function (options, el) { - var jq = Syn.jquery(el), - off; - if (typeof options === 'object' && options.clientX === undefined && options.clientY === undefined && options.pageX === undefined && options.pageY === undefined && jq) { - el = jq(el); - off = el.offset(); - options.pageX = off.left + el.width() / 2; - options.pageY = off.top + el.height() / 2; - } - } - }, - // place for key data - key: { - ctrlKey: null, - altKey: null, - shiftKey: null, - metaKey: null - }, - //triggers an event on an element, returns true if default events should be run - /** - * Dispatches an event and returns true if default events should be run. - * @hide - * @param {Object} event - * @param {Object} element - * @param {Object} type - * @param {Object} autoPrevent - */ - dispatch: function (event, element, type, autoPrevent) { - - // dispatchEvent doesn't always work in IE (mostly in a popup) - if (element.dispatchEvent && event) { - var preventDefault = event.preventDefault, - prevents = autoPrevent ? -1 : 0; - - //automatically prevents the default behavior for this event - //this is to protect agianst nasty browser freezing bug in safari - if (autoPrevent) { - bind(element, type, function ontype(ev) { - ev.preventDefault(); - unbind(this, type, ontype); - }); - } - - event.preventDefault = function () { - prevents++; - if (++prevents > 0) { - preventDefault.apply(this, []); - } - }; - element.dispatchEvent(event); - return prevents <= 0; - } else { - try { - window.event = event; - } catch (e) {} - //source element makes sure element is still in the document - return element.sourceIndex <= 0 || (element.fireEvent && element.fireEvent('on' + type, event)); - } - }, - /** - * @attribute - * @hide - * An object of eventType -> function that create that event. - */ - create: { - //-------- PAGE EVENTS --------------------- - page: { - event: function (type, options, element) { - var doc = Syn.helpers.getWindow(element) - .document || document, - event; - if (doc.createEvent) { - event = doc.createEvent("Events"); - - event.initEvent(type, true, true); - return event; - } else { - try { - event = createEventObject(type, options, element); - } catch (e) {} - return event; - } - } - }, - // unique events - focus: { - event: function (type, options, element) { - Syn.onParents(element, function (el) { - if (Syn.isFocusable(el)) { - if (el.nodeName.toLowerCase() !== 'html') { - Syn.__tryFocus(el); - activeElement = el; - } else if (activeElement) { - // TODO: The HTML element isn't focasable in IE, but it is - // in FF. We should detect this and do a true focus instead - // of just a blur - var doc = Syn.helpers.getWindow(element) - .document; - if (doc !== window.document) { - return false; - } else if (doc.activeElement) { - doc.activeElement.blur(); - activeElement = null; - } else { - activeElement.blur(); - activeElement = null; - } - - } - return false; - } - }); - return true; - } - } - }, - /** - * @attribute support - * @hide - * - * Feature detected properties of a browser's event system. - * Support has the following properties: - * - * - `backspaceWorks` - typing a backspace removes a character - * - `clickChanges` - clicking on an option element creates a change event. - * - `clickSubmits` - clicking on a form button submits the form. - * - `focusChanges` - focus/blur creates a change event. - * - `keypressOnAnchorClicks` - Keying enter on an anchor triggers a click. - * - `keypressSubmits` - enter key submits - * - `keyCharacters` - typing a character shows up - * - `keysOnNotFocused` - enters keys when not focused. - * - `linkHrefJS` - An achor's href JavaScript is run. - * - `mouseDownUpClicks` - A mousedown followed by mouseup creates a click event. - * - `mouseupSubmits` - a mouseup on a form button submits the form. - * - `radioClickChanges` - clicking a radio button changes the radio. - * - `tabKeyTabs` - A tab key changes tabs. - * - `textareaCarriage` - a new line in a textarea creates a carriage return. - * - * - */ - support: { - clickChanges: false, - clickSubmits: false, - keypressSubmits: false, - mouseupSubmits: false, - radioClickChanges: false, - focusChanges: false, - linkHrefJS: false, - keyCharacters: false, - backspaceWorks: false, - mouseDownUpClicks: false, - tabKeyTabs: false, - keypressOnAnchorClicks: false, - optionClickBubbles: false, - ready: 0 - }, - /** - * @function Syn.trigger trigger() - * @parent actions - * @signature `Syn.trigger(type, options, element)` - * Creates a synthetic event and dispatches it on the element. - * This will run any default actions for the element. - * Typically you want to use Syn, but if you want the return value, use this. - * @param {String} type - * @param {Object} options - * @param {HTMLElement} element - * @return {Boolean} true if default events were run, false if otherwise. - */ - trigger: function (type, options, element) { - if (!options) { - options = {}; - } - - var create = Syn.create, - setup = create[type] && create[type].setup, - kind = key.test(type) ? 'key' : (page.test(type) ? "page" : "mouse"), - createType = create[type] || {}, - createKind = create[kind], - event, ret, autoPrevent, dispatchEl = element; - - //any setup code? - if (Syn.support.ready === 2 && setup) { - setup(type, options, element); - } - - autoPrevent = options._autoPrevent; - //get kind - delete options._autoPrevent; - - if (createType.event) { - ret = createType.event(type, options, element); - } else { - //convert options - options = createKind.options ? createKind.options(type, options, element) : options; - - if (!Syn.support.changeBubbles && /option/i.test(element.nodeName)) { - dispatchEl = element.parentNode; //jQuery expects clicks on select - } - - //create the event - event = createKind.event(type, options, dispatchEl); - - //send the event - ret = Syn.dispatch(event, dispatchEl, type, autoPrevent); - } - - if (ret && Syn.support.ready === 2 && Syn.defaults[type]) { - Syn.defaults[type].call(element, options, autoPrevent); - } - return ret; - }, - eventSupported: function (eventName) { - var el = document.createElement("div"); - eventName = "on" + eventName; - - var isSupported = (eventName in el); - if (!isSupported) { - el.setAttribute(eventName, "return;"); - isSupported = typeof el[eventName] === "function"; - } - el = null; - - return isSupported; - } - - }); - /** - * @Prototype - */ - extend(Syn.init.prototype, { - /** - * @function Syn.then then() - * @parent chained - *

    - * Then is used to chain a sequence of actions to be run one after the other. - * This is useful when many asynchronous actions need to be performed before some - * final check needs to be made. - *

    - *

    The following clicks and types into the id='age' element and then checks that only numeric characters can be entered.

    - *

    Example

    - * @codestart - * Syn('click',{},'age') - * .then('type','I am 12',function(){ - * equals($('#age').val(),"12") - * }) - * @codeend - * If the element argument is undefined, then the last element is used. - * - * @param {String} type The type of event or action to create: "_click", "_dblclick", "_drag", "_type". - * @param {Object} options Optiosn to pass to the event. - * @param {String|HTMLElement} [element] A element's id or an element. If undefined, defaults to the previous element. - * @param {Function} [callback] A function to callback after the action has run, but before any future chained actions are run. - */ - then: function (type, options, element, callback) { - if (Syn.autoDelay) { - this.delay(); - } - var args = Syn.args(options, element, callback), - self = this; - - //if stack is empty run right away - //otherwise ... unshift it - this.queue.unshift(function (el, prevented) { - - if (typeof this[type] === "function") { - this.element = args.element || el; - this[type](args.options, this.element, function (defaults, el) { - if (args.callback) { - args.callback.apply(self, arguments); - } - self.done.apply(self, arguments); - }); - } else { - this.result = Syn.trigger(type, args.options, args.element); - if (args.callback) { - args.callback.call(this, args.element, this.result); - } - return this; - } - }); - return this; - }, - /** - * @function Syn.delay delay() - * @parent chained - * Delays the next command a set timeout. - * @param {Number} [timeout] - * @param {Function} [callback] - */ - delay: function (timeout, callback) { - if (typeof timeout === 'function') { - callback = timeout; - timeout = null; - } - timeout = timeout || 600; - var self = this; - this.queue.unshift(function () { - schedule(function () { - if (callback) { - callback.apply(self, []); - } - self.done.apply(self, arguments); - }, timeout); - }); - return this; - }, - done: function (defaults, el) { - if (el) { - this.element = el; - } - if (this.queue.length) { - this.queue.pop() - .call(this, this.element, defaults); - } - - }, - /** - * @function Syn.click click() - * @parent mouse - * @signature `Syn.click(options, element, callback, force)` - * Clicks an element by triggering a mousedown, - * mouseup, - * and a click event. - *

    Example

    - * @codestart - * Syn.click({},'create',function(){ - * //check something - * }) - * @codeend - * You can also provide the coordinates of the click. - * If jQuery is present, it will set clientX and clientY - * for you. Here's how to set it yourself: - * @codestart - * Syn.click( - * {clientX: 20, clientY: 100}, - * 'create', - * function(){ - * //check something - * }) - * @codeend - * You can also provide pageX and pageY and Syn will convert it for you. - * @param {Object} options - * @param {HTMLElement} element - * @param {Function} callback - */ - "_click": function (options, element, callback, force) { - Syn.helpers.addOffset(options, element); - Syn.trigger("mousedown", options, element); - - //timeout is b/c IE is stupid and won't call focus handlers - schedule(function () { - Syn.trigger("mouseup", options, element); - if (!Syn.support.mouseDownUpClicks || force) { - Syn.trigger("click", options, element); - callback(true); - } else { - //we still have to run the default (presumably) - Syn.create.click.setup('click', options, element); - Syn.defaults.click.call(element); - //must give time for callback - schedule(function () { - callback(true); - }, 1); - } - - }, 1); - }, - /** - * @function Syn.rightClick rightClick() - * @parent mouse - * @signature `Syn.rightClick(options, element, callback)` - * Right clicks in browsers that support it (everyone but opera). - * @param {Object} options - * @param {Object} element - * @param {Object} callback - */ - "_rightClick": function (options, element, callback) { - Syn.helpers.addOffset(options, element); - var mouseopts = extend(extend({}, Syn.mouse.browser.right.mouseup), options); - - Syn.trigger("mousedown", mouseopts, element); - - //timeout is b/c IE is stupid and won't call focus handlers - schedule(function () { - Syn.trigger("mouseup", mouseopts, element); - if (Syn.mouse.browser.right.contextmenu) { - Syn.trigger("contextmenu", extend(extend({}, Syn.mouse.browser.right.contextmenu), options), element); - } - callback(true); - }, 1); - }, - /** - * @function Syn.dblclick dblclick() - * @parent mouse - * @signature `Syn.dblclick(options, element, callback)` - * Dblclicks an element. This runs two [Syn.click click] events followed by - * a dblclick on the element. - *

    Example

    - * @codestart - * Syn.dblclick({},'open') - * @codeend - * @param {Object} options - * @param {HTMLElement} element - * @param {Function} callback - */ - "_dblclick": function (options, element, callback) { - Syn.helpers.addOffset(options, element); - var self = this; - this._click(options, element, function () { - schedule(function () { - self._click(options, element, function () { - Syn.trigger("dblclick", options, element); - callback(true); - }, true); - }, 2); - - }); - } - }); - - var actions = ["click", "dblclick", "move", "drag", "key", "type", 'rightClick'], - makeAction = function (name) { - Syn[name] = function (options, element, callback) { - return Syn("_" + name, options, element, callback); - }; - Syn.init.prototype[name] = function (options, element, callback) { - return this.then("_" + name, options, element, callback); - }; - }, - i = 0; - - for (; i < actions.length; i++) { - makeAction(actions[i]); - } - - return Syn; -})(); - -// ## src/mouse.js -var __m3 = (function (Syn) { - //handles mosue events - - var h = Syn.helpers, - getWin = h.getWindow; - - Syn.mouse = {}; - h.extend(Syn.defaults, { - mousedown: function (options) { - Syn.trigger("focus", {}, this); - }, - click: function () { - // prevents the access denied issue in IE if the click causes the element to be destroyed - var element = this, - href, type, createChange, radioChanged, nodeName, scope; - try { - href = element.href; - type = element.type; - createChange = Syn.data(element, "createChange"); - radioChanged = Syn.data(element, "radioChanged"); - scope = getWin(element); - nodeName = element.nodeName.toLowerCase(); - } catch (e) { - return; - } - //get old values - - //this code was for restoring the href attribute to prevent popup opening - //if ((href = Syn.data(element, "href"))) { - // element.setAttribute('href', href) - //} - - //run href javascript - if (!Syn.support.linkHrefJS && /^\s*javascript:/.test(href)) { - //eval js - var code = href.replace(/^\s*javascript:/, ""); - - //try{ - if (code !== "//" && code.indexOf("void(0)") === -1) { - if (window.selenium) { - eval("with(selenium.browserbot.getCurrentWindow()){" + code + "}"); - } else { - eval("with(scope){" + code + "}"); - } - } - } - - //submit a form - if (!(Syn.support.clickSubmits) && (nodeName === "input" && - type === "submit") || - nodeName === 'button') { - - var form = Syn.closest(element, "form"); - if (form) { - Syn.trigger("submit", {}, form); - } - - } - //follow a link, probably needs to check if in an a. - if (nodeName === "a" && element.href && !/^\s*javascript:/.test(href)) { - scope.location.href = href; - - } - - //change a checkbox - if (nodeName === "input" && type === "checkbox") { - - //if(!Syn.support.clickChecks && !Syn.support.changeChecks){ - // element.checked = !element.checked; - //} - if (!Syn.support.clickChanges) { - Syn.trigger("change", {}, element); - } - } - - //change a radio button - if (nodeName === "input" && type === "radio") { // need to uncheck others if not checked - if (radioChanged && !Syn.support.radioClickChanges) { - Syn.trigger("change", {}, element); - } - } - // change options - if (nodeName === "option" && createChange) { - Syn.trigger("change", {}, element.parentNode); //does not bubble - Syn.data(element, "createChange", false); - } - } - }); - - //add create and setup behavior for mosue events - h.extend(Syn.create, { - mouse: { - options: function (type, options, element) { - var doc = document.documentElement, - body = document.body, - center = [options.pageX || 0, options.pageY || 0], - //browser might not be loaded yet (doing support code) - left = Syn.mouse.browser && Syn.mouse.browser.left[type], - right = Syn.mouse.browser && Syn.mouse.browser.right[type]; - return h.extend({ - bubbles: true, - cancelable: true, - view: window, - detail: 1, - screenX: 1, - screenY: 1, - clientX: options.clientX || center[0] - (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0), - clientY: options.clientY || center[1] - (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0), - ctrlKey: !! Syn.key.ctrlKey, - altKey: !! Syn.key.altKey, - shiftKey: !! Syn.key.shiftKey, - metaKey: !! Syn.key.metaKey, - button: left && left.button !== null ? left.button : right && right.button || (type === 'contextmenu' ? 2 : 0), - relatedTarget: document.documentElement - }, options); - }, - event: function (type, defaults, element) { //Everyone Else - var doc = getWin(element) - .document || document, - event; - if (doc.createEvent) { - try { - event = doc.createEvent('MouseEvents'); - event.initMouseEvent(type, defaults.bubbles, defaults.cancelable, - defaults.view, defaults.detail, - defaults.screenX, defaults.screenY, - defaults.clientX, defaults.clientY, - defaults.ctrlKey, defaults.altKey, - defaults.shiftKey, defaults.metaKey, - defaults.button, defaults.relatedTarget); - } catch (e) { - event = h.createBasicStandardEvent(type, defaults, doc); - } - event.synthetic = true; - return event; - } else { - try { - event = h.createEventObject(type, defaults, element); - } catch (e) {} - - return event; - } - - } - }, - click: { - setup: function (type, options, element) { - var nodeName = element.nodeName.toLowerCase(); - - //we need to manually 'check' in browser that can't check - //so checked has the right value - if (!Syn.support.clickChecks && !Syn.support.changeChecks && nodeName === "input") { - type = element.type.toLowerCase(); //pretty sure lowercase isn't needed - if (type === 'checkbox') { - element.checked = !element.checked; - } - if (type === "radio") { - //do the checks manually - if (!element.checked) { //do nothing, no change - try { - Syn.data(element, "radioChanged", true); - } catch (e) {} - element.checked = true; - } - } - } - - if (nodeName === "a" && element.href && !/^\s*javascript:/.test(element.href)) { - - //save href - Syn.data(element, "href", element.href); - - //remove b/c safari/opera will open a new tab instead of changing the page - // this has been removed because newer versions don't have this problem - //element.setAttribute('href', 'javascript://') - //however this breaks scripts using the href - //we need to listen to this and prevent the default behavior - //and run the default behavior ourselves. Boo! - } - //if select or option, save old value and mark to change - if (/option/i.test(element.nodeName)) { - var child = element.parentNode.firstChild, - i = -1; - while (child) { - if (child.nodeType === 1) { - i++; - if (child === element) { - break; - } - } - child = child.nextSibling; - } - if (i !== element.parentNode.selectedIndex) { - //shouldn't this wait on triggering - //change? - element.parentNode.selectedIndex = i; - Syn.data(element, "createChange", true); - } - } - - } - }, - mousedown: { - setup: function (type, options, element) { - var nn = element.nodeName.toLowerCase(); - //we have to auto prevent default to prevent freezing error in safari - if (Syn.browser.safari && (nn === "select" || nn === "option")) { - options._autoPrevent = true; - } - } - } - }); - - return Syn; -})(__m2); - -// ## src/browsers.js -var __m4 = (function (Syn) { - Syn.key.browsers = { - webkit: { - 'prevent': { - "keyup": [], - "keydown": ["char", "keypress"], - "keypress": ["char"] - }, - 'character': { - "keydown": [0, "key"], - "keypress": ["char", "char"], - "keyup": [0, "key"] - }, - 'specialChars': { - "keydown": [0, "char"], - "keyup": [0, "char"] - }, - 'navigation': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - 'special': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - 'tab': { - "keydown": [0, "char"], - "keyup": [0, "char"] - }, - 'pause-break': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - 'caps': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - 'escape': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - 'num-lock': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - 'scroll-lock': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - 'print': { - "keyup": [0, "key"] - }, - 'function': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - '\r': { - "keydown": [0, "key"], - "keypress": ["char", "key"], - "keyup": [0, "key"] - } - }, - gecko: { - 'prevent': { - "keyup": [], - "keydown": ["char"], - "keypress": ["char"] - }, - 'character': { - "keydown": [0, "key"], - "keypress": ["char", 0], - "keyup": [0, "key"] - }, - 'specialChars': { - "keydown": [0, "key"], - "keypress": [0, "key"], - "keyup": [0, "key"] - }, - 'navigation': { - "keydown": [0, "key"], - "keypress": [0, "key"], - "keyup": [0, "key"] - }, - 'special': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - '\t': { - "keydown": [0, "key"], - "keypress": [0, "key"], - "keyup": [0, "key"] - }, - 'pause-break': { - "keydown": [0, "key"], - "keypress": [0, "key"], - "keyup": [0, "key"] - }, - 'caps': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - 'escape': { - "keydown": [0, "key"], - "keypress": [0, "key"], - "keyup": [0, "key"] - }, - 'num-lock': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - 'scroll-lock': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - 'print': { - "keyup": [0, "key"] - }, - 'function': { - "keydown": [0, "key"], - "keyup": [0, "key"] - }, - '\r': { - "keydown": [0, "key"], - "keypress": [0, "key"], - "keyup": [0, "key"] - } - }, - msie: { - 'prevent': { - "keyup": [], - "keydown": ["char", "keypress"], - "keypress": ["char"] - }, - 'character': { - "keydown": [null, "key"], - "keypress": [null, "char"], - "keyup": [null, "key"] - }, - 'specialChars': { - "keydown": [null, "char"], - "keyup": [null, "char"] - }, - 'navigation': { - "keydown": [null, "key"], - "keyup": [null, "key"] - }, - 'special': { - "keydown": [null, "key"], - "keyup": [null, "key"] - }, - 'tab': { - "keydown": [null, "char"], - "keyup": [null, "char"] - }, - 'pause-break': { - "keydown": [null, "key"], - "keyup": [null, "key"] - }, - 'caps': { - "keydown": [null, "key"], - "keyup": [null, "key"] - }, - 'escape': { - "keydown": [null, "key"], - "keypress": [null, "key"], - "keyup": [null, "key"] - }, - 'num-lock': { - "keydown": [null, "key"], - "keyup": [null, "key"] - }, - 'scroll-lock': { - "keydown": [null, "key"], - "keyup": [null, "key"] - }, - 'print': { - "keyup": [null, "key"] - }, - 'function': { - "keydown": [null, "key"], - "keyup": [null, "key"] - }, - '\r': { - "keydown": [null, "key"], - "keypress": [null, "key"], - "keyup": [null, "key"] - } - }, - opera: { - 'prevent': { - "keyup": [], - "keydown": [], - "keypress": ["char"] - }, - 'character': { - "keydown": [null, "key"], - "keypress": [null, "char"], - "keyup": [null, "key"] - }, - 'specialChars': { - "keydown": [null, "char"], - "keypress": [null, "char"], - "keyup": [null, "char"] - }, - 'navigation': { - "keydown": [null, "key"], - "keypress": [null, "key"] - }, - 'special': { - "keydown": [null, "key"], - "keypress": [null, "key"], - "keyup": [null, "key"] - }, - 'tab': { - "keydown": [null, "char"], - "keypress": [null, "char"], - "keyup": [null, "char"] - }, - 'pause-break': { - "keydown": [null, "key"], - "keypress": [null, "key"], - "keyup": [null, "key"] - }, - 'caps': { - "keydown": [null, "key"], - "keyup": [null, "key"] - }, - 'escape': { - "keydown": [null, "key"], - "keypress": [null, "key"] - }, - 'num-lock': { - "keyup": [null, "key"], - "keydown": [null, "key"], - "keypress": [null, "key"] - }, - 'scroll-lock': { - "keydown": [null, "key"], - "keypress": [null, "key"], - "keyup": [null, "key"] - }, - 'print': {}, - 'function': { - "keydown": [null, "key"], - "keypress": [null, "key"], - "keyup": [null, "key"] - }, - '\r': { - "keydown": [null, "key"], - "keypress": [null, "key"], - "keyup": [null, "key"] - } - } - }; - - Syn.mouse.browsers = { - webkit: { - "right": { - "mousedown": { - "button": 2, - "which": 3 - }, - "mouseup": { - "button": 2, - "which": 3 - }, - "contextmenu": { - "button": 2, - "which": 3 - } - }, - "left": { - "mousedown": { - "button": 0, - "which": 1 - }, - "mouseup": { - "button": 0, - "which": 1 - }, - "click": { - "button": 0, - "which": 1 - } - } - }, - opera: { - "right": { - "mousedown": { - "button": 2, - "which": 3 - }, - "mouseup": { - "button": 2, - "which": 3 - } - }, - "left": { - "mousedown": { - "button": 0, - "which": 1 - }, - "mouseup": { - "button": 0, - "which": 1 - }, - "click": { - "button": 0, - "which": 1 - } - } - }, - msie: { - "right": { - "mousedown": { - "button": 2 - }, - "mouseup": { - "button": 2 - }, - "contextmenu": { - "button": 0 - } - }, - "left": { - "mousedown": { - "button": 1 - }, - "mouseup": { - "button": 1 - }, - "click": { - "button": 0 - } - } - }, - chrome: { - "right": { - "mousedown": { - "button": 2, - "which": 3 - }, - "mouseup": { - "button": 2, - "which": 3 - }, - "contextmenu": { - "button": 2, - "which": 3 - } - }, - "left": { - "mousedown": { - "button": 0, - "which": 1 - }, - "mouseup": { - "button": 0, - "which": 1 - }, - "click": { - "button": 0, - "which": 1 - } - } - }, - gecko: { - "left": { - "mousedown": { - "button": 0, - "which": 1 - }, - "mouseup": { - "button": 0, - "which": 1 - }, - "click": { - "button": 0, - "which": 1 - } - }, - "right": { - "mousedown": { - "button": 2, - "which": 3 - }, - "mouseup": { - "button": 2, - "which": 3 - }, - "contextmenu": { - "button": 2, - "which": 3 - } - } - } - }; - - //set browser - Syn.key.browser = - (function () { - if (Syn.key.browsers[window.navigator.userAgent]) { - return Syn.key.browsers[window.navigator.userAgent]; - } - for (var browser in Syn.browser) { - if (Syn.browser[browser] && Syn.key.browsers[browser]) { - return Syn.key.browsers[browser]; - } - } - return Syn.key.browsers.gecko; - })(); - - Syn.mouse.browser = - (function () { - if (Syn.mouse.browsers[window.navigator.userAgent]) { - return Syn.mouse.browsers[window.navigator.userAgent]; - } - for (var browser in Syn.browser) { - if (Syn.browser[browser] && Syn.mouse.browsers[browser]) { - return Syn.mouse.browsers[browser]; - } - } - return Syn.mouse.browsers.gecko; - })(); - return Syn; -})(__m2, __m3); - -// ## src/typeable.js -var __m6 = (function (Syn) { - // Holds functions that test for typeability - var typeables = []; - - // IE <= 8 doesn't implement [].indexOf. - // This shim was extracted from CoffeeScript: - var __indexOf = [].indexOf || function (item) { - for (var i = 0, l = this.length; i < l; i++) { - if (i in this && this[i] === item) { - return i; - } - } - return -1; - }; - - /* - * @function typeable - * Registers a function that is used to determine if an - * element can be typed into. The user can define as many - * test functions as needed. By default there are 2 typeable - * functions, one for inputs and textareas, and another - * for contenteditable elements. - * - * @param {Function} fn Function to register. - */ - Syn.typeable = function (fn) { - if (__indexOf.call(typeables, fn) === -1) { - typeables.push(fn); - } - }; - - /* - * @function test - * Tests whether an element can be typed into using the test - * functions registered by [Syn.typeable typeable]. If any of the - * test functions returns true, `test` will return true and allow - * the element to be typed into. - * - * @param {HTMLElement} el the element to test. - * @return {Boolean} true if the element can be typed into. - */ - Syn.typeable.test = function (el) { - for (var i = 0, len = typeables.length; i < len; i++) { - if (typeables[i](el)) { - return true; - } - } - return false; - }; - - var type = Syn.typeable; - - // Inputs and textareas - var typeableExp = /input|textarea/i; - type(function (el) { - return typeableExp.test(el.nodeName); - }); - - // Content editable - type(function (el) { - return __indexOf.call(["", "true"], el.getAttribute("contenteditable")) !== -1; - }); - - return Syn; -})(__m2); - -// ## src/key.js -var __m5 = (function (Syn) { - var h = Syn.helpers, - - // gets the selection of an input or textarea - getSelection = function (el) { - var real, r, start; - - // use selectionStart if we can - if (el.selectionStart !== undefined) { - // this is for opera, so we don't have to focus to type how we think we would - if (document.activeElement && document.activeElement !== el && - el.selectionStart === el.selectionEnd && el.selectionStart === 0) { - return { - start: el.value.length, - end: el.value.length - }; - } - return { - start: el.selectionStart, - end: el.selectionEnd - }; - } else { - //check if we aren't focused - try { - //try 2 different methods that work differently (IE breaks depending on type) - if (el.nodeName.toLowerCase() === 'input') { - real = h.getWindow(el) - .document.selection.createRange(); - r = el.createTextRange(); - r.setEndPoint("EndToStart", real); - - start = r.text.length; - return { - start: start, - end: start + real.text.length - }; - } else { - real = h.getWindow(el) - .document.selection.createRange(); - r = real.duplicate(); - var r2 = real.duplicate(), - r3 = real.duplicate(); - r2.collapse(); - r3.collapse(false); - r2.moveStart('character', -1); - r3.moveStart('character', -1); - //select all of our element - r.moveToElementText(el); - //now move our endpoint to the end of our real range - r.setEndPoint('EndToEnd', real); - start = r.text.length - real.text.length; - var end = r.text.length; - if (start !== 0 && r2.text === "") { - start += 2; - } - if (end !== 0 && r3.text === "") { - end += 2; - } - //if we aren't at the start, but previous is empty, we are at start of newline - return { - start: start, - end: end - }; - } - } catch (e) { - var prop = formElExp.test(el.nodeName) ? "value" : "textContent"; - - return { - start: el[prop].length, - end: el[prop].length - }; - } - } - }, - // gets all focusable elements - getFocusable = function (el) { - var document = h.getWindow(el) - .document, - res = []; - - var els = document.getElementsByTagName('*'), - len = els.length; - - for (var i = 0; i < len; i++) { - if (Syn.isFocusable(els[i]) && els[i] !== document.documentElement) { - res.push(els[i]); - } - } - return res; - }, - formElExp = /input|textarea/i, - textProperty = (function(){ - var el = document.createElement("span"); - return el.textContent != null ? 'textContent' : 'innerText'; - })(), - - // Get the text from an element. - getText = function (el) { - if (formElExp.test(el.nodeName)) { - return el.value; - } - return el[textProperty]; - }, - // Set the text of an element. - setText = function (el, value) { - if (formElExp.test(el.nodeName)) { - el.value = value; - } else { - el[textProperty] = value; - } - }; - - /** - * - */ - h.extend(Syn, { - /** - * @attribute - * @parent keys - * A list of the keys and their keycodes codes you can type. - * You can add type keys with - * @codestart - * Syn('key','delete','title'); - * - * //or - * - * Syn('type','One Two Three[left][left][delete]','title') - * @codeend - * - * The following are a list of keys you can type: - * @codestart text - * \b - backspace - * \t - tab - * \r - enter - * ' ' - space - * a-Z 0-9 - normal characters - * /!@#$*,.? - All other typeable characters - * page-up - scrolls up - * page-down - scrolls down - * end - scrolls to bottom - * home - scrolls to top - * insert - changes how keys are entered - * delete - deletes the next character - * left - moves cursor left - * right - moves cursor right - * up - moves the cursor up - * down - moves the cursor down - * f1-12 - function buttons - * shift, ctrl, alt - special keys - * pause-break - the pause button - * scroll-lock - locks scrolling - * caps - makes caps - * escape - escape button - * num-lock - allows numbers on keypad - * print - screen capture - * subtract - subtract (keypad) - - * dash - dash - - * divide - divide (keypad) / - * forward-slash - forward slash / - * decimal - decimal (keypad) . - * period - period . - * @codeend - */ - keycodes: { - //backspace - '\b': 8, - - //tab - '\t': 9, - - //enter - '\r': 13, - - //special - 'shift': 16, - 'ctrl': 17, - 'alt': 18, - - //weird - 'pause-break': 19, - 'caps': 20, - 'escape': 27, - 'num-lock': 144, - 'scroll-lock': 145, - 'print': 44, - - //navigation - 'page-up': 33, - 'page-down': 34, - 'end': 35, - 'home': 36, - 'left': 37, - 'up': 38, - 'right': 39, - 'down': 40, - 'insert': 45, - 'delete': 46, - - //normal characters - ' ': 32, - '0': 48, - '1': 49, - '2': 50, - '3': 51, - '4': 52, - '5': 53, - '6': 54, - '7': 55, - '8': 56, - '9': 57, - 'a': 65, - 'b': 66, - 'c': 67, - 'd': 68, - 'e': 69, - 'f': 70, - 'g': 71, - 'h': 72, - 'i': 73, - 'j': 74, - 'k': 75, - 'l': 76, - 'm': 77, - 'n': 78, - 'o': 79, - 'p': 80, - 'q': 81, - 'r': 82, - 's': 83, - 't': 84, - 'u': 85, - 'v': 86, - 'w': 87, - 'x': 88, - 'y': 89, - 'z': 90, - //normal-characters, numpad - 'num0': 96, - 'num1': 97, - 'num2': 98, - 'num3': 99, - 'num4': 100, - 'num5': 101, - 'num6': 102, - 'num7': 103, - 'num8': 104, - 'num9': 105, - '*': 106, - '+': 107, - 'subtract': 109, - 'decimal': 110, - //normal-characters, others - 'divide': 111, - ';': 186, - '=': 187, - ',': 188, - 'dash': 189, - '-': 189, - 'period': 190, - '.': 190, - 'forward-slash': 191, - '/': 191, - '`': 192, - '[': 219, - '\\': 220, - ']': 221, - "'": 222, - - //ignore these, you shouldn't use them - 'left window key': 91, - 'right window key': 92, - 'select key': 93, - - 'f1': 112, - 'f2': 113, - 'f3': 114, - 'f4': 115, - 'f5': 116, - 'f6': 117, - 'f7': 118, - 'f8': 119, - 'f9': 120, - 'f10': 121, - 'f11': 122, - 'f12': 123 - }, - - // selects text on an element - selectText: function (el, start, end) { - if (el.setSelectionRange) { - if (!end) { - Syn.__tryFocus(el); - el.setSelectionRange(start, start); - } else { - el.selectionStart = start; - el.selectionEnd = end; - } - } else if (el.createTextRange) { - //Syn.__tryFocus(el); - var r = el.createTextRange(); - r.moveStart('character', start); - end = end || start; - r.moveEnd('character', end - el.value.length); - - r.select(); - } - }, - getText: function (el) { - //first check if the el has anything selected .. - if (Syn.typeable.test(el)) { - var sel = getSelection(el); - return el.value.substring(sel.start, sel.end); - } - //otherwise get from page - var win = Syn.helpers.getWindow(el); - if (win.getSelection) { - return win.getSelection() - .toString(); - } else if (win.document.getSelection) { - return win.document.getSelection() - .toString(); - } else { - return win.document.selection.createRange() - .text; - } - }, - getSelection: getSelection - }); - - h.extend(Syn.key, { - // retrieves a description of what events for this character should look like - data: function (key) { - //check if it is described directly - if (Syn.key.browser[key]) { - return Syn.key.browser[key]; - } - for (var kind in Syn.key.kinds) { - if (h.inArray(key, Syn.key.kinds[kind]) > -1) { - return Syn.key.browser[kind]; - } - } - return Syn.key.browser.character; - }, - - //returns the special key if special - isSpecial: function (keyCode) { - var specials = Syn.key.kinds.special; - for (var i = 0; i < specials.length; i++) { - if (Syn.keycodes[specials[i]] === keyCode) { - return specials[i]; - } - } - }, - /** - * @hide - * gets the options for a key and event type ... - * @param {Object} key - * @param {Object} event - */ - options: function (key, event) { - var keyData = Syn.key.data(key); - - if (!keyData[event]) { - //we shouldn't be creating this event - return null; - } - - var charCode = keyData[event][0], - keyCode = keyData[event][1], - result = {}; - - if (keyCode === 'key') { - result.keyCode = Syn.keycodes[key]; - } else if (keyCode === 'char') { - result.keyCode = key.charCodeAt(0); - } else { - result.keyCode = keyCode; - } - - if (charCode === 'char') { - result.charCode = key.charCodeAt(0); - } else if (charCode !== null) { - result.charCode = charCode; - } - - // all current browsers have which property to normalize keyCode/charCode - if (result.keyCode) { - result.which = result.keyCode; - } else { - result.which = result.charCode; - } - - return result; - }, - //types of event keys - kinds: { - special: ["shift", 'ctrl', 'alt', 'caps'], - specialChars: ["\b"], - navigation: ["page-up", 'page-down', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'], - 'function': ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'] - }, - //returns the default function - // some keys have default functions - // some 'kinds' of keys have default functions - getDefault: function (key) { - //check if it is described directly - if (Syn.key.defaults[key]) { - return Syn.key.defaults[key]; - } - for (var kind in Syn.key.kinds) { - if (h.inArray(key, Syn.key.kinds[kind]) > -1 && Syn.key.defaults[kind]) { - return Syn.key.defaults[kind]; - } - } - return Syn.key.defaults.character; - }, - // default behavior when typing - defaults: { - 'character': function (options, scope, key, force, sel) { - if (/num\d+/.test(key)) { - key = key.match(/\d+/)[0]; - } - - if (force || (!Syn.support.keyCharacters && Syn.typeable.test(this))) { - var current = getText(this), - before = current.substr(0, sel.start), - after = current.substr(sel.end), - character = key; - - setText(this, before + character + after); - //handle IE inserting \r\n - var charLength = character === "\n" && Syn.support.textareaCarriage ? 2 : character.length; - Syn.selectText(this, before.length + charLength); - } - }, - 'c': function (options, scope, key, force, sel) { - if (Syn.key.ctrlKey) { - Syn.key.clipboard = Syn.getText(this); - } else { - Syn.key.defaults.character.apply(this, arguments); - } - }, - 'v': function (options, scope, key, force, sel) { - if (Syn.key.ctrlKey) { - Syn.key.defaults.character.call(this, options, scope, Syn.key.clipboard, true, sel); - } else { - Syn.key.defaults.character.apply(this, arguments); - } - }, - 'a': function (options, scope, key, force, sel) { - if (Syn.key.ctrlKey) { - Syn.selectText(this, 0, getText(this) - .length); - } else { - Syn.key.defaults.character.apply(this, arguments); - } - }, - 'home': function () { - Syn.onParents(this, function (el) { - if (el.scrollHeight !== el.clientHeight) { - el.scrollTop = 0; - return false; - } - }); - }, - 'end': function () { - Syn.onParents(this, function (el) { - if (el.scrollHeight !== el.clientHeight) { - el.scrollTop = el.scrollHeight; - return false; - } - }); - }, - 'page-down': function () { - //find the first parent we can scroll - Syn.onParents(this, function (el) { - if (el.scrollHeight !== el.clientHeight) { - var ch = el.clientHeight; - el.scrollTop += ch; - return false; - } - }); - }, - 'page-up': function () { - Syn.onParents(this, function (el) { - if (el.scrollHeight !== el.clientHeight) { - var ch = el.clientHeight; - el.scrollTop -= ch; - return false; - } - }); - }, - '\b': function (options, scope, key, force, sel) { - //this assumes we are deleting from the end - if (!Syn.support.backspaceWorks && Syn.typeable.test(this)) { - var current = getText(this), - before = current.substr(0, sel.start), - after = current.substr(sel.end); - - if (sel.start === sel.end && sel.start > 0) { - //remove a character - setText(this, before.substring(0, before.length - 1) + after); - Syn.selectText(this, sel.start - 1); - } else { - setText(this, before + after); - Syn.selectText(this, sel.start); - } - - //set back the selection - } - }, - 'delete': function (options, scope, key, force, sel) { - if (!Syn.support.backspaceWorks && Syn.typeable.test(this)) { - var current = getText(this), - before = current.substr(0, sel.start), - after = current.substr(sel.end); - if (sel.start === sel.end && sel.start <= getText(this) - .length - 1) { - setText(this, before + after.substring(1)); - } else { - setText(this, before + after); - } - Syn.selectText(this, sel.start); - } - }, - '\r': function (options, scope, key, force, sel) { - - var nodeName = this.nodeName.toLowerCase(); - // submit a form - if (nodeName === 'input') { - Syn.trigger("change", {}, this); - } - - if (!Syn.support.keypressSubmits && nodeName === 'input') { - var form = Syn.closest(this, "form"); - if (form) { - Syn.trigger("submit", {}, form); - } - - } - //newline in textarea - if (!Syn.support.keyCharacters && nodeName === 'textarea') { - Syn.key.defaults.character.call(this, options, scope, "\n", - undefined, sel); - } - // 'click' hyperlinks - if (!Syn.support.keypressOnAnchorClicks && nodeName === 'a') { - Syn.trigger("click", {}, this); - } - }, - // - // Gets all focusable elements. If the element (this) - // doesn't have a tabindex, finds the next element after. - // If the element (this) has a tabindex finds the element - // with the next higher tabindex OR the element with the same - // tabindex after it in the document. - // @return the next element - // - '\t': function (options, scope) { - // focusable elements - var focusEls = getFocusable(this), - // will be set to our guess for the next element - current = null, - i = 0, - el, - //the tabindex of the tabable element we are looking at - firstNotIndexed, - orders = []; - for (; i < focusEls.length; i++) { - orders.push([focusEls[i], i]); - } - var sort = function (order1, order2) { - var el1 = order1[0], - el2 = order2[0], - tab1 = Syn.tabIndex(el1) || 0, - tab2 = Syn.tabIndex(el2) || 0; - if (tab1 === tab2) { - return order1[1] - order2[1]; - } else { - if (tab1 === 0) { - return 1; - } else if (tab2 === 0) { - return -1; - } else { - return tab1 - tab2; - } - } - }; - orders.sort(sort); - //now find current - for (i = 0; i < orders.length; i++) { - el = orders[i][0]; - if (this === el) { - if (!Syn.key.shiftKey) { - current = orders[i + 1][0]; - if (!current) { - current = orders[0][0]; - } - } else { - current = orders[i - 1][0]; - if (!current) { - current = orders[focusEls.length - 1][0]; - } - } - } - } - - //restart if we didn't find anything - if (!current) { - current = firstNotIndexed; - } else { - Syn.__tryFocus(current); - } - return current; - }, - 'left': function (options, scope, key, force, sel) { - if (Syn.typeable.test(this)) { - if (Syn.key.shiftKey) { - Syn.selectText(this, sel.start === 0 ? 0 : sel.start - 1, sel.end); - } else { - Syn.selectText(this, sel.start === 0 ? 0 : sel.start - 1); - } - } - }, - 'right': function (options, scope, key, force, sel) { - if (Syn.typeable.test(this)) { - if (Syn.key.shiftKey) { - Syn.selectText(this, sel.start, sel.end + 1 > getText(this) - .length ? getText(this) - .length : sel.end + 1); - } else { - Syn.selectText(this, sel.end + 1 > getText(this) - .length ? getText(this) - .length : sel.end + 1); - } - } - }, - 'up': function () { - if (/select/i.test(this.nodeName)) { - - this.selectedIndex = this.selectedIndex ? this.selectedIndex - 1 : 0; - //set this to change on blur? - } - }, - 'down': function () { - if (/select/i.test(this.nodeName)) { - Syn.changeOnBlur(this, "selectedIndex", this.selectedIndex); - this.selectedIndex = this.selectedIndex + 1; - //set this to change on blur? - } - }, - 'shift': function () { - return null; - }, - 'ctrl': function () { - return null; - } - } - }); - - h.extend(Syn.create, { - keydown: { - setup: function (type, options, element) { - if (h.inArray(options, Syn.key.kinds.special) !== -1) { - Syn.key[options + "Key"] = element; - } - } - }, - keypress: { - setup: function (type, options, element) { - // if this browsers supports writing keys on events - // but doesn't write them if the element isn't focused - // focus on the element (ignored if already focused) - if (Syn.support.keyCharacters && !Syn.support.keysOnNotFocused) { - Syn.__tryFocus(element); - } - } - }, - keyup: { - setup: function (type, options, element) { - if (h.inArray(options, Syn.key.kinds.special) !== -1) { - Syn.key[options + "Key"] = null; - } - } - }, - key: { - // return the options for a key event - options: function (type, options, element) { - //check if options is character or has character - options = typeof options !== "object" ? { - character: options - } : options; - - //don't change the orignial - options = h.extend({}, options); - if (options.character) { - h.extend(options, Syn.key.options(options.character, type)); - delete options.character; - } - - options = h.extend({ - ctrlKey: !! Syn.key.ctrlKey, - altKey: !! Syn.key.altKey, - shiftKey: !! Syn.key.shiftKey, - metaKey: !! Syn.key.metaKey - }, options); - - return options; - }, - // creates a key event - event: function (type, options, element) { //Everyone Else - var doc = h.getWindow(element) - .document || document, - event; - if (doc.createEvent) { - try { - event = doc.createEvent("KeyEvents"); - event.initKeyEvent(type, true, true, window, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode); - } catch (e) { - event = h.createBasicStandardEvent(type, options, doc); - } - event.synthetic = true; - return event; - } else { - try { - event = h.createEventObject.apply(this, arguments); - h.extend(event, options); - } catch (e) {} - - return event; - } - } - } - }); - - var convert = { - "enter": "\r", - "backspace": "\b", - "tab": "\t", - "space": " " - }; - - /** - * - */ - h.extend(Syn.init.prototype, { - /** - * @function Syn.key key() - * @parent keys - * @signature `Syn.key(options, element, callback)` - * Types a single key. The key should be - * a string that matches a - * [Syn.static.keycodes]. - * - * The following sends a carridge return - * to the 'name' element. - * @codestart - * Syn.key('\r','name') - * @codeend - * For each character, a keydown, keypress, and keyup is triggered if - * appropriate. - * @param {String|Number} options - * @param {HTMLElement} [element] - * @param {Function} [callback] - * @return {HTMLElement} the element currently focused. - */ - _key: function (options, element, callback) { - //first check if it is a special up - if (/-up$/.test(options) && h.inArray(options.replace("-up", ""), - Syn.key.kinds.special) !== -1) { - Syn.trigger('keyup', options.replace("-up", ""), element); - return callback(true, element); - } - - // keep reference to current activeElement - var activeElement = h.getWindow(element) - .document.activeElement, - caret = Syn.typeable.test(element) && getSelection(element), - key = convert[options] || options, - // should we run default events - runDefaults = Syn.trigger('keydown', key, element), - - // a function that gets the default behavior for a key - getDefault = Syn.key.getDefault, - - // how this browser handles preventing default events - prevent = Syn.key.browser.prevent, - - // the result of the default event - defaultResult, - - keypressOptions = Syn.key.options(key, 'keypress'); - - if (runDefaults) { - //if the browser doesn't create keypresses for this key, run default - if (!keypressOptions) { - defaultResult = getDefault(key) - .call(element, keypressOptions, h.getWindow(element), - key, undefined, caret); - } else { - //do keypress - // check if activeElement changed b/c someone called focus in keydown - if (activeElement !== h.getWindow(element) - .document.activeElement) { - element = h.getWindow(element) - .document.activeElement; - } - - runDefaults = Syn.trigger('keypress', keypressOptions, element); - if (runDefaults) { - defaultResult = getDefault(key) - .call(element, keypressOptions, h.getWindow(element), - key, undefined, caret); - } - } - } else { - //canceled ... possibly don't run keypress - if (keypressOptions && h.inArray('keypress', prevent.keydown) === -1) { - // check if activeElement changed b/c someone called focus in keydown - if (activeElement !== h.getWindow(element) - .document.activeElement) { - element = h.getWindow(element) - .document.activeElement; - } - - Syn.trigger('keypress', keypressOptions, element); - } - } - if (defaultResult && defaultResult.nodeName) { - element = defaultResult; - } - - if (defaultResult !== null) { - Syn.schedule(function () { - if (Syn.support.oninput) { - Syn.trigger('input', Syn.key.options(key, 'input'), element); - } - Syn.trigger('keyup', Syn.key.options(key, 'keyup'), element); - callback(runDefaults, element); - }, 1); - } else { - callback(runDefaults, element); - } - - //do mouseup - return element; - // is there a keypress? .. if not , run default - // yes -> did we prevent it?, if not run ... - }, - /** - * @function Syn.type type() - * @parent keys - * @signature `Syn.type(options, element, callback)` - * Types sequence of [Syn.key key actions]. Each - * character is typed, one at a type. - * Multi-character keys like 'left' should be - * enclosed in square brackents. - * - * The following types 'JavaScript MVC' then deletes the space. - * @codestart - * Syn.type('JavaScript MVC[left][left][left]\b','name') - * @codeend - * - * Type is able to handle (and move with) tabs (\t). - * The following simulates tabing and entering values in a form and - * eventually submitting the form. - * @codestart - * Syn.type("Justin\tMeyer\t27\tjustinbmeyer@gmail.com\r") - * @codeend - * @param {String} options the text to type - * @param {HTMLElement} [element] an element or an id of an element - * @param {Function} [callback] a function to callback - */ - _type: function (options, element, callback) { - //break it up into parts ... - //go through each type and run - var parts = (options + "") - .match(/(\[[^\]]+\])|([^\[])/g), - self = this, - runNextPart = function (runDefaults, el) { - var part = parts.shift(); - if (!part) { - callback(runDefaults, el); - return; - } - el = el || element; - if (part.length > 1) { - part = part.substr(1, part.length - 2); - } - self._key(part, el, runNextPart); - }; - - runNextPart(); - - } - }); - - return Syn; -})(__m2, __m6, __m4); - -// ## src/drag/drag.js -var __m7 = (function (Syn) { - - // check if elementFromPageExists - (function dragSupport() { - - // document body has to exists for this test - if (!document.body) { - Syn.schedule(dragSupport, 1); - return; - } - var div = document.createElement('div'); - document.body.appendChild(div); - Syn.helpers.extend(div.style, { - width: "100px", - height: "10000px", - backgroundColor: "blue", - position: "absolute", - top: "10px", - left: "0px", - zIndex: 19999 - }); - document.body.scrollTop = 11; - if (!document.elementFromPoint) { - return; - } - var el = document.elementFromPoint(3, 1); - if (el === div) { - Syn.support.elementFromClient = true; - } else { - Syn.support.elementFromPage = true; - } - document.body.removeChild(div); - document.body.scrollTop = 0; - })(); - - //gets an element from a point - var elementFromPoint = function (point, element) { - var clientX = point.clientX, - clientY = point.clientY, - win = Syn.helpers.getWindow(element), - el; - - if (Syn.support.elementFromPage) { - var off = Syn.helpers.scrollOffset(win); - clientX = clientX + off.left; //convert to pageX - clientY = clientY + off.top; //convert to pageY - } - el = win.document.elementFromPoint ? win.document.elementFromPoint(clientX, clientY) : element; - if (el === win.document.documentElement && (point.clientY < 0 || point.clientX < 0)) { - return element; - } else { - return el; - } - }, - //creates an event at a certain point - createEventAtPoint = function (event, point, element) { - var el = elementFromPoint(point, element); - Syn.trigger(event, point, el || element); - return el; - }, - // creates a mousemove event, but first triggering mouseout / mouseover if appropriate - mouseMove = function (point, element, last) { - var el = elementFromPoint(point, element); - if (last !== el && el && last) { - var options = Syn.helpers.extend({}, point); - options.relatedTarget = el; - Syn.trigger("mouseout", options, last); - options.relatedTarget = last; - Syn.trigger("mouseover", options, el); - } - - Syn.trigger("mousemove", point, el || element); - return el; - }, - // start and end are in clientX, clientY - startMove = function (start, end, duration, element, callback) { - var startTime = new Date(), - distX = end.clientX - start.clientX, - distY = end.clientY - start.clientY, - win = Syn.helpers.getWindow(element), - current = elementFromPoint(start, element), - cursor = win.document.createElement('div'), - calls = 0, - move; - move = function onmove() { - //get what fraction we are at - var now = new Date(), - scrollOffset = Syn.helpers.scrollOffset(win), - fraction = (calls === 0 ? 0 : now - startTime) / duration, - options = { - clientX: distX * fraction + start.clientX, - clientY: distY * fraction + start.clientY - }; - calls++; - if (fraction < 1) { - Syn.helpers.extend(cursor.style, { - left: (options.clientX + scrollOffset.left + 2) + "px", - top: (options.clientY + scrollOffset.top + 2) + "px" - }); - current = mouseMove(options, element, current); - Syn.schedule(onmove, 15); - } else { - current = mouseMove(end, element, current); - win.document.body.removeChild(cursor); - callback(); - } - }; - Syn.helpers.extend(cursor.style, { - height: "5px", - width: "5px", - backgroundColor: "red", - position: "absolute", - zIndex: 19999, - fontSize: "1px" - }); - win.document.body.appendChild(cursor); - move(); - }, - startDrag = function (start, end, duration, element, callback) { - createEventAtPoint("mousedown", start, element); - startMove(start, end, duration, element, function () { - createEventAtPoint("mouseup", end, element); - callback(); - }); - }, - center = function (el) { - var j = Syn.jquery()(el), - o = j.offset(); - return { - pageX: o.left + (j.outerWidth() / 2), - pageY: o.top + (j.outerHeight() / 2) - }; - }, - convertOption = function (option, win, from) { - var page = /(\d+)[x ](\d+)/, - client = /(\d+)X(\d+)/, - relative = /([+-]\d+)[xX ]([+-]\d+)/, - parts; - //check relative "+22x-44" - if (typeof option === 'string' && relative.test(option) && from) { - var cent = center(from); - parts = option.match(relative); - option = { - pageX: cent.pageX + parseInt(parts[1]), - pageY: cent.pageY + parseInt(parts[2]) - }; - } - if (typeof option === "string" && page.test(option)) { - parts = option.match(page); - option = { - pageX: parseInt(parts[1]), - pageY: parseInt(parts[2]) - }; - } - if (typeof option === 'string' && client.test(option)) { - parts = option.match(client); - option = { - clientX: parseInt(parts[1]), - clientY: parseInt(parts[2]) - }; - } - if (typeof option === 'string') { - option = Syn.jquery()(option, win.document)[0]; - } - if (option.nodeName) { - option = center(option); - } - if (option.pageX) { - var off = Syn.helpers.scrollOffset(win); - option = { - clientX: option.pageX - off.left, - clientY: option.pageY - off.top - }; - } - return option; - }, - // if the client chords are not going to be visible ... scroll the page so they will be ... - adjust = function (from, to, win) { - if (from.clientY < 0) { - var off = Syn.helpers.scrollOffset(win); - var top = off.top + (from.clientY) - 100, - diff = top - off.top; - - // first, lets see if we can scroll 100 px - if (top > 0) { - - } else { - top = 0; - diff = -off.top; - } - from.clientY = from.clientY - diff; - to.clientY = to.clientY - diff; - Syn.helpers.scrollOffset(win, { - top: top, - left: off.left - }); - } - }; - /** - * @add Syn prototype - */ - Syn.helpers.extend(Syn.init.prototype, { - /** - * @function Syn.move move() - * @parent mouse - * @signature `Syn.move(options, from, callback)` - * Moves the cursor from one point to another. - * - * ### Quick Example - * - * The following moves the cursor from (0,0) in - * the window to (100,100) in 1 second. - * - * Syn.move( - * { - * from: {clientX: 0, clientY: 0}, - * to: {clientX: 100, clientY: 100}, - * duration: 1000 - * }, - * document.document) - * - * ## Options - * - * There are many ways to configure the endpoints of the move. - * - * ### PageX and PageY - * - * If you pass pageX or pageY, these will get converted - * to client coordinates. - * - * Syn.move( - * { - * from: {pageX: 0, pageY: 0}, - * to: {pageX: 100, pageY: 100} - * }, - * document.document) - * - * ### String Coordinates - * - * You can set the pageX and pageY as strings like: - * - * Syn.move( - * { - * from: "0x0", - * to: "100x100" - * }, - * document.document) - * - * ### Element Coordinates - * - * If jQuery is present, you can pass an element as the from or to option - * and the coordinate will be set as the center of the element. - - * Syn.move( - * { - * from: $(".recipe")[0], - * to: $("#trash")[0] - * }, - * document.document) - * - * ### Query Strings - * - * If jQuery is present, you can pass a query string as the from or to option. - * - * Syn.move( - * { - * from: ".recipe", - * to: "#trash" - * }, - * document.document) - * - * ### No From - * - * If you don't provide a from, the element argument passed to Syn is used. - * - * Syn.move( - * { to: "#trash" }, - * 'myrecipe') - * - * ### Relative - * - * You can move the drag relative to the center of the from element. - * - * Syn.move("+20 +30", "myrecipe"); - * - * @param {Object} options options to configure the drag - * @param {HTMLElement} from the element to move - * @param {Function} callback a callback that happens after the drag motion has completed - */ - _move: function (options, from, callback) { - //need to convert if elements - var win = Syn.helpers.getWindow(from), - fro = convertOption(options.from || from, win, from), - to = convertOption(options.to || options, win, from); - - if (options.adjust !== false) { - adjust(fro, to, win); - } - startMove(fro, to, options.duration || 500, from, callback); - - }, - /** - * @function Syn.drag drag() - * @parent mouse - * @signature `Syn.drag(options, from, callback)` - * Creates a mousedown and drags from one point to another. - * Check out [Syn.prototype.move move] for API details. - * - * @param {Object} options - * @param {Object} from - * @param {Object} callback - */ - _drag: function (options, from, callback) { - //need to convert if elements - var win = Syn.helpers.getWindow(from), - fro = convertOption(options.from || from, win, from), - to = convertOption(options.to || options, win, from); - - if (options.adjust !== false) { - adjust(fro, to, win); - } - startDrag(fro, to, options.duration || 500, from, callback); - - } - }); - return Syn; -})(__m2); - -// ## src/syn.js -var __m1 = (function (Syn) { - window.Syn = Syn; - - return Syn; - })(__m2, __m3, __m4, __m5, __m7); - -}(window); \ No newline at end of file diff --git a/tests/e2e/test.html b/tests/e2e/test.html deleted file mode 100644 index 3c869eb8..00000000 --- a/tests/e2e/test.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Basic Test Suite - - - - - - - - -
    -
    - - - diff --git a/tests/e2e/tests.js b/tests/e2e/tests.js deleted file mode 100644 index 078ba122..00000000 --- a/tests/e2e/tests.js +++ /dev/null @@ -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, '
    a&b
    ', '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, '
    a&b&c
    ', '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, '
    0
    1
    2
    ', 'view1 rendered correctly') - - m.render(dummyEl, view2) - equal(dummyEl.innerHTML, '
    0
    ', '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, '
    0
    1
    2
    ', 'view1 rendered correctly') - - m.render(dummyEl, view2) - equal(dummyEl.innerHTML, '
    0
    ', '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("

    1

    2

    "), m("i", "foo")]) - equal(dummyEl.childNodes[2].nodeName, "I") -}) -test("mixing trusted content w/ text nodes", function() { - m.render(dummyEl, [m.trust("

    1

    123

    2

    "), m("i", "foo")]) - equal(dummyEl.childNodes[3].nodeName, "I") -}) -test("mixing trusted content w/ td", function() { - m.render(dummyEl, [m.trust("12"), 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 ') -}) diff --git a/tests/index.html b/tests/index.html deleted file mode 100644 index f1e4baee..00000000 --- a/tests/index.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - -

    Open the console to see the test report

    \ No newline at end of file diff --git a/tests/input-cursor.html b/tests/input-cursor.html deleted file mode 100644 index 8611e06e..00000000 --- a/tests/input-cursor.html +++ /dev/null @@ -1,48 +0,0 @@ -

    Typing in the fields below should not move the cursor to the end of the input. Especially in Chrome

    -

    All inputs should update with the same value

    -

    Typing in an input should not prevent it from being updated by other inputs

    -
    - - diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js deleted file mode 100644 index f9881430..00000000 --- a/tests/mithril-tests.js +++ /dev/null @@ -1,4425 +0,0 @@ -function testMithril(mock) { - m.deps(mock) - - //m - test(function() {return m.version().constructor === String}) - test(function() {return m("div").tag === "div"}) - test(function() {return m(".foo").tag === "div"}) - test(function() {return m(".foo").attrs.className === "foo"}) - test(function() {return m("[title=bar]").tag === "div"}) - test(function() {return m("[title=bar]").attrs.title === "bar"}) - test(function() {return m("[title=\'bar\']").attrs.title === "bar"}) - test(function() {return m("[title=\"bar\"]").attrs.title === "bar"}) - test(function() {return m("div", "test").children[0] === "test"}) - test(function() {return m("div", "test", "test2").children[1] === "test2"}) - test(function() {return m("div", ["test"]).children[0] === "test"}) - test(function() {return m("div", {title: "bar"}, "test").attrs.title === "bar"}) - test(function() {return m("div", {title: "bar"}, "test").children[0] === "test"}) - test(function() {return m("div", {title: "bar"}, ["test"]).children[0] === "test"}) - test(function() {return m("div", {title: "bar"}, m("div")).children[0].tag === "div"}) - test(function() {return m("div", {title: "bar"}, [m("div")]).children[0].tag === "div"}) - test(function() {return m("div", {title: "bar"}, "test0", "test1", "test2", "test3").children[3] === "test3"}) // splat - test(function() {return m("div", {title: "bar"}, m("div"), m("i"), m("span")).children[2].tag === "span"}) - test(function() {return m("div", ["a", "b"]).children.length === 2}) - test(function() {return m("div", [m("div")]).children[0].tag === "div"}) - test(function() {return m("div", m("div")).children[0].tag === "div"}) //yes, this is expected behavior: see method signature - test(function() {return m("div", [undefined]).tag === "div"}) - test(function() {return m("div", [{foo: "bar"}])}) //as long as it doesn't throw errors, it's fine - test(function() {return m("svg", [m("g")])}) - test(function() {return m("svg", [m("a[href='http://google.com']")])}) - test(function() {return m(".foo", {"class": "bar"}).attrs["class"] === "foo bar"}) - test(function() {return m(".foo", {className: "bar"}).attrs.className === "foo bar"}) - test(function() {return m(".foo", {className: ""}).attrs.className === "foo"}) - test(function() {return m("div", {className: ""}).attrs.className === ""}) //https://github.com/lhorie/mithril.js/issues/382 and 512 - test(function() {return m("div", {"class": ""}).attrs.className === undefined}) - test(function() {return m("div", {className: ""}).attrs["class"] === undefined}) - test(function() {return m("div", {"class": ""}).attrs["class"] === ""}) - test(function() {return m("div", [1, 2, 3], 4).children.length === 2}) - test(function() {return m("div", [1, 2, 3], 4).children[0].length === 3}) - test(function() {return m("div", [1, 2, 3], 4).children[1] === 4}) - test(function() {return m("div", [1, 2, 3]).children.length === 3}) - test(function() {return m("div", [1, 2, 3], [4, 5, 6, 7]).children.length === 2}) - test(function() {return m("div", [1, 2, 3], [4, 5, 6, 7]).children[0].length === 3}) - test(function() {return m("div", [1, 2, 3], [4, 5, 6, 7]).children[1].length === 4}) - test(function() {return m("div", [1], [2], [3]).children.length === 3}) - test(function() { - //class changes shouldn't trigger dom recreation - var v1 = m(".foo", {"class": "", onclick: function() {}}) - var v2 = m(".foo", {"class": "bar", onclick: function() {}}) - return Object.keys(v1.attrs).join() === Object.keys(v2.attrs).join() - }) - test(function() { - //m should proxy object first arg to m.component - var component = { - controller: function(args) { - this.args = args - }, - view: function () { - return m("div", "testing") - } - } - var args = {age: 12} - var c1 = m(component, args).controller() - var c2 = m.component(component, args).controller() - - return c1.args === args && c1.args === c2.args - }) - - //m.mount - test(function() { - var root = mock.document.createElement("div") - var whatever = 1 - var app = { - view: function() { - return [ - whatever % 2 ? m('span', '% 2') : undefined, - m('div', 'bugs'), - m('a') - ] - } - } - m.mount(root, app) - mock.requestAnimationFrame.$resolve() - - whatever++ - m.redraw() - mock.requestAnimationFrame.$resolve() - - whatever++ - m.redraw() - mock.requestAnimationFrame.$resolve() - - return root.childNodes.length - }) - test(function() { - mock.requestAnimationFrame.$resolve() - - var root1 = mock.document.createElement("div") - var mod1 = m.mount(root1, { - controller: function() {this.value = "test1"}, - view: function(ctrl) {return ctrl.value} - }) - - var root2 = mock.document.createElement("div") - var mod2 = m.mount(root2, { - controller: function() {this.value = "test2"}, - view: function(ctrl) {return ctrl.value} - }) - - mock.requestAnimationFrame.$resolve() - - return (root1.childNodes[0].nodeValue === "test1" && root2.childNodes[0].nodeValue === "test2") - && (mod1.value && mod1.value === "test1") && (mod2.value && mod2.value === "test2") - }) - test(function() { - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var unloaded = false - m.mount(root, { - controller: function() { - this.value = "test1" - this.onunload = function() { - unloaded = true - } - }, - view: function(ctrl) {return ctrl.value} - }) - - mock.requestAnimationFrame.$resolve() - - m.mount(root, null) - - mock.requestAnimationFrame.$resolve() - - return unloaded - }) - test(function() { - //component should pass args to both controller and view - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var slot1, slot2 - var component = { - controller: function(options) {slot1 = options.a}, - view: function(ctrl, options) {slot2 = options.a} - } - m.mount(root, m.component(component, {a: 1})) - - mock.requestAnimationFrame.$resolve() - - return slot1 === 1 && slot2 === 1 - }) - test(function() { - //component should work without controller - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var slot2 - var component = { - view: function(ctrl, options) {slot2 = options.a} - } - m.mount(root, m.component(component, {a: 1})) - - mock.requestAnimationFrame.$resolve() - - return slot2 === 1 - }) - test(function() { - //component controller should only run once - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var count1 = 0, count2 = 0 - var component = { - view: function() { - return sub - } - } - var sub = { - controller: function() { - count1++ - }, - view: function() { - count2++ - return m("div", "test") - } - } - m.mount(root, component) - - mock.requestAnimationFrame.$resolve() - - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - return count1 === 1 && count2 === 2 - }) - test(function() { - //sub component controller should only run once - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var count1 = 0, count2 = 0, count3 = 0, count4 = 0 - var component = { - view: function() { - return sub - } - } - var sub = { - controller: function() { - count1++ - }, - view: function() { - count2++ - return subsub - } - } - var subsub = { - controller: function() { - count3++ - }, - view: function() { - count4++ - return m("div", "test") - } - } - m.mount(root, component) - - mock.requestAnimationFrame.$resolve() - - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - return count1 === 1 && count2 === 2 && count3 === 1 && count4 === 2 - }) - test(function() { - //keys in components should work - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var list = [1, 2, 3] - var component = { - controller: function() {}, - view: function() { - return list.map(function(i) { - return m.component(sub, {key: i}) - }) - } - } - var sub = { - controller: function() {}, - view: function() { - return m("div") - } - } - m.mount(root, component) - - var firstBefore = root.childNodes[0] - - mock.requestAnimationFrame.$resolve() - - list.reverse() - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - var firstAfter = root.childNodes[2] - - return firstBefore === firstAfter - }) - test(function() { - //keys in subcomponents should work - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var list = [1, 2, 3] - var component = { - controller: function() {}, - view: function() { - return list.map(function(i) { - return m.component(sub, {key: i}) - }) - } - } - var sub = { - view: function() { - return subsub - } - } - var subsub = { - controller: function() {}, - view: function() { - return m("div") - } - } - m.mount(root, component) - - var firstBefore = root.childNodes[0] - - mock.requestAnimationFrame.$resolve() - - list.reverse() - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - var firstAfter = root.childNodes[2] - - return firstBefore === firstAfter - }) - test(function() { - //keys in components should work even if component internally messes them up - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var list = [1, 2, 3] - var component = { - controller: function() {}, - view: function() { - return list.map(function(i) { - return m.component(sub, {key: i}) - }) - } - } - var sub = { - controller: function() {}, - view: function() { - return m("div", {key: 1}) - } - } - m.mount(root, component) - - var firstBefore = root.childNodes[0] - - mock.requestAnimationFrame.$resolve() - - list.reverse() - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - var firstAfter = root.childNodes[2] - - return firstBefore === firstAfter - }) - test(function() { - //keys in subcomponents should work even if component internally messes them up - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var list = [1, 2, 3] - var component = { - controller: function() {}, - view: function() { - return list.map(function(i) { - return m.component(sub, {key: i}) - }) - } - } - var sub = { - controller: function() {}, - view: function() { - return subsub - } - } - var subsub = { - controller: function() {}, - view: function() { - return m("div", {key: 1}) - } - } - m.mount(root, component) - - var firstBefore = root.childNodes[0] - - mock.requestAnimationFrame.$resolve() - - list.reverse() - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - var firstAfter = root.childNodes[2] - - return firstBefore === firstAfter - }) - test(function() { - //component identity should stay intact if components are descendants of keyed elements - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var list = [1, 2, 3] - var component = { - controller: function() {}, - view: function() { - return list.map(function(i) { - return m("div", {key: i}, sub) - }) - } - } - var sub = { - controller: function() {}, - view: function() { - return m("div") - } - } - m.mount(root, component) - - var firstBefore = root.childNodes[0].childNodes[0] - - mock.requestAnimationFrame.$resolve() - - list.reverse() - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - var firstAfter = root.childNodes[2].childNodes[0] - - return firstBefore === firstAfter - }) - test(function() { - //subcomponent identity should stay intact if components are descendants of keyed elements - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var list = [1, 2, 3] - var component = { - controller: function() {}, - view: function() { - return list.map(function(i) { - return m("div", {key: i}, sub) - }) - } - } - var sub = { - controller: function() {}, - view: function() { - return subsub - } - } - var subsub = { - controller: function() {}, - view: function() { - return m("div") - } - } - m.mount(root, component) - - var firstBefore = root.childNodes[0].childNodes[0] - - mock.requestAnimationFrame.$resolve() - - list.reverse() - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - var firstAfter = root.childNodes[2].childNodes[0] - - return firstBefore === firstAfter - }) - test(function() { - //component should call onunload when removed from template - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var list = [1, 2, 3] - var unloaded - var component = { - controller: function() {}, - view: function() { - return list.map(function(i) { - return m.component(sub, {key: i}) - }) - } - } - var sub = { - controller: function(opts) { - this.onunload = function() { - unloaded = opts.key - } - }, - view: function() { - return m("div") - } - } - m.mount(root, component) - - mock.requestAnimationFrame.$resolve() - - list.pop() - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - return unloaded === 3 - }) - test(function() { - //subcomponent should call onunload when removed from template - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var list = [1, 2, 3] - var unloaded1, unloaded2 - var component = { - controller: function() {}, - view: function() { - return list.map(function(i) { - return m.component(sub, {key: i}) - }) - } - } - var sub = { - controller: function(opts) { - this.onunload = function() { - unloaded1 = opts.key - } - }, - view: function(ctrl, opts) { - return m.component(subsub, {key: opts.key}) - } - } - var subsub = { - controller: function(opts) { - this.onunload = function() { - unloaded2 = opts.key - } - }, - view: function() { - return m("div") - } - } - m.mount(root, component) - - mock.requestAnimationFrame.$resolve() - - list.pop() - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - return unloaded1 === 3 && unloaded2 === 3 - }) - test(function() { - //calling m.redraw synchronously from controller constructor should not trigger extra redraws - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var count = 0 - var component = { - controller: function() {}, - view: function() { - return sub - } - } - var sub = { - controller: function() { - m.redraw() - }, - view: function() { - count++ - return m("div") - } - } - m.mount(root, component) - - mock.requestAnimationFrame.$resolve() - - return count === 1 - }) - test(function() { - //calling m.redraw synchronously from controller constructor should not trigger extra redraws - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var count = 0 - var component = { - controller: function() {}, - view: function() { - return sub - } - } - var sub = { - controller: function() {}, - view: function() { - return subsub - } - } - var subsub = { - controller: function() { - m.redraw() - }, - view: function() { - count++ - return m("div") - } - } - m.mount(root, component) - - mock.requestAnimationFrame.$resolve() - - return count === 1 - }) - test(function() { - //calling preventDefault from component's onunload should prevent route change - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var loaded = false - var testEnabled = true - var component = { - controller: function() {}, - view: function() { - return sub - } - } - var sub = { - controller: function() { - this.onunload = function(e) {if (testEnabled) e.preventDefault()} - }, - view: function() { - return m("div") - } - } - m.route(root, "/a", { - "/a": component, - "/b": {controller: function() {loaded = true}, view: function() {}} - }) - - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - testEnabled = false - - return loaded === false - }) - test(function() { - //calling preventDefault from subcomponent's onunload should prevent route change - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var loaded = false - var testEnabled = true - var component = { - controller: function() {}, - view: function() { - return sub - } - } - var sub = { - controller: function() {}, - view: function() { - return subsub - } - } - var subsub = { - controller: function() { - this.onunload = function(e) {if (testEnabled) e.preventDefault()} - }, - view: function() { - return m("div") - } - } - m.route(root, "/a", { - "/a": component, - "/b": {controller: function() {loaded = true}, view: function() {}} - }) - - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - testEnabled = false - - return loaded === false - }) - test(function() { - //calling preventDefault from non-curried component's onunload should prevent route change - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var loaded = false - var testEnabled = true - var component = { - controller: function() {}, - view: function() { - return sub - } - } - var sub = { - controller: function() { - this.onunload = function(e) {if (testEnabled) e.preventDefault()} - }, - view: function() { - return m("div") - } - } - m.route(root, "/a", { - "/a": component, - "/b": {controller: function() {loaded = true}, view: function() {}} - }) - - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - testEnabled = false - - return loaded === false - }) - test(function() { - //calling preventDefault from non-curried subcomponent's onunload should prevent route change - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var loaded = false - var testEnabled = true - var component = { - controller: function() {}, - view: function() { - return sub - } - } - var sub = { - controller: function() {}, - view: function() { - return subsub - } - } - var subsub = { - controller: function() { - this.onunload = function(e) {if (testEnabled) e.preventDefault()} - }, - view: function() { - return m("div") - } - } - m.route(root, "/a", { - "/a": component, - "/b": {controller: function() {loaded = true}, view: function() {}} - }) - - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - testEnabled = false - - return loaded === false - }) - test(function() { - // nested components under keyed components should render - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var count = 0 - var App = { - controller: function() {}, - view: function() { - return m('.outer', [ - m('.inner', m.component(CommentList, { list: [1, 2, 3] })) - ]) - } - } - var CommentList = { - controller: function() {}, - view: function(ctrl, props) { - return m('.list', props.list.map(function(i) { - return m('.comment', [ - m.component(Reply, {key: i}) - ]) - })) - } - } - var Reply = { - controller: function() {}, - view: function() { - count++ - return m(".reply") - } - } - m.mount(root, App) - - mock.requestAnimationFrame.$resolve() - - return count === 3 - }) - test(function() { - // a route change should initialize a component's controller - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var countA = 0 - var countB = 0 - var subA = { - controller: function(){ countA += 1 }, - view: function() { return m("div") } - } - var subB = { - controller: function() { countB += 1 }, - view: function() { return m("div") } - } - m.route(root, "/a", { - "/a": { - view: function () { - return m('.page-a', [ - m('h1'), m.component(subA, { x: 11 }) - ]) - } - }, - "/b": { - view: function() { - return m('.page-b', [ - m('h2'), m.component(subB, { y: 22 }) - ]) - } - } - }) - - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - m.route("/a") - - mock.requestAnimationFrame.$resolve() - - return countA === 2 && countB === 1 - }) - test(function() { - var root = mock.document.createElement("div") - var component = {}, unloaded = false - component.controller = function() { - this.onunload = function() {unloaded = true} - } - component.view = function() {} - m.mount(root, component) - m.mount(root, {controller: function() {}, view: function() {}}) - - return unloaded === true - }) - test(function() { - mock.requestAnimationFrame.$resolve() - - var root = mock.document.createElement("div") - var initCount = 0 - var component = {} - component.view = function() { - return m("div", {config: function(el, init) { - if (!init) initCount++ - }}) - } - m.mount(root, component) - - mock.requestAnimationFrame.$resolve() - - m.redraw() - - mock.requestAnimationFrame.$resolve() - - return initCount === 1 - }) - test(function() { - var root = mock.document.createElement("div") - - var dom = mock.document.createElement("div") - - var show = true - - var component = { - view: function() { - return [ - m(".foo", {key: 1, config: test, onclick: function() {show = !show}}), - show ? m(".bar", {key: 2}) : null - ] - } - } - - function test(el, init) { - if (!init) { - root.appendChild(dom) - } - } - - m.mount(root, component) - - mock.requestAnimationFrame.$resolve() - - show = false - m.redraw() - - mock.requestAnimationFrame.$resolve() - - show = true - m.redraw() - - mock.requestAnimationFrame.$resolve() - - return root.childNodes.length === 3 - }) - test(function() { - var root = mock.document.createElement("div") - var show = true - var testcomponent = { - controller: function() {}, - view: function() { - return m('div', 'component'); - } - } - - var app = { - view: function() { - return show ? [ - m('h1', '1'), - testcomponent - ] : [ - m('h1', '2') - ]; - } - }; - - m.mount(root, app); - - mock.requestAnimationFrame.$resolve() - - show = false - m.redraw() - - mock.requestAnimationFrame.$resolve() - - show = true - m.redraw() - - mock.requestAnimationFrame.$resolve() - - return root.childNodes.length === 2 - }) - test(function() { - // Components should not require a view - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var Component = { - view: function () { - return m('.comp') - } - } - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/foo", { - "/foo": { - view: function() { - return [ Component ] - } - } - }) - - mock.requestAnimationFrame.$resolve() - - return root.childNodes[0].nodeName === "DIV" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/551 - var root = mock.document.createElement("div") - var a = false, found = false, unloaded = false, redraws = 0 - var Root = { - view: function() { - return Comp - } - } - var Comp = { - view: function() { - redraws++ - return m("div", {config: Comp.config}, [ - m("div", {onclick: function() { - a = !a - m.redraw(true) - found = root.childNodes[0].childNodes[1] - }}, "asd"), - a ? m("#a", "aaa") : null, - "test" - ]) - }, - config: function(el, init, ctx) { - if (!init) ctx.onunload = function() { - unloaded = true - } - } - } - m.mount(root, Root) - - var target = root.childNodes[0].childNodes[0] - target.onclick({currentTarget: target}) - - mock.requestAnimationFrame.$resolve() - - return !unloaded && found.id === "a" && redraws === 3 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/551 - var root = mock.document.createElement("div") - var a = false, found = false, unloaded = false, redraws = 0 - var Root = { - view: function() { - return Comp - } - } - var Comp = { - view: function() { - redraws++ - return m("div", {config: Comp.config}, [ - 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" - ]) - }, - config: function(el, init, ctx) { - if (!init) ctx.onunload = function() { - unloaded = true - } - } - } - m.mount(root, Root) - - var target = root.childNodes[0].childNodes[0] - target.onclick({currentTarget: target}) - - mock.requestAnimationFrame.$resolve() - - return !unloaded && found.id === "a" && redraws === 2 - }) - test(function() { - var root = mock.document.createElement("div") - var redraws = 0 - var Root = { - view: function() { - redraws++ - return m("div", {onclick: function() {m.redraw(true)}}) - } - } - - m.mount(root, Root) - - var target = root.childNodes[0] - target.onclick({currentTarget: target}) - - mock.requestAnimationFrame.$resolve() - - return redraws === 3 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/555 - var root = mock.document.createElement("div") - var MyComponent = { - controller: function(args) { - this.name = args.name; - }, - view: function(ctrl) { - return m('div', ctrl.name); - } - } - var FooPage = { - view: function() { - return m('div', [ - m('a[href=/]', {config: m.route}, 'foo'), - m('a[href=/bar]', {config: m.route}, 'bar'), - m.component(MyComponent, {name: 'Jane'}) - ]); - } - }; - var BarPage = { - view: function() { - return m('div', [ - m('a[href=/]', {config: m.route}, 'foo'), - m('a[href=/bar]', {config: m.route}, 'bar'), - m.component(MyComponent, {name: 'Bob'}) - ]); - } - }; - m.route(root, '/', { - '/': FooPage, - '/bar': BarPage - }) - - mock.requestAnimationFrame.$resolve() - - m.route("/bar") - - mock.requestAnimationFrame.$resolve() - - return root.childNodes[0].childNodes[2].childNodes[0].nodeValue === "Bob" - }) - test(function() { - var root = mock.document.createElement("div") - var redraws = 0, data - var Root = { - view: function() { - return Comp - } - } - - var Comp = { - controller: function() { - this.foo = m.request({method: "GET", url: "/foo"}) - }, - view: function(ctrl) { - redraws++ - data = ctrl.foo() - return m("div") - } - } - - m.mount(root, Root) - - mock.requestAnimationFrame.$resolve() - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - - mock.requestAnimationFrame.$resolve() - m.mount(root, null) - mock.requestAnimationFrame.$resolve() - - return redraws === 1 && data.url === "/foo" - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var redraws1 = 0, redraws2 = 0 - var Root = { - view: function() { - return m("div", [ - Comp1, - Comp2 - ]) - } - } - var Comp1 = { - controller: function() { - this.foo = m.request({method: "GET", url: "/foo"}) - }, - view: function() { - redraws1++ - return m("div") - } - } - var Comp2 = { - controller: function() { - this.bar = m.request({method: "GET", url: "/bar"}) - }, - view: function() { - redraws2++ - return m("div") - } - } - - m.mount(root, Root) - - mock.requestAnimationFrame.$resolve() - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - - mock.requestAnimationFrame.$resolve() - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - - mock.requestAnimationFrame.$resolve() - m.mount(root, null) - mock.requestAnimationFrame.$resolve() - - return redraws1 === 1 && redraws2 === 1 - }) - test(function() { - var root = mock.document.createElement("div") - var redraws1 = 0, redraws2 = 0 - var Root1 = { - view: function() { - return Comp1 - } - } - var Root2 = { - view: function() { - return Comp2 - } - } - var Comp1 = { - controller: function() { - this.foo = m.request({method: "GET", url: "/foo"}) - }, - view: function() { - redraws1++ - return m("div") - } - } - var Comp2 = { - controller: function() { - this.bar = m.request({method: "GET", url: "/bar"}) - }, - view: function() { - redraws2++ - return m("div") - } - } - - - m.route(root, "/", { - "/": Root1, - "/root2": Root2 - }) - - mock.requestAnimationFrame.$resolve() - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - - m.route("/root2") - - - mock.requestAnimationFrame.$resolve() - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - - mock.requestAnimationFrame.$resolve() - m.mount(root, null) - mock.requestAnimationFrame.$resolve() - - return redraws1 === 1 && redraws2 === 1 - }) - test(function() { - var root = mock.document.createElement("div") - - var cond = true - var controller1 = null, controller2 = null - var Root = { - view: function() { - return cond ? Comp1 : Comp2 - } - } - - var Comp1 = { - view: function(ctrl) { - controller1 = ctrl - return m("div") - } - } - var Comp2 = { - view: function(ctrl) { - controller2 = ctrl - return m("div") - } - } - - m.mount(root, Root) - - mock.requestAnimationFrame.$resolve() - - cond = false - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - return controller1 !== controller2 - }) - test(function() { - var root = mock.document.createElement("div") - - var cond = true - var unloaded = false - var Root = { - view: function() { - return cond ? Comp1 : Comp2 - } - } - - var Comp1 = { - view: function() { - return m("div", {config: function(el, init, ctx) { - ctx.onunload = function() {unloaded = true} - }}) - } - } - var Comp2 = { - view: function() { - return m("div") - } - } - - m.mount(root, Root) - - mock.requestAnimationFrame.$resolve() - - cond = false - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - return unloaded - }) - test(function() { - var root = mock.document.createElement("div") - - var cond = true - var initialized = null - var Root = { - view: function() { - return cond ? Comp1 : Comp2 - } - } - - var Comp1 = { - view: function() { - return m("div") - } - } - var Comp2 = { - view: function() { - return m("div", {config: function(el, init) { - initialized = init - }}) - } - } - - m.mount(root, Root) - - mock.requestAnimationFrame.$resolve() - - cond = false - m.redraw(true) - - mock.requestAnimationFrame.$resolve() - - return initialized === false - }) - test(function() { - var root = mock.document.createElement("div") - var el - var FooPage = { - view: 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(BarComponent) : '' - ]); - } - }; - var BarComponent = { - view: function() { - return m('#bar', 'test'); - } - }; - m.mount(root, FooPage); - - root.childNodes[0].childNodes[0].onclick({}) - - return el.id === "bar" - }) - - //m.withAttr - test(function() { - //the handler is called with the correct value & context when callbackThis not given - var _this = {}; - var value, context; - var handler = m.withAttr("test", function(data) { - value = data; - context = this; - }); - handler.call(_this, {currentTarget: {test: "foo"}}); - return value === "foo" && context === _this; - }) - test(function() { - //the handler is called with the correct value & context when callbackThis is given - var _this = {}; - var value, context; - var handler = m.withAttr("test", function(data) { - value = data; - context = this; - }, _this); - handler({currentTarget: {test: "foo"}}); - return value === "foo" && context === _this; - }) - - //m.trust - test(function() {return m.trust("test").valueOf() === "test"}) - - //m.render - test(function() { - var root = mock.document.createElement("div") - m.render(root, "test") - return root.childNodes[0].nodeValue === "test" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", {"class": "a"})) - var elementBefore = root.childNodes[0] - m.render(root, m("div", {"class": "b"})) - var elementAfter = root.childNodes[0] - return elementBefore === elementAfter - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m(".a")) - var elementBefore = root.childNodes[0] - m.render(root, m(".b")) - var elementAfter = root.childNodes[0] - return elementBefore === elementAfter - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", {id: "a"})) - var elementBefore = root.childNodes[0] - m.render(root, m("div", {title: "b"})) - var elementAfter = root.childNodes[0] - return elementBefore !== elementAfter - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("#a")) - var elementBefore = root.childNodes[0] - m.render(root, m("[title=b]")) - var elementAfter = root.childNodes[0] - return elementBefore !== elementAfter - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("#a")) - var elementBefore = root.childNodes[0] - m.render(root, "test") - var elementAfter = root.childNodes[0] - return elementBefore !== elementAfter - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", [undefined])) - return root.childNodes[0].childNodes[0].nodeValue === "" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("svg", [m("g")])) - var g = root.childNodes[0].childNodes[0] - return g.nodeName === "G" && g.namespaceURI === "http://www.w3.org/2000/svg" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("svg", [m("a[href='http://google.com']")])) - return root.childNodes[0].childNodes[0].nodeName === "A" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div.classname", [m("a", {href: "/first"})])) - m.render(root, m("div", [m("a", {href: "/second"})])) - return root.childNodes[0].childNodes.length === 1 - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("ul", [m("li")])) - m.render(root, m("ul", [m("li"), undefined])) - return root.childNodes[0].childNodes[1].nodeValue === "" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("ul", [m("li"), m("li")])) - m.render(root, m("ul", [m("li"), undefined])) - return root.childNodes[0].childNodes[1].nodeValue === "" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("ul", [m("li")])) - m.render(root, m("ul", [undefined])) - return root.childNodes[0].childNodes[0].nodeValue === "" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("ul", [m("li")])) - m.render(root, m("ul", [{}])) - return root.childNodes[0].childNodes.length === 0 - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("ul", [m("li")])) - m.render(root, m("ul", [{tag: "b", attrs: {}}])) - return root.childNodes[0].childNodes[0].nodeName === "B" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("ul", [m("li")])) - m.render(root, m("ul", [{tag: new String("b"), attrs: {}}])) - return root.childNodes[0].childNodes[0].nodeName === "B" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("ul", [m("li", [m("a")])])) - m.render(root, m("ul", [{subtree: "retain"}])) - return root.childNodes[0].childNodes[0].childNodes[0].nodeName === "A" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/43 - var root = mock.document.createElement("div") - m.render(root, m("a", {config: m.route}, "test")) - m.render(root, m("a", {config: m.route}, "test")) - return root.childNodes[0].childNodes[0].nodeValue === "test" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/44 (1) - var root = mock.document.createElement("div") - m.render(root, m("#foo", [null, m("#bar")])) - m.render(root, m("#foo", ["test", m("#bar")])) - return root.childNodes[0].childNodes[0].nodeValue === "test" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/44 (2) - var root = mock.document.createElement("div") - m.render(root, m("#foo", [null, m("#bar")])) - m.render(root, m("#foo", [m("div"), m("#bar")])) - return root.childNodes[0].childNodes[0].nodeName === "DIV" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/44 (3) - var root = mock.document.createElement("div") - m.render(root, m("#foo", ["test", m("#bar")])) - m.render(root, m("#foo", [m("div"), m("#bar")])) - return root.childNodes[0].childNodes[0].nodeName === "DIV" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/44 (4) - var root = mock.document.createElement("div") - m.render(root, m("#foo", [m("div"), m("#bar")])) - m.render(root, m("#foo", ["test", m("#bar")])) - return root.childNodes[0].childNodes[0].nodeValue === "test" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/44 (5) - var root = mock.document.createElement("div") - m.render(root, m("#foo", [m("#bar")])) - m.render(root, m("#foo", [m("#bar"), [m("#baz")]])) - return root.childNodes[0].childNodes[1].id === "baz" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/48 - var root = mock.document - m.render(root, m("html", [m("#foo")])) - var result = root.childNodes[0].childNodes[0].id === "foo" - root.childNodes = [mock.document.createElement("html")] - return result - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/49 - var root = mock.document.createElement("div") - m.render(root, m("a", "test")) - m.render(root, m("a.foo", "test")) - return root.childNodes[0].childNodes[0].nodeValue === "test" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/49 - var root = mock.document.createElement("div") - m.render(root, m("a.foo", "test")) - m.render(root, m("a", "test")) - return root.childNodes[0].childNodes[0].nodeValue === "test" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/49 - var root = mock.document.createElement("div") - m.render(root, m("a.foo", "test")) - m.render(root, m("a", "test1")) - return root.childNodes[0].childNodes[0].nodeValue === "test1" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/49 - var root = mock.document.createElement("div") - m.render(root, m("a", "test")) - m.render(root, m("a", "test1")) - return root.childNodes[0].childNodes[0].nodeValue === "test1" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/50 - var root = mock.document.createElement("div") - m.render(root, m("#foo", [[m("div", "a"), m("div", "b")], m("#bar")])) - return root.childNodes[0].childNodes[1].childNodes[0].nodeValue === "b" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/50 - var root = mock.document.createElement("div") - m.render(root, m("#foo", [[m("div", "a"), m("div", "b")], m("#bar")])) - m.render(root, m("#foo", [[m("div", "a"), m("div", "b"), m("div", "c")], m("#bar")])) - return root.childNodes[0].childNodes[2].childNodes[0].nodeValue === "c" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/50 - var root = mock.document.createElement("div") - m.render(root, m("#foo", [[m("div", "a"), m("div", "b")], [m("div", "c"), m("div", "d")], m("#bar")])) - return root.childNodes[0].childNodes[3].childNodes[0].nodeValue === "d" && root.childNodes[0].childNodes[4].id === "bar" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/50 - var root = mock.document.createElement("div") - m.render(root, m("#foo", [[m("div", "a"), m("div", "b")], "test"])) - return root.childNodes[0].childNodes[1].childNodes[0].nodeValue === "b" && root.childNodes[0].childNodes[2].nodeValue === "test" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/50 - var root = mock.document.createElement("div") - m.render(root, m("#foo", [["a", "b"], "test"])) - return root.childNodes[0].childNodes[1].nodeValue === "b" && root.childNodes[0].childNodes[2].nodeValue === "test" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/51 - var root = mock.document.createElement("div") - m.render(root, m("main", [m("button"), m("article", [m("section"), m("nav")])])) - m.render(root, m("main", [m("button"), m("article", [m("span"), m("nav")])])) - return root.childNodes[0].childNodes[1].childNodes[0].nodeName === "SPAN" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/51 - var root = mock.document.createElement("div") - m.render(root, m("main", [m("button"), m("article", [m("section"), m("nav")])])) - m.render(root, m("main", [m("button"), m("article", ["test", m("nav")])])) - return root.childNodes[0].childNodes[1].childNodes[0].nodeValue === "test" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/51 - var root = mock.document.createElement("div") - m.render(root, m("main", [m("button"), m("article", [m("section"), m("nav")])])) - m.render(root, m("main", [m("button"), m("article", [m.trust("test"), m("nav")])])) - return root.childNodes[0].childNodes[1].childNodes[0].nodeValue === "test" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/55 - var root = mock.document.createElement("div") - m.render(root, m("#a")) - var elementBefore = root.childNodes[0] - m.render(root, m("#b")) - var elementAfter = root.childNodes[0] - return elementBefore !== elementAfter - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/56 - var root = mock.document.createElement("div") - m.render(root, [null, "foo"]) - m.render(root, ["bar"]) - return root.childNodes.length === 1 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/56 - var root = mock.document.createElement("div") - m.render(root, m("div", "foo")) - return root.childNodes.length === 1 - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", [m("button"), m("ul")])) - var valueBefore = root.childNodes[0].childNodes[0].nodeName - m.render(root, m("div", [undefined, m("ul")])) - var valueAfter = root.childNodes[0].childNodes[0].nodeValue - return valueBefore === "BUTTON" && valueAfter === "" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", [m("ul"), undefined])) - var valueBefore1 = root.childNodes[0].childNodes[0].nodeName - var valueBefore2 = root.childNodes[0].childNodes[1].nodeValue - m.render(root, m("div", [undefined, m("ul")])) - var valueAfter1 = root.childNodes[0].childNodes[0].nodeValue - var valueAfter2 = root.childNodes[0].childNodes[1].nodeName - return valueBefore1 === "UL" && valueAfter1 === "" && valueBefore2 === "" && valueAfter2 === "UL" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/79 - var root = mock.document.createElement("div") - m.render(root, m("div", {style: {background: "red"}})) - var valueBefore = root.childNodes[0].style.background - m.render(root, m("div", {style: {}})) - var valueAfter = root.childNodes[0].style.background - return valueBefore === "red" && valueAfter === "" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div[style='background:red']")) - return root.childNodes[0].style === "background:red" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", {style: {background: "red"}})) - var valueBefore = root.childNodes[0].style.background - m.render(root, m("div", {})) - var valueAfter = root.childNodes[0].style.background - return valueBefore === "red" && valueAfter === undefined - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/87 - var root = mock.document.createElement("div") - m.render(root, m("div", [[m("a"), m("a")], m("button")])) - m.render(root, m("div", [[m("a")], m("button")])) - return root.childNodes[0].childNodes.length === 2 && root.childNodes[0].childNodes[1].nodeName === "BUTTON" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/87 - var root = mock.document.createElement("div") - m.render(root, m("div", [m("a"), m("b"), m("button")])) - m.render(root, m("div", [m("a"), m("button")])) - return root.childNodes[0].childNodes.length === 2 && root.childNodes[0].childNodes[1].nodeName === "BUTTON" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/99 - var root = mock.document.createElement("div") - m.render(root, m("div", [m("img"), m("h1")])) - m.render(root, m("div", [m("a")])) - return root.childNodes[0].childNodes.length === 1 && root.childNodes[0].childNodes[0].nodeName === "A" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/120 - var root = mock.document.createElement("div") - m.render(root, m("div", ["a", "b", "c", "d"])) - m.render(root, m("div", [["d", "e"]])) - var children = root.childNodes[0].childNodes - return children.length === 2 && children[0].nodeValue === "d" && children[1].nodeValue === "e" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/120 - var root = mock.document.createElement("div") - m.render(root, m("div", [["a", "b", "c", "d"]])) - m.render(root, m("div", ["d", "e"])) - var children = root.childNodes[0].childNodes - return children.length === 2 && children[0].nodeValue === "d" && children[1].nodeValue === "e" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/120 - var root = mock.document.createElement("div") - m.render(root, m("div", ["x", [["a"], "b", "c", "d"]])) - m.render(root, m("div", ["d", ["e"]])) - var children = root.childNodes[0].childNodes - return children.length === 2 && children[0].nodeValue === "d" && children[1].nodeValue === "e" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/120 - var root = mock.document.createElement("div") - m.render(root, m("div", ["b"])) - m.render(root, m("div", [["e"]])) - var children = root.childNodes[0].childNodes - return children.length === 1 && children[0].nodeValue === "e" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/120 - var root = mock.document.createElement("div") - m.render(root, m("div", ["a", ["b"]])) - m.render(root, m("div", ["d", [["e"]]])) - var children = root.childNodes[0].childNodes - return children.length === 2 && children[0].nodeValue === "d" && children[1].nodeValue === "e" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/120 - var root = mock.document.createElement("div") - m.render(root, m("div", ["a", [["b"]]])) - m.render(root, m("div", ["d", ["e"]])) - var children = root.childNodes[0].childNodes - return children.length === 2 && children[0].nodeValue === "d" && children[1].nodeValue === "e" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/120 - var root = mock.document.createElement("div") - m.render(root, m("div", ["a", [["b"], "c"]])) - m.render(root, m("div", ["d", [[["e"]], "x"]])) - var children = root.childNodes[0].childNodes - return children.length === 3 && children[0].nodeValue === "d" && children[1].nodeValue === "e" - }) - test(function() { - var root = mock.document.createElement("div") - - var success = false - m.render(root, m("div", {config: function(elem, isInitialized, ctx) {ctx.data = 1}})) - m.render(root, m("div", {config: function(elem, isInitialized, ctx) {success = ctx.data === 1}})) - return success - }) - test(function() { - var root = mock.document.createElement("div") - - var index = 0; - var success = true; - var statefulConfig = function(elem, isInitialized, ctx) {ctx.data = index++} - var node = m("div", {config: statefulConfig}); - m.render(root, [node, node]); - - index = 0; - var checkConfig = function(elem, isInitialized, ctx) { - success = success && (ctx.data === index++) - } - node = m("div", {config: checkConfig}); - m.render(root, [node, node]); - return success; - }) - test(function() { - var root = mock.document.createElement("div") - var parent - m.render(root, m("div", m("a", { - config: function(el) {parent = el.parentNode.parentNode} - }))); - return parent === root - }) - test(function() { - var root = mock.document.createElement("div") - var count = 0 - m.render(root, m("div", m("a", { - config: function() { - var island = mock.document.createElement("div") - count++ - if (count > 2) throw new Error("too much recursion...") - m.render(island, m("div")) - } - }))); - return count === 1 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/129 - var root = mock.document.createElement("div") - m.render(root, m("div", [["foo", "bar"], ["foo", "bar"], ["foo", "bar"]])); - m.render(root, m("div", ["asdf", "asdf2", "asdf3"])); - return true - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/98 - //insert at beginning - var root = mock.document.createElement("div") - m.render(root, [m("a", {key: 1}, 1), m("a", {key: 2}, 2), m("a", {key: 3}, 3)]) - var firstBefore = root.childNodes[0] - m.render(root, [m("a", {key: 4}, 4), m("a", {key: 1}, 1), m("a", {key: 2}, 2), m("a", {key: 3}, 3)]) - var firstAfter = root.childNodes[1] - return firstBefore === firstAfter && root.childNodes[0].childNodes[0].nodeValue === "4" && root.childNodes.length === 4 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/98 - var root = mock.document.createElement("div") - m.render(root, [m("a", {key: 1}, 1), m("a", {key: 2}, 2), m("a", {key: 3}, 3)]) - var firstBefore = root.childNodes[0] - m.render(root, [m("a", {key: 4}, 4), m("a", {key: 1}, 1), m("a", {key: 2}, 2)]) - var firstAfter = root.childNodes[1] - return firstBefore === firstAfter && root.childNodes[0].childNodes[0].nodeValue === "4" && root.childNodes.length === 3 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/98 - var root = mock.document.createElement("div") - m.render(root, [m("a", {key: 1}, 1), m("a", {key: 2}, 2), m("a", {key: 3}, 3)]) - var firstBefore = root.childNodes[1] - m.render(root, [m("a", {key: 2}, 2), m("a", {key: 3}, 3), m("a", {key: 4}, 4)]) - var firstAfter = root.childNodes[0] - return firstBefore === firstAfter && root.childNodes[0].childNodes[0].nodeValue === "2" && root.childNodes.length === 3 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/98 - var root = mock.document.createElement("div") - m.render(root, [m("a", {key: 1}, 1), m("a", {key: 2}, 2), m("a", {key: 3}, 3), m("a", {key: 4}, 4), m("a", {key: 5}, 5)]) - var firstBefore = root.childNodes[0] - var secondBefore = root.childNodes[1] - var fourthBefore = root.childNodes[3] - m.render(root, [m("a", {key: 4}, 4), m("a", {key: 10}, 10), m("a", {key: 1}, 1), m("a", {key: 2}, 2)]) - var firstAfter = root.childNodes[2] - var secondAfter = root.childNodes[3] - var fourthAfter = root.childNodes[0] - return firstBefore === firstAfter && secondBefore === secondAfter && fourthBefore === fourthAfter && root.childNodes[1].childNodes[0].nodeValue === "10" && root.childNodes.length === 4 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/98 - var root = mock.document.createElement("div") - m.render(root, [m("a", {key: 1}, 1), m("a", {key: 2}, 2), m("a", {key: 3}, 3), m("a", {key: 4}, 4), m("a", {key: 5}, 5)]) - var firstBefore = root.childNodes[0] - var secondBefore = root.childNodes[1] - var fourthBefore = root.childNodes[3] - m.render(root, [m("a", {key: 4}, 4), m("a", {key: 10}, 10), m("a", {key: 2}, 2), m("a", {key: 1}, 1), m("a", {key: 6}, 6), m("a", {key: 7}, 7)]) - var firstAfter = root.childNodes[3] - var secondAfter = root.childNodes[2] - var fourthAfter = root.childNodes[0] - return firstBefore === firstAfter && secondBefore === secondAfter && fourthBefore === fourthAfter && root.childNodes[1].childNodes[0].nodeValue === "10" && root.childNodes[4].childNodes[0].nodeValue === "6" && root.childNodes[5].childNodes[0].nodeValue === "7" && root.childNodes.length === 6 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/149 - var root = mock.document.createElement("div") - m.render(root, [m("a", {key: 1}), m("a", {key: 2}), m("a"), m("a", {key: 4}), m("a", {key: 5})]) - var firstBefore = root.childNodes[0] - var secondBefore = root.childNodes[1] - var thirdBefore = root.childNodes[2] - var fourthBefore = root.childNodes[3] - var fifthBefore = root.childNodes[4] - m.render(root, [m("a", {key: 4}), m("a", {key: 5}), m("a"), m("a", {key: 1}), m("a", {key: 2})]) - var firstAfter = root.childNodes[3] - var secondAfter = root.childNodes[4] - var thirdAfter = root.childNodes[2] - var fourthAfter = root.childNodes[0] - var fifthAfter = root.childNodes[1] - return firstBefore === firstAfter && secondBefore === secondAfter && thirdBefore === thirdAfter && fourthBefore === fourthAfter && fifthBefore === fifthAfter - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/246 - //insert at beginning with non-keyed in the middle - var root = mock.document.createElement("div") - m.render(root, [m("a", {key: 1}, 1)]) - var firstBefore = root.childNodes[0] - m.render(root, [m("a", {key: 2}, 2), m("br"), m("a", {key: 1}, 1)]) - var firstAfter = root.childNodes[2] - return firstBefore === firstAfter && root.childNodes[0].childNodes[0].nodeValue === "2" && root.childNodes.length === 3 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/134 - var root = mock.document.createElement("div") - m.render(root, m("div", {contenteditable: true}, "test")) - mock.document.activeElement = root.childNodes[0] - m.render(root, m("div", {contenteditable: true}, "test1")) - m.render(root, m("div", {contenteditable: false}, "test2")) - return root.childNodes[0].childNodes[0].nodeValue === "test2" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/136 - var root = mock.document.createElement("div") - m.render(root, m("textarea", ["test"])) - m.render(root, m("textarea", ["test1"])) - return root.childNodes[0].value === "test1" - }) - test(function() { - var root = mock.document.createElement("div") - var unloaded = 0 - m.render(root, [ - m("div", { - key: 1, - config: function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - }) - ]) - m.render(root, [ - m("div", {key: 2}), - m("div", { - key: 1, - config: function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - }) - ]) - return unloaded === 0 - }) - test(function() { - var root = mock.document.createElement("div") - var unloadedParent = 0 - var unloadedChild = 0 - var configParent = function(el, init, ctx) { - ctx.onunload = function() { - unloadedParent++ - } - } - var configChild = function(el, init, ctx) { - ctx.onunload = function() { - unloadedChild++ - } - } - m.render(root, m("div", {config: configParent}, m("a", {config: configChild}))) - m.render(root, m("main", {config: configParent}, m("a", {config: configChild}))) - return unloadedParent === 1 && unloadedChild === 0 - }) - test(function() { - var root = mock.document.createElement("div") - var unloadedParent = 0 - var unloadedChild = 0 - var configParent = function(el, init, ctx) { - ctx.onunload = function() { - unloadedParent++ - } - } - var configChild = function(el, init, ctx) { - ctx.onunload = function() { - unloadedChild++ - } - } - m.render(root, m("div", {config: configParent}, m("a", {config: configChild}))) - m.render(root, m("main", {config: configParent}, m("b", {config: configChild}))) - return unloadedParent === 1 && unloadedChild === 1 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/150 - var root = mock.document.createElement("div") - m.render(root, [m("a"), m("div")]) - m.render(root, [[], m("div")]) - return root.childNodes.length === 1 && root.childNodes[0].nodeName === "DIV" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/156 - var root = mock.document.createElement("div") - m.render(root, m("div", [ - ["a", "b", "c", "d"].map(function() { - return [m("div"), " "] - }), - m("span") - ])) - return root.childNodes[0].childNodes[8].nodeName === "SPAN" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/157 - var root = mock.document.createElement("div") - m.render(root, m("ul", [m("li", {key: 0}, 0), m("li", {key: 2}, 2), m("li", {key: 4}, 4)])) - m.render(root, m("ul", [m("li", {key: 0}, 0), m("li", {key: 1}, 1), m("li", {key: 2}, 2), m("li", {key: 3}, 3), m("li", {key: 4}, 4), m("li", {key: 5}, 5)])) - return root.childNodes[0].childNodes.map(function(n) {return n.childNodes[0].nodeValue}).join("") === "012345" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/157 - var root = mock.document.createElement("div") - m.render(root, m("input", {value: "a"})) - m.render(root, m("input", {value: "aa"})) - return root.childNodes[0].childNodes.length === 0 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/157 - var root = mock.document.createElement("div") - m.render(root, m("br", {"class": "a"})) - m.render(root, m("br", {"class": "aa"})) - return root.childNodes[0].childNodes.length === 0 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/194 - var root = mock.document.createElement("div") - m.render(root, m("ul", [m("li", {key: 0}, 0), m("li", {key: 1}, 1), m("li", {key: 2}, 2), m("li", {key: 3}, 3), m("li", {key: 4}, 4), m("li", {key: 5}, 5)])) - m.render(root, m("ul", [m("li", {key: 0}, 0), m("li", {key: 1}, 1), m("li", {key: 2}, 2), m("li", {key: 4}, 4), m("li", {key: 5}, 5)])) - return root.childNodes[0].childNodes.map(function(n) {return n.childNodes[0].nodeValue}).join("") === "01245" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/194 - var root = mock.document.createElement("div") - m.render(root, m("ul", [m("li", {key: 0}, 0), m("li", {key: 1}, 1), m("li", {key: 2}, 2), m("li", {key: 3}, 3), m("li", {key: 4}, 4), m("li", {key: 5}, 5)])) - m.render(root, m("ul", [m("li", {key: 1}, 1), m("li", {key: 2}, 2), m("li", {key: 3}, 3), m("li", {key: 4}, 4), m("li", {key: 5}, 5), m("li", {key: 6}, 6)])) - m.render(root, m("ul", [m("li", {key: 12}, 12), m("li", {key: 13}, 13), m("li", {key: 14}, 14), m("li", {key: 15}, 15), m("li", {key: 16}, 16), m("li", {key: 17}, 17)])) - return root.childNodes[0].childNodes.map(function(n) {return n.childNodes[0].nodeValue}).join(",") === "12,13,14,15,16,17" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/206 - var root = mock.document.createElement("div") - m.render(root, m("div", undefined)) - m.render(root, m("div", [m("div")])) - return root.childNodes[0].childNodes.length === 1 - }) - test(function() { - // https://github.com/lhorie/mithril.js/issues/206 - var root = mock.document.createElement("div") - m.render(root, m("div", null)) - m.render(root, m("div", [m("div")])) - return root.childNodes[0].childNodes.length === 1 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/200 - var root = mock.document.createElement("div") - - var unloaded1 = false - function unloadable1(element, isInit, context) { - context.onunload = function() { - unloaded1 = true - } - } - m.render(root, [ m("div", {config: unloadable1}) ]) - m.render(root, [ ]) - - var unloaded2 = false - function unloadable2(element, isInit, context) { - context.onunload = function() { - unloaded2 = true - } - } - m.render(root, [ m("div", {config: unloadable2}) ]) - m.render(root, [ ]) - - return unloaded1 === true && unloaded2 === true - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, [m("div.blue")]) - m.render(root, [m("div.green", [m("div")]), m("div.blue")]) - return root.childNodes.length === 2 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/277 - var root = mock.document.createElement("div") - function Field() { - this.tag = "div"; - this.attrs = {}; - this.children = "hello"; - } - m.render(root, new Field()) - return root.childNodes.length === 1 - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, {foo: 123}) - return root.childNodes.length === 0 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/299 - var root = mock.document.createElement("div") - m.render(root, m("div", [m("div", {key: 1}, 1), m("div", {key: 2}, 2), m("div", {key: 3}, 3), m("div", {key: 4}, 4), m("div", {key: 5}, 5), null, null, null, null, null, null, null, null, null, null])) - m.render(root, m("div", [null, null, m("div", {key: 3}, 3), null, null, m("div", {key: 6}, 6), null, null, m("div", {key: 9}, 9), null, null, m("div", {key: 12}, 12), null, null, m("div", {key: 15}, 15)])) - m.render(root, m("div", [m("div", {key: 1}, 1), m("div", {key: 2}, 2), m("div", {key: 3}, 3), m("div", {key: 4}, 4), m("div", {key: 5}, 5), null, null, null, null, null, null, null, null, null, null])) - return root.childNodes[0].childNodes.map(function(c) {return c.childNodes ? c.childNodes[0].nodeValue : c.nodeValue}).slice(0, 5).join("") === "12345" - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/377 - var root = mock.document.createElement("div") - m.render(root, m("div", [m("div", 1), m("div", 2), [m("div", {key: 3}, 3), m("div", {key: 4}, 4), m("div", {key:5}, 5)], [m("div", {key: 6}, 6)]])) - m.render(root, m("div", [m("div", 1), null, [m("div", {key: 3}, 3), m("div", {key: 4}, 4), m("div", {key:5}, 5)], [m("div", {key: 6}, 6)]])) - return root.childNodes[0].childNodes.map(function(c) {return c.childNodes ? c.childNodes[0].nodeValue : c.nodeValue}).join("") === "13456" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", [console.log()])) // don't throw in Firefox - return true - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, [ - m("#div-1", {key: 1}), - m("#div-2", {key: 2}), - m("#div-3", {key: 3}) - ]) - root.appendChild(root.childNodes[1]) - m.render(root, [ - m("#div-1", {key: 1}), - m("#div-3", {key: 3}), - m("#div-2", {key: 2}) - ]) - return root.childNodes.map(function(node) {return node.id}).join() === "div-1,div-3,div-2" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", function() {})) - return root.childNodes[0].childNodes.length === 0 - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", "foo", m("a"))) - m.render(root, m("div", "test")) - return root.childNodes[0].childNodes.length === 1 - }) - test(function() { - //if an element is preceded by a conditional, it should not lose its identity - var root = mock.document.createElement("div") - m.render(root, m("div", [m("a"), m("input[autofocus]")])) - var before = root.childNodes[0].childNodes[1] - m.render(root, m("div", [undefined, m("input[autofocus]")])) - var after = root.childNodes[0].childNodes[1] - return before === after - }) - test(function() { - //unkeyed element should maintain identity if mixed w/ keyed elements and identity can be inferred - var root = mock.document.createElement("div") - m.render(root, m("div", [m("a", {key: 1}), m("a", {key: 2}), m("a", {key: 3}), m("i")])) - var before = root.childNodes[0].childNodes[3] - m.render(root, m("div", [m("b", {key: 3}), m("b", {key: 4}), m("i"), m("b", {key: 1})])) - var after = root.childNodes[0].childNodes[2] - return before === after - }) - test(function() { - //unkeyed element should maintain identity if mixed w/ keyed elements and text nodes and identity can be inferred - var root = mock.document.createElement("div") - m.render(root, m("div", [m("a", {key: 1}), m("a", {key: 2}), "foo", m("a", {key: 3}), m("i")])) - var before = root.childNodes[0].childNodes[4] - m.render(root, m("div", [m("a", {key: 3}), m("a", {key: 4}), "bar", m("i"), m("a", {key: 1})])) - var after = root.childNodes[0].childNodes[3] - return before === after - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", [m("a", {key: 1}), m("a", {key: 2}), null, m("a", {key: 3}), m("i")])) - var before = root.childNodes[0].childNodes[4] - m.render(root, m("div", [m("a", {key: 3}), m("a", {key: 4}), null, m("i"), m("a", {key: 1})])) - var after = root.childNodes[0].childNodes[3] - return before === after - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", [m("a", {key: 1}), m("a", {key: 2}), undefined, m("a", {key: 3}), m("i")])) - var before = root.childNodes[0].childNodes[4] - m.render(root, m("div", [m("a", {key: 3}), m("a", {key: 4}), undefined, m("i"), m("a", {key: 1})])) - var after = root.childNodes[0].childNodes[3] - return before === after - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m("div", [m("a", {key: 1}), m("a", {key: 2}), m.trust("a"), m("a", {key: 3}), m("i")])) - var before = root.childNodes[0].childNodes[4] - m.render(root, m("div", [m("a", {key: 3}), m("a", {key: 4}), m.trust("a"), m("i"), m("a", {key: 1})])) - var after = root.childNodes[0].childNodes[3] - return before === after - }) - test(function() { - var root = mock.document.createElement("div") - var vdom = m("div.a", {"class": undefined}) - m.render(root, vdom) - return root.childNodes[0]["class"] === "a" - }) - test(function() { - var root = mock.document.createElement("div") - m.render(root, m(".a", [1])) - m.render(root, m(".a", [])) - return root.childNodes[0].childNodes.length === 0 - }) - //end m.render - - //m.redraw - test(function() { - mock.requestAnimationFrame.$resolve() //setup - var controller - var root = mock.document.createElement("div") - m.mount(root, { - controller: function() {controller = this}, - view: function(ctrl) {return ctrl.value} - }) - mock.requestAnimationFrame.$resolve() - var valueBefore = root.childNodes[0].nodeValue - controller.value = "foo" - m.redraw() - mock.requestAnimationFrame.$resolve() - return valueBefore === "" && root.childNodes[0].nodeValue === "foo" - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - var count = 0 - var root = mock.document.createElement("div") - m.mount(root, { - controller: function() {}, - view: function() { - count++ - } - }) - mock.requestAnimationFrame.$resolve() //teardown - m.redraw() //should run synchronously - - m.redraw() //rest should run asynchronously since they're spamming - m.redraw() - m.redraw() - mock.requestAnimationFrame.$resolve() //teardown - - return count === 3 - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - var count = 0 - var root = mock.document.createElement("div") - m.mount(root, { - controller: function() {}, - view: function() { - count++ - } - }) - mock.requestAnimationFrame.$resolve() //teardown - m.redraw(true) //should run synchronously - - m.redraw(true) //forced to run synchronously - m.redraw(true) - m.redraw(true) - mock.requestAnimationFrame.$resolve() //teardown - return count === 5 - }) - - //m.route - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/test1", { - "/test1": {controller: function() {}, view: function() {return "foo"}} - }) - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/test1" && root.childNodes[0].nodeValue === "foo" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.pathname = "/" - - var root = mock.document.createElement("div") - m.route.mode = "pathname" - m.route(root, "/test2", { - "/test2": { - controller: function() {}, - view: function() { - return [ - "foo", - m("a", { href: "/test2", config: m.route }, "Test2") - ] - } - } - }) - mock.requestAnimationFrame.$resolve() - - var result = mock.location.pathname === "/test2" && root.childNodes[0].nodeValue === "foo" && root.childNodes[1].href === "/test2" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.hash = "#" - - var root = mock.document.createElement("div") - m.route.mode = "hash" - m.route(root, "/test3", { - "/test3": {controller: function() {}, view: function() {return "foo"}} - }) - mock.requestAnimationFrame.$resolve() - - var result = mock.location.hash === "#/test3" && root.childNodes[0].nodeValue === "foo" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/test4/foo", { - "/test4/:test": {controller: function() {}, view: function() {return m.route.param("test")}} - }) - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/test4/foo" && root.childNodes[0].nodeValue === "foo" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var component = {controller: function() {}, view: function() {return m.route.param("test")}} - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/test5/foo", { - "/": component, - "/test5/:test": component - }) - var paramValueBefore = m.route.param("test") - mock.requestAnimationFrame.$resolve() - m.route("/") - var paramValueAfter = m.route.param("test") - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/" && paramValueBefore === "foo" && paramValueAfter === undefined - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var component = {controller: function() {}, view: function() {return m.route.param("a1")}} - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/test6/foo", { - "/": component, - "/test6/:a1": component - }) - var paramValueBefore = m.route.param("a1") - mock.requestAnimationFrame.$resolve() - m.route("/") - var paramValueAfter = m.route.param("a1") - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/" && paramValueBefore === "foo" && paramValueAfter === undefined - - m.mount(root, null) //teardown - - return result - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/61 - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var component = {controller: function() {}, view: function() {return m.route.param("a1")}} - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/test7/foo", { - "/": component, - "/test7/:a1": component - }) - var routeValueBefore = m.route() - mock.requestAnimationFrame.$resolve() - m.route("/") - var routeValueAfter = m.route() - mock.requestAnimationFrame.$resolve() - - var result = routeValueBefore === "/test7/foo" && routeValueAfter === "/" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/test8/foo/SEP/bar/baz", { - "/test8/:test/SEP/:path...": { - controller: function() {}, - view: function() { - return m.route.param("test") + "_" + m.route.param("path") - } - } - }) - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/test8/foo/SEP/bar/baz" && root.childNodes[0].nodeValue === "foo_bar/baz" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/test9/foo/bar/SEP/baz", { - "/test9/:test.../SEP/:path": { - controller: function() {}, - view: function() { - return m.route.param("test") + "_" + m.route.param("path") - } - } - }) - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/test9/foo/bar/SEP/baz" && root.childNodes[0].nodeValue === "foo/bar_baz" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/test10/foo%20bar", { - "/test10/:test": { - controller: function() {}, - view: function() { - return m.route.param("test") - } - } - }) - mock.requestAnimationFrame.$resolve() - - var result = root.childNodes[0].nodeValue === "foo bar" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/", { - "/": {controller: function() {}, view: function() {return "foo"}}, - "/test11": {controller: function() {}, view: function() {return "bar"}} - }) - mock.requestAnimationFrame.$resolve() - m.route("/test11/") - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/test11/" && root.childNodes[0].nodeValue === "bar" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/", { - "/": {controller: function() {}, view: function() {}}, - "/test12": {controller: function() {}, view: function() {}} - }) - mock.requestAnimationFrame.$resolve() - m.route("/test12?a=foo&b=bar") - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/test12?a=foo&b=bar" && m.route.param("a") === "foo" && m.route.param("b") === "bar" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/", { - "/": {controller: function() {}, view: function() {return "bar"}}, - "/test13/:test": {controller: function() {}, view: function() {return m.route.param("test")}} - }) - mock.requestAnimationFrame.$resolve() - m.route("/test13/foo?test=bar") - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/test13/foo?test=bar" && root.childNodes[0].nodeValue === "foo" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/", { - "/": {controller: function() {}, view: function() {return "bar"}}, - "/test14": {controller: function() {}, view: function() {return "foo"}} - }) - mock.requestAnimationFrame.$resolve() - m.route("/test14?test&test2=") - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/test14?test&test2=" && m.route.param("test") == null && m.route.param("test2") === "" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/", { - "/": {controller: function() {}, view: function() {}}, - "/test12": {controller: function() {}, view: function() {}} - }) - mock.requestAnimationFrame.$resolve() - m.route("/test12", {a: "foo", b: "bar"}) - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/test12?a=foo&b=bar" && m.route.param("a") === "foo" && m.route.param("b") === "bar" - - m.mount(root, null) //teardown - - return result - }) - test(function() { // test route params returning params object if no key is given - mock.requestAnimationFrame.$resolve() //setup - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/", { - "/": {controller: function() {}, view: function() {}}, - "/test12": {controller: function() {}, view: function() {}} - }) - mock.requestAnimationFrame.$resolve() - m.route("/test12", {a: "foo", b: "bar"}) - mock.requestAnimationFrame.$resolve() - - var params = m.route.param(); - - var result = params.a === "foo" && params.b === "bar" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var route1, route2 - m.route.mode = "search" - m.route(root, "/", { - "/": {controller: function() {route1 = m.route()}, view: function() {}}, - "/test13": {controller: function() {route2 = m.route()}, view: function() {}} - }) - mock.requestAnimationFrame.$resolve() - m.route("/test13") - mock.requestAnimationFrame.$resolve() - - var result = route1 === "/" && route2 === "/test13" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var unloaded = 0 - m.route.mode = "search" - m.route(root, "/", { - "/": { - controller: function() {}, - view: function() { - return m("div", { - config: function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - }) - } - }, - "/test14": {controller: function() {}, view: function() {}} - }) - mock.requestAnimationFrame.$resolve() - m.route("/test14") - mock.requestAnimationFrame.$resolve() - - var result = unloaded === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var unloaded = 0 - m.route.mode = "search" - m.route(root, "/", { - "/": { - controller: function() {}, - view: function() { - return [ - m("div"), - m("div", { - config: function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - }) - ] - } - }, - "/test15": { - controller: function() {}, - view: function() { - return [m("div")] - } - } - }) - mock.requestAnimationFrame.$resolve() - m.route("/test15") - mock.requestAnimationFrame.$resolve() - - var result = unloaded === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var unloaded = 0 - m.route.mode = "search" - m.route(root, "/", { - "/": { - controller: function() {}, - view: function() { - return m("div", { - config: function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - }) - } - }, - "/test16": { - controller: function() {}, - view: function() { - return m("a") - } - } - }) - mock.requestAnimationFrame.$resolve() - m.route("/test16") - mock.requestAnimationFrame.$resolve() - - var result = unloaded === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var unloaded = 0 - m.route.mode = "search" - m.route(root, "/", { - "/": { - controller: function() {}, - view: function() { - return [ - m("div", { - config: function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - }) - ] - } - }, - "/test17": { - controller: function() {}, - view: function() { - return m("a") - } - } - }) - mock.requestAnimationFrame.$resolve() - m.route("/test17") - mock.requestAnimationFrame.$resolve() - - var result = unloaded === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var unloaded = 0 - m.route.mode = "search" - m.route(root, "/", { - "/": { - controller: function() {}, - view: function() { - return m("div", { - config: function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - }) - } - }, - "/test18": { - controller: function() {}, - view: function() { - return [m("a")] - } - } - }) - mock.requestAnimationFrame.$resolve() - m.route("/test18") - mock.requestAnimationFrame.$resolve() - - var result = unloaded === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var unloaded = 0 - m.route.mode = "search" - m.route(root, "/", { - "/": { - controller: function() {}, - view: function() { - return [ - m("div", { - key: 1, - config: function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - }) - ] - } - }, - "/test20": { - controller: function() {}, - view: function() { - return [ - m("div", { - key: 2, - config: function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - }) - ] - } - } - }) - mock.requestAnimationFrame.$resolve() - m.route("/test20") - mock.requestAnimationFrame.$resolve() - - var result = unloaded === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var unloaded = 0 - m.route.mode = "search" - m.route(root, "/", { - "/": { - controller: function() {}, - view: function() { - return [ - m("div", { - key: 1, - config: function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - }) - ] - } - }, - "/test21": { - controller: function() {}, - view: function() { - return [ - m("div", { - config: function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - }) - ] - } - } - }) - mock.requestAnimationFrame.$resolve() - m.route("/test21") - mock.requestAnimationFrame.$resolve() - - var result = unloaded === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/foo", { - "/foo": { - controller: function() {}, - view: function() { - return m("div", "foo"); - } - }, - "/bar": { - controller: function() {}, - view: function() { - return m("div", "bar"); - } - } - }) - mock.requestAnimationFrame.$resolve() - var foo = root.childNodes[0].childNodes[0].nodeValue; - m.route("/bar") - mock.requestAnimationFrame.$resolve() - var bar = root.childNodes[0].childNodes[0].nodeValue; - - var result = (foo === "foo" && bar === "bar") - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var unloaded = 0 - var config = function(el, init, ctx) { - ctx.onunload = function() { - unloaded++ - } - } - m.route.mode = "search" - m.route(root, "/foo1", { - "/foo1": { - controller: function() {}, - view: function() { - return m("div", m("a", {config: config}, "foo")); - } - }, - "/bar1": { - controller: function() {}, - view: function() { - return m("main", m("a", {config: config}, "foo")); - } - } - }) - mock.requestAnimationFrame.$resolve() - m.route("/bar1") - mock.requestAnimationFrame.$resolve() - - var result = unloaded === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var strategy - m.route.mode = "search" - m.route(root, "/foo1", { - "/foo1": { - controller: function() { - strategy = m.redraw.strategy() - m.redraw.strategy("none") - }, - view: function() { - return m("div"); - } - } - }) - mock.requestAnimationFrame.$resolve() - - var result = strategy === "all" && root.childNodes.length === 0 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var strategy, count = 0 - var config = function(el, init) {if (!init) count++} - m.route.mode = "search" - m.route(root, "/foo1", { - "/foo1": { - controller: function() {}, - view: function() { - return m("div", {config: config}); - } - }, - "/bar1": { - controller: function() { - strategy = m.redraw.strategy() - m.redraw.strategy("redraw") - }, - view: function() { - return m("div", {config: config}); - } - } - }) - mock.requestAnimationFrame.$resolve() - m.route("/bar1") - mock.requestAnimationFrame.$resolve() - - var result = strategy === "all" && count === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var strategy - m.route.mode = "search" - 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() - - var result = strategy === "diff" && root.childNodes[0].childNodes[0].nodeValue === "1" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var count = 0 - var config = function(el, init ) {if (!init) count++} - m.route.mode = "search" - m.route(root, "/foo1", { - "/foo1": { - controller: function() {}, - view: function() { - return m("div", {config: config, onclick: function() { - m.redraw.strategy("all") - }}); - } - } - }) - root.childNodes[0].onclick({}) - mock.requestAnimationFrame.$resolve() - - var result = count === 2 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var value - m.route(root, "/foo+bar", { - "/:arg": { - controller: function() {value = m.route.param("arg")}, - view: function() { - return "" - } - } - }) - var result = value === "foo+bar" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/", { - "/": {controller: function() {}, view: function() {return "foo"}}, - "/test22": {controller: function() {}, view: function() {return "bar"}} - }) - mock.requestAnimationFrame.$resolve() - m.route(String("/test22/")) - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/test22/" && root.childNodes[0].nodeValue === "bar" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - m.route.mode = "search" - m.route(root, "/", { - "/": {controller: function() {}, view: function() {return "foo"}}, - "/test23": {controller: function() {}, view: function() {return "bar"}} - }) - mock.requestAnimationFrame.$resolve() - m.route(new String("/test23/")) - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/test23/" && root.childNodes[0].nodeValue === "bar" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var value - m.route(root, String("/foo+bar"), { - "/:arg": { - controller: function() {value = m.route.param("arg")}, - view: function() { - return "" - } - } - }) - var result = value === "foo+bar" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() //setup - mock.location.search = "?" - - var root = mock.document.createElement("div") - var value - m.route(root, new String("/foo+bar"), { - "/:arg": { - controller: function() {value = m.route.param("arg")}, - view: function() { - return "" - } - } - }) - var result = value === "foo+bar" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - - var a = {} - a.controller = function() {m.route("/b")} - a.view = function() {return "a"} - - var b = {} - b.controller = function() {} - b.view = function() {return "b"} - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - var result = root.childNodes[0].nodeValue === "b" - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - - var a = {} - a.controller = function() { - m.route("/b?foo=1", {foo: 2}) - } - a.view = function() {return "a"} - - var b = {} - b.controller = function() {} - b.view = function() {return "b"} - - m.route(root, "/", { - "/": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - var result = mock.location.search === "?/b?foo=2" - - m.mount(root, null) //teardown - - return result - - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - mock.history.$$length = 0 - - var root = mock.document.createElement("div") - - var a = {} - a.controller = function() {} - a.view = function() {return "a"} - - var b = {} - b.controller = function() {} - b.view = function() {return "b"} - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = mock.history.$$length === 1 - - m.mount(root, null) //teardown - - return result - - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - mock.history.$$length = 0 - - var root = mock.document.createElement("div") - - var a = {} - a.controller = function() {} - a.view = function() {return "a"} - - var b = {} - b.controller = function() {} - b.view = function() {return "b"} - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/a") - - mock.requestAnimationFrame.$resolve() - - var result = mock.history.$$length === 0 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {} - a.view = function() { - return m("a", {config: function(el, init) { - if (!init) initCount++ - }}) - } - - var b = {} - b.controller = function() {} - b.view = a.view - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 2 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {} - a.view = function() { - return m("a", {config: function(el, init, ctx) { - ctx.retain = false - if (!init) initCount++ - }}) - } - - var b = {} - b.controller = function() {} - b.view = a.view - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 2 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {} - a.view = function() { - return m("a", {config: function(el, init, ctx) { - ctx.retain = true - if (!init) initCount++ - }}) - } - - var b = {} - b.controller = function() {} - b.view = a.view - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {m.redraw.strategy("diff")} - a.view = function() { - return m("a", {config: function(el, init) { - if (!init) initCount++ - }}) - } - - var b = {} - b.controller = function() {m.redraw.strategy("diff")} - b.view = a.view - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {m.redraw.strategy("diff")} - a.view = function() { - return m("a", {config: function(el, init, ctx) { - ctx.retain = true - if (!init) initCount++ - }}) - } - - var b = {} - b.controller = function() {m.redraw.strategy("diff")} - b.view = a.view - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {m.redraw.strategy("diff")} - a.view = function() { - return m("a", {config: function(el, init, ctx) { - ctx.retain = false - if (!init) initCount++ - }}) - } - - var b = {} - b.controller = function() {m.redraw.strategy("diff")} - b.view = a.view - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 2 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {} - a.view = function() { - return m("div", m("a", {config: function(el, init, ctx) { - ctx.retain = true - if (!init) initCount++ - }})) - } - - var b = {} - b.controller = function() {} - b.view = function() { - return m("section", m("a", {config: function(el, init, ctx) { - ctx.retain = true - if (!init) initCount++ - }})) - } - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {} - a.view = function() { - return m("div", m("a", {config: function(el, init, ctx) { - ctx.retain = false - if (!init) initCount++ - }})) - } - - var b = {} - b.controller = function() {} - b.view = function() { - return m("section", m("a", {config: function(el, init, ctx) { - ctx.retain = false - if (!init) initCount++ - }})) - } - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 2 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {} - a.view = function() { - return m("div", m("a", {config: function(el, init) { - if (!init) initCount++ - }})) - } - - var b = {} - b.controller = function() {} - b.view = function() { - return m("section", m("a", {config: function(el, init) { - if (!init) initCount++ - }})) - } - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 2 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {m.redraw.strategy("diff")} - a.view = function() { - return m("div", m("a", {config: function(el, init, ctx) { - ctx.retain = true - if (!init) initCount++ - }})) - } - - var b = {} - b.controller = function() {m.redraw.strategy("diff")} - b.view = function() { - return m("section", m("a", {config: function(el, init, ctx) { - ctx.retain = true - if (!init) initCount++ - }})) - } - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {m.redraw.strategy("diff")} - a.view = function() { - return m("div", m("a", {config: function(el, init, ctx) { - ctx.retain = false - if (!init) initCount++ - }})) - } - - var b = {} - b.controller = function() {m.redraw.strategy("diff")} - b.view = function() { - return m("section", m("a", {config: function(el, init, ctx) { - ctx.retain = false - if (!init) initCount++ - }})) - } - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 2 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {m.redraw.strategy("diff")} - a.view = function() { - return m("div", m("a", {config: function(el, init) { - if (!init) initCount++ - }})) - } - - var b = {} - b.controller = function() {m.redraw.strategy("diff")} - b.view = function() { - return m("section", m("a", {config: function(el, init) { - if (!init) initCount++ - }})) - } - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - - var result = initCount === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - //retain flag should work inside component - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - var initCount = 0 - - var a = {} - a.controller = function() {} - a.view = function() { - return m("div", m("a", {config: function(el, init, ctx) { - ctx.retain = true - if (!init) initCount++ - }})) - } - - var b = {} - b.controller = function() {m.redraw.strategy("diff")} - b.view = function() { - return m("section", m("a", {config: function(el, init, ctx) { - ctx.retain = true - if (!init) initCount++ - }})) - } - - m.route(root, "/a", { - "/a": {view: function() {return m("div", a)}}, - "/b": {view: function() {return m("div", b)}} - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - var result = initCount === 1 - - m.mount(root, null) //teardown - - return result - }) - test(function() { - // https://github.com/lhorie/mithril.js/pull/571 - mock.requestAnimationFrame.$resolve() - mock.location.search = "?" - - var root = mock.document.createElement("div") - - var a = {} - a.controller = function() {} - a.view = function() { - return m("div", {config: function(elm) { - elm.childNodes[0].modified = true - }}, m("div")) - } - - var b = {} - b.controller = function() {} - b.view = function() { - return m("div", m("div")) - } - - m.route(root, "/a", { - "/a": a, - "/b": b - }) - mock.requestAnimationFrame.$resolve() - - m.route("/b") - - mock.requestAnimationFrame.$resolve() - var result = !root.childNodes[0].childNodes[0].modified; - - m.mount(root, null) //teardown - - return result - }); - //end m.route - - //m.route.parseQueryString - test(function() { - var args = m.route.parseQueryString("foo=bar&hello%5B%5D=world&hello%5B%5D=mars&hello%5B%5D=pluto") - return args["hello[]"] instanceof Array && args["hello[]"].indexOf("world") > -1 && args["hello[]"].indexOf("mars") > -1 && args["hello[]"].indexOf("pluto") > -1 - }) - test(function() { - var args = m.route.parseQueryString("foo=bar&hello=world&hello=mars&bam=&yup") - return args.foo === "bar" && args.hello[0] === "world" && args.hello[1] === "mars" && args.bam === "" && args.yup == null - }) - test(function() { - var args = m.route.parseQueryString("") - return Object.keys(args).length === 0 - }) - //m.route.buildQueryString - test(function() { - var string = m.route.buildQueryString({ - foo: "bar", - hello: ["world", "mars", "mars"], - world: { - test: 3 - }, - bam: "", - yup: null, - removed: undefined - }) - return string === "foo=bar&hello=world&hello=mars&world%5Btest%5D=3&bam=&yup" - }) - test(function() { - var string = m.route.buildQueryString({}); - return string === "" - }) - //m.prop - test(function() { - var prop = m.prop("test") - return prop() === "test" - }) - test(function() { - var prop = m.prop("test") - prop("foo") - return prop() === "foo" - }) - test(function() { - var prop = m.prop("test") - return JSON.stringify(prop) === '"test"' - }) - test(function() { - var obj = {prop: m.prop("test")} - return JSON.stringify(obj) === '{"prop":"test"}' - }) - test(function() { - var defer = m.deferred() - var prop = m.prop(defer.promise) - defer.resolve("test") - - return prop() === "test" - }) - test(function() { - var defer = m.deferred() - var prop = m.prop(defer.promise).then(function () { - return "test2" - }) - defer.resolve("test") - - return prop() === "test2" - }) - test(function() { - var prop = m.prop(null) - return prop() === null - }) - - //m.request - test(function() { - var prop = m.request({method: "GET", url: "test"}) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop().method === "GET" && prop().url === "test" - }) - test(function() { - var prop = m.request({method: "GET", url: "test"}).then(function() {return "foo"}) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop() === "foo" - }) - test(function() { - var prop = m.request({method: "POST", url: "http://domain.com:80", data: {}}).then(function(value) {return value}) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop().url === "http://domain.com:80" - }) - test(function() { - var prop = m.request({method: "POST", url: "http://domain.com:80/:test1", data: {test1: "foo"}}).then(function(value) {return value}) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop().url === "http://domain.com:80/foo" - }) - test(function() { - var error = m.prop("no error") - var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new Error("error occurred")}}).then(null, error) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop().message === "error occurred" && error().message === "error occurred" - }) - test(function() { - var error = m.prop("no error") - var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new Error("error occurred")}})["catch"](error) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop().message === "error occurred" && error().message === "error occurred" - }) - test(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) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop() === "foo" && data() === "" - }) - test(function() { - // Data returned by finally() functions do *not* propagate to next promise. - var data = m.prop(""); - var prop = m.request({method: "GET", url: "test"}).then(function() {return "foo"})["finally"](function() {return "bar"}).then(data) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop() === "foo" && data() === "foo" - }) - test(function() { - // Errors thrown in finally() can be caught by next promise. - var error = m.prop("no error") - var prop = m.request({method: "GET", url: "test"}).then(function() {return "foo"})["finally"](function() {throw new Error("error occurred")})["catch"](error) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop().message === "error occurred" && error().message === "error occurred" - }) - test(function() { - // Promise finally() method occurrs after catch(). - var error = m.prop("no error") - var lastly = function() { error("lastly") }; - var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new Error("error occurred")}})["catch"](error)["finally"](lastly) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop().message === "error occurred" && error() === "lastly" - }) - test(function() { - var error = m.prop("no error"), exception - var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new TypeError("error occurred")}}).then(null, error) - try {mock.XMLHttpRequest.$instances.pop().onreadystatechange()} - catch (e) {exception = e} - return prop() === undefined && error() === "no error" && exception.message === "error occurred" - }) - test(function() { - var error = m.prop("no error") - m.request({method: "POST", url: "test", data: {foo: 1}}).then(null, error) - var xhr = mock.XMLHttpRequest.$instances.pop() - xhr.onreadystatechange() - return xhr.$headers["Content-Type"] === "application/json; charset=utf-8" - }) - test(function() { - var error = m.prop("no error") - m.request({method: "POST", url: "test"}).then(null, error) - var xhr = mock.XMLHttpRequest.$instances.pop() - xhr.onreadystatechange() - return xhr.$headers["Content-Type"] === undefined - }) - test(function() { - var prop = m.request({method: "POST", url: "test", initialValue: "foo"}).then(function(data) { return data; }) - var initialValue = prop(); - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - - return initialValue === "foo" - }) - test(function() { - var prop = m.request({method: "POST", url: "test", initialValue: "foo"}) - var initialValue = prop(); - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - - return initialValue === "foo" - }) - test(function() { - var prop = m.request({method: "POST", url: "test", initialValue: "foo"}).then(function() {return "bar"}) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop() === "bar" - }) - test(function() { - var prop = m.request({method: "GET", url: "test", data: {foo: 1}}) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop().url === "test?foo=1" - }) - test(function() { - var prop = m.request({method: "POST", url: "test", data: {foo: 1}}) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop().url === "test" - }) - test(function() { - var prop = m.request({method: "GET", url: "test", data: {foo: [1, 2]}}) - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop().url === "test?foo=1&foo=2" - }) - test(function() { - var value - var prop1 = m.request({method: "GET", url: "test", initialValue: 123}) - var val1 = prop1() - var prop2 = prop1.then(function() {return 1}) - var val2 = prop2() - var prop3 = prop1.then(function(v) {value = v}) - var val3 = prop3() - mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return val1 === 123 && val2 === 123 && val3 === 123 && value.method === "GET" && value.url === "test" - }) - - // m.request over jsonp - test(function(){ - // script tags cannot be appended directly on the document - var body = mock.document.createElement("body") - mock.document.body = body - mock.document.appendChild(body) - - var error = m.prop("no error") - var data - m.request({url: "/test", dataType: "jsonp"}).then(function(received) {data = received}, error) - var callbackKey = Object.keys(mock).filter(function(globalKey){ - return globalKey.indexOf("mithril_callback") > -1 - }).pop() - var scriptTag = [].slice.call(mock.document.getElementsByTagName("script")).filter(function(script){ - return script.src.indexOf(callbackKey) > -1 - }).pop() - mock[callbackKey]({foo: "bar"}) - mock.document.removeChild(body) - return scriptTag.src.indexOf("/test?callback=mithril_callback") > -1 && data.foo === "bar" - }) - test(function(){ - // script tags cannot be appended directly on the document - var body = mock.document.createElement("body") - mock.document.body = body - mock.document.appendChild(body) - - var error = m.prop("no error") - var data - m.request({url: "/test", dataType: "jsonp", callbackKey: "jsonpCallback"}).then(function(received) {data = received}, error) - var callbackKey = Object.keys(mock).filter(function(globalKey){ - return globalKey.indexOf("mithril_callback") > -1 - }).pop() - var scriptTag = [].slice.call(mock.document.getElementsByTagName("script")).filter(function(script){ - return script.src.indexOf(callbackKey) > -1 - }).pop(); - mock[callbackKey]({foo: "bar1"}) - mock.document.removeChild(body) - return scriptTag.src.indexOf("/test?jsonpCallback=mithril_callback") > -1 && data.foo === "bar1" - }) - test(function(){ - var body = mock.document.createElement("body") - mock.document.body = body - mock.document.appendChild(body) - - var req = m.request({url: "/test", dataType: "jsonp"}) - var callbackKey = Object.keys(mock).filter(function(globalKey){ - return globalKey.indexOf("mithril_callback") > -1 - }).pop() - ;[].slice.call(mock.document.getElementsByTagName("script")).filter(function(script){ - return script.src.indexOf(callbackKey) > -1 - }).pop(); - mock[callbackKey]({foo: "bar1"}) - var out = {foo: "bar1"} - mock.document.removeChild(body) - return JSON.stringify(out) === JSON.stringify(req()) - }) - test(function(){ - var body = mock.document.createElement("body") - mock.document.body = body - mock.document.appendChild(body) - - m.request({url: "/test", dataType: "jsonp", data: {foo: "bar"}}) - var callbackKey = Object.keys(mock).filter(function(globalKey){ - return globalKey.indexOf("mithril_callback") > -1 - }).pop() - var scriptTag = [].slice.call(mock.document.getElementsByTagName("script")).filter(function(script){ - return script.src.indexOf(callbackKey) > -1 - }).pop(); - mock[callbackKey]({foo: "bar"}) - return scriptTag.src.indexOf("foo=bar") > -1 - }) - test(function(){ - var body = mock.document.createElement("body"); - mock.document.body = body; - mock.document.appendChild(body); - - m.request({url: "/test", dataType: "jsonp", method: "GET", data: {foo: "bar"}}); - var callbackKey = Object.keys(mock).filter(function(globalKey){ - return globalKey.indexOf("mithril_callback") > -1 - }).pop() - var scriptTag = [].slice.call(mock.document.getElementsByTagName("script")).filter(function(script){ - return script.src.indexOf(callbackKey) > -1 - }).pop(); - mock[callbackKey]({foo: "bar"}) - mock.document.removeChild(body); - return scriptTag.src.match(/foo=bar/g).length === 1; - }) - - //m.deferred - test(function() { - var value - var deferred = m.deferred() - deferred.promise.then(function(data) {value = data}) - deferred.resolve("test") - return value === "test" - }) - test(function() { - var value - var deferred = m.deferred() - deferred.promise.then(function() {return "foo"}).then(function(data) {value = data}) - deferred.resolve("test") - return value === "foo" - }) - test(function() { - var value - var deferred = m.deferred() - deferred.promise.then(null, function(data) {value = data}) - deferred.reject("test") - return value === "test" - }) - test(function() { - var value - var deferred = m.deferred() - deferred.promise["catch"](function(data) {value = data}) - deferred.reject("test") - return value === "test" - }) - test(function() { - var value - var deferred = m.deferred() - deferred.promise.then(null, function() {return "foo"}).then(function(data) {value = data}) - deferred.reject("test") - return value === "foo" - }) - test(function() { - var value - var deferred = m.deferred() - deferred.promise["catch"](function() {return "foo"}).then(function(data) {value = data}) - deferred.reject("test") - return value === "foo" - }) - test(function() { - var value1, value2 - var deferred = m.deferred() - deferred.promise.then(function() {throw new Error()}).then(function() {value1 = 1}, function(data) {value2 = data}) - deferred.resolve("test") - return value1 === undefined && value2 instanceof Error - }) - test(function() { - //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 test tests two implementation details that differ from the Promises/A+ spec: - //1) A+ requires the `then` callback to be called in a different event loop from the resolve call, i.e. it must be asynchronous (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) - //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 - var value1, value2, value3 - var deferred = m.deferred() - try { - deferred.promise - .then(function() {foo.bar.baz}) //throws ReferenceError - .then(function() {value1 = 1}, function(data) {value2 = data}) - deferred.resolve("test") - } - catch (e) {value3 = e} - return value1 === undefined && value2 === undefined && (value3 instanceof ReferenceError || value3 instanceof TypeError) - }) - test(function() { - 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) - return value1 === 1 && value2 === 2 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/80 - var deferred = m.deferred(), value - deferred.resolve(1) - deferred.promise.then(function(data) { - value = data - }) - return value === 1 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/80 - var deferred = m.deferred(), value - deferred.reject(1) - deferred.promise.then(null, function(data) { - value = data - }) - return value === 1 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/80 - var deferred = m.deferred(), value - deferred.resolve(1) - deferred.resolve(2) - deferred.promise.then(function(data) { - value = data - }) - return value === 1 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/80 - var deferred = m.deferred(), value - deferred.promise.then(function(data) { - value = data - }) - deferred.resolve(1) - deferred.resolve(2) - return value === 1 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/80 - var deferred = m.deferred(), value1, value2 - deferred.promise.then(function(data) { - value1 = data - }, function(data) { - value2 = data - }) - deferred.resolve(1) - deferred.reject(2) - return value1 === 1 && value2 === undefined - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/80 - var deferred = m.deferred(), value1, value2 - deferred.promise.then(function(data) { - value1 = data - }, function(data) { - value2 = data - }) - deferred.reject(1) - deferred.resolve(2) - return value1 === undefined && value2 === 1 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/80 - var deferred = m.deferred(), value - deferred.promise.then(null, function(data) { - value = data - }) - deferred.reject(1) - deferred.reject(2) - return value === 1 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/85 - var deferred = m.deferred(), value - deferred.resolve() - deferred.promise.then(function() { - value = 1 - }) - return value === 1 - }) - test(function() { - //https://github.com/lhorie/mithril.js/issues/85 - var deferred = m.deferred(), value - deferred.reject() - deferred.promise.then(null, function() { - value = 1 - }) - return value === 1 - }) - test(function() { - var deferred = m.deferred() - deferred.resolve(1) - return deferred.promise() === 1 - }) - test(function() { - var deferred = m.deferred() - var promise = deferred.promise.then(function(data) {return data + 1}) - deferred.resolve(1) - return promise() === 2 - }) - test(function() { - var deferred = m.deferred() - deferred.reject(1) - return deferred.promise() === undefined - }) - - //m.sync - test(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") - return value[0] === "test" && value[1] === "foo" - }) - test(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") - return value[0] === "test" && value[1] === "foo" - }) - test(function() { - var value - var deferred = m.deferred() - m.sync([deferred.promise])["catch"](function(data) {value = data}) - deferred.reject("fail") - return value[0] === "fail" - }) - test(function() { - var value = 1 - m.sync([]).then(function() {value = 2}) - return value === 2 - }) - test(function() { - var success - m.sync([]).then(function(value) {success = value instanceof Array}) - return success - }) - - //m.startComputation/m.endComputation - test(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() - return root.childNodes[0].nodeValue === "foo" - }) - - // config context - test(function() { - var root = mock.document.createElement("div") - - var success = false; - m.render(root, m("div", {config: function(elem, isInitialized, ctx) {ctx.data=1}})); - m.render(root, m("div", {config: function(elem, isInitialized, ctx) {success = ctx.data===1}})); - return success; - }) - - // more complex config context - test(function() { - var root = mock.document.createElement("div") - - var idx = 0; - var success = true; - var statefulConfig = function(elem, isInitialized, ctx) {ctx.data=idx++} - var node = m("div", {config: statefulConfig}); - m.render(root, [node, node]); - - idx = 0; - var checkConfig = function(elem, isInitialized, ctx) { - success = success && (ctx.data === idx++) - } - node = m("div", {config: checkConfig}); - m.render(root, [node, node]); - return success; - }) - - //console.log presence - test(function() { - return m.deps.factory.toString().indexOf("console") < 0 - }) - test(function() { - return m.deps.factory.toString().indexOf("document.write") < 0 - }) -} - -//mock -testMithril(mock.window); - -test.print(function(value) {console.log(value)}) diff --git a/tests/mock.js b/tests/mock.js deleted file mode 100644 index fb97f6d5..00000000 --- a/tests/mock.js +++ /dev/null @@ -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 -}()) diff --git a/tests/svg.html b/tests/svg.html deleted file mode 100644 index b57adf93..00000000 --- a/tests/svg.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - SVG test - - - -

    Since it's not possible to test SVG functionality from a NodeJS environment, this page can be used to test it in a browser.

    -

    This page should contain:

    -
      -
    • an HTML link labeled "HTML link"
    • -
    • an SVG link labeled "SVG link"
    • -
    • a tilted blue square
    • -
    • a cat picture
    • -
    • an animated line drawing
    • -
    • a clock with the current time
    • -
    -

    The links should open in a new tab. All items should display title tooltips when hovered over.

    - -
    - - - - diff --git a/tests/test.js b/tests/test.js deleted file mode 100644 index 844cf8aa..00000000 --- a/tests/test.js +++ /dev/null @@ -1,28 +0,0 @@ -if (!this.console) { - var log = function(value) {document.write("
    " + value + "
    ")} - 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") - } -}