From 2c2ca73c223908877ed5362deff7f5fd50d2a41c Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Tue, 10 Jan 2017 21:29:34 +0100 Subject: [PATCH 01/68] [ospec] Add the possibility to name new test suites --- ospec/ospec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ospec/ospec.js b/ospec/ospec.js index f153e841..36a8807f 100644 --- a/ospec/ospec.js +++ b/ospec/ospec.js @@ -1,8 +1,10 @@ "use strict" -module.exports = new function init() { +module.exports = new function init(name) { var spec = {}, subjects = [], results = [], only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty + if (name != null) spec[name] = ctx = {} + function o(subject, predicate) { if (predicate === undefined) return new Assert(subject) ctx[unique(subject)] = predicate @@ -210,6 +212,7 @@ module.exports = new function init() { } } console.log( + (name ? name + ": " : "") + results.length + " assertions completed in " + Math.round(new Date - start) + "ms, " + "of which " + results.filter(function(result){return result.error}).length + " failed" ) From 5c616c0fbbfb4dc520162f056730397fcdfd457b Mon Sep 17 00:00:00 2001 From: ludbek Date: Wed, 5 Apr 2017 22:01:21 +0545 Subject: [PATCH 02/68] removed console.log --- render/tests/test-hyperscript.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/render/tests/test-hyperscript.js b/render/tests/test-hyperscript.js index 3ef03358..641eea2e 100644 --- a/render/tests/test-hyperscript.js +++ b/render/tests/test-hyperscript.js @@ -17,13 +17,6 @@ o.spec("hyperscript", function() { o(vnode.tag).equals("a") }) o("v1.0.1 bug-for-bug regression suite", function(){ - o - console.log(m('a', { - class: null - }).attrs, { - class: undefined, - className: null - }) o(m('a', { class: null }).attrs).deepEquals({ @@ -47,12 +40,6 @@ o.spec("hyperscript", function() { class: undefined, className: true }) - console.log(m('a.x', { - class: null - }).attrs, { - class: undefined, - className: "x null" - }) o(m('a.x', { class: null }).attrs).deepEquals({ From 6d23e0913c53e9ab549a66efd1f73f2862877f10 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Thu, 6 Apr 2017 20:41:55 +0000 Subject: [PATCH 03/68] docs: try to improve releasing docs --- docs/releasing.md | 77 ++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/docs/releasing.md b/docs/releasing.md index 12ceb19b..7a9cbe3c 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -1,49 +1,56 @@ -# Releasing +# Mithril Release Processes -## Publishing to NPM +## Releasing a new Mithril version -Releasing new builds of mithril to NPM is mostly automated via `npm run release` +### Prepare the release -1. Update information in `docs/change-log.md` to match reality & the new version that will be released -2. `npm run release ` +1. Determine patch level of the change +2. Update information in `docs/change-log.md` to match reality & the new version that will be released -All further steps are automated and run as follows: +### Merge from `next` to `master` -3. New bundles are generated using updated version -4. Tests are run -5. Linting is run (but doesn't fail build) -6. Version number in package.json is incremented -7. `git add` called on bundle output -8. `package.json` and updated bundles are committed to git -9. previous commit is tagged using new version number -10. `git push --follow-tags` pushes up new version commit & tag to github -11. Travis sees new release, starts build -12. Travis generates new bundles before running tests -13. Travis runs tests -14. Travis lints files (but can't fail build) -15. If build fails, abort -16. Build succeeded, so travis will commit back any changes to the repo (but there won't be any) -17. Travis sees that this commit has a tag associated with it -18. Travis will use the encrypted npm creds in `.travis.yml` to publish a new version to npm +3. Switch to `master` and merge `next` on top of it -## Publishing a GitHub release +```bash +$ git co master +$ git merge next +``` -Happens automatically as part of the [Publishing to NPM](#publishing-to-npm) process described above. +4. Clean & update npm dependencies and ensure the tests are passing. -Does require a manual description to be added though, as the auto-generated one isn't very interesting. I suggest coming up with a fun title & then copying the `docs/change-log.md` entry for the build. +```bash +$ npm prune +$ npm i +$ npm test +``` -## Updating `docs/change-log.md` +### Publish the release -This is still a manual process, I'm sorry. +5. `npm run release `, see the docs for [`npm version`](https://docs.npmjs.com/cli/version) +6. Travis will push the new release to npm & create a GitHub release -## Updating docs (outside of a new version) +### Update the GitHub release + +7. The GitHub Release will require a manual description & title to be added. I suggest coming up with a fun title & then copying the `docs/change-log.md` entry for the build. + +## Updating mithril.js.org Fixes to documentation can land whenever, updates to the site are published via Travis. -1. `git co next` -2. `git pull lhorie next` -3. `git co master` -4. `git co next -- ./docs` -5. Ensure that no new features are added -6. `git push lhorie` -7. After the Travis build completes new docs should appear in ~3 minutes +```bash +# These steps assume that lhorie/mithril.js is a git remote named "lhorie" + +# Ensure your next branch is up to date +$ git co next +$ git pull lhorie next + +# Splat the docs folder from next onto master +$ git co master +$ git co next -- ./docs + +# Manually ensure that no new feature docs were added + +$ git push lhorie +``` + +After the Travis build completes the updated docs should appear on https://mithril.js.org in a few minutes. From 9a54b12702cde040a3dc445be17a3be33f2b3364 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Thu, 6 Apr 2017 21:01:52 +0000 Subject: [PATCH 04/68] docs: fix some links --- docs/change-log.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/change-log.md b/docs/change-log.md index 042b8008..9c493aa4 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -12,8 +12,8 @@ #### Bug fixes -- hyperscript: Allow `0` as the second argument to `m()` - [#1752](https://github.com/lhorie/mithril.js/issues/#1752) / [#1753](https://github.com/lhorie/mithril.js/pull/#1753) ([@StephanHoyer](https://github.com/StephanHoyer)) -- hyperscript: restore `attrs.class` handling to what it was in v1.0.1 - [#1764](https://github.com/lhorie/mithril.js/issues/#1764) / [#1769](https://github.com/lhorie/mithril.js/pull/#1769) +- hyperscript: Allow `0` as the second argument to `m()` - [#1752](https://github.com/lhorie/mithril.js/issues/1752) / [#1753](https://github.com/lhorie/mithril.js/pull/1753) ([@StephanHoyer](https://github.com/StephanHoyer)) +- hyperscript: restore `attrs.class` handling to what it was in v1.0.1 - [#1764](https://github.com/lhorie/mithril.js/issues/1764) / [#1769](https://github.com/lhorie/mithril.js/pull/1769) - documentation improvements ([@JAForbes](https://github.com/JAForbes), [@smuemd](https://github.com/smuemd), [@hankeypancake](https://github.com/hankeypancake)) ### v1.1.0 From 34d6a710a9c343797f0e004c4e2db8dd00ab8a74 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Fri, 7 Apr 2017 12:56:31 -0700 Subject: [PATCH 05/68] style: fix quotes (#1787) --- render/tests/test-hyperscript.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/render/tests/test-hyperscript.js b/render/tests/test-hyperscript.js index 641eea2e..4f6c4548 100644 --- a/render/tests/test-hyperscript.js +++ b/render/tests/test-hyperscript.js @@ -17,89 +17,89 @@ o.spec("hyperscript", function() { o(vnode.tag).equals("a") }) o("v1.0.1 bug-for-bug regression suite", function(){ - o(m('a', { + o(m("a", { class: null }).attrs).deepEquals({ class: undefined, className: null }) - o(m('a', { + o(m("a", { class: undefined }).attrs).deepEquals({ class: undefined, }) - o(m('a', { + o(m("a", { class: false }).attrs).deepEquals({ class: undefined, className: false }) - o(m('a', { + o(m("a", { class: true }).attrs).deepEquals({ class: undefined, className: true }) - o(m('a.x', { + o(m("a.x", { class: null }).attrs).deepEquals({ class: undefined, className: "x null" }) - o(m('a.x', { + o(m("a.x", { class: undefined }).attrs).deepEquals({ class: undefined, className: "x" }) - o(m('a.x', { + o(m("a.x", { class: false }).attrs).deepEquals({ class: undefined, className: "x false" }) - o(m('a.x', { + o(m("a.x", { class: true }).attrs).deepEquals({ class: undefined, className: "x true" }) - o(m('a', { + o(m("a", { className: null }).attrs).deepEquals({ className: null }) - o(m('a', { + o(m("a", { className: undefined }).attrs).deepEquals({ className: undefined }) - o(m('a', { + o(m("a", { className: false }).attrs).deepEquals({ className: false }) - o(m('a', { + o(m("a", { className: true }).attrs).deepEquals({ className: true }) - o(m('a.x', { + o(m("a.x", { className: null }).attrs).deepEquals({ className: "x" }) - o(m('a.x', { + o(m("a.x", { className: undefined }).attrs).deepEquals({ className: "x" }) - o(m('a.x', { + o(m("a.x", { className: false }).attrs).deepEquals({ className: "x" }) - o(m('a.x', { + o(m("a.x", { className: true }).attrs).deepEquals({ className: "x true" From 5e654452e19e6be5ef8d7cf369490d30eb7b3442 Mon Sep 17 00:00:00 2001 From: spacejack Date: Sun, 9 Apr 2017 15:19:24 -0400 Subject: [PATCH 06/68] Remove url interpolation from tutorial app --- docs/simple-application.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/simple-application.md b/docs/simple-application.md index b226aa2c..0342096d 100644 --- a/docs/simple-application.md +++ b/docs/simple-application.md @@ -392,8 +392,7 @@ var User = { load: function(id) { return m.request({ method: "GET", - url: "https://rem-rest-api.herokuapp.com/api/users/:id", - data: {id: id}, + url: "https://rem-rest-api.herokuapp.com/api/users/" + id, withCredentials: true, }) .then(function(result) { @@ -508,8 +507,7 @@ var User = { load: function(id) { return m.request({ method: "GET", - url: "https://rem-rest-api.herokuapp.com/api/users/:id", - data: {id: id}, + url: "https://rem-rest-api.herokuapp.com/api/users/" + id, withCredentials: true, }) .then(function(result) { @@ -520,7 +518,7 @@ var User = { save: function() { return m.request({ method: "PUT", - url: "https://rem-rest-api.herokuapp.com/api/users/:id", + url: "https://rem-rest-api.herokuapp.com/api/users/" + User.current.id, data: User.current, withCredentials: true, }) From 96fb9a5bbd8fe18c75cfbdd787013d9cf684a430 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Mon, 10 Apr 2017 23:31:44 -0700 Subject: [PATCH 07/68] docs: add examples for tutorial --- docs/index.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/index.md b/docs/index.md index 86aca2cc..504dd2b1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -85,6 +85,11 @@ m.render(root, "My first app") As you can see, you use the same code to both create and update HTML. Mithril automatically figures out the most efficient way of updating the text, rather than blindly recreating it from scratch. +#### Live Example + +

See the Pen Mithril Hello World by Pat Cavit (@tivac) on CodePen.

+ + --- ### DOM elements @@ -119,6 +124,11 @@ m("main", [ ]) ``` +#### Live Example + +

See the Pen Simple Mithril Example by Pat Cavit (@tivac) on CodePen.

+ + Note: If you prefer `` syntax, [it's possible to use it via a Babel plugin](jsx.md). ```jsx @@ -185,6 +195,11 @@ You can now update the label of the button by clicking the button. Since we used If you're wondering about performance, it turns out Mithril is very fast at rendering updates, because it only touches the parts of the DOM it absolutely needs to. So in our example above, when you click the button, the text in it is the only part of the DOM Mithril actually updates. +#### Live Example + +

See the Pen Mithril Component Example by Pat Cavit (@tivac) on CodePen.

+ + --- ### Routing @@ -218,6 +233,11 @@ The `"/splash"` right after `root` means that's the default route, i.e. if the h Also, as you would expect, clicking on the link on the splash page takes you to the click counter screen we created earlier. Notice that now your URL will point to `http://localhost/#!/hello`. You can navigate back and forth to the splash page using the browser's back and next button. +#### Live Example + +

See the Pen Mithril Routing Example by Pat Cavit (@tivac) on CodePen.

+ + --- ### XHR @@ -260,6 +280,11 @@ var Hello = { Clicking the button should now update the count. +#### Live Example + +

See the Pen Mithril XHR Example by Pat Cavit (@tivac) on CodePen.

+ + --- We covered how to create and update HTML, how to create components, routes for a Single Page Application, and interacted with a server via XHR. From 451cf00951ca268e9bedfbe733482d5f59971d61 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Mon, 10 Apr 2017 23:31:56 -0700 Subject: [PATCH 08/68] docs: add incrementing value to repeated anchors --- docs/generate.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/generate.js b/docs/generate.js index 52bc3e70..4bc76499 100644 --- a/docs/generate.js +++ b/docs/generate.js @@ -27,6 +27,7 @@ function generate(pathname) { if (pathname.match(/\.md$/)) { var outputFilename = pathname.replace(/\.md$/, ".html") var markdown = fs.readFileSync(pathname, "utf-8") + var anchors = {} var fixed = markdown .replace(/`((?:\S| -> |, )+)(\|)(\S+)`/gim, function(match, a, b, c) { // fix pipes in code tags return "" + (a + b + c).replace(/\|/g, "|") + "" @@ -53,6 +54,12 @@ function generate(pathname) { .replace(/(.+?)<\/h.>/gim, function(match, n, id, text) { // fix anchors var anchor = text.toLowerCase().replace(/<(\/?)code>/g, "").replace(/.+?<\/a>/g, "").replace(/\.|\[|\]|"|\/|\(|\)/g, "").replace(/\s/g, "-"); + if(anchor in anchors) { + anchor += ++anchors[anchor] + } else { + anchors[anchor] = 0; + } + return `${text}`; }) fs.writeFileSync("./dist/archive/v" + version + "/" + outputFilename.replace(/^docs\//, ""), html, "utf-8") From 4cfd0c4070eaf97bc31bdf9435f3f1598a1f6e1a Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Mon, 10 Apr 2017 23:43:10 -0700 Subject: [PATCH 09/68] docs: load mithril on mithril.js.org For futzing around in the dev console --- docs/generate.js | 2 +- docs/index.md | 2 ++ docs/layout.html | 5 +++-- package.json | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/generate.js b/docs/generate.js index 52bc3e70..4a6e8b54 100644 --- a/docs/generate.js +++ b/docs/generate.js @@ -48,7 +48,7 @@ function generate(pathname) { var title = fixed.match(/^#([^\n\r]+)/i) || [] var html = layout .replace(/Mithril\.js<\/title>/, "<title>" + title[1] + " - Mithril.js") - .replace(/\[version\]/, version) // update version + .replace(/\[version\]/g, version) // update version .replace(/\[body\]/, markedHtml) .replace(/(.+?)<\/h.>/gim, function(match, n, id, text) { // fix anchors var anchor = text.toLowerCase().replace(/<(\/?)code>/g, "").replace(/.+?<\/a>/g, "").replace(/\.|\[|\]|"|\/|\(|\)/g, "").replace(/\s/g, "-"); diff --git a/docs/index.md b/docs/index.md index 86aca2cc..5a634a3e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -65,6 +65,8 @@ Let's create an HTML file to follow along: ``` +Mithril is also loaded onto this page already, so you can start poking at the `m` object in the developer console right away if you'd like! + --- ### Hello world diff --git a/docs/layout.html b/docs/layout.html index f5ce0231..02e07a0b 100644 --- a/docs/layout.html +++ b/docs/layout.html @@ -26,8 +26,9 @@ License: MIT. © Leo Horie. - - + + + + --- ### Hello world From b718ae6f32d87c421f85217f5f57b5a089d99588 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Tue, 11 Apr 2017 08:28:58 +0000 Subject: [PATCH 11/68] Bundled output for commit d01e8d458e4c2ce91fcd61350276f0eb4f8ef0ab [skip ci] --- mithril.js | 2 +- mithril.min.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mithril.js b/mithril.js index 1c1495d4..1b1a7b0a 100644 --- a/mithril.js +++ b/mithril.js @@ -1220,7 +1220,7 @@ m.request = requestService.request m.jsonp = requestService.jsonp m.parseQueryString = parseQueryString m.buildQueryString = buildQueryString -m.version = "1.0.1" +m.version = "1.1.1" m.vnode = Vnode if (typeof module !== "undefined") module["exports"] = m else window.m = m diff --git a/mithril.min.js b/mithril.min.js index 35332cf2..1a5bb54a 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -40,4 +40,4 @@ d("hash");default:return d("pathname").slice(q.prefix.length)+d("search")+d("has e={},f=g(a,e,e),l=b.history.state;if(null!=l)for(var m in l)e[m]=l[m];for(var u in d)if(l=new RegExp("^"+u.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$"),l.test(f)){f.replace(l,function(){for(var b=u.match(/:[^\/]+/g)||[],g=[].slice.call(arguments,1,-2),f=0;f Date: Tue, 11 Apr 2017 13:05:12 -0400 Subject: [PATCH 12/68] Support vnode event callbacks --- render/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/render.js b/render/render.js index df26a1df..b9a54b08 100644 --- a/render/render.js +++ b/render/render.js @@ -558,7 +558,7 @@ module.exports = function($window) { function updateEvent(vnode, key, value) { var element = vnode.dom var callback = typeof onevent !== "function" ? value : function(e) { - var result = value.call(element, e) + var result = value.call(element, e, vnode) onevent.call(element, e) return result } From 81b7ff56ee2c1b0f7a9a7d037a2dc8ab9c1eb26d Mon Sep 17 00:00:00 2001 From: Isiah Meadows Date: Tue, 11 Apr 2017 13:09:38 -0400 Subject: [PATCH 13/68] Add tests for event `vnode` second argument --- render/tests/test-event.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/render/tests/test-event.js b/render/tests/test-event.js index 31d2d012..6b75397e 100644 --- a/render/tests/test-event.js +++ b/render/tests/test-event.js @@ -28,6 +28,7 @@ o.spec("event", function() { o(spy.this).equals(div.dom) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) + o(spy.args[1]).equals(div) o(onevent.callCount).equals(1) o(onevent.this).equals(div.dom) o(onevent.args[0].type).equals("click") @@ -64,6 +65,7 @@ o.spec("event", function() { o(spy.this).equals(div.dom) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) + o(spy.args[1]).equals(div) o(onevent.callCount).equals(1) o(onevent.this).equals(div.dom) o(onevent.args[0].type).equals("click") @@ -85,6 +87,7 @@ o.spec("event", function() { o(spy.this).equals(div.dom) o(spy.args[0].type).equals("transitionend") o(spy.args[0].target).equals(div.dom) + o(spy.args[1]).equals(div) o(onevent.callCount).equals(1) o(onevent.this).equals(div.dom) o(onevent.args[0].type).equals("transitionend") From 3409a72c4e422f7172203a0fe0a8221bb41c05c1 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Tue, 11 Apr 2017 10:31:48 -0700 Subject: [PATCH 14/68] docs: add code of conduct and nav link --- docs/code-of-conduct.md | 74 +++++++++++++++++++++++++++++++++++++++++ docs/nav-guides.md | 1 + 2 files changed, 75 insertions(+) create mode 100644 docs/code-of-conduct.md diff --git a/docs/code-of-conduct.md b/docs/code-of-conduct.md new file mode 100644 index 00000000..50ebe61d --- /dev/null +++ b/docs/code-of-conduct.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [github@patcavit.com](mailto:github@patcavit.com?subject=Mithril Code of Conduct). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/docs/nav-guides.md b/docs/nav-guides.md index 3a8c8313..db55712a 100644 --- a/docs/nav-guides.md +++ b/docs/nav-guides.md @@ -19,6 +19,7 @@ - [Mithril Jobs](https://github.com/lhorie/mithril.js/wiki/JOBS) - [How to contribute](contributing.md) - [Credits](credits.md) + - [Code of Conduct](code-of-conduct.md) - Misc - [Framework comparison](framework-comparison.md) - [Change log/Migration](change-log.md) From a92ffb307799a1d216eb969ca0099729d301d922 Mon Sep 17 00:00:00 2001 From: Isiah Meadows Date: Tue, 11 Apr 2017 13:33:46 -0400 Subject: [PATCH 15/68] Clarify events, document extra `vnode` property --- docs/hyperscript.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/hyperscript.md b/docs/hyperscript.md index 8ebe297b..38a6808d 100644 --- a/docs/hyperscript.md +++ b/docs/hyperscript.md @@ -181,7 +181,7 @@ Mithril does not attempt to add units to number values. ### Events -Mithril supports event handler binding for all DOM events, including events whose specs do not define an `on${event}` property, such as `touchstart` +Mithril supports event handler binding for all DOM events, using `addEventListener` under the hood to add events in case they don't have a corresponding `on${event}` property, such as the native `touchstart` or third-party events like Bootstrap's `show.bs.modal`. ```javascript function doSomething(e) { @@ -191,6 +191,38 @@ function doSomething(e) { m("div", {onclick: doSomething}) ``` +You may access the element itself through `this` like so, which is useful for things like form validation: + +```javascript +function submit(e) { + this.validate() + m.request("api/form-endpoint", { + method: "POST", + data: new FormData(this), + background: true + }) + .then(function () { + m.route.set("/form-submit") + }) +} + +m("form", {onsubmit: submit}, [ + // ... +]) +``` + +It also provides access to the event's own literal vnode via `vnode`, so you may easily access its `attrs` and `children` through it, from an external function. + +```javascript +function handleClick(e, vnode) { + vnode.attrs.context.validateValue(this.value) +} + +m("div", [ + m("input", {type: "text", context: this, onclick: handleClick}) +]) +``` + --- ### Properties From 05990024e1913863e3f22ad97d03b886f0cf41e4 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Tue, 11 Apr 2017 11:17:25 -0700 Subject: [PATCH 16/68] docs: flesh out releasing process further --- docs/releasing.md | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/docs/releasing.md b/docs/releasing.md index 7a9cbe3c..a137e1d0 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -4,19 +4,44 @@ ### Prepare the release -1. Determine patch level of the change -2. Update information in `docs/change-log.md` to match reality & the new version that will be released +1. Ensure your local branch is up to date + +```bash +$ git co next +$ git pull --rebase lhorie next +``` + +2. Determine patch level of the change +3. Update information in `docs/change-log.md` to match reality of the new version being prepared for release +4. Commit changes to `next` + +``` +$ git add . +$ git commit -m "Preparing for release" + +# Push to your branch +$ git push + +# Push to lhorie/mithril.js +$ git push lhorie next +``` ### Merge from `next` to `master` -3. Switch to `master` and merge `next` on top of it +5. Switch to `master` and make sure it's up to date ```bash $ git co master +$ git pull --rebase lhorie master +``` + +6. merge `next` on top of it + +```bash $ git merge next ``` -4. Clean & update npm dependencies and ensure the tests are passing. +7. Clean & update npm dependencies and ensure the tests are passing. ```bash $ npm prune @@ -26,12 +51,19 @@ $ npm test ### Publish the release -5. `npm run release `, see the docs for [`npm version`](https://docs.npmjs.com/cli/version) -6. Travis will push the new release to npm & create a GitHub release +8. `npm run release `, see the docs for [`npm version`](https://docs.npmjs.com/cli/version) +9. The changes will be automatically pushed to your fork +10. Push the changes to `lhorie/mithril.js` + +```bash +$ git push lhorie master +``` + +11. Travis will push the new release to npm & create a GitHub release ### Update the GitHub release -7. The GitHub Release will require a manual description & title to be added. I suggest coming up with a fun title & then copying the `docs/change-log.md` entry for the build. +12. The GitHub Release will require a manual description & title to be added. I suggest coming up with a fun title & then copying the `docs/change-log.md` entry for the build. ## Updating mithril.js.org From 7ad1b2edcf794b654f4f5f88e153afcf0e1679ff Mon Sep 17 00:00:00 2001 From: cavemansspa Date: Wed, 12 Apr 2017 10:23:50 -0400 Subject: [PATCH 17/68] Add example for mounting a component with args --- docs/mount.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/mount.md b/docs/mount.md index 517e5cd3..9e0957a6 100644 --- a/docs/mount.md +++ b/docs/mount.md @@ -27,6 +27,10 @@ var Counter = { m.mount(document.body, Counter) ``` +To pass arguments when mounting a component use: +```javascript +m.mount(element, {view: function () {return m(Component, attrs)}}) +``` --- ### Signature From 034ef4d31876dce0373b9c74ce7eafc13b4c6394 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Thu, 13 Apr 2017 09:42:22 -0700 Subject: [PATCH 18/68] docs: add master -> next merge for package.json --- docs/releasing.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/releasing.md b/docs/releasing.md index a137e1d0..df5e3aeb 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -61,9 +61,33 @@ $ git push lhorie master 11. Travis will push the new release to npm & create a GitHub release +### Merge `master` back into `next` + +This helps to ensure that the `version` field of `package.json` doesn't get out of date. + +12. Switch to `next` and make sure it's up to date + +```bash +$ git co next +$ git pull --rebase lhorie next +``` + +13. Merge `master` back onto `next` + +```bash +$ git merge master +``` + +14. Push the changes to your fork & `lhorie/mithril.js` + +```bash +$ git push +$ git push lhorie next +``` + ### Update the GitHub release -12. The GitHub Release will require a manual description & title to be added. I suggest coming up with a fun title & then copying the `docs/change-log.md` entry for the build. +15. The GitHub Release will require a manual description & title to be added. I suggest coming up with a fun title & then copying the `docs/change-log.md` entry for the build. ## Updating mithril.js.org From 9a55a290cc1ea0adccefd7a124d12eed7d43b903 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Thu, 13 Apr 2017 09:48:30 -0700 Subject: [PATCH 19/68] feat: perf testing (#1789) --- .travis.yml | 6 +- package.json | 6 +- performance/index.html | 23 +++ performance/test-perf.js | 336 ++++++++++++++++++++++++++++++++++++ test-utils/pushStateMock.js | 2 +- 5 files changed, 368 insertions(+), 5 deletions(-) create mode 100644 performance/index.html create mode 100644 performance/test-perf.js diff --git a/.travis.yml b/.travis.yml index 75e9b45c..cc4576b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,10 @@ install: # Bundle before running tests so the bundle is always up-to-date before_script: npm run build -# This is the default, but leaving so it is obvious -# script: npm test +# Run tests, lint, and then check for perf regressions +script: +- npm test +- npm run perf # After a successful build commit changes back to repo after_success: diff --git a/package.json b/package.json index 6ea29cd0..4a93fd78 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,11 @@ "build-min": "node bundler/cli browser.js -o mithril.min.js -m", "lintdocs": "node docs/lint", "gendocs": "node docs/generate", - "lint": "eslint .", + "lint": "eslint . || true", "lint:fix": "eslint . --fix", + "perf": "node performance/test-perf.js", "test": "node ospec/bin/ospec", - "posttest": "npm run lint || true", + "posttest": "npm run lint", "cover": "istanbul cover --print both ospec/bin/ospec", "release": "npm version -m 'v%s'", "preversion": "npm run test", @@ -24,6 +25,7 @@ "postversion": "git push --follow-tags" }, "devDependencies": { + "benchmark": "^2.1.4", "eslint": "^3.16.1", "istanbul": "^0.4.3", "marked": "^0.3.6" diff --git a/performance/index.html b/performance/index.html new file mode 100644 index 00000000..a0f288c5 --- /dev/null +++ b/performance/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/performance/test-perf.js b/performance/test-perf.js new file mode 100644 index 00000000..2ba9b622 --- /dev/null +++ b/performance/test-perf.js @@ -0,0 +1,336 @@ +/* global Benchmark */ +"use strict" + +/* Based off of preact's perf tests, so including their MIT license */ +/* +The MIT License (MIT) + +Copyright (c) 2017 Jason Miller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +var browserMock = require("../test-utils/browserMock") + +// Do this silly dance so browser testing works +var B = typeof Benchmark === "undefined" ? require("benchmark") : Benchmark + +var m, scratch; + +// set up browser env on before running tests +var doc = typeof document !== "undefined" ? document : null + +if(!doc) { + var mock = browserMock() + if (typeof global !== "undefined") { global.window = mock } + + doc = mock.document +} + +// Have to include mithril AFTER browser polyfill is set up +m = require("../mithril") // eslint-disable-line global-require + +scratch = doc.createElement("div"); + +(doc.body || doc.documentElement).appendChild(scratch) + +// Initialize benchmark suite +var suite = new B.Suite("mithril perf") + +suite.on("start", function() { + this.start = Date.now(); +}) + +suite.on("cycle", function(e) { + console.log(e.target.toString()) + + scratch.innerHTML = "" +}) + +suite.on("complete", function() { + console.log("Completed perf tests in " + (Date.now() - this.start) + "ms") +}) + +suite.on("error", console.error.bind(console)) + +suite.add({ + name : "rerender without changes", + onStart : function() { + this.vdom = m("div", {class: "foo bar", "data-foo": "bar", p: 2}, + m("header", + m("h1", {class: "asdf"}, "a ", "b", " c ", 0, " d"), + m("nav", + m("a", {href: "/foo"}, "Foo"), + m("a", {href: "/bar"}, "Bar") + ) + ), + m("main", + m("form", {onSubmit: function onSubmit() {}}, + m("input", {type: "checkbox", checked: true}), + m("input", {type: "checkbox", checked: false}), + m("fieldset", + m("label", + m("input", {type: "radio", checked: true}) + ), + m("label", + m("input", {type: "radio"}) + ) + ), + m("button-bar", + m("button", + {style: "width:10px; height:10px; border:1px solid #FFF;"}, + "Normal CSS" + ), + m("button", + {style: "top:0 ; right: 20"}, + "Poor CSS" + ), + m("button", + {style: "invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;", icon: true}, + "Poorer CSS" + ), + m("button", + {style: {margin: 0, padding: "10px", overflow: "visible"}}, + "Object CSS" + ) + ) + ) + ) + ) + }, + fn : function() { + m.render(scratch, this.vdom) + } +}) + +suite.add({ + name : "construct large VDOM tree", + + onStart : function() { + var fields = [] + + for(var i=100; i--;) { + fields.push((i * 999).toString(36)) + } + + this.fields = fields; + }, + + fn : function () { + m("div", {class: "foo bar", "data-foo": "bar", p: 2}, + m("header", + m("h1", {class: "asdf"}, "a ", "b", " c ", 0, " d"), + m("nav", + m("a", {href: "/foo"}, "Foo"), + m("a", {href: "/bar"}, "Bar") + ) + ), + m("main", + m("form", + {onSubmit: function onSubmit() {}}, + m("input", {type: "checkbox", checked: true}), + m("input", {type: "checkbox"}), + m("fieldset", + this.fields.map(function (field) { + return m("label", + field, + ":", + m("input", {placeholder: field}) + ) + }) + ), + m("button-bar", + m("button", + {style: "width:10px; height:10px; border:1px solid #FFF;"}, + "Normal CSS" + ), + m("button", + {style: "top:0 ; right: 20"}, + "Poor CSS" + ), + m("button", + {style: "invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;", icon: true}, + "Poorer CSS" + ), + m("button", + {style: {margin: 0, padding: "10px", overflow: "visible"}}, + "Object CSS" + ) + ) + ) + ) + ) + } +}) + +suite.add({ + name : "mutate styles/properties", + + onStart : function () { + var counter = 0 + var keyLooper = function (n) { return function (c) { return c % n ? (c + "px") : c } } + var get = function (obj, i) { return obj[i%obj.length] } + var classes = ["foo", "foo bar", "", "baz-bat", null, "fooga"] + var styles = [] + var multivalue = ["0 1px", "0 0 1px 0", "0", "1px", "20px 10px", "7em 5px", "1px 0 5em 2px"] + var stylekeys = [ + ["left", keyLooper(3)], + ["top", keyLooper(2)], + ["margin", function (c) { return get(multivalue, c).replace("1px", c+"px") }], + ["padding", function (c) { return get(multivalue, c) }], + ["position", function (c) { return c%5 ? c%2 ? "absolute" : "relative" : null }], + ["display", function (c) { return c%10 ? c%2 ? "block" : "inline" : "none" }], + ["color", function (c) { return ("rgba(" + (c%255) + ", " + (255 - c%255) + ", " + (50+c%150) + ", " + (c%50/50) + ")") }], + ["border", function (c) { return c%5 ? ((c%10) + "px " + (c%2?"solid":"dotted") + " " + (stylekeys[6][1](c))) : "" }] + ] + var i, j, style, conf + + for (i=0; i<1000; i++) { + style = {} + for (j=0; j Date: Fri, 14 Apr 2017 12:46:08 +0200 Subject: [PATCH 20/68] Auto Dependency Updates ... --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4a93fd78..b9b1a2bc 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,8 @@ }, "devDependencies": { "benchmark": "^2.1.4", - "eslint": "^3.16.1", - "istanbul": "^0.4.3", + "eslint": "^3.19.0", + "istanbul": "^0.4.5", "marked": "^0.3.6" }, "bin": { From ca0cd412a325aa70771f6dda58dd516439489c3f Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Mon, 24 Apr 2017 18:41:58 +0000 Subject: [PATCH 21/68] Bundled output for commit 040a70ff9949c693e1de104050de035fb61bcc9a [skip ci] --- mithril.js | 2 +- mithril.min.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mithril.js b/mithril.js index 1b1a7b0a..c89f6e64 100644 --- a/mithril.js +++ b/mithril.js @@ -912,7 +912,7 @@ var coreRenderer = function($window) { function updateEvent(vnode, key2, value) { var element = vnode.dom var callback = typeof onevent !== "function" ? value : function(e) { - var result = value.call(element, e) + var result = value.call(element, e, vnode) onevent.call(element, e) return result } diff --git a/mithril.min.js b/mithril.min.js index 1a5bb54a..d8018044 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -30,7 +30,7 @@ h&&(r(a),a.dom)){var b=a.domSize||1;if(1 Date: Wed, 26 Apr 2017 18:34:28 +0200 Subject: [PATCH 22/68] docs: URL Param + Variadic route doc (#1813) --- docs/route.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/route.md b/docs/route.md index d5a9c83a..518f248d 100644 --- a/docs/route.md +++ b/docs/route.md @@ -319,6 +319,20 @@ m.route(document.body, "/edit/pictures/image.jpg", { }) ``` +#### Handling 404s + +For isomorphic / universal javascript app, an url param and a variadic route combined is very usefull to display custom 404 error page. + +In a case of 404 Not Found error, the server send back the custom page to client. When Mithril is loaded, it will redirect client to the default route because it can't know that route. + +```javascript +m.route(document.body, "/", { + "/": homeComponent, + // [...] + "/:404...": errorPageComponent +}); + ``` + #### History state It's possible to take full advantage of the underlying `history.pushState` API to improve user's navigation experience. For example, an application could "remember" the state of a large form when the user leaves a page by navigating away, such that if the user pressed the back button in the browser, they'd have the form filled rather than a blank form. From f8ccd418d3fa40b8c8a195026e6aea90a88b89c0 Mon Sep 17 00:00:00 2001 From: spacejack Date: Wed, 26 Apr 2017 20:07:25 -0400 Subject: [PATCH 23/68] Add test for select option with empty string value --- render/tests/test-input.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/render/tests/test-input.js b/render/tests/test-input.js index 73eecf1e..57706b9c 100644 --- a/render/tests/test-input.js +++ b/render/tests/test-input.js @@ -80,6 +80,16 @@ o.spec("form inputs", function() { o(select.dom.selectedIndex).equals(0) }) + o("select option can have empty string value", function() { + var select = {tag: "select", children :[ + {tag: "option", attrs: {value: ""}, text: "aaa"} + ]} + + render(root, [select]) + + o(select.dom.firstChild.value).equals("") + }) + o("select yields invalid value without children", function() { var select = {tag: "select", attrs: {value: "a"}} From b6a251b5b88611d6978d55dc86204c65c6de422f Mon Sep 17 00:00:00 2001 From: micellius Date: Thu, 27 Apr 2017 17:21:00 +0300 Subject: [PATCH 24/68] docs(jsx.md): add missing "!" Add missing "!" to result of interpolation for `{greeting + "!"}` expression --- docs/jsx.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index 9ce6163c..b10d86eb 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -39,7 +39,7 @@ When using JSX, it's possible to interpolate Javascript expressions within JSX t var greeting = "Hello" var url = "http://google.com" var link = {greeting + "!"} -// yields Hello +// yields Hello! ``` Components can be used by using a convention of uppercasing the first letter of the component name: From 0587139252d0cdff702ab5c8ff5adaa7fb48d31d Mon Sep 17 00:00:00 2001 From: Scotty Simpson Date: Thu, 27 Apr 2017 16:49:19 -0700 Subject: [PATCH 25/68] docs: Update components.md (#1818) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I went with "its", but "our" could work, too. Both, though — hm. Seems silly. --- docs/components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/components.md b/docs/components.md index 229f1293..4df9dfe4 100644 --- a/docs/components.md +++ b/docs/components.md @@ -316,7 +316,7 @@ var Login = { Normally, in the context of a larger application, a login component like the one above exists alongside components for user registration and password recovery. Imagine that we want to be able to prepopulate the email field when navigating from the login screen to the registration or password recovery screens (or vice versa), so that the user doesn't need to re-type their email if they happened to fill the wrong page (or maybe you want to bump the user to the registration form if a username is not found). -Right away, we see that sharing the `username` and `password` fields from this component to another is difficult. This is because the fat component encapsulates its our state, which by definition makes this state difficult to access from outside. +Right away, we see that sharing the `username` and `password` fields from this component to another is difficult. This is because the fat component encapsulates its state, which by definition makes this state difficult to access from outside. It makes more sense to refactor this component and pull the state code out of the component and into the application's data layer. This can be as simple as creating a new module: From b24c37c42dd32a5be85584d17e2457c611f511eb Mon Sep 17 00:00:00 2001 From: Isiah Meadows Date: Fri, 28 Apr 2017 16:40:13 -0400 Subject: [PATCH 26/68] Revert "Pass `vnode` as second argument" --- docs/hyperscript.md | 34 +--------------------------------- render/render.js | 2 +- render/tests/test-event.js | 3 --- 3 files changed, 2 insertions(+), 37 deletions(-) diff --git a/docs/hyperscript.md b/docs/hyperscript.md index 38a6808d..8ebe297b 100644 --- a/docs/hyperscript.md +++ b/docs/hyperscript.md @@ -181,7 +181,7 @@ Mithril does not attempt to add units to number values. ### Events -Mithril supports event handler binding for all DOM events, using `addEventListener` under the hood to add events in case they don't have a corresponding `on${event}` property, such as the native `touchstart` or third-party events like Bootstrap's `show.bs.modal`. +Mithril supports event handler binding for all DOM events, including events whose specs do not define an `on${event}` property, such as `touchstart` ```javascript function doSomething(e) { @@ -191,38 +191,6 @@ function doSomething(e) { m("div", {onclick: doSomething}) ``` -You may access the element itself through `this` like so, which is useful for things like form validation: - -```javascript -function submit(e) { - this.validate() - m.request("api/form-endpoint", { - method: "POST", - data: new FormData(this), - background: true - }) - .then(function () { - m.route.set("/form-submit") - }) -} - -m("form", {onsubmit: submit}, [ - // ... -]) -``` - -It also provides access to the event's own literal vnode via `vnode`, so you may easily access its `attrs` and `children` through it, from an external function. - -```javascript -function handleClick(e, vnode) { - vnode.attrs.context.validateValue(this.value) -} - -m("div", [ - m("input", {type: "text", context: this, onclick: handleClick}) -]) -``` - --- ### Properties diff --git a/render/render.js b/render/render.js index b9a54b08..df26a1df 100644 --- a/render/render.js +++ b/render/render.js @@ -558,7 +558,7 @@ module.exports = function($window) { function updateEvent(vnode, key, value) { var element = vnode.dom var callback = typeof onevent !== "function" ? value : function(e) { - var result = value.call(element, e, vnode) + var result = value.call(element, e) onevent.call(element, e) return result } diff --git a/render/tests/test-event.js b/render/tests/test-event.js index 6b75397e..31d2d012 100644 --- a/render/tests/test-event.js +++ b/render/tests/test-event.js @@ -28,7 +28,6 @@ o.spec("event", function() { o(spy.this).equals(div.dom) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) - o(spy.args[1]).equals(div) o(onevent.callCount).equals(1) o(onevent.this).equals(div.dom) o(onevent.args[0].type).equals("click") @@ -65,7 +64,6 @@ o.spec("event", function() { o(spy.this).equals(div.dom) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) - o(spy.args[1]).equals(div) o(onevent.callCount).equals(1) o(onevent.this).equals(div.dom) o(onevent.args[0].type).equals("click") @@ -87,7 +85,6 @@ o.spec("event", function() { o(spy.this).equals(div.dom) o(spy.args[0].type).equals("transitionend") o(spy.args[0].target).equals(div.dom) - o(spy.args[1]).equals(div) o(onevent.callCount).equals(1) o(onevent.this).equals(div.dom) o(onevent.args[0].type).equals("transitionend") From 0f9d5f1631275fb7aa13a750b4f80610614ad49b Mon Sep 17 00:00:00 2001 From: spacejack Date: Sun, 30 Apr 2017 15:04:37 -0400 Subject: [PATCH 27/68] Fix select option to use empty string value, add tests. --- render/render.js | 2 +- render/tests/test-input.js | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/render/render.js b/render/render.js index df26a1df..4800bb44 100644 --- a/render/render.js +++ b/render/render.js @@ -481,7 +481,7 @@ module.exports = function($window) { //setting select[value] to same value while having select open blinks select dropdown in Chrome if (vnode.tag === "select" && key === "value" && vnode.dom.value == value && vnode.dom === $doc.activeElement) return //setting option[value] to same value while having select open blinks select dropdown in Chrome - if (vnode.tag === "option" && key === "value" && vnode.dom.value == value) return + if (vnode.tag === "option" && key === "value" && old != null && vnode.dom.value == value) return // If you assign an input type that is not supported by IE 11 with an assignment expression, an error will occur. if (vnode.tag === "input" && key === "type") { element.setAttribute(key, value) diff --git a/render/tests/test-input.js b/render/tests/test-input.js index 57706b9c..e75da0ef 100644 --- a/render/tests/test-input.js +++ b/render/tests/test-input.js @@ -90,6 +90,47 @@ o.spec("form inputs", function() { o(select.dom.firstChild.value).equals("") }) + o("option value defaults to textContent unless explicitly set", function() { + var select = {tag: "select", children :[ + {tag: "option", text: "aaa"} + ]} + + render(root, [select]) + + o(select.dom.firstChild.value).equals("aaa") + o(select.dom.value).equals("aaa") + + //test that value changes when content changes + select = {tag: "select", children :[ + {tag: "option", text: "bbb"} + ]} + + render(root, [select]) + + o(select.dom.firstChild.value).equals("bbb") + o(select.dom.value).equals("bbb") + + //test that value can be set to "" in subsequent render + select = {tag: "select", children :[ + {tag: "option", attrs: {value: ""}, text: "aaa"} + ]} + + render(root, [select]) + + o(select.dom.firstChild.value).equals("") + o(select.dom.value).equals("") + + //test that value reverts to textContent when value omitted + select = {tag: "select", children :[ + {tag: "option", text: "ccc"} + ]} + + render(root, [select]) + + o(select.dom.firstChild.value).equals("ccc") + o(select.dom.value).equals("ccc") + }) + o("select yields invalid value without children", function() { var select = {tag: "select", attrs: {value: "a"}} From 08a6638926bab905277c2d60c4dd68e9beb91724 Mon Sep 17 00:00:00 2001 From: spacejack Date: Sun, 30 Apr 2017 15:14:44 -0400 Subject: [PATCH 28/68] Minimize vdom diff in option value test --- render/tests/test-input.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/render/tests/test-input.js b/render/tests/test-input.js index e75da0ef..d61bad54 100644 --- a/render/tests/test-input.js +++ b/render/tests/test-input.js @@ -122,13 +122,13 @@ o.spec("form inputs", function() { //test that value reverts to textContent when value omitted select = {tag: "select", children :[ - {tag: "option", text: "ccc"} + {tag: "option", text: "aaa"} ]} render(root, [select]) - o(select.dom.firstChild.value).equals("ccc") - o(select.dom.value).equals("ccc") + o(select.dom.firstChild.value).equals("aaa") + o(select.dom.value).equals("aaa") }) o("select yields invalid value without children", function() { From 414e74ba475cf9746a6ef58b4b43ef0c32a0ca7d Mon Sep 17 00:00:00 2001 From: Scotty Simpson Date: Sun, 30 Apr 2017 23:36:47 -0700 Subject: [PATCH 29/68] docs: Fix typo in es6.md (#1827) --- docs/es6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/es6.md b/docs/es6.md index 3c19613f..7a453c5d 100644 --- a/docs/es6.md +++ b/docs/es6.md @@ -5,7 +5,7 @@ --- -Mithril is written in ES5, and is fully compatible with ES6 as well. ES6 is a recent update to Javascript that introduces new syntax sugar for various common cases. It's not yet fully supported by all major browsers and it's not a requirement for writing application, but it may be pleasing to use depending on your team's preferences. +Mithril is written in ES5, and is fully compatible with ES6 as well. ES6 is a recent update to Javascript that introduces new syntax sugar for various common cases. It's not yet fully supported by all major browsers and it's not a requirement for writing an application, but it may be pleasing to use depending on your team's preferences. In some limited environments, it's possible to use a significant subset of ES6 directly without extra tooling (for example, in internal applications that do not support IE). However, for the vast majority of use cases, a compiler toolchain like [Babel](https://babeljs.io) is required to compile ES6 features down to ES5. From 7e6a4869e9838b2d7b1f2191bb99b12e28ddd2dd Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Mon, 1 May 2017 15:46:08 +0000 Subject: [PATCH 30/68] Bundled output for commit 8191f7894c2ad7a94584fd47ca376bd19ad3ea79 [skip ci] --- mithril.js | 4 +-- mithril.min.js | 86 +++++++++++++++++++++++++------------------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/mithril.js b/mithril.js index c89f6e64..9c2806ae 100644 --- a/mithril.js +++ b/mithril.js @@ -837,7 +837,7 @@ var coreRenderer = function($window) { //setting select[value] to same value while having select open blinks select dropdown in Chrome if (vnode.tag === "select" && key2 === "value" && vnode.dom.value == value && vnode.dom === $doc.activeElement) return //setting option[value] to same value while having select open blinks select dropdown in Chrome - if (vnode.tag === "option" && key2 === "value" && vnode.dom.value == value) return + if (vnode.tag === "option" && key2 === "value" && old != null && vnode.dom.value == value) return // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error0 will occur. if (vnode.tag === "input" && key2 === "type") { element.setAttribute(key2, value) @@ -912,7 +912,7 @@ var coreRenderer = function($window) { function updateEvent(vnode, key2, value) { var element = vnode.dom var callback = typeof onevent !== "function" ? value : function(e) { - var result = value.call(element, e, vnode) + var result = value.call(element, e) onevent.call(element, e) return result } diff --git a/mithril.min.js b/mithril.min.js index d8018044..1513acc3 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,43 +1,43 @@ -(function(){function B(b,d,f,g,e,n){return{tag:b,key:d,attrs:f,children:g,text:e,dom:n,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function C(b){var d=arguments[1],f=2,g;if(null==b||"string"!==typeof b&&"function"!==typeof b&&"function"!==typeof b.view)throw Error("The selector must be either a string or a component.");if("string"===typeof b){var e;if(!(e=M[b])){g="div";for(var n=[],k={};e=P.exec(b);){var q=e[1],m=e[2];""===q&&""!==m?g=m:"#"===q?k.id=m:"."===q? -n.push(m):"["===e[3][0]&&((q=e[6])&&(q=q.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===e[4]?n.push(q):k[e[4]]=q||!0)}0a.indexOf("?")?"?":"&";a+=e+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(w){throw Error(a); -}}function q(a){return a.responseText}function m(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;dl.status||304===l.status||S.test(a.url))d(m(a.type, -h));else{var c=Error(l.responseText),p;for(p in h)c[p]=h[p];f(c)}}catch(v){f(v)}};g&&null!=a.data?l.send(a.data):l.send()});return!0===a.background?w:u(w)},jsonp:function(a,k){var u=f();a=g(a,k);var q=new d(function(d,f){var g=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=b.document.createElement("script");b[g]=function(e){k.parentNode.removeChild(k);d(m(a.type,e));delete b[g]};k.onerror=function(){k.parentNode.removeChild(k);f(Error("JSONP request failed"));delete b[g]};null== -a.data&&(a.data={});a.url=e(a.url,a.data);a.data[a.callbackKey||"callback"]=g;k.src=n(a.url,a.data);b.document.documentElement.appendChild(k)});return!0===a.background?q:u(q)},setCompletionCallback:function(a){u=a}}}(window,x),O=function(b){function d(h,c,p,a,b,d,e){for(;p=v&&y>=t;){var r=c[v],z=p[t];if(r!==z||b)if(null==r)v++;else if(null==z)t++;else if(r.key===z.key){var A=null!=u&&v>=c.length-u.length||null==u&&b;v++;t++;k(h,r,z,e,m(c,v,g),A,n);b&&r.tag===z.tag&&l(h,q(r),g)}else if(r=c[w],r!==z||b)if(null==r)w--;else if(null==z)t++; -else if(r.key===z.key)A=null!=u&&w>=c.length-u.length||null==u&&b,k(h,r,z,e,m(c,w+1,g),A,n),(b||t=v&&y>=t;){r=c[w];z=p[y];if(r!==z||b)if(null==r)w--;else{if(null!=z)if(r.key===z.key)A=null!=u&&w>=c.length-u.length||null==u&&b,k(h,r,z,e,m(c,w+1,g),A,n),b&&r.tag===z.tag&&l(h,q(r),g),null!=r.dom&&(g=r.dom),w--;else{if(!G){G=c;var r=w,A={},E;for(E=0;Ea.indexOf("?")?"?":"&";a+=e+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(A){throw Error(a); +}}function r(a){return a.responseText}function n(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;dl.status||304===l.status||S.test(a.url))d(n(a.type, +g));else{var c=Error(l.responseText),p;for(p in g)c[p]=g[p];e(c)}}catch(u){e(u)}};f&&null!=a.data?l.send(a.data):l.send()});return!0===a.background?A:t(A)},jsonp:function(a,k){var t=e();a=f(a,k);var r=new d(function(d,e){var f=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=b.document.createElement("script");b[f]=function(e){k.parentNode.removeChild(k);d(n(a.type,e));delete b[f]};k.onerror=function(){k.parentNode.removeChild(k);e(Error("JSONP request failed"));delete b[f]};null== +a.data&&(a.data={});a.url=h(a.url,a.data);a.data[a.callbackKey||"callback"]=f;k.src=m(a.url,a.data);b.document.documentElement.appendChild(k)});return!0===a.background?r:t(r)},setCompletionCallback:function(a){t=a}}}(window,x),O=function(b){function d(g,c,p,a,b,d,h){for(;p=u&&t>=y;){var w=c[u];var v=p[y];if(w!==v||b)if(null==w)u++;else if(null==v)y++;else if(w.key===v.key){var E=null!=A&&u>=c.length-A.length||null==A&&b;u++;y++;k(g,w,v,h,n(c,u,f),E,m);b&&w.tag===v.tag&&l(g,r(w),f)}else if(w=c[q],w!==v||b)if(null== +w)q--;else if(null==v)y++;else if(w.key===v.key)E=null!=A&&q>=c.length-A.length||null==A&&b,k(g,w,v,h,n(c,q+1,f),E,m),(b||y=u&&t>=y;){w=c[q];v=p[t];if(w!==v||b)if(null==w)q--;else{if(null!=v)if(w.key===v.key)E=null!=A&&q>=c.length-A.length||null==A&&b,k(g,w,v,h,n(c,q+1,f),E,m),b&&w.tag===v.tag&&l(g,r(w),f),null!=w.dom&&(f=w.dom),q--;else{if(!D){var D=c;E=q;w={};var F;for(F=0;F Date: Sun, 23 Apr 2017 23:30:05 -0700 Subject: [PATCH 31/68] Correct example code --- docs/request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/request.md b/docs/request.md index 0af751af..3ed049a5 100644 --- a/docs/request.md +++ b/docs/request.md @@ -287,7 +287,7 @@ function upload(e) { var data = new FormData() for (var i = 0; i < files.length; i++) { - data.append("file" + i, file) + data.append("file" + i, files[i]) } m.request({ From bef58fdfa5daf03231a4a058739218127999ec68 Mon Sep 17 00:00:00 2001 From: Pat Cavit Date: Tue, 2 May 2017 09:24:12 -0700 Subject: [PATCH 32/68] chore: Update repo references (#1834) --- .travis.yml | 14 +++++++------- docs/change-log.md | 18 +++++++++--------- docs/contributing.md | 2 +- docs/examples.md | 12 ++++++------ docs/framework-comparison.md | 6 +++--- docs/installation.md | 2 +- docs/layout.html | 4 ++-- docs/nav-guides.md | 2 +- docs/nav-methods.md | 2 +- docs/releasing.md | 26 ++++++++++++++------------ docs/simple-application.md | 2 +- docs/testing.md | 2 +- ospec/package.json | 2 +- package.json | 2 +- stream/package.json | 2 +- 15 files changed, 50 insertions(+), 48 deletions(-) diff --git a/.travis.yml b/.travis.yml index cc4576b5..494a4fef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,12 +16,12 @@ install: - npm install @alrra/travis-scripts@^3.0.1 gh-pages@^0.12.0 # Bundle before running tests so the bundle is always up-to-date -before_script: npm run build +before_script: npm run build --silent # Run tests, lint, and then check for perf regressions script: -- npm test -- npm run perf +- npm test --silent +- npm run perf --silent # After a successful build commit changes back to repo after_success: @@ -44,7 +44,7 @@ after_success: # this doesn't have the built-in branch protection like commit-changes if [ "$TRAVIS_EVENT_TYPE" == "push" ] && \ [ "$TRAVIS_BRANCH" == "master" ] && \ - [ "$TRAVIS_REPO_SLUG" == "lhorie/mithril.js" ] + [ "$TRAVIS_REPO_SLUG" == "MithrilJS/mithril.js" ] then # Generate docs npm run gendocs @@ -59,7 +59,7 @@ after_success: $(npm bin)/gh-pages \ --dist ./dist \ --add \ - --repo "git@github.com:lhorie/mithril.js.git" \ + --repo "git@github.com:MithrilJS/mithril.js.git" \ --message "Generated docs for commit $TRAVIS_COMMIT [skip ci]" else echo "Not submitting documentation updates" @@ -83,7 +83,7 @@ deploy: skip_cleanup: true on: tags: true - repo: lhorie/mithril.js + repo: MithrilJS/mithril.js branch: master - provider: npm @@ -93,5 +93,5 @@ deploy: secure: ADElvD1oxn9GfEG7dDOggX96b36A/cGEybovAc0221CCKzv5kWCavMrtxneiJYI6N/n24abSlbM90vMfU84FEzH0Ev28dGVokRP4ad6VRkISszKlYVEP8Lds4QxfKh78jZlUxmxM0B3vmQ1kYJbTBqp3ICtaJ5ptEQHWhrLtxnc= on: tags: true - repo: lhorie/mithril.js + repo: MithrilJS/mithril.js branch: master diff --git a/docs/change-log.md b/docs/change-log.md index 9c493aa4..eedac49b 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -12,8 +12,8 @@ #### Bug fixes -- hyperscript: Allow `0` as the second argument to `m()` - [#1752](https://github.com/lhorie/mithril.js/issues/1752) / [#1753](https://github.com/lhorie/mithril.js/pull/1753) ([@StephanHoyer](https://github.com/StephanHoyer)) -- hyperscript: restore `attrs.class` handling to what it was in v1.0.1 - [#1764](https://github.com/lhorie/mithril.js/issues/1764) / [#1769](https://github.com/lhorie/mithril.js/pull/1769) +- hyperscript: Allow `0` as the second argument to `m()` - [#1752](https://github.com/MithrilJS/mithril.js/issues/1752) / [#1753](https://github.com/MithrilJS/mithril.js/pull/1753) ([@StephanHoyer](https://github.com/StephanHoyer)) +- hyperscript: restore `attrs.class` handling to what it was in v1.0.1 - [#1764](https://github.com/MithrilJS/mithril.js/issues/1764) / [#1769](https://github.com/MithrilJS/mithril.js/pull/1769) - documentation improvements ([@JAForbes](https://github.com/JAForbes), [@smuemd](https://github.com/smuemd), [@hankeypancake](https://github.com/hankeypancake)) ### v1.1.0 @@ -26,10 +26,10 @@ #### Bug fixes -- fix IE11 input[type] error - [#1610](https://github.com/lhorie/mithril.js/issues/1610) -- apply [#1609](https://github.com/lhorie/mithril.js/issues/1609) to unkeyed children case -- fix abort detection [#1612](https://github.com/lhorie/mithril.js/issues/1612) -- fix input value focus issue when value is loosely equal to old value [#1593](https://github.com/lhorie/mithril.js/issues/1593) +- fix IE11 input[type] error - [#1610](https://github.com/MithrilJS/mithril.js/issues/1610) +- apply [#1609](https://github.com/MithrilJS/mithril.js/issues/1609) to unkeyed children case +- fix abort detection [#1612](https://github.com/MithrilJS/mithril.js/issues/1612) +- fix input value focus issue when value is loosely equal to old value [#1593](https://github.com/MithrilJS/mithril.js/issues/1593) --- @@ -37,12 +37,12 @@ #### News -- performance improvements in IE [#1598](https://github.com/lhorie/mithril.js/pull/1598) +- performance improvements in IE [#1598](https://github.com/MithrilJS/mithril.js/pull/1598) #### Bug fixes -- prevent infinite loop in non-existent default route - [#1579](https://github.com/lhorie/mithril.js/issues/1579) -- call correct lifecycle methods on children of recycled keyed vnodes - [#1609](https://github.com/lhorie/mithril.js/issues/1609) +- prevent infinite loop in non-existent default route - [#1579](https://github.com/MithrilJS/mithril.js/issues/1579) +- call correct lifecycle methods on children of recycled keyed vnodes - [#1609](https://github.com/MithrilJS/mithril.js/issues/1609) --- diff --git a/docs/contributing.md b/docs/contributing.md index 5b4770d3..795177af 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -4,7 +4,7 @@ ## How do I go about contributing ideas or new features? -Create an [issue thread on Github](https://github.com/lhorie/mithril.js/issues/new) to suggest your idea so the community can discuss it. +Create an [issue thread on Github](https://github.com/MithrilJS/mithril.js/issues/new) to suggest your idea so the community can discuss it. If the consensus is that it's a good idea, the fastest way to get it into a release is to send a pull request. Without a PR, the time to implement the feature will depend on the bandwidth of the development team and its list of priorities. diff --git a/docs/examples.md b/docs/examples.md index 140ad3f6..749900cc 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -2,10 +2,10 @@ Here are some examples of Mithril in action -- [Animation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/animation/mosaic.html) -- [DBMonster](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html) -- [Markdown Editor](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/editor/index.html) -- SVG: [Clock](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/clock.html), [Ring](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/ring.html), [Tiger](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/svg/tiger.html) -- [ThreadItJS](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/threaditjs/index.html) -- [TodoMVC](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/todomvc/index.html) +- [Animation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/animation/mosaic.html) +- [DBMonster](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html) +- [Markdown Editor](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/editor/index.html) +- SVG: [Clock](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/clock.html), [Ring](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/ring.html), [Tiger](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/svg/tiger.html) +- [ThreadItJS](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/threaditjs/index.html) +- [TodoMVC](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/todomvc/index.html) diff --git a/docs/framework-comparison.md b/docs/framework-comparison.md index cf8a3483..0969b2c9 100644 --- a/docs/framework-comparison.md +++ b/docs/framework-comparison.md @@ -74,7 +74,7 @@ What these numbers show is that not only does Mithril initializes significantly Update performance can be even more important than first-render performance, since updates can happen many times while a Single Page Application is running. -A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [React implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/react/index.html) and a [Mithril implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html). Sample results are shown below: +A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [React implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/react/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Sample results are shown below: React | Mithril ------- | ------- @@ -139,7 +139,7 @@ Also, remember that frameworks like Angular and Mithril are designed for non-tri ##### Update performance -A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/angular/index.html) and a [Mithril implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: +A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare an [Angular implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/angular/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: Angular | Mithril ------- | ------- @@ -193,7 +193,7 @@ Library load times matter in applications that don't stay open for long periods ##### Update performance -A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [Vue implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/vue/index.html) and a [Mithril implementation](http://cdn.rawgit.com/lhorie/mithril.js/rewrite/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: +A useful tool to benchmark update performance is a tool developed by the Ember team called DbMonster. It updates a table as fast as it can and measures frames per second (FPS) and Javascript times (min, max and mean). The FPS count can be difficult to evaluate since it also includes browser repaint times and `setTimeout` clamping delay, so the most meaningful number to look at is the mean render time. You can compare a [Vue implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/vue/index.html) and a [Mithril implementation](http://cdn.rawgit.com/MithrilJS/mithril.js/master/examples/dbmonster/mithril/index.html). Both implementations are naive (i.e. no optimizations). Sample results are shown below: Vue | Mithril ------ | ------- diff --git a/docs/installation.md b/docs/installation.md index f0c527bc..c4604ad5 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -219,7 +219,7 @@ If you don't have the ability to run a bundler script due to company security po Hello world - + diff --git a/docs/layout.html b/docs/layout.html index 02e07a0b..fffa4f37 100644 --- a/docs/layout.html +++ b/docs/layout.html @@ -14,8 +14,8 @@ diff --git a/docs/nav-guides.md b/docs/nav-guides.md index db55712a..872ce86b 100644 --- a/docs/nav-guides.md +++ b/docs/nav-guides.md @@ -16,7 +16,7 @@ - [Keys](keys.md) - [Autoredraw system](autoredraw.md) - Social - - [Mithril Jobs](https://github.com/lhorie/mithril.js/wiki/JOBS) + - [Mithril Jobs](https://github.com/MithrilJS/mithril.js/wiki/JOBS) - [How to contribute](contributing.md) - [Credits](credits.md) - [Code of Conduct](code-of-conduct.md) diff --git a/docs/nav-methods.md b/docs/nav-methods.md index 06809b50..06ae1f42 100644 --- a/docs/nav-methods.md +++ b/docs/nav-methods.md @@ -16,4 +16,4 @@ - Optional - [Stream](stream.md) - Tooling - - [Ospec](https://github.com/lhorie/mithril.js/blob/rewrite/ospec) + - [Ospec](https://github.com/MithrilJS/mithril.js/blob/master/ospec) diff --git a/docs/releasing.md b/docs/releasing.md index df5e3aeb..37bec28d 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -1,5 +1,7 @@ # Mithril Release Processes +**Note** These steps all assume that `MithrilJS/mithril.js` is a git remote named `mithriljs`, adjust accordingly if that doesn't match your setup. + ## Releasing a new Mithril version ### Prepare the release @@ -8,7 +10,7 @@ ```bash $ git co next -$ git pull --rebase lhorie next +$ git pull --rebase mithriljs next ``` 2. Determine patch level of the change @@ -22,8 +24,8 @@ $ git commit -m "Preparing for release" # Push to your branch $ git push -# Push to lhorie/mithril.js -$ git push lhorie next +# Push to MithrilJS/mithril.js +$ git push mithriljs next ``` ### Merge from `next` to `master` @@ -32,7 +34,7 @@ $ git push lhorie next ```bash $ git co master -$ git pull --rebase lhorie master +$ git pull --rebase mithriljs master ``` 6. merge `next` on top of it @@ -53,10 +55,10 @@ $ npm test 8. `npm run release `, see the docs for [`npm version`](https://docs.npmjs.com/cli/version) 9. The changes will be automatically pushed to your fork -10. Push the changes to `lhorie/mithril.js` +10. Push the changes to `MithrilJS/mithril.js` ```bash -$ git push lhorie master +$ git push mithriljs master ``` 11. Travis will push the new release to npm & create a GitHub release @@ -69,7 +71,7 @@ This helps to ensure that the `version` field of `package.json` doesn't get out ```bash $ git co next -$ git pull --rebase lhorie next +$ git pull --rebase mithriljs next ``` 13. Merge `master` back onto `next` @@ -78,11 +80,11 @@ $ git pull --rebase lhorie next $ git merge master ``` -14. Push the changes to your fork & `lhorie/mithril.js` +14. Push the changes to your fork & `MithrilJS/mithril.js` ```bash $ git push -$ git push lhorie next +$ git push mithriljs next ``` ### Update the GitHub release @@ -94,11 +96,11 @@ $ git push lhorie next Fixes to documentation can land whenever, updates to the site are published via Travis. ```bash -# These steps assume that lhorie/mithril.js is a git remote named "lhorie" +# These steps assume that MithrilJS/mithril.js is a git remote named "mithriljs" # Ensure your next branch is up to date $ git co next -$ git pull lhorie next +$ git pull mithriljs next # Splat the docs folder from next onto master $ git co master @@ -106,7 +108,7 @@ $ git co next -- ./docs # Manually ensure that no new feature docs were added -$ git push lhorie +$ git push mithriljs ``` After the Travis build completes the updated docs should appear on https://mithril.js.org in a few minutes. diff --git a/docs/simple-application.md b/docs/simple-application.md index b226aa2c..cd37bbd9 100644 --- a/docs/simple-application.md +++ b/docs/simple-application.md @@ -616,4 +616,4 @@ This concludes the tutorial. In this tutorial, we went through the process of creating a very simple application where we can list users from a server and edit them individually. As an extra exercise, try to implement user creation and deletion on your own. -If you want to see more examples of Mithril code, check the [examples](examples.md) page. If you have questions, feel free to drop by the [Mithril chat room](https://gitter.im/lhorie/mithril.js). +If you want to see more examples of Mithril code, check the [examples](examples.md) page. If you have questions, feel free to drop by the [Mithril chat room](https://gitter.im/MithrilJS/mithril.js). diff --git a/docs/testing.md b/docs/testing.md index 1ac9a7d2..03bce6b0 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -1,6 +1,6 @@ # Testing -Mithril comes with a testing framework called [ospec](https://github.com/lhorie/mithril.js/tree/rewrite/ospec). What makes it different from most test frameworks is that it avoids all configurability for the sake of avoiding [yak shaving](http://catb.org/jargon/html/Y/yak-shaving.html) and [analysis paralysis](https://en.wikipedia.org/wiki/Analysis_paralysis). +Mithril comes with a testing framework called [ospec](https://github.com/MithrilJS/mithril.js/tree/master/ospec). What makes it different from most test frameworks is that it avoids all configurability for the sake of avoiding [yak shaving](http://catb.org/jargon/html/Y/yak-shaving.html) and [analysis paralysis](https://en.wikipedia.org/wiki/Analysis_paralysis). The easist way to setup the test runner is to create an NPM script for it. Open your project's `package.json` file and edit the `test` line under the `scripts` section: diff --git a/ospec/package.json b/ospec/package.json index fdda49db..388720f3 100644 --- a/ospec/package.json +++ b/ospec/package.json @@ -12,5 +12,5 @@ "bin": { "ospec": "./bin/ospec" }, - "repository": "lhorie/mithril.js#rewrite" + "repository": "MithrilJS/mithril.js" } diff --git a/package.json b/package.json index b9b1a2bc..5acc1de5 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "author": "Leo Horie", "license": "MIT", "main": "mithril.js", - "repository": "lhorie/mithril.js", + "repository": "MithrilJS/mithril.js", "scripts": { "dev": "node bundler/cli browser.js -o mithril.js -w", "build": "npm run build-browser & npm run build-min", diff --git a/stream/package.json b/stream/package.json index f48972d5..4eb14a07 100644 --- a/stream/package.json +++ b/stream/package.json @@ -9,5 +9,5 @@ "keywords": [ "stream", "reactive", "data" ], "author": "Leo Horie ", "license": "MIT", - "repository": "lhorie/mithril.js" + "repository": "MithrilJS/mithril.js" } From af3da1683218d537687c3d5d6eda39b2386989bf Mon Sep 17 00:00:00 2001 From: Andrea Coiutti Date: Tue, 2 May 2017 18:34:13 +0200 Subject: [PATCH 33/68] docs: Add favicon (#1839) --- docs/favicon.ico | Bin 0 -> 15086 bytes docs/favicon.png | Bin 0 -> 1697 bytes docs/generate.js | 5 +++-- docs/layout.html | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 docs/favicon.ico create mode 100644 docs/favicon.png diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..61807fae19adedb602ddd0dae259131a9ed7e63c GIT binary patch literal 15086 zcmc(l36K@V8OM7DWl^Gwf{GUp1rHSOE1KvcF(wAf7>_b#Jd$|CQY$IXXsH1)vPMa5 zjL}l7WQ{Q?V~nX_ys97~OGKkWycHv|g06_iE+FpWvXkF`rh8t$dGp@B$3msIdb|7k z{@>B}G0&L53^M!gZ%9X*2X-^&P-D#K(Y8Fyn8nbBk&1u8CdNzyuqPNq%nym8$6;)e zY~cUF0Q3z+wqCOhgN&IJn((ecCYT?ZUzSZm!JPBM~}4(W1qKgG7WR=(6(j}i3eoQ%%3^!r2f?+?&5JV#%n z`kr>X8@bMmtbBqN-fhQ6V~+fA>6Q(!Ykhh70)9k}Ode?*vhVuxhh*)nS_oLWAk2xgK^gxPvNS^T;ESUE$=>^)#R@iCZuxW;;6Rr_grFQ&54X zHqji3=Y@O(H#m!Bo?PQFq`mO9&D0!dt3Ntf8Uk;+$J1P`b-c$` zXxkTx^5!QQ(`k|QI{~_s8)C8 zWbnSjgWb(nR73tiWZisT0e%qunD#yMY-SBQN+8Ue$<810mz6Mc3Sh4C@Ym%)s*>>W z2`3NmNc!P6SEhf@t16tsMD{;U9`>#|kSuAe!;>z^GW#o2{|k!p;dI7fy3ZoJOMmiN zKDs`)`aa3>D32nlP9^6#@Sj+j)j2Z#k#@1N5q-g#F?h?C*CEJosw-^mbdkP6+gwV2 z-b0%_OL?Wo)7rffdyglWl0Mt2E{phxu60)TYtn1;9d+sve19t&?=9<-N5_8k+Qm+X zwWV{GqU^t4r=C%?^S`WY*8k!R}Y;u`OS%UF})!e4_DF zZS6}jKYM^>XYp@$X3#q@(tFaG=X|7cb z`2J&u&!itz74(?MEzQWvzUFc*XFGrXK1_Su>~!JVDV~|KEd1lZy8T1*|53`XJ9z}g zlnIp+3Jy=Ia~bnKJzstVylNZbgZkd~xz#^O3#D=_Y*} zo;zRrdz9$7TNXB949|1&wCz7YxbY=$Ae!TYoeM2*oNW(A??Gzg&%taaVZzpw+bvz= z>!yTU@?So*6*0jpaJK{NjjgKla~oveEy^eJ8y%d_!`31_(7iOTe`06A&Tv?bH;B5I zzEY&c_tHw|9E~IPAveb2ar3q-qigrrNacUy!LD?8+WhZ|u1uj$d}Y#z93VmBA3S+g0A~Ab#VUZ z?DMkvmpBvsFI4c^;j?LnWF$*`4A}+5^zDVbhY#<@xp zn0PJp$H+(MdB^k8B>6{=Cl9SF)$);Ri~?)|<{AO+2Cr;k)?Flski@ZVNeVr6~Toe~EbL*8Cseo?~QfCc7odX8o6+_9llGp8naeXx%5 zH#d}Sr-yNRg=b?G^nrZ)a%$6*wn@*A*jWLio;UndPle1;==`$izp49Ov4UQoth-OT z<};?|!`#9LtY>}{dOvW!+ck6Wdg|&=>=5j@{Xl!dGA8qh{Rvgzhjy<@&#UNr4{XWZ zu7d1(lkxX>?5rlr|2S&ip>W4Klpg5sD^|}ksbU$o*_o28Rv}5>t+VoQ9 z_e9X{{wBell6sqK?Hc3G2XXzW%X45#?t>EA$bolSLSF@>E`f zj%NJyzm;udx69P;Lz~XT7jOM2j;#v5uflJg4R$Eg_WvVn#TYpQzaGG6o#*w2t2494 z3iCX84&KK2ea6PDvDQJVwedm}2VxJCeWK4HcccHgnOy}gz z#=b^l+N1X7h>^QAYHN$w3HV3WnC7UZD{42UMJc!SmZ-HyX(1|fN8Cn=>98S588f2u z>NHttZ=thEX}A7JI}6>7q%G~OJxb@xdG}|n={g5$U)e`_aQ=Sj*2EQw4s!{( zJfHJC8poXW&b7fB%6vDAf1jmu=rW?ypG}t~`-pz;6@P1E9Zs;)iLr5c1|3ax1|Odz zz|Vz#Ffq^Xao|`hQ$KXB%+oE|gQd%2(H*x1y! z5SSn5Tl;$7dyKq0XX>2xHnfMa&sjh|_WOGYjd%6AY=2Jd%CqUwHbuXG&y%d;Vq$v7 zu{7x61IYT{89Y@<7Hk($w%?K*)XmF431KLhYm)kQyoVhwTg0`{;=2v9_e++xo(pZTu=qVoG_u-* zTo?^a=iW6Cj&MZfMI&l$7~|;F;bdqUk9S#`_glu2?{I1_yRU{bz`D1wx127QvSwVI z=nV9`oV^q&f11$B_BP)7lW`7?eIltRv}KgD^Zo14)eq{!xSz45{wWLSxx?ag4^vbf z?<@mCfPSCFy78-aKRgc$?MSd6TD)}L%3PZO-G2`mOS_%{W_1x4tcG?bxU}ubYd+I2 z;XNL1xzz`&V%QP9tyA03NQJpo?y(S~#7ljcv z4%DSZy72LMN2?+Inq=@@--`zn(6dOoP*TtP(NJVG7c|fPb-rF-_Lgnp;(7&Xw<(gP z4Q3)+buX)+y8-zZrGvIVy#l5hc@rnkc=&lWU>*4SDV+K1L|qNu!R;t7+ZV@LY$ z3C=llnA4YoKNYObP1@gx;f)DRykL&7=x}@1reGE#S9144W;*fDHct0#g1uNW(2zA( zg1Ou0lV^VpUM1BTRqNHuq7l=8%)OG8KM?(MoPmCwc+hd%gygRfjl3W9CQp6iZ$rYl zpx+)n1zlrVd$xXux)|DQ2e7Hu!4>Fumh#vlO>gSDKbWha(cEzzuY$&U72ZbKt=VIc zH0lwoA@9Eb>8|?<@dI#&NzeuC zoDW^=q0So1(KQeMG#0N<^6~m7{%2rMb8=p~54h)SS?!h$n~H$^ntycS-$o zc?k@qM}w*O8+yx^j@?QGlfs_J#%-6wOG(u2B;ly{Z^)lPJe&OO&=!+FE(KQ4i~##1 zE?29CR O@tH9_t;Y0CHs*g3ezns8 literal 0 HcmV?d00001 diff --git a/docs/favicon.png b/docs/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..507126309faa675212895b7f9dc54e215e5f4da8 GIT binary patch literal 1697 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+081LKbXpAgso|NjHkFfahk zWPvak85kKEm>`G=iGj?9sRStl>jeuiFhg`fI6yW~3y=-b2p0qj07bxpj1WPf31}LD z;y`DBw1Q257y?uV*9cY*bSEQF1gse9V7NJ8afpwA@*wl!?gX6WdPz_3`!i)mB92n^V%TEaa?Os+A;Ca8VSdP{zm z5NE&7qE&ld?AzNU{rK;Vch_wvI`FE*e2G=Ic~C#&qNT`_d3rZ<-f9(o{JKQ+kcmRD z#54ZZHo2$5lC|vzHLk4RZg|3AhqFx3u?5VmD=wv-+4k`E$y5*b!*12a*&EmVwkW># zy@=_E=I7I|Kk3Q`xGHH@sci-YVUoAI3quF1tOt<8S>O>_%)r3C1cVvaTx?$j6l5>) z^mS!_$jZcHA<23AgfcK(!aZFaLo80024^}4Ig0FU5nLiBsVN~Q@JwvR%o~p;2`!s8 zwJ~Yhnq^a>XNp`*O5Hs*yQ^#Wy!$uLJl@N)>Zd{Rxt*Kul%KD4(RAMCKhN>M>~B$a z28~emr~i3Qi#Vi(eOq`qOQZkEA6ffV2RGia>D#iaM&|nRRBkpQW~1Og>YrbM4a=;q<_XKIa!2*=mZWc)qb&)sb^mXzez^*aJtG z@J=t-xlC-u+RKtWyR6ziy?$Mos(?pRztwf3>~EsM&^niE$vQ6!B;EA|mgZ#$m$@{~ zntoC~`f`A$+4O`qed8HZbHWp6 zW$>LGblHQ!T6l3SVlD7(S*5zlT#p7I-#8=0~{?ww~`z38>gmc%=E zO!5l0$bFr0`BzPLb$a-PGrO&CN7p~Oucmf*H^<~|P^wccag8WRNi0dVN-jzTQVd20 zhUU5krn&}3A%;d)re;<~rrHKZRt5%hTNc`*Xvob^$xN%nt>Mj0$!4GiNstY}`DrEP ziAAXl<>lpinR(g8$%zH2dih1^v)|cB0TqQ;g+!DDC6+4`6y>L7=AMithril.js + From 73d9265c6d7e0e611e3a47392233d54d22ec7980 Mon Sep 17 00:00:00 2001 From: Scotty Simpson Date: Tue, 2 May 2017 14:28:59 -0700 Subject: [PATCH 34/68] fix: selector [value=""] is mishandled (#1843) --- render/hyperscript.js | 2 +- render/tests/test-hyperscript.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/render/hyperscript.js b/render/hyperscript.js index 3d4b21e4..5b5fda29 100644 --- a/render/hyperscript.js +++ b/render/hyperscript.js @@ -17,7 +17,7 @@ function compileSelector(selector) { var attrValue = match[6] if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\") if (match[4] === "class") classes.push(attrValue) - else attrs[match[4]] = attrValue || true + else attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true } } if (classes.length > 0) attrs.className = classes.join(" ") diff --git a/render/tests/test-hyperscript.js b/render/tests/test-hyperscript.js index 4f6c4548..498a9dbf 100644 --- a/render/tests/test-hyperscript.js +++ b/render/tests/test-hyperscript.js @@ -218,6 +218,18 @@ o.spec("hyperscript", function() { o(vnode.tag).equals("div") o(vnode.attrs.a).equals(true) }) + o("handles explicit empty string value for input", function() { + var vnode = m('input[value=""]') + + o(vnode.tag).equals("input") + o(vnode.attrs.value).equals("") + }) + o("handles explicit empty string value for option", function() { + var vnode = m('option[value=""]') + + o(vnode.tag).equals("option") + o(vnode.attrs.value).equals("") + }) }) o.spec("attrs", function() { o("handles string attr", function() { From 2db60816011c512e09d447e837f52f687a25b158 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Tue, 2 May 2017 21:31:10 +0000 Subject: [PATCH 35/68] Bundled output for commit 73d9265c6d7e0e611e3a47392233d54d22ec7980 [skip ci] --- mithril.js | 2 +- mithril.min.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mithril.js b/mithril.js index 9c2806ae..48b55bea 100644 --- a/mithril.js +++ b/mithril.js @@ -28,7 +28,7 @@ function compileSelector(selector) { var attrValue = match[6] if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\") if (match[4] === "class") classes.push(attrValue) - else attrs[match[4]] = attrValue || true + else attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true } } if (classes.length > 0) attrs.className = classes.join(" ") diff --git a/mithril.min.js b/mithril.min.js index 1513acc3..90285386 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,5 +1,5 @@ (function(){function z(b,d,e,f,h,m){return{tag:b,key:d,attrs:e,children:f,text:h,dom:m,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function B(b){var d,e=arguments[1],f=2;if(null==b||"string"!==typeof b&&"function"!==typeof b&&"function"!==typeof b.view)throw Error("The selector must be either a string or a component.");if("string"===typeof b&&!(d=M[b])){var h="div";for(var m=[],k={};d=P.exec(b);){var r=d[1],n=d[2];""===r&&""!==n?h=n:"#"===r?k.id=n:"."===r?m.push(n): -"["===d[3][0]&&((r=d[6])&&(r=r.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===d[4]?m.push(r):k[d[4]]=r||!0)}0 Date: Wed, 3 May 2017 20:55:56 +0200 Subject: [PATCH 36/68] Override namespace with xmlns attribute (#1825) Fixes issue #1819 foreignObject inside SVG --- render/render.js | 22 +++++++++++++--------- render/tests/test-createElement.js | 8 +++++++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/render/render.js b/render/render.js index 4800bb44..de1766b3 100644 --- a/render/render.js +++ b/render/render.js @@ -6,9 +6,18 @@ module.exports = function($window) { var $doc = $window.document var $emptyFragment = $doc.createDocumentFragment() + var nameSpace = { + svg: "http://www.w3.org/2000/svg", + math: "http://www.w3.org/1998/Math/MathML" + } + var onevent function setEventCallback(callback) {return onevent = callback} + function getNameSpace(vnode) { + return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag] + } + //create function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) { for (var i = start; i < end; i++) { @@ -66,14 +75,11 @@ module.exports = function($window) { } function createElement(parent, vnode, hooks, ns, nextSibling) { var tag = vnode.tag - switch (vnode.tag) { - case "svg": ns = "http://www.w3.org/2000/svg"; break - case "math": ns = "http://www.w3.org/1998/Math/MathML"; break - } - var attrs = vnode.attrs var is = attrs && attrs.is + ns = getNameSpace(vnode) || ns + var element = ns ? is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) : is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag) @@ -289,10 +295,8 @@ module.exports = function($window) { } function updateElement(old, vnode, recycling, hooks, ns) { var element = vnode.dom = old.dom - switch (vnode.tag) { - case "svg": ns = "http://www.w3.org/2000/svg"; break - case "math": ns = "http://www.w3.org/1998/Math/MathML"; break - } + ns = getNameSpace(vnode) || ns + if (vnode.tag === "textarea") { if (vnode.attrs == null) vnode.attrs = {} if (vnode.text != null) { diff --git a/render/tests/test-createElement.js b/render/tests/test-createElement.js index 9e2827cf..4e3591f4 100644 --- a/render/tests/test-createElement.js +++ b/render/tests/test-createElement.js @@ -55,7 +55,10 @@ o.spec("createElement", function() { o(vnode.dom.childNodes[1].nodeName).equals("B") }) o("creates svg", function() { - var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", children: [{tag: "a", ns: "http://www.w3.org/2000/svg", attrs: {"xlink:href": "javascript:;"}}]} + var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", children: [ + {tag: "a", ns: "http://www.w3.org/2000/svg", attrs: {"xlink:href": "javascript:;"}}, + {tag: "foreignObject", children: [{tag: "body", attrs: {xmlns: "http://www.w3.org/1999/xhtml"}}]} + ]} render(root, [vnode]) o(vnode.dom.nodeName).equals("svg") @@ -64,6 +67,9 @@ o.spec("createElement", function() { o(vnode.dom.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg") o(vnode.dom.firstChild.attributes["href"].nodeValue).equals("javascript:;") o(vnode.dom.firstChild.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink") + o(vnode.dom.childNodes[1].nodeName).equals("foreignObject") + o(vnode.dom.childNodes[1].firstChild.nodeName).equals("body") + o(vnode.dom.childNodes[1].firstChild.namespaceURI).equals("http://www.w3.org/1999/xhtml") }) o("sets attributes correctly for svg", function() { var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", attrs: {viewBox: "0 0 100 100"}} From 10b8507c1be4c24517fd46edf21133ca297b1b6f Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Wed, 3 May 2017 18:57:28 +0000 Subject: [PATCH 37/68] Bundled output for commit de4433cd319bc37f3ac2915d04cf8f2310c3bcf2 [skip ci] --- mithril.js | 17 +++++----- mithril.min.js | 86 +++++++++++++++++++++++++------------------------- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/mithril.js b/mithril.js index 48b55bea..412d4164 100644 --- a/mithril.js +++ b/mithril.js @@ -375,8 +375,15 @@ var requestService = _8(window, PromisePolyfill) var coreRenderer = function($window) { var $doc = $window.document var $emptyFragment = $doc.createDocumentFragment() + var nameSpace = { + svg: "http://www.w3.org/2000/svg", + math: "http://www.w3.org/1998/Math/MathML" + } var onevent function setEventCallback(callback) {return onevent = callback} + function getNameSpace(vnode) { + return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag] + } //create function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) { for (var i = start; i < end; i++) { @@ -433,12 +440,9 @@ var coreRenderer = function($window) { } function createElement(parent, vnode, hooks, ns, nextSibling) { var tag = vnode.tag - switch (vnode.tag) { - case "svg": ns = "http://www.w3.org/2000/svg"; break - case "math": ns = "http://www.w3.org/1998/Math/MathML"; break - } var attrs2 = vnode.attrs var is = attrs2 && attrs2.is + ns = getNameSpace(vnode) || ns var element = ns ? is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) : is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag) @@ -649,10 +653,7 @@ var coreRenderer = function($window) { } function updateElement(old, vnode, recycling, hooks, ns) { var element = vnode.dom = old.dom - switch (vnode.tag) { - case "svg": ns = "http://www.w3.org/2000/svg"; break - case "math": ns = "http://www.w3.org/1998/Math/MathML"; break - } + ns = getNameSpace(vnode) || ns if (vnode.tag === "textarea") { if (vnode.attrs == null) vnode.attrs = {} if (vnode.text != null) { diff --git a/mithril.min.js b/mithril.min.js index 90285386..963bcc5e 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,43 +1,43 @@ -(function(){function z(b,d,e,f,h,m){return{tag:b,key:d,attrs:e,children:f,text:h,dom:m,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function B(b){var d,e=arguments[1],f=2;if(null==b||"string"!==typeof b&&"function"!==typeof b&&"function"!==typeof b.view)throw Error("The selector must be either a string or a component.");if("string"===typeof b&&!(d=M[b])){var h="div";for(var m=[],k={};d=P.exec(b);){var r=d[1],n=d[2];""===r&&""!==n?h=n:"#"===r?k.id=n:"."===r?m.push(n): -"["===d[3][0]&&((r=d[6])&&(r=r.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===d[4]?m.push(r):k[d[4]]=""===r?r:r||!0)}0a.indexOf("?")?"?":"&";a+=e+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(A){throw Error(a); -}}function r(a){return a.responseText}function n(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;dl.status||304===l.status||S.test(a.url))d(n(a.type, -g));else{var c=Error(l.responseText),p;for(p in g)c[p]=g[p];e(c)}}catch(u){e(u)}};f&&null!=a.data?l.send(a.data):l.send()});return!0===a.background?A:t(A)},jsonp:function(a,k){var t=e();a=f(a,k);var r=new d(function(d,e){var f=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=b.document.createElement("script");b[f]=function(e){k.parentNode.removeChild(k);d(n(a.type,e));delete b[f]};k.onerror=function(){k.parentNode.removeChild(k);e(Error("JSONP request failed"));delete b[f]};null== -a.data&&(a.data={});a.url=h(a.url,a.data);a.data[a.callbackKey||"callback"]=f;k.src=m(a.url,a.data);b.document.documentElement.appendChild(k)});return!0===a.background?r:t(r)},setCompletionCallback:function(a){t=a}}}(window,x),O=function(b){function d(g,c,p,a,b,d,h){for(;p=u&&t>=y;){var w=c[u];var v=p[y];if(w!==v||b)if(null==w)u++;else if(null==v)y++;else if(w.key===v.key){var E=null!=A&&u>=c.length-A.length||null==A&&b;u++;y++;k(g,w,v,h,n(c,u,f),E,m);b&&w.tag===v.tag&&l(g,r(w),f)}else if(w=c[q],w!==v||b)if(null== -w)q--;else if(null==v)y++;else if(w.key===v.key)E=null!=A&&q>=c.length-A.length||null==A&&b,k(g,w,v,h,n(c,q+1,f),E,m),(b||y=u&&t>=y;){w=c[q];v=p[t];if(w!==v||b)if(null==w)q--;else{if(null!=v)if(w.key===v.key)E=null!=A&&q>=c.length-A.length||null==A&&b,k(g,w,v,h,n(c,q+1,f),E,m),b&&w.tag===v.tag&&l(g,r(w),f),null!=w.dom&&(f=w.dom),q--;else{if(!D){var D=c;E=q;w={};var F;for(F=0;Fb.indexOf("?")?"?":"&";b+=e+d}return b}function k(b){try{return""!==b?JSON.parse(b):null}catch(A){throw Error(b); +}}function r(b){return b.responseText}function n(b,a){if("function"===typeof b)if(Array.isArray(a))for(var d=0;dl.status||304===l.status||S.test(b.url))d(n(b.type, +a));else{var g=Error(l.responseText),c;for(c in a)g[c]=a[c];e(g)}}catch(q){e(q)}};f&&null!=b.data?l.send(b.data):l.send()});return!0===b.background?A:t(A)},jsonp:function(b,k){var t=e();b=f(b,k);var r=new d(function(d,e){var f=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=a.document.createElement("script");a[f]=function(e){k.parentNode.removeChild(k);d(n(b.type,e));delete a[f]};k.onerror=function(){k.parentNode.removeChild(k);e(Error("JSONP request failed"));delete a[f]};null== +b.data&&(b.data={});b.url=h(b.url,b.data);b.data[b.callbackKey||"callback"]=f;k.src=m(b.url,b.data);a.document.documentElement.appendChild(k)});return!0===b.background?r:t(r)},setCompletionCallback:function(b){t=b}}}(window,w),O=function(a){function d(g,c,q,b,a,d,h){for(;q=y&&t>=x;){var u=c[y];var v=q[x];if(u!==v||a)if(null==u)y++;else if(null==v)x++;else if(u.key===v.key){var E=null!=A&&y>=c.length-A.length||null==A&&a;y++;x++;k(g,u,v,h,n(c,y,f),E,m);a&&u.tag===v.tag&&l(g,r(u),f)}else if(u=c[p],u!==v||a)if(null==u)p--;else if(null==v)x++;else if(u.key===v.key)E=null!=A&&p>=c.length-A.length||null==A&& +a,k(g,u,v,h,n(c,p+1,f),E,m),(a||x=y&&t>=x;){u=c[p];v=q[t];if(u!==v||a)if(null==u)p--;else{if(null!=v)if(u.key===v.key)E=null!=A&&p>=c.length-A.length||null==A&&a,k(g,u,v,h,n(c,p+1,f),E,m),a&&u.tag===v.tag&&l(g,r(u),f),null!=u.dom&&(f=u.dom),p--;else{if(!D){var D=c;E=p;u={};var F;for(F=0;F Date: Wed, 3 May 2017 21:21:57 -0400 Subject: [PATCH 38/68] Improve the documentation of routing parameters I was stuck on this for a while earlier today: it turns out that `m.route.param` will not return the right results in the `onmatch` function on a `RouterResolver`, though it will inside of a `render` function on the same. This behavior is documented in the `onmatch` section but not the `m.route.param` section, which is where I was looking when I ran up against this earlier. From an API design perspective it would make sense to me for `m.route.param` to return valid results inside `onmatch`, but I don't know enough about Mithril's internals to know how complex it would be to implement. --- docs/route.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/route.md b/docs/route.md index 518f248d..b224a6f4 100644 --- a/docs/route.md +++ b/docs/route.md @@ -124,7 +124,7 @@ Argument | Type | Required | Description ##### m.route.param -Retrieves a route parameter. A route parameter is a key-value pair. Route parameters may come from a few different places: +Retrieves a route parameter. A route parameter is a key-value pair. Note that in the `onmatch` function of a RouterResolver, route parameters are unavailable from `m.route.param` and are passed as an argument instead due to internal timing of the route resolution mechanism. Route parameters may come from a few different places: - route interpolations (e.g. if a route is `/users/:id`, and it resolves to `/users/1`, the route parameter has a key `id` and value `"1"`) - router querystrings (e.g. if the path is `/users?page=1`, the route parameter has a key `page` and value `"1"`) From 0a1c634d99658525a113b2e624aad10401310a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Yves=20G=C3=A9rardy?= Date: Thu, 4 May 2017 16:05:39 +0200 Subject: [PATCH 39/68] Clarify m.route.params in RouteResolver.onmatch --- docs/route.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/route.md b/docs/route.md index b224a6f4..f9dcb47e 100644 --- a/docs/route.md +++ b/docs/route.md @@ -124,7 +124,7 @@ Argument | Type | Required | Description ##### m.route.param -Retrieves a route parameter. A route parameter is a key-value pair. Note that in the `onmatch` function of a RouterResolver, route parameters are unavailable from `m.route.param` and are passed as an argument instead due to internal timing of the route resolution mechanism. Route parameters may come from a few different places: +Retrieves a route parameter from the last fully resolved route. A route parameter is a key-value pair. Route parameters may come from a few different places: - route interpolations (e.g. if a route is `/users/:id`, and it resolves to `/users/1`, the route parameter has a key `id` and value `"1"`) - router querystrings (e.g. if the path is `/users?page=1`, the route parameter has a key `page` and value `"1"`) @@ -137,6 +137,8 @@ Argument | Type | Required | Description `key` | `String` | No | A route parameter name (e.g. `id` in route `/users/:id`, or `page` in path `/users/1?page=3`, or a key in `history.state`) **returns** | `String|Object` | | Returns a value for the specified key. If a key is not specified, it returns an object that contains all the interpolation keys + Note that in the `onmatch` function of a RouterResolver, the new route hasn't yet been fully resolved, and `m.route.params()` will return the parameters of the previous route, if any. `onmatch` receives the parameters of the new route as an argument. + #### RouteResolver A RouterResolver is an object that contains an `onmatch` method and/or a `render` method. Both methods are optional, but at least one must be present. A RouteResolver is not a component, and therefore it does NOT have lifecycle methods. As a rule of thumb, RouteResolvers should be in the same file as the `m.route` call, whereas component definitions should be in their own modules. From 085f5642be7c9f206471548f46de13aeb33993ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Yves=20G=C3=A9rardy?= Date: Thu, 4 May 2017 16:28:06 +0200 Subject: [PATCH 40/68] Docs: Correct typos [skip CI] --- docs/route.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/route.md b/docs/route.md index f9dcb47e..6f5c571b 100644 --- a/docs/route.md +++ b/docs/route.md @@ -137,11 +137,11 @@ Argument | Type | Required | Description `key` | `String` | No | A route parameter name (e.g. `id` in route `/users/:id`, or `page` in path `/users/1?page=3`, or a key in `history.state`) **returns** | `String|Object` | | Returns a value for the specified key. If a key is not specified, it returns an object that contains all the interpolation keys - Note that in the `onmatch` function of a RouterResolver, the new route hasn't yet been fully resolved, and `m.route.params()` will return the parameters of the previous route, if any. `onmatch` receives the parameters of the new route as an argument. + Note that in the `onmatch` function of a RouteResolver, the new route hasn't yet been fully resolved, and `m.route.params()` will return the parameters of the previous route, if any. `onmatch` receives the parameters of the new route as an argument. #### RouteResolver -A RouterResolver is an object that contains an `onmatch` method and/or a `render` method. Both methods are optional, but at least one must be present. A RouteResolver is not a component, and therefore it does NOT have lifecycle methods. As a rule of thumb, RouteResolvers should be in the same file as the `m.route` call, whereas component definitions should be in their own modules. +A RouteResolver is an object that contains an `onmatch` method and/or a `render` method. Both methods are optional, but at least one must be present. A RouteResolver is not a component, and therefore it does NOT have lifecycle methods. As a rule of thumb, RouteResolvers should be in the same file as the `m.route` call, whereas component definitions should be in their own modules. `routeResolver = {onmatch, render}` @@ -508,7 +508,7 @@ In example 2, since `Layout` is the top-level component in both routes, the DOM #### Authentication -The RouterResolver's `onmatch` hook can be used to run logic before the top level component in a route is initializated. The example below shows how to implement a login wall that prevents users from seeing the `/secret` page unless they login. +The RouteResolver's `onmatch` hook can be used to run logic before the top level component in a route is initializated. The example below shows how to implement a login wall that prevents users from seeing the `/secret` page unless they login. ```javascript var isLoggedIn = false From dfcbc8e2b3ebd3a9c0237c33ba2b88660e6bfa20 Mon Sep 17 00:00:00 2001 From: Scotty Simpson Date: Fri, 12 May 2017 09:40:28 -0700 Subject: [PATCH 41/68] docs: Update request.md (#1851) --- docs/request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/request.md b/docs/request.md index 3ed049a5..3277c378 100644 --- a/docs/request.md +++ b/docs/request.md @@ -77,7 +77,7 @@ m.request({ }) ``` -A call to `m.request` return a [promise](promise.md) and trigger a redraw upon completion of its promise chain. +A call to `m.request` returns a [promise](promise.md) and triggers a redraw upon completion of its promise chain. By default, `m.request` assumes the response is in JSON format and parses it into a Javascript object (or array). From 95addffda674d974f8213ad6c865a8f1f6979ad0 Mon Sep 17 00:00:00 2001 From: "Paul D. Fernhout" Date: Sat, 20 May 2017 17:09:32 +0000 Subject: [PATCH 42/68] Update ospec to ignore hidden directories and files --- ospec/bin/ospec | 1 + 1 file changed, 1 insertion(+) diff --git a/ospec/bin/ospec b/ospec/bin/ospec index 11777a09..00ab7fab 100644 --- a/ospec/bin/ospec +++ b/ospec/bin/ospec @@ -17,6 +17,7 @@ function traverseDirectory(pathname, callback) { var promises = [] for (var i = 0; i < pathnames.length; i++) { if (pathnames[i] === "node_modules") continue + if (pathnames[i][0] === ".") continue pathnames[i] = path.join(pathname, pathnames[i]) promises.push(traverseDirectory(pathnames[i], callback)) } From e031fe49a17619da95cc1c2ae582c55b4a7ac495 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Sat, 27 May 2017 00:38:25 +0200 Subject: [PATCH 43/68] [test-utils/domMock] improve attributes handling --- render/tests/test-attributes.js | 6 +- render/tests/test-component.js | 18 +- render/tests/test-createElement.js | 12 +- render/tests/test-event.js | 2 +- render/tests/test-onbeforeupdate.js | 18 +- render/tests/test-onupdate.js | 6 +- render/tests/test-updateElement.js | 8 +- test-utils/domMock.js | 168 ++++++++++++--- test-utils/tests/test-domMock.js | 312 ++++++++++++++++++++++++---- 9 files changed, 450 insertions(+), 100 deletions(-) diff --git a/render/tests/test-attributes.js b/render/tests/test-attributes.js index c6a3df31..8e706205 100644 --- a/render/tests/test-attributes.js +++ b/render/tests/test-attributes.js @@ -54,7 +54,7 @@ o.spec("attributes", function() { render(root, [a]) - o(a.dom.attributes["readonly"].nodeValue).equals("") + o(a.dom.attributes["readonly"].value).equals("") }) o("when input readonly is false, attribute is not present", function() { var a = {tag: "input", attrs: {readonly: false}} @@ -118,7 +118,7 @@ o.spec("attributes", function() { render(root, canvas) - o(canvas.dom.attributes["width"].nodeValue).equals("100%") + o(canvas.dom.attributes["width"].value).equals("100%") o(canvas.dom.width).equals(100) }) }) @@ -128,7 +128,7 @@ o.spec("attributes", function() { render(root, [a]); - o(a.dom.attributes["class"].nodeValue).equals("test") + o(a.dom.attributes["class"].value).equals("test") }) }) o.spec("contenteditable throws on untrusted children", function() { diff --git a/render/tests/test-component.js b/render/tests/test-component.js index 94863236..c391ddd2 100644 --- a/render/tests/test-component.js +++ b/render/tests/test-component.js @@ -30,7 +30,7 @@ o.spec("component", function() { render(root, [node]) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("receives arguments", function() { @@ -44,7 +44,7 @@ o.spec("component", function() { render(root, [node]) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("updates", function() { @@ -57,7 +57,7 @@ o.spec("component", function() { render(root, [{tag: component, attrs: {id: "c"}, text: "d"}]) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("c") + o(root.firstChild.attributes["id"].value).equals("c") o(root.firstChild.firstChild.nodeValue).equals("d") }) o("updates root from null", function() { @@ -400,7 +400,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls oninit when returning fragment", function() { @@ -423,7 +423,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls oninit before view", function() { @@ -479,7 +479,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("does not calls oncreate on redraw", function() { @@ -520,7 +520,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls onupdate", function() { @@ -546,7 +546,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls onupdate when returning fragment", function() { @@ -572,7 +572,7 @@ o.spec("component", function() { o(called).equals(1) o(root.firstChild.nodeName).equals("DIV") - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") o(root.firstChild.firstChild.nodeValue).equals("b") }) o("calls onremove", function() { diff --git a/render/tests/test-createElement.js b/render/tests/test-createElement.js index 4e3591f4..6e3dcdd7 100644 --- a/render/tests/test-createElement.js +++ b/render/tests/test-createElement.js @@ -24,8 +24,8 @@ o.spec("createElement", function() { render(root, [vnode]) o(vnode.dom.nodeName).equals("DIV") - o(vnode.dom.attributes["id"].nodeValue).equals("a") - o(vnode.dom.attributes["title"].nodeValue).equals("b") + o(vnode.dom.attributes["id"].value).equals("a") + o(vnode.dom.attributes["title"].value).equals("b") }) o("creates style", function() { var vnode = {tag: "div", attrs: {style: {backgroundColor: "red"}}} @@ -48,8 +48,8 @@ o.spec("createElement", function() { render(root, [vnode]) o(vnode.dom.nodeName).equals("DIV") - o(vnode.dom.attributes["id"].nodeValue).equals("a") - o(vnode.dom.attributes["title"].nodeValue).equals("b") + o(vnode.dom.attributes["id"].value).equals("a") + o(vnode.dom.attributes["title"].value).equals("b") o(vnode.dom.childNodes.length).equals(2) o(vnode.dom.childNodes[0].nodeName).equals("A") o(vnode.dom.childNodes[1].nodeName).equals("B") @@ -65,7 +65,7 @@ o.spec("createElement", function() { o(vnode.dom.namespaceURI).equals("http://www.w3.org/2000/svg") o(vnode.dom.firstChild.nodeName).equals("a") o(vnode.dom.firstChild.namespaceURI).equals("http://www.w3.org/2000/svg") - o(vnode.dom.firstChild.attributes["href"].nodeValue).equals("javascript:;") + o(vnode.dom.firstChild.attributes["href"].value).equals("javascript:;") o(vnode.dom.firstChild.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink") o(vnode.dom.childNodes[1].nodeName).equals("foreignObject") o(vnode.dom.childNodes[1].firstChild.nodeName).equals("body") @@ -75,7 +75,7 @@ o.spec("createElement", function() { var vnode = {tag: "svg", ns: "http://www.w3.org/2000/svg", attrs: {viewBox: "0 0 100 100"}} render(root, [vnode]) - o(vnode.dom.attributes["viewBox"].nodeValue).equals("0 0 100 100") + o(vnode.dom.attributes["viewBox"].value).equals("0 0 100 100") }) o("creates mathml", function() { var vnode = {tag: "math", ns: "http://www.w3.org/1998/Math/MathML", children: [{tag: "mrow", ns: "http://www.w3.org/1998/Math/MathML"}]} diff --git a/render/tests/test-event.js b/render/tests/test-event.js index 31d2d012..1f7eec2a 100644 --- a/render/tests/test-event.js +++ b/render/tests/test-event.js @@ -69,7 +69,7 @@ o.spec("event", function() { o(onevent.args[0].type).equals("click") o(onevent.args[0].target).equals(div.dom) o(div.dom).equals(updated.dom) - o(div.dom.attributes["id"].nodeValue).equals("b") + o(div.dom.attributes["id"].value).equals("b") }) o("handles ontransitionend", function() { diff --git a/render/tests/test-onbeforeupdate.js b/render/tests/test-onbeforeupdate.js index 9814122b..d8b2f2d6 100644 --- a/render/tests/test-onbeforeupdate.js +++ b/render/tests/test-onbeforeupdate.js @@ -21,7 +21,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") }) o("prevents update in text", function() { @@ -65,7 +65,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("accepts arguments for comparison", function() { @@ -86,7 +86,7 @@ o.spec("onbeforeupdate", function() { } o(count).equals(1) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("is not called on creation", function() { @@ -167,7 +167,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("a") + o(root.firstChild.attributes["id"].value).equals("a") }) o("does not prevent update if returning true in component and true in vnode", function() { @@ -183,7 +183,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("does not prevent update if returning false in component but true in vnode", function() { @@ -199,7 +199,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("does not prevent update if returning true in component but false in vnode", function() { @@ -215,7 +215,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("does not prevent update if returning true from component", function() { @@ -231,7 +231,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("accepts arguments for comparison in component", function() { @@ -258,7 +258,7 @@ o.spec("onbeforeupdate", function() { } o(count).equals(1) - o(root.firstChild.attributes["id"].nodeValue).equals("b") + o(root.firstChild.attributes["id"].value).equals("b") }) o("is not called on component creation", function() { diff --git a/render/tests/test-onupdate.js b/render/tests/test-onupdate.js index 13f62c46..4b74d288 100644 --- a/render/tests/test-onupdate.js +++ b/render/tests/test-onupdate.js @@ -170,9 +170,9 @@ o.spec("onupdate", function() { function update(vnode) { called = true - o(vnode.dom.parentNode.attributes["id"].nodeValue).equals("11") - o(vnode.dom.attributes["id"].nodeValue).equals("22") - o(vnode.dom.childNodes[0].attributes["id"].nodeValue).equals("33") + o(vnode.dom.parentNode.attributes["id"].value).equals("11") + o(vnode.dom.attributes["id"].value).equals("22") + o(vnode.dom.childNodes[0].attributes["id"].value).equals("33") } o(called).equals(true) }) diff --git a/render/tests/test-updateElement.js b/render/tests/test-updateElement.js index 1774e61f..313fe1a9 100644 --- a/render/tests/test-updateElement.js +++ b/render/tests/test-updateElement.js @@ -21,7 +21,7 @@ o.spec("updateElement", function() { o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) - o(updated.dom.attributes["id"].nodeValue).equals("c") + o(updated.dom.attributes["id"].value).equals("c") }) o("adds attr", function() { var vnode = {tag: "a", attrs: {id: "b"}} @@ -32,7 +32,7 @@ o.spec("updateElement", function() { o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) - o(updated.dom.attributes["title"].nodeValue).equals("d") + o(updated.dom.attributes["title"].value).equals("d") }) o("adds attr from empty attrs", function() { var vnode = {tag: "a"} @@ -43,7 +43,7 @@ o.spec("updateElement", function() { o(updated.dom).equals(vnode.dom) o(updated.dom).equals(root.firstChild) - o(updated.dom.attributes["title"].nodeValue).equals("d") + o(updated.dom.attributes["title"].value).equals("d") }) o("removes attr", function() { var vnode = {tag: "a", attrs: {id: "b", title: "d"}} @@ -209,7 +209,7 @@ o.spec("updateElement", function() { render(root, [vnode]) render(root, [updated]) - o(updated.dom.attributes["class"].nodeValue).equals("b") + o(updated.dom.attributes["class"].value).equals("b") }) o("updates svg child", function() { var vnode = {tag: "svg", children: [{ diff --git a/test-utils/domMock.js b/test-utils/domMock.js index a827070a..2e0b4a6b 100644 --- a/test-utils/domMock.js +++ b/test-utils/domMock.js @@ -1,6 +1,38 @@ "use strict" -module.exports = function() { +/* +Known limitations: + +- `option.selected` can't be set/read when the option doesn't have a `select` parent +- `element.attributes` is just a map of attribute names => Attr objects stubs +- ... + +*/ + +/* +options: +- spy:(f: Function) => Function +*/ + +module.exports = function(options) { + options = options || {} + var spy = options.spy || function(f){return f} + var spymap = [] + function registerSpies(element, spies) { + if(options.spy) { + var i = spymap.indexOf(element) + if (i === -1) { + spymap.push(element, spies) + } else { + var existing = spymap[i + 1] + for (var k in spies) existing[k] = spies[k] + } + } + } + function getSpies(element) { + if(options.spy) return spymap[spymap.indexOf(element) + 1] + } + function isModernEvent(type) { return type === "transitionstart" || type === "transitionend" || type === "animationstart" || type === "animationend" } @@ -62,14 +94,22 @@ module.exports = function() { } function getAttribute(name) { if (this.attributes[name] == null) return null - return this.attributes[name].nodeValue + return this.attributes[name].value } function setAttribute(name, value) { - var nodeValue = String(value) + /*eslint-disable no-implicit-coercion*/ + // this is the correct kind of conversion, passing a Symbol throws in browsers too. + var nodeValue = "" + value + /*eslint-enable no-implicit-coercion*/ + this.attributes[name] = { namespaceURI: null, - get nodeValue() {return nodeValue}, - set nodeValue(value) {nodeValue = String(value)}, + get value() {return nodeValue}, + set value(value) { + /*eslint-disable no-implicit-coercion*/ + nodeValue = "" + value + /*eslint-enable no-implicit-coercion*/ + }, } } function setAttributeNS(ns, name, value) { @@ -79,6 +119,9 @@ module.exports = function() { function removeAttribute(name) { delete this.attributes[name] } + function hasAttribute(name) { + return name in this.attributes + } var declListTokenizer = /;|"(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*'/g /** * This will split a semicolon-separated CSS declaration list into an array of @@ -150,6 +193,7 @@ module.exports = function() { appendChild: appendChild, removeChild: removeChild, insertBefore: insertBefore, + hasAttribute: hasAttribute, getAttribute: getAttribute, setAttribute: setAttribute, setAttributeNS: setAttributeNS, @@ -204,7 +248,7 @@ module.exports = function() { throw new Error("setting element.style is not portable") }, get className() { - return this.attributes["class"] ? this.attributes["class"].nodeValue : "" + return this.attributes["class"] ? this.attributes["class"].value : "" }, set className(value) { if (this.namespaceURI === "http://www.w3.org/2000/svg") throw new Error("Cannot set property className of SVGElement") @@ -222,7 +266,7 @@ module.exports = function() { } }, dispatchEvent: function(e) { - if (this.nodeName === "INPUT" && this.attributes["type"] != null && this.attributes["type"].nodeValue === "checkbox" && e.type === "click") { + if (this.nodeName === "INPUT" && this.attributes["type"] != null && this.attributes["type"].value === "checkbox" && e.type === "click") { this.checked = !this.checked } @@ -256,17 +300,55 @@ module.exports = function() { enumerable: true, }) - element.value = "" - } - - if (element.nodeName === "TEXTAREA") { - var value + var value = "" + var valueSetter = spy(function(v) { + /*eslint-disable no-implicit-coercion*/ + value = v === null ? "" : "" + v + /*eslint-enable no-implicit-coercion*/ + }) Object.defineProperty(element, "value", { get: function() { - return value != null ? value : - this.firstChild ? this.firstChild.nodeValue : "" + return value + }, + set: valueSetter, + enumerable: true, + }) + + var typeSetter = spy(function(v) { + this.setAttribute("type", v) + }) + Object.defineProperty(element, "type", { + get: function() { + if (!this.hasAttribute("type")) return "text" + var type = this.getAttribute("type") + return (/^(?:radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image)$/) + .test(type) + ? type + : "text" + }, + set: typeSetter, + enumerable: true, + }) + registerSpies(element, { + valueSetter: valueSetter, + typeSetter: typeSetter + }) + } + + + if (element.nodeName === "TEXTAREA") { + var wasNeverSet = true + var value = "" + Object.defineProperty(element, "value", { + get: function() { + return wasNeverSet && this.firstChild ? this.firstChild.nodeValue : value + }, + set: function(v) { + wasNeverSet = false + /*eslint-disable no-implicit-coercion*/ + value = v === null ? "" : "" + v + /*eslint-enable no-implicit-coercion*/ }, - set: function(v) {value = v}, enumerable: true, }) } @@ -275,11 +357,11 @@ module.exports = function() { if (element.nodeName === "CANVAS") { Object.defineProperty(element, "width", { - get: function() {return this.attributes["width"] ? Math.floor(parseInt(this.attributes["width"].nodeValue) || 0) : 300}, + get: function() {return this.attributes["width"] ? Math.floor(parseInt(this.attributes["width"].value) || 0) : 300}, set: function(value) {this.setAttribute("width", Math.floor(Number(value) || 0).toString())}, }) Object.defineProperty(element, "height", { - get: function() {return this.attributes["height"] ? Math.floor(parseInt(this.attributes["height"].nodeValue) || 0) : 300}, + get: function() {return this.attributes["height"] ? Math.floor(parseInt(this.attributes["height"].value) || 0) : 300}, set: function(value) {this.setAttribute("height", Math.floor(Number(value) || 0).toString())}, }) } @@ -296,7 +378,7 @@ module.exports = function() { } function getOptionValue(element) { return element.attributes["value"] != null ? - element.attributes["value"].nodeValue : + element.attributes["value"].value : element.firstChild != null ? element.firstChild.nodeValue : "" } if (element.nodeName === "SELECT") { @@ -317,14 +399,14 @@ module.exports = function() { }, enumerable: true, }) - Object.defineProperty(element, "value", { - get: function() { - if (this.selectedIndex > -1) return getOptionValue(getOptions(this)[this.selectedIndex]) - return "" - }, - set: function(value) { + var valueSetter = spy(function(value) { + if (value === null) { + selectedIndex = -1 + } else { var options = getOptions(this) - var stringValue = String(value) + /*eslint-disable no-implicit-coercion*/ + var stringValue = "" + value + /*eslint-enable no-implicit-coercion*/ for (var i = 0; i < options.length; i++) { if (getOptionValue(options[i]) === stringValue) { // selectedValue = stringValue @@ -334,19 +416,37 @@ module.exports = function() { } // selectedValue = stringValue selectedIndex = -1 + } + }) + Object.defineProperty(element, "value", { + get: function() { + if (this.selectedIndex > -1) return getOptionValue(getOptions(this)[this.selectedIndex]) + return "" }, + set: valueSetter, enumerable: true, }) + registerSpies(element, { + valueSetter: valueSetter + }) } if (element.nodeName === "OPTION") { + var valueSetter = spy(function(value) { + /*eslint-disable no-implicit-coercion*/ + this.setAttribute("value", value === null ? "" : "" + value) + /*eslint-enable no-implicit-coercion*/ + }) Object.defineProperty(element, "value", { get: function() {return getOptionValue(this)}, - set: function(value) { - this.setAttribute("value", value) - }, + set: valueSetter, enumerable: true, }) + registerSpies(element, { + valueSetter: valueSetter + }) + Object.defineProperty(element, "selected", { + // TODO? handle `selected` without a parent (works in browsers) get: function() { var options = getOptions(this.parentNode) var index = options.indexOf(this) @@ -372,13 +472,19 @@ module.exports = function() { return element }, createTextNode: function(text) { - var nodeValue = String(text) + /*eslint-disable no-implicit-coercion*/ + var nodeValue = "" + text + /*eslint-enable no-implicit-coercion*/ return { nodeType: 3, nodeName: "#text", parentNode: null, get nodeValue() {return nodeValue}, - set nodeValue(value) {nodeValue = String(value)}, + set nodeValue(value) { + /*eslint-disable no-implicit-coercion*/ + nodeValue = "" + value + /*eslint-enable no-implicit-coercion*/ + }, } }, createDocumentFragment: function() { @@ -409,5 +515,7 @@ module.exports = function() { $window.document.documentElement.appendChild($window.document.body) activeElement = $window.document.body + if (options.spy) $window.__getSpies = getSpies + return $window } diff --git a/test-utils/tests/test-domMock.js b/test-utils/tests/test-domMock.js index aee68832..a71d8e8e 100644 --- a/test-utils/tests/test-domMock.js +++ b/test-utils/tests/test-domMock.js @@ -77,6 +77,27 @@ o.spec("domMock", function() { o(node.nodeValue).equals("true") }) + if (typeof Symbol === "function") { + o("doesn't work with symbols", function(){ + var threw = false + try { + $document.createTextNode(Symbol("nono")) + } catch(e) { + threw = true + } + o(threw).equals(true) + }) + o("symbols can't be used as nodeValue", function(){ + var threw = false + try { + var node = $document.createTextNode("a") + node.nodeValue = Symbol("nono") + } catch(e) { + threw = true + } + o(threw).equals(true) + }) + } }) o.spec("createDocumentFragment", function() { @@ -327,39 +348,54 @@ o.spec("domMock", function() { var div = $document.createElement("div") div.setAttribute("id", "aaa") - o(div.attributes["id"].nodeValue).equals("aaa") + o(div.attributes["id"].value).equals("aaa") o(div.attributes["id"].namespaceURI).equals(null) }) o("works w/ number", function() { var div = $document.createElement("div") div.setAttribute("id", 123) - o(div.attributes["id"].nodeValue).equals("123") + o(div.attributes["id"].value).equals("123") }) o("works w/ null", function() { var div = $document.createElement("div") div.setAttribute("id", null) - o(div.attributes["id"].nodeValue).equals("null") + o(div.attributes["id"].value).equals("null") }) o("works w/ undefined", function() { var div = $document.createElement("div") div.setAttribute("id", undefined) - o(div.attributes["id"].nodeValue).equals("undefined") + o(div.attributes["id"].value).equals("undefined") }) o("works w/ object", function() { var div = $document.createElement("div") div.setAttribute("id", {}) - o(div.attributes["id"].nodeValue).equals("[object Object]") + o(div.attributes["id"].value).equals("[object Object]") }) o("setting via attributes map stringifies", function() { var div = $document.createElement("div") div.setAttribute("id", "a") - div.attributes["id"].nodeValue = 123 + div.attributes["id"].value = 123 - o(div.attributes["id"].nodeValue).equals("123") + o(div.attributes["id"].value).equals("123") + }) + }) + o.spec("hasAttribute", function() { + o("works", function() { + var div = $document.createElement("div") + + o(div.hasAttribute("id")).equals(false) + + div.setAttribute("id", "aaa") + + o(div.hasAttribute("id")).equals(true) + + div.removeAttribute("id") + + o(div.hasAttribute("id")).equals(false) }) }) @@ -368,14 +404,14 @@ o.spec("domMock", function() { var div = $document.createElement("div") div.setAttributeNS("http://www.w3.org/1999/xlink", "href", "aaa") - o(div.attributes["href"].nodeValue).equals("aaa") + o(div.attributes["href"].value).equals("aaa") o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink") }) o("works w/ number", function() { var div = $document.createElement("div") div.setAttributeNS("http://www.w3.org/1999/xlink", "href", 123) - o(div.attributes["href"].nodeValue).equals("123") + o(div.attributes["href"].value).equals("123") o(div.attributes["href"].namespaceURI).equals("http://www.w3.org/1999/xlink") }) }) @@ -416,18 +452,18 @@ o.spec("domMock", function() { o(div.childNodes[0].nodeName).equals("BR") o(div.childNodes[1].nodeType).equals(1) o(div.childNodes[1].nodeName).equals("A") - o(div.childNodes[1].attributes["class"].nodeValue).equals("aaa") - o(div.childNodes[1].attributes["id"].nodeValue).equals("xyz") + o(div.childNodes[1].attributes["class"].value).equals("aaa") + o(div.childNodes[1].attributes["id"].value).equals("xyz") o(div.childNodes[1].childNodes[0].nodeType).equals(3) o(div.childNodes[1].childNodes[0].nodeValue).equals("123") o(div.childNodes[1].childNodes[1].nodeType).equals(1) o(div.childNodes[1].childNodes[1].nodeName).equals("B") - o(div.childNodes[1].childNodes[1].attributes["class"].nodeValue).equals("bbb") + o(div.childNodes[1].childNodes[1].attributes["class"].value).equals("bbb") o(div.childNodes[1].childNodes[2].nodeType).equals(3) o(div.childNodes[1].childNodes[2].nodeValue).equals("234") o(div.childNodes[1].childNodes[3].nodeType).equals(1) o(div.childNodes[1].childNodes[3].nodeName).equals("BR") - o(div.childNodes[1].childNodes[3].attributes["class"].nodeValue).equals("ccc") + o(div.childNodes[1].childNodes[3].attributes["class"].value).equals("ccc") o(div.childNodes[1].childNodes[4].nodeType).equals(3) o(div.childNodes[1].childNodes[4].nodeValue).equals("345") }) @@ -628,14 +664,14 @@ o.spec("domMock", function() { a.setAttribute("href", "") o(a.href).notEquals("") - o(a.attributes["href"].nodeValue).equals("") + o(a.attributes["href"].value).equals("") }) o("is path if property is set", function() { var a = $document.createElement("a") a.href = "" o(a.href).notEquals("") - o(a.attributes["href"].nodeValue).equals("") + o(a.attributes["href"].value).equals("") }) }) o.spec("input[checked]", function() { @@ -656,7 +692,7 @@ o.spec("domMock", function() { input.setAttribute("checked", "") o(input.checked).equals(true) - o(input.attributes["checked"].nodeValue).equals("") + o(input.attributes["checked"].value).equals("") input.removeAttribute("checked") @@ -699,20 +735,95 @@ o.spec("domMock", function() { o("value" in input).equals(true) o("value" in a).equals(false) }) + o("converts null to ''", function() { + var input = $document.createElement("input") + input.value = "x" + + o(input.value).equals("x") + + input.value = null + + o(input.value).equals("") + }) + o("converts values to strings", function() { + var input = $document.createElement("input") + input.value = 5 + + o(input.value).equals("5") + + input.value = 0 + + o(input.value).equals("0") + + input.value = undefined + + o(input.value).equals("undefined") + }) + if (typeof Symbol === "function") o("throws when set to a symbol", function() { + var threw = false + var input = $document.createElement("input") + try { + input.value = Symbol("") + } catch (e) { + o(e instanceof TypeError).equals(true) + threw = true + } + + o(input.value).equals("") + o(threw).equals(true) + }) + }) + o.spec("input[type]", function(){ + o("only exists in input elements", function() { + var input = $document.createElement("input") + var a = $document.createElement("a") + + o("type" in input).equals(true) + o("type" in a).equals(false) + }) + o("is 'text' by default", function() { + var input = $document.createElement("input") + + o(input.type).equals("text") + }) + "radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image" + .split("|").forEach(function(type) { + o("can be set to " + type, function(){ + var input = $document.createElement("input") + input.type = type + + o(input.getAttribute("type")).equals(type) + o(input.type).equals(type) + }) + o("bad values set the attribute, but the getter corrects to 'text', " + type, function(){ + var input = $document.createElement("input") + input.type = "badbad" + type + + o(input.getAttribute("type")).equals("badbad" + type) + o(input.type).equals("text") + }) + }) }) o.spec("textarea[value]", function() { - o("reads from child if no value", function() { - var input = $document.createElement("textarea") - input.appendChild($document.createTextNode("aaa")) + o("reads from child if no value was ever set", function() { + var textarea = $document.createElement("textarea") + textarea.appendChild($document.createTextNode("aaa")) - o(input.value).equals("aaa") + o(textarea.value).equals("aaa") }) o("ignores child if value set", function() { - var input = $document.createElement("textarea") - input.value = "aaa" - input.setAttribute("value", "bbb") + var textarea = $document.createElement("textarea") + textarea.value = null + textarea.appendChild($document.createTextNode("aaa")) - o(input.value).equals("aaa") + o(textarea.value).equals("") + }) + o("textarea[value] doesn't reflect `attributes.value`", function() { + var textarea = $document.createElement("textarea") + textarea.value = "aaa" + textarea.setAttribute("value", "bbb") + + o(textarea.value).equals("aaa") }) }) o.spec("select[value] and select[selectedIndex]", function() { @@ -773,10 +884,76 @@ o.spec("domMock", function() { option2.setAttribute("value", "b") select.appendChild(option2) + var option3 = $document.createElement("option") + option3.setAttribute("value", "") + select.appendChild(option3) + + var option4 = $document.createElement("option") + option4.setAttribute("value", "null") + select.appendChild(option4) + select.value = "b" o(select.value).equals("b") o(select.selectedIndex).equals(1) + + select.value = "" + + o(select.value).equals("") + o(select.selectedIndex).equals(2) + + select.value = "null" + + o(select.value).equals("null") + o(select.selectedIndex).equals(3) + + select.value = null + + o(select.value).equals("") + o(select.selectedIndex).equals(-1) + }) + o("setting valid value works with type conversion", function() { + var select = $document.createElement("select") + + var option1 = $document.createElement("option") + option1.setAttribute("value", "0") + select.appendChild(option1) + + var option2 = $document.createElement("option") + option2.setAttribute("value", "undefined") + select.appendChild(option2) + + var option3 = $document.createElement("option") + option3.setAttribute("value", "") + select.appendChild(option3) + + select.value = 0 + + o(select.value).equals("0") + o(select.selectedIndex).equals(0) + + select.value = undefined + + o(select.value).equals("undefined") + o(select.selectedIndex).equals(1) + + if (typeof Symbol === "function") { + var threw = false + try { + select.value = Symbol("x") + } catch (e) { + threw = true + } + o(threw).equals(true) + o(select.value).equals("undefined") + o(select.selectedIndex).equals(1) + } + }) + o("option.value = null is converted to the empty string", function() { + var option = $document.createElement("option") + option.value = null + + o(option.value).equals("") }) o("setting valid value works with optgroup", function() { var select = $document.createElement("select") @@ -920,55 +1097,55 @@ o.spec("domMock", function() { var canvas = $document.createElement("canvas") canvas.width = 100 - o(canvas.attributes["width"].nodeValue).equals("100") + o(canvas.attributes["width"].value).equals("100") o(canvas.width).equals(100) canvas.height = 100 - o(canvas.attributes["height"].nodeValue).equals("100") + o(canvas.attributes["height"].value).equals("100") o(canvas.height).equals(100) }) o("setting string casts to number", function() { var canvas = $document.createElement("canvas") canvas.width = "100" - o(canvas.attributes["width"].nodeValue).equals("100") + o(canvas.attributes["width"].value).equals("100") o(canvas.width).equals(100) canvas.height = "100" - o(canvas.attributes["height"].nodeValue).equals("100") + o(canvas.attributes["height"].value).equals("100") o(canvas.height).equals(100) }) o("setting float casts to int", function() { var canvas = $document.createElement("canvas") canvas.width = 1.2 - o(canvas.attributes["width"].nodeValue).equals("1") + o(canvas.attributes["width"].value).equals("1") o(canvas.width).equals(1) canvas.height = 1.2 - o(canvas.attributes["height"].nodeValue).equals("1") + o(canvas.attributes["height"].value).equals("1") o(canvas.height).equals(1) }) o("setting percentage fails", function() { var canvas = $document.createElement("canvas") canvas.width = "100%" - o(canvas.attributes["width"].nodeValue).equals("0") + o(canvas.attributes["width"].value).equals("0") o(canvas.width).equals(0) canvas.height = "100%" - o(canvas.attributes["height"].nodeValue).equals("0") + o(canvas.attributes["height"].value).equals("0") o(canvas.height).equals(0) }) o("setting attribute works", function() { var canvas = $document.createElement("canvas") canvas.setAttribute("width", "100%") - o(canvas.attributes["width"].nodeValue).equals("100%") + o(canvas.attributes["width"].value).equals("100%") o(canvas.width).equals(100) canvas.setAttribute("height", "100%") - o(canvas.attributes["height"].nodeValue).equals("100%") + o(canvas.attributes["height"].value).equals("100%") o(canvas.height).equals(100) }) }) @@ -979,7 +1156,7 @@ o.spec("domMock", function() { el.className = "a" o(el.className).equals("a") - o(el.attributes["class"].nodeValue).equals("a") + o(el.attributes["class"].value).equals("a") }) o("setter throws in svg", function(done) { var el = $document.createElementNS("http://www.w3.org/2000/svg", "svg") @@ -991,4 +1168,69 @@ o.spec("domMock", function() { } }) }) + o.spec("spies", function() { + var $window + o.beforeEach(function() { + $window = domMock({spy: o.spy}) + }) + o("basics", function() { + o(typeof $window.__getSpies).equals("function") + o("__getSpies" in domMock()).equals(false) + }) + o("input elements have spies on value and type setters", function() { + var input = $window.document.createElement("input") + + var spies = $window.__getSpies(input) + + o(typeof spies).equals("object") + o(spies).notEquals(null) + o(typeof spies.valueSetter).equals("function") + o(typeof spies.typeSetter).equals("function") + o(spies.valueSetter.callCount).equals(0) + o(spies.typeSetter.callCount).equals(0) + + input.value = "aaa" + input.type = "radio" + + o(spies.valueSetter.callCount).equals(1) + o(spies.valueSetter.this).equals(input) + o(spies.valueSetter.args[0]).equals("aaa") + + o(spies.typeSetter.callCount).equals(1) + o(spies.typeSetter.this).equals(input) + o(spies.typeSetter.args[0]).equals("radio") + }) + o("select elements have spies on value setters", function() { + var select = $window.document.createElement("select") + + var spies = $window.__getSpies(select) + + o(typeof spies).equals("object") + o(spies).notEquals(null) + o(typeof spies.valueSetter).equals("function") + o(spies.valueSetter.callCount).equals(0) + + select.value = "aaa" + + o(spies.valueSetter.callCount).equals(1) + o(spies.valueSetter.this).equals(select) + o(spies.valueSetter.args[0]).equals("aaa") + }) + o("option elements have spies on value setters", function() { + var option = $window.document.createElement("option") + + var spies = $window.__getSpies(option) + + o(typeof spies).equals("object") + o(spies).notEquals(null) + o(typeof spies.valueSetter).equals("function") + o(spies.valueSetter.callCount).equals(0) + + option.value = "aaa" + + o(spies.valueSetter.callCount).equals(1) + o(spies.valueSetter.this).equals(option) + o(spies.valueSetter.args[0]).equals("aaa") + }) + }) }) From 58bc4146355495959e54d4381a260f14d55148db Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Sun, 28 May 2017 18:24:12 +0200 Subject: [PATCH 44/68] tests for validating #1595 and #1804 --- render/tests/test-attributes.js | 384 ++++++++++++++++++++++++++++++++ test-utils/domMock.js | 2 + 2 files changed, 386 insertions(+) diff --git a/render/tests/test-attributes.js b/render/tests/test-attributes.js index 8e706205..c469fcdb 100644 --- a/render/tests/test-attributes.js +++ b/render/tests/test-attributes.js @@ -11,6 +11,46 @@ o.spec("attributes", function() { root = $window.document.body render = vdom($window).render }) + o.spec("basics", function() { + o("works (create/update/remove)", function() { + + var a = {tag: "div", attrs: {}} + var b = {tag: "div", attrs: {id: "test"}} + var c = {tag: "div", attrs: {}} + + render(root, [a]); + + o(a.dom.hasAttribute("id")).equals(false) + + render(root, [b]); + + o(b.dom.getAttribute("id")).equals("test") + + render(root, [c]); + + o(c.dom.hasAttribute("id")).equals(false) + }) + o("undefined attr is equivalent to a lack of attr", function() { + var a = {tag: "div", attrs: {id: undefined}} + var b = {tag: "div", attrs: {id: "test"}} + var c = {tag: "div", attrs: {id: undefined}} + + render(root, [a]); + + o(a.dom.hasAttribute("id")).equals(false) + + render(root, [b]); + + o(b.dom.hasAttribute("id")).equals(true) + o(b.dom.getAttribute("id")).equals("test") + + render(root, [c]); + + // #1804 + // TODO: uncomment + // o(c.dom.hasAttribute("id")).equals(false) + }) + }) o.spec("customElements", function(){ o("when vnode is customElement, custom setAttribute called", function(){ @@ -96,6 +136,159 @@ o.spec("attributes", function() { o(a.dom.attributes["checked"]).equals(undefined) }) }) + o.spec("input.value", function() { + o("can be set as text", function() { + var a = {tag: "input", attrs: {value: "test"}} + + render(root, [a]); + + o(a.dom.value).equals("test") + }) + o("a lack of attribute removes `value`", function() { + var a = {tag: "input", attrs: {}} + var b = {tag: "input", attrs: {value: "test"}} + // var c = {tag: "input", attrs: {}} + + render(root, [a]) + + o(a.dom.value).equals("") + + render(root, [b]) + + o(a.dom.value).equals("test") + + // https://github.com/MithrilJS/mithril.js/issues/1804#issuecomment-304521235 + // TODO: Uncomment + // render(root, [c]) + + // o(a.dom.value).equals("") + }) + o("can be set as number", function() { + var a = {tag: "input", attrs: {value: 1}} + + render(root, [a]); + + o(a.dom.value).equals("1") + }) + o("null becomes the empty string", function() { + var a = {tag: "input", attrs: {value: null}} + var b = {tag: "input", attrs: {value: "test"}} + var c = {tag: "input", attrs: {value: null}} + + render(root, [a]); + + o(a.dom.value).equals("") + o(a.dom.getAttribute("value")).equals(null) + + render(root, [b]); + + o(b.dom.value).equals("test") + o(b.dom.getAttribute("value")).equals(null) + + render(root, [c]); + + o(c.dom.value).equals("") + o(c.dom.getAttribute("value")).equals(null) + }) + o("'' and 0 are different values", function() { + var a = {tag: "input", attrs: {value: 0}, children:[{tag:"#", children:""}]} + var b = {tag: "input", attrs: {value: ""}, children:[{tag:"#", children:""}]} + + render(root, [a]); + + o(a.dom.value).equals("0") + + render(root, [b]); + + o(a.dom.value).equals("") + + // #1959 redux + // TODO: UNCOMMENT + // render(root, [a]); + + // o(a.dom.value).equals("0") + }) + o("isn't set when equivalent to the previous value and focused", function() { + var $window = domMock({spy: o.spy}) + var root = $window.document.body + var render = vdom($window).render + + var a = {tag: "input"} + var b = {tag: "input", attrs: {value: "1"}} + var c = {tag: "input", attrs: {value: "1"}} + var d = {tag: "input", attrs: {value: 1}} + var e = {tag: "input", attrs: {value: 2}} + + render(root, [a]) + var spies = $window.__getSpies(a.dom) + a.dom.focus() + + o(spies.valueSetter.callCount).equals(0) + + render(root, [b]) + + o(b.dom.value).equals("1") + o(spies.valueSetter.callCount).equals(1) + + render(root, [c]) + + o(c.dom.value).equals("1") + o(spies.valueSetter.callCount).equals(1) + + render(root, [d]) + + o(d.dom.value).equals("1") + o(spies.valueSetter.callCount).equals(1) + + render(root, [e]) + + o(d.dom.value).equals("2") + o(spies.valueSetter.callCount).equals(2) + }) + }) + o.spec("input.type", function() { + o("the input.type setter is never used", function() { + var $window = domMock({spy: o.spy}) + var root = $window.document.body + var render = vdom($window).render + + var a = {tag: "input", attrs: {type: "radio"}} + var b = {tag: "input", attrs: {type: "text"}} + var c = {tag: "input", attrs: {}} + + render(root, [a]) + var spies = $window.__getSpies(a.dom) + + o(spies.typeSetter.callCount).equals(0) + o(a.dom.getAttribute("type")).equals("radio") + + render(root, [b]) + + o(spies.typeSetter.callCount).equals(0) + o(b.dom.getAttribute("type")).equals("text") + + render(root, [c]) + + o(spies.typeSetter.callCount).equals(0) + o(c.dom.hasAttribute("type")).equals(false) + }) + }) + o.spec("textarea.value", function() { + o("can be removed by not passing a value", function() { + var a = {tag: "textarea", attrs: {value:"x"}} + // var b = {tag: "textarea", attrs: {}} + + render(root, [a]) + + o(a.dom.value).equals("x") + + // https://github.com/MithrilJS/mithril.js/issues/1804#issuecomment-304521235 + // TODO: Uncomment + // render(root, [b]) + + // o(b.dom.value).equals("") + }) + }) o.spec("link href", function() { o("when link href is true, attribute is present", function() { var a = {tag: "a", attrs: {href: true}} @@ -131,6 +324,197 @@ o.spec("attributes", function() { o(a.dom.attributes["class"].value).equals("test") }) }) + o.spec("option.value", function() { + o("can be set as text", function() { + var a = {tag: "option", attrs: {value: "test"}} + + render(root, [a]); + + o(a.dom.value).equals("test") + }) + o("can be set as number", function() { + var a = {tag: "option", attrs: {value: 1}} + + render(root, [a]); + + o(a.dom.value).equals("1") + }) + o("null becomes the empty string", function() { + var a = {tag: "option", attrs: {value: null}} + var b = {tag: "option", attrs: {value: "test"}} + var c = {tag: "option", attrs: {value: null}} + + render(root, [a]); + + o(a.dom.value).equals("") + o(a.dom.getAttribute("value")).equals("") + + render(root, [b]); + + o(b.dom.value).equals("test") + o(b.dom.getAttribute("value")).equals("test") + + render(root, [c]); + + o(c.dom.value).equals("") + o(c.dom.getAttribute("value")).equals("") + }) + o("'' and 0 are different values", function() { + var a = {tag: "option", attrs: {value: 0}, children:[{tag:"#", children:""}]} + var b = {tag: "option", attrs: {value: ""}, children:[{tag:"#", children:""}]} + + render(root, [a]); + + o(a.dom.value).equals("0") + + render(root, [b]); + + o(a.dom.value).equals("") + + // #1959 redux + // TODO: UNCOMMENT + // render(root, [a]); + + // o(a.dom.value).equals("0") + }) + o("isn't set when equivalent to the previous value", function() { + var $window = domMock({spy: o.spy}) + var root = $window.document.body + var render = vdom($window).render + + var a = {tag: "option"} + var b = {tag: "option", attrs: {value: "1"}} + var c = {tag: "option", attrs: {value: "1"}} + var d = {tag: "option", attrs: {value: 1}} + var e = {tag: "option", attrs: {value: 2}} + + render(root, [a]) + var spies = $window.__getSpies(a.dom) + + o(spies.valueSetter.callCount).equals(0) + + render(root, [b]) + + o(b.dom.value).equals("1") + o(spies.valueSetter.callCount).equals(1) + + render(root, [c]) + + o(c.dom.value).equals("1") + o(spies.valueSetter.callCount).equals(1) + + render(root, [d]) + + o(d.dom.value).equals("1") + o(spies.valueSetter.callCount).equals(1) + + render(root, [e]) + + o(d.dom.value).equals("2") + o(spies.valueSetter.callCount).equals(2) + }) + }) + o.spec("select.value", function() { + function makeSelect(value) { + var attrs = (arguments.length === 0) ? {} : {value: value} + return {tag: "select", attrs: attrs, children: [ + {tag:"option", attrs: {value: "1"}}, + {tag:"option", attrs: {value: "2"}}, + {tag:"option", attrs: {value: "a"}}, + {tag:"option", attrs: {value: "0"}}, + {tag:"option", attrs: {value: ""}} + ]} + } + o("can be set as text", function() { + var a = makeSelect() + var b = makeSelect("2") + var c = makeSelect("a") + + render(root, [a]) + + o(a.dom.value).equals("1") + o(a.dom.selectedIndex).equals(0) + + render(root, [b]) + + o(b.dom.value).equals("2") + o(b.dom.selectedIndex).equals(1) + + render(root, [c]) + + o(c.dom.value).equals("a") + o(c.dom.selectedIndex).equals(2) + }) + o("setting null unsets the value", function() { + var a = makeSelect(null) + + render(root, [a]) + + o(a.dom.value).equals("") + o(a.dom.selectedIndex).equals(-1) + }) + o("values are type converted", function() { + var a = makeSelect(1) + var b = makeSelect(2) + + render(root, [a]) + + o(a.dom.value).equals("1") + o(a.dom.selectedIndex).equals(0) + + render(root, [b]) + + o(b.dom.value).equals("2") + o(b.dom.selectedIndex).equals(1) + }) + o("'' and 0 are different values when focused", function() { + var a = makeSelect("") + // var b = makeSelect(0) + + render(root, [a]) + a.dom.focus() + + o(a.dom.value).equals("") + + // #1959 redux + // TODO: UNCOMMENT + // render(root, [b]) + + // o(b.dom.value).equals("0") + }) + o("updates with the same value do not re-set the attribute if the select has focus", function() { + var $window = domMock({spy: o.spy}) + var root = $window.document.body + var render = vdom($window).render + + var a = makeSelect() + var b = makeSelect("1") + var c = makeSelect(1) + var d = makeSelect("2") + + render(root, [a]) + var spies = $window.__getSpies(a.dom) + a.dom.focus() + + o(spies.valueSetter.callCount).equals(0) + o(a.dom.value).equals("1") + + render(root, [b]) + + o(spies.valueSetter.callCount).equals(0) + o(b.dom.value).equals("1") + + render(root, [c]) + + o(spies.valueSetter.callCount).equals(0) + o(c.dom.value).equals("1") + + render(root, [d]) + + o(spies.valueSetter.callCount).equals(1) + o(d.dom.value).equals("2") + }) + }) o.spec("contenteditable throws on untrusted children", function() { o("including text nodes", function() { var div = {tag: "div", attrs: {contenteditable: true}, text: ""} diff --git a/test-utils/domMock.js b/test-utils/domMock.js index 2e0b4a6b..4726a127 100644 --- a/test-utils/domMock.js +++ b/test-utils/domMock.js @@ -30,6 +30,7 @@ module.exports = function(options) { } } function getSpies(element) { + if (element == null || typeof element !== "object") throw new Error("Element expected") if(options.spy) return spymap[spymap.indexOf(element) + 1] } @@ -314,6 +315,7 @@ module.exports = function(options) { enumerable: true, }) + // we currently emulate the non-ie behavior, but emulating ie may be more useful (throw when an invalid type is set) var typeSetter = spy(function(v) { this.setAttribute("type", v) }) From 662e0f1a9a03e53270cea22136e16126cd3d1e16 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Mon, 29 May 2017 08:06:28 +0000 Subject: [PATCH 45/68] Bundled output for commit a9838be1d9c63c9ec07204b6eb21eec3fdb1e894 [skip ci] --- mithril.min.js | 86 +++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/mithril.min.js b/mithril.min.js index 963bcc5e..24c9474d 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,43 +1,43 @@ -(function(){function z(a,d,e,f,h,m){return{tag:a,key:d,attrs:e,children:f,text:h,dom:m,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function B(a){var d,e=arguments[1],f=2;if(null==a||"string"!==typeof a&&"function"!==typeof a&&"function"!==typeof a.view)throw Error("The selector must be either a string or a component.");if("string"===typeof a&&!(d=M[a])){var h="div";for(var m=[],k={};d=P.exec(a);){var r=d[1],n=d[2];""===r&&""!==n?h=n:"#"===r?k.id=n:"."===r?m.push(n): -"["===d[3][0]&&((r=d[6])&&(r=r.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===d[4]?m.push(r):k[d[4]]=""===r?r:r||!0)}0b.indexOf("?")?"?":"&";b+=e+d}return b}function k(b){try{return""!==b?JSON.parse(b):null}catch(A){throw Error(b); -}}function r(b){return b.responseText}function n(b,a){if("function"===typeof b)if(Array.isArray(a))for(var d=0;dl.status||304===l.status||S.test(b.url))d(n(b.type, -a));else{var g=Error(l.responseText),c;for(c in a)g[c]=a[c];e(g)}}catch(q){e(q)}};f&&null!=b.data?l.send(b.data):l.send()});return!0===b.background?A:t(A)},jsonp:function(b,k){var t=e();b=f(b,k);var r=new d(function(d,e){var f=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+l++,k=a.document.createElement("script");a[f]=function(e){k.parentNode.removeChild(k);d(n(b.type,e));delete a[f]};k.onerror=function(){k.parentNode.removeChild(k);e(Error("JSONP request failed"));delete a[f]};null== -b.data&&(b.data={});b.url=h(b.url,b.data);b.data[b.callbackKey||"callback"]=f;k.src=m(b.url,b.data);a.document.documentElement.appendChild(k)});return!0===b.background?r:t(r)},setCompletionCallback:function(b){t=b}}}(window,w),O=function(a){function d(g,c,q,b,a,d,h){for(;q=y&&t>=x;){var u=c[y];var v=q[x];if(u!==v||a)if(null==u)y++;else if(null==v)x++;else if(u.key===v.key){var E=null!=A&&y>=c.length-A.length||null==A&&a;y++;x++;k(g,u,v,h,n(c,y,f),E,m);a&&u.tag===v.tag&&l(g,r(u),f)}else if(u=c[p],u!==v||a)if(null==u)p--;else if(null==v)x++;else if(u.key===v.key)E=null!=A&&p>=c.length-A.length||null==A&& -a,k(g,u,v,h,n(c,p+1,f),E,m),(a||x=y&&t>=x;){u=c[p];v=q[t];if(u!==v||a)if(null==u)p--;else{if(null!=v)if(u.key===v.key)E=null!=A&&p>=c.length-A.length||null==A&&a,k(g,u,v,h,n(c,p+1,f),E,m),a&&u.tag===v.tag&&l(g,r(u),f),null!=u.dom&&(f=u.dom),p--;else{if(!D){var D=c;E=p;u={};var F;for(F=0;Fb.indexOf("?")?"?":"&";b+=e+d}return b}function l(b){try{return""!==b?JSON.parse(b):null}catch(w){throw Error(b); +}}function r(b){return b.responseText}function p(b,a){if("function"===typeof b)if(Array.isArray(a))for(var d=0;dk.status||304===k.status||T.test(b.url))d(p(b.type, +a));else{var h=Error(k.responseText),c;for(c in a)h[c]=a[c];e(h)}}catch(q){e(q)}};f&&null!=b.data?k.send(b.data):k.send()});return!0===b.background?w:m(w)},jsonp:function(b,l){var m=e();b=f(b,l);var r=new d(function(d,e){var f=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+k++,l=a.document.createElement("script");a[f]=function(e){l.parentNode.removeChild(l);d(p(b.type,e));delete a[f]};l.onerror=function(){l.parentNode.removeChild(l);e(Error("JSONP request failed"));delete a[f]};null== +b.data&&(b.data={});b.url=g(b.url,b.data);b.data[b.callbackKey||"callback"]=f;l.src=n(b.url,b.data);a.document.documentElement.appendChild(l)});return!0===b.background?r:m(r)},setCompletionCallback:function(b){m=b}}}(window,x),P=function(a){function d(h,c,q,b,a,d,f){for(;q=m&&A>=B;){var v=c[m];t=q[B];if(v!==t||a)if(null==v)m++;else if(null==t)B++;else if(v.key===t.key){var F=null!=w&&m>=c.length-w.length||null==w&&a;m++;B++;l(h,v,t,f,p(c,m,g),F,n);a&&v.tag===t.tag&&k(h,r(v),g)}else if(v=c[u],v!==t||a)if(null==v)u--;else if(null==t)B++;else if(v.key===t.key)F=null!=w&&u>=c.length-w.length||null==w&&a, +l(h,v,t,f,p(c,u+1,g),F,n),(a||B=m&&A>=B;){v=c[u];t=q[A];if(v!==t||a)if(null==v)u--;else{if(null!=t)if(v.key===t.key)F=null!=w&&u>=c.length-w.length||null==w&&a,l(h,v,t,f,p(c,u+1,g),F,n),a&&v.tag===t.tag&&k(h,r(v),g),null!=v.dom&&(g=v.dom),u--;else{if(!C){C=c;F=u;v={};var E;for(E=0;E Date: Mon, 29 May 2017 13:59:31 +0200 Subject: [PATCH 46/68] [test-utils/domMock] restore Attr.nodeValue for backwards compatibility --- test-utils/domMock.js | 4 ++++ test-utils/tests/test-domMock.js | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/test-utils/domMock.js b/test-utils/domMock.js index 4726a127..ad0c669d 100644 --- a/test-utils/domMock.js +++ b/test-utils/domMock.js @@ -111,6 +111,10 @@ module.exports = function(options) { nodeValue = "" + value /*eslint-enable no-implicit-coercion*/ }, + get nodeValue() {return nodeValue}, + set nodeValue(value) { + this.value = value + } } } function setAttributeNS(ns, name, value) { diff --git a/test-utils/tests/test-domMock.js b/test-utils/tests/test-domMock.js index a71d8e8e..e56b3c04 100644 --- a/test-utils/tests/test-domMock.js +++ b/test-utils/tests/test-domMock.js @@ -349,6 +349,7 @@ o.spec("domMock", function() { div.setAttribute("id", "aaa") o(div.attributes["id"].value).equals("aaa") + o(div.attributes["id"].nodeValue).equals("aaa") o(div.attributes["id"].namespaceURI).equals(null) }) o("works w/ number", function() { @@ -381,6 +382,10 @@ o.spec("domMock", function() { div.attributes["id"].value = 123 o(div.attributes["id"].value).equals("123") + + div.attributes["id"].nodeValue = 456 + + o(div.attributes["id"].value).equals("456") }) }) o.spec("hasAttribute", function() { From d21bfd4a36e940430052c6f860a0298bccd2b597 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Mon, 29 May 2017 16:47:14 +0200 Subject: [PATCH 47/68] Enable the tests for the updated #1595, add an additional sanity check --- render/tests/test-attributes.js | 48 +++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/render/tests/test-attributes.js b/render/tests/test-attributes.js index c469fcdb..21c090f0 100644 --- a/render/tests/test-attributes.js +++ b/render/tests/test-attributes.js @@ -193,6 +193,7 @@ o.spec("attributes", function() { o("'' and 0 are different values", function() { var a = {tag: "input", attrs: {value: 0}, children:[{tag:"#", children:""}]} var b = {tag: "input", attrs: {value: ""}, children:[{tag:"#", children:""}]} + var c = {tag: "input", attrs: {value: 0}, children:[{tag:"#", children:""}]} render(root, [a]); @@ -200,13 +201,12 @@ o.spec("attributes", function() { render(root, [b]); - o(a.dom.value).equals("") + o(b.dom.value).equals("") - // #1959 redux - // TODO: UNCOMMENT - // render(root, [a]); + // #1595 redux + render(root, [c]); - // o(a.dom.value).equals("0") + o(c.dom.value).equals("0") }) o("isn't set when equivalent to the previous value and focused", function() { var $window = domMock({spy: o.spy}) @@ -362,6 +362,7 @@ o.spec("attributes", function() { o("'' and 0 are different values", function() { var a = {tag: "option", attrs: {value: 0}, children:[{tag:"#", children:""}]} var b = {tag: "option", attrs: {value: ""}, children:[{tag:"#", children:""}]} + var c = {tag: "option", attrs: {value: 0}, children:[{tag:"#", children:""}]} render(root, [a]); @@ -371,11 +372,10 @@ o.spec("attributes", function() { o(a.dom.value).equals("") - // #1959 redux - // TODO: UNCOMMENT - // render(root, [a]); + // #1595 redux + render(root, [c]); - // o(a.dom.value).equals("0") + o(c.dom.value).equals("0") }) o("isn't set when equivalent to the previous value", function() { var $window = domMock({spy: o.spy}) @@ -469,18 +469,38 @@ o.spec("attributes", function() { }) o("'' and 0 are different values when focused", function() { var a = makeSelect("") - // var b = makeSelect(0) + var b = makeSelect(0) render(root, [a]) a.dom.focus() o(a.dom.value).equals("") - // #1959 redux - // TODO: UNCOMMENT - // render(root, [b]) + // #1595 redux + render(root, [b]) - // o(b.dom.value).equals("0") + o(b.dom.value).equals("0") + }) + o("'' and null are different values when focused", function() { + var a = makeSelect("") + var b = makeSelect(null) + var c = makeSelect("") + + render(root, [a]) + a.dom.focus() + + o(a.dom.value).equals("") + o(a.dom.selectedIndex).equals(4) + + render(root, [b]) + + o(b.dom.value).equals("") + o(b.dom.selectedIndex).equals(-1) + + render(root, [c]) + + o(c.dom.value).equals("") + o(c.dom.selectedIndex).equals(4) }) o("updates with the same value do not re-set the attribute if the select has focus", function() { var $window = domMock({spy: o.spy}) From 4616160a5243a99524b817383fa9c10e281da90a Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Mon, 29 May 2017 17:19:55 +0200 Subject: [PATCH 48/68] redo #1595 --- render/render.js | 23 +++++++++++++++++------ render/tests/test-attributes.js | 8 ++++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/render/render.js b/render/render.js index de1766b3..d0346fd2 100644 --- a/render/render.js +++ b/render/render.js @@ -480,12 +480,23 @@ module.exports = function($window) { else if (key[0] === "o" && key[1] === "n" && typeof value === "function") updateEvent(vnode, key, value) else if (key === "style") updateStyle(element, old, value) else if (key in element && !isAttribute(key) && ns === undefined && !isCustomElement(vnode)) { - //setting input[value] to same value by typing on focused element moves cursor to end in Chrome - if (vnode.tag === "input" && key === "value" && vnode.dom.value == value && vnode.dom === $doc.activeElement) return - //setting select[value] to same value while having select open blinks select dropdown in Chrome - if (vnode.tag === "select" && key === "value" && vnode.dom.value == value && vnode.dom === $doc.activeElement) return - //setting option[value] to same value while having select open blinks select dropdown in Chrome - if (vnode.tag === "option" && key === "value" && old != null && vnode.dom.value == value) return + if (key === "value") { + /*eslint-disable no-implicit-coercion*/ + var normalized = "" + value + /*eslint-enable no-implicit-coercion*/ + //setting input[value] to same value by typing on focused element moves cursor to end in Chrome + if (vnode.tag === "input" && vnode.dom.value === normalized && vnode.dom === $doc.activeElement) return + //setting select[value] to same value while having select open blinks select dropdown in Chrome + if (vnode.tag === "select") { + if (value === null) { + if (vnode.dom.selectedIndex === -1 && vnode.dom === $doc.activeElement) return + } else { + if (old !== null && vnode.dom.value === normalized && vnode.dom === $doc.activeElement) return + } + } + //setting option[value] to same value while having select open blinks select dropdown in Chrome + if (vnode.tag === "option" && old != null && vnode.dom.value === normalized) return + } // If you assign an input type that is not supported by IE 11 with an assignment expression, an error will occur. if (vnode.tag === "input" && key === "type") { element.setAttribute(key, value) diff --git a/render/tests/test-attributes.js b/render/tests/test-attributes.js index 21c090f0..64a3fdfd 100644 --- a/render/tests/test-attributes.js +++ b/render/tests/test-attributes.js @@ -203,10 +203,10 @@ o.spec("attributes", function() { o(b.dom.value).equals("") - // #1595 redux - render(root, [c]); + // #1595 redux + render(root, [c]); - o(c.dom.value).equals("0") + o(c.dom.value).equals("0") }) o("isn't set when equivalent to the previous value and focused", function() { var $window = domMock({spy: o.spy}) @@ -476,7 +476,7 @@ o.spec("attributes", function() { o(a.dom.value).equals("") - // #1595 redux + // #1595 redux render(root, [b]) o(b.dom.value).equals("0") From 40429c8377dbc528d8ba3f468bfdd7d3f26fa184 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Gerardy Date: Tue, 30 May 2017 14:17:46 +0200 Subject: [PATCH 49/68] Pass namespace when creating new elements while updating a keyed list, fix #1820 --- render/render.js | 2 +- render/tests/test-render.js | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/render/render.js b/render/render.js index de1766b3..f143df72 100644 --- a/render/render.js +++ b/render/render.js @@ -224,7 +224,7 @@ module.exports = function($window) { if (movable.dom != null) nextSibling = movable.dom } else { - var dom = createNode(parent, v, hooks, undefined, nextSibling) + var dom = createNode(parent, v, hooks, ns, nextSibling) nextSibling = dom } } diff --git a/render/tests/test-render.js b/render/tests/test-render.js index 92bcd93c..5ffecf02 100644 --- a/render/tests/test-render.js +++ b/render/tests/test-render.js @@ -271,4 +271,26 @@ o.spec("render", function() { o(updateA.callCount).equals(2) o(removeA.callCount).equals(1) }) + o("svg namespace is preserved in keyed diff (#1820)", function(){ + // note that this only exerciese one branch of the keyed diff algo + var svg = {tag:"svg", children: [ + {tag:"g", key: 0}, + {tag:"g", key: 1} + ]} + render(root, [svg]) + + o(svg.dom.namespaceURI).equals("http://www.w3.org/2000/svg") + o(svg.dom.childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg") + o(svg.dom.childNodes[1].namespaceURI).equals("http://www.w3.org/2000/svg") + + svg = {tag:"svg", children: [ + {tag:"g", key: 1, attrs: {x: 1}}, + {tag:"g", key: 2, attrs: {x: 2}} + ]} + render(root, [svg]) + + o(svg.dom.namespaceURI).equals("http://www.w3.org/2000/svg") + o(svg.dom.childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg") + o(svg.dom.childNodes[1].namespaceURI).equals("http://www.w3.org/2000/svg") + }) }) From e075be6154e6bd75be6828d3abe2582667b99a97 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Tue, 30 May 2017 14:13:56 +0000 Subject: [PATCH 50/68] Bundled output for commit f931b0b947a324b7da597377b370a3d80c727c11 [skip ci] --- mithril.js | 2 +- mithril.min.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mithril.js b/mithril.js index 412d4164..21fc381a 100644 --- a/mithril.js +++ b/mithril.js @@ -582,7 +582,7 @@ var coreRenderer = function($window) { if (movable.dom != null) nextSibling = movable.dom } else { - var dom = createNode(parent, v, hooks, undefined, nextSibling) + var dom = createNode(parent, v, hooks, ns, nextSibling) nextSibling = dom } } diff --git a/mithril.min.js b/mithril.min.js index 24c9474d..57e2e016 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -20,10 +20,10 @@ b=h.tag;if(null!=b.$$reentrantLock$$)return K;b.$$reentrantLock$$=!0;h.state=nul q)b(c,0,c.length,q);else{if(c.length===q.length){var t=!1;for(var m=0;m=m&&A>=B;){var v=c[m];t=q[B];if(v!==t||a)if(null==v)m++;else if(null==t)B++;else if(v.key===t.key){var F=null!=w&&m>=c.length-w.length||null==w&&a;m++;B++;l(h,v,t,f,p(c,m,g),F,n);a&&v.tag===t.tag&&k(h,r(v),g)}else if(v=c[u],v!==t||a)if(null==v)u--;else if(null==t)B++;else if(v.key===t.key)F=null!=w&&u>=c.length-w.length||null==w&&a, l(h,v,t,f,p(c,u+1,g),F,n),(a||B=m&&A>=B;){v=c[u];t=q[A];if(v!==t||a)if(null==v)u--;else{if(null!=t)if(v.key===t.key)F=null!=w&&u>=c.length-w.length||null==w&&a,l(h,v,t,f,p(c,u+1,g),F,n),a&&v.tag===t.tag&&k(h,r(v),g),null!=v.dom&&(g=v.dom),u--;else{if(!C){C=c;F=u;v={};var E;for(E=0;E Date: Wed, 31 May 2017 02:44:15 -0400 Subject: [PATCH 51/68] Simplify ESLint guard --- render/render.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/render/render.js b/render/render.js index d0346fd2..570591c0 100644 --- a/render/render.js +++ b/render/render.js @@ -481,9 +481,7 @@ module.exports = function($window) { else if (key === "style") updateStyle(element, old, value) else if (key in element && !isAttribute(key) && ns === undefined && !isCustomElement(vnode)) { if (key === "value") { - /*eslint-disable no-implicit-coercion*/ - var normalized = "" + value - /*eslint-enable no-implicit-coercion*/ + var normalized = "" + value // eslint-disable-line no-implicit-coercion //setting input[value] to same value by typing on focused element moves cursor to end in Chrome if (vnode.tag === "input" && vnode.dom.value === normalized && vnode.dom === $doc.activeElement) return //setting select[value] to same value while having select open blinks select dropdown in Chrome From 6c06d41f949a6ea24b86e101494e0455e149fa3e Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Wed, 31 May 2017 06:58:00 +0000 Subject: [PATCH 52/68] Bundled output for commit 712be2bcc3f0053dfd1d34e2655a48eff92c35b8 [skip ci] --- mithril.js | 21 +- mithril.min.js | 85 ++-- package-lock.json | 986 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 + 4 files changed, 1048 insertions(+), 48 deletions(-) create mode 100644 package-lock.json diff --git a/mithril.js b/mithril.js index 21fc381a..2d820867 100644 --- a/mithril.js +++ b/mithril.js @@ -833,12 +833,21 @@ var coreRenderer = function($window) { else if (key2[0] === "o" && key2[1] === "n" && typeof value === "function") updateEvent(vnode, key2, value) else if (key2 === "style") updateStyle(element, old, value) else if (key2 in element && !isAttribute(key2) && ns === undefined && !isCustomElement(vnode)) { - //setting input[value] to same value by typing on focused element moves cursor to end in Chrome - if (vnode.tag === "input" && key2 === "value" && vnode.dom.value == value && vnode.dom === $doc.activeElement) return - //setting select[value] to same value while having select open blinks select dropdown in Chrome - if (vnode.tag === "select" && key2 === "value" && vnode.dom.value == value && vnode.dom === $doc.activeElement) return - //setting option[value] to same value while having select open blinks select dropdown in Chrome - if (vnode.tag === "option" && key2 === "value" && old != null && vnode.dom.value == value) return + if (key2 === "value") { + var normalized0 = "" + value // eslint-disable-line no-implicit-coercion + //setting input[value] to same value by typing on focused element moves cursor to end in Chrome + if (vnode.tag === "input" && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return + //setting select[value] to same value while having select open blinks select dropdown in Chrome + if (vnode.tag === "select") { + if (value === null) { + if (vnode.dom.selectedIndex === -1 && vnode.dom === $doc.activeElement) return + } else { + if (old !== null && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return + } + } + //setting option[value] to same value while having select open blinks select dropdown in Chrome + if (vnode.tag === "option" && old != null && vnode.dom.value === normalized0) return + } // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error0 will occur. if (vnode.tag === "input" && key2 === "type") { element.setAttribute(key2, value) diff --git a/mithril.min.js b/mithril.min.js index 57e2e016..c02bdaa5 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,43 +1,44 @@ -(function(){function y(a,d,e,f,g,n){return{tag:a,key:d,attrs:e,children:f,text:g,dom:n,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function z(a){var d,e=arguments[1],f=2;if(null==a||"string"!==typeof a&&"function"!==typeof a&&"function"!==typeof a.view)throw Error("The selector must be either a string or a component.");if("string"===typeof a&&!(d=N[a])){var g="div";for(var n=[],l={};d=Q.exec(a);){var r=d[1],p=d[2];""===r&&""!==p?g=p:"#"===r?l.id=p:"."===r?n.push(p): -"["===d[3][0]&&((r=d[6])&&(r=r.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===d[4]?n.push(r):l[d[4]]=""===r?r:r||!0)}0b.indexOf("?")?"?":"&";b+=e+d}return b}function l(b){try{return""!==b?JSON.parse(b):null}catch(w){throw Error(b); -}}function r(b){return b.responseText}function p(b,a){if("function"===typeof b)if(Array.isArray(a))for(var d=0;dk.status||304===k.status||T.test(b.url))d(p(b.type, -a));else{var h=Error(k.responseText),c;for(c in a)h[c]=a[c];e(h)}}catch(q){e(q)}};f&&null!=b.data?k.send(b.data):k.send()});return!0===b.background?w:m(w)},jsonp:function(b,l){var m=e();b=f(b,l);var r=new d(function(d,e){var f=b.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+k++,l=a.document.createElement("script");a[f]=function(e){l.parentNode.removeChild(l);d(p(b.type,e));delete a[f]};l.onerror=function(){l.parentNode.removeChild(l);e(Error("JSONP request failed"));delete a[f]};null== -b.data&&(b.data={});b.url=g(b.url,b.data);b.data[b.callbackKey||"callback"]=f;l.src=n(b.url,b.data);a.document.documentElement.appendChild(l)});return!0===b.background?r:m(r)},setCompletionCallback:function(b){m=b}}}(window,x),P=function(a){function d(h,c,q,b,a,d,f){for(;q=m&&A>=B;){var v=c[m];t=q[B];if(v!==t||a)if(null==v)m++;else if(null==t)B++;else if(v.key===t.key){var F=null!=w&&m>=c.length-w.length||null==w&&a;m++;B++;l(h,v,t,f,p(c,m,g),F,n);a&&v.tag===t.tag&&k(h,r(v),g)}else if(v=c[u],v!==t||a)if(null==v)u--;else if(null==t)B++;else if(v.key===t.key)F=null!=w&&u>=c.length-w.length||null==w&&a, -l(h,v,t,f,p(c,u+1,g),F,n),(a||B=m&&A>=B;){v=c[u];t=q[A];if(v!==t||a)if(null==v)u--;else{if(null!=t)if(v.key===t.key)F=null!=w&&u>=c.length-w.length||null==w&&a,l(h,v,t,f,p(c,u+1,g),F,n),a&&v.tag===t.tag&&k(h,r(v),g),null!=v.dom&&(g=v.dom),u--;else{if(!C){C=c;F=u;v={};var E;for(E=0;Ea.indexOf("?")?"?":"&";a+=e+d}return a}function l(a){try{return""!==a?JSON.parse(a):null}catch(w){throw Error(a); +}}function r(a){return a.responseText}function p(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;dk.status||304===k.status||T.test(a.url))d(p(a.type, +b));else{var h=Error(k.responseText),c;for(c in b)h[c]=b[c];e(h)}}catch(q){e(q)}};f&&null!=a.data?k.send(a.data):k.send()});return!0===a.background?w:m(w)},jsonp:function(a,l){var m=e();a=f(a,l);var r=new d(function(d,e){var f=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+k++,l=b.document.createElement("script");b[f]=function(e){l.parentNode.removeChild(l);d(p(a.type,e));delete b[f]};l.onerror=function(){l.parentNode.removeChild(l);e(Error("JSONP request failed"));delete b[f]};null== +a.data&&(a.data={});a.url=g(a.url,a.data);a.data[a.callbackKey||"callback"]=f;l.src=n(a.url,a.data);b.document.documentElement.appendChild(l)});return!0===a.background?r:m(r)},setCompletionCallback:function(a){m=a}}}(window,x),P=function(b){function d(h,c,q,a,b,d,f){for(;q=m&&F>=C;){var v=c[m];t=q[C];if(v!==t||b)if(null==v)m++;else if(null==t)C++;else if(v.key===t.key){var E=null!=w&&m>=c.length-w.length||null==w&&b;m++;C++;l(h,v,t,f,p(c,m,g),E,n);b&&v.tag===t.tag&&k(h,r(v),g)}else if(v=c[u],v!==t||b)if(null==v)u--;else if(null==t)C++;else if(v.key===t.key)E=null!=w&&u>=c.length-w.length||null==w&&b, +l(h,v,t,f,p(c,u+1,g),E,n),(b||C=m&&F>=C;){v=c[u];t=q[F];if(v!==t||b)if(null==v)u--;else{if(null!=t)if(v.key===t.key)E=null!=w&&u>=c.length-w.length||null==w&&b,l(h,v,t,f,p(c,u+1,g),E,n),b&&v.tag===t.tag&&k(h,r(v),g),null!=v.dom&&(g=v.dom),u--;else{if(!B){B=c;E=u;v={};var y;for(y=0;y Date: Mon, 12 Jun 2017 18:26:45 +0200 Subject: [PATCH 53/68] Add tests for #1870 --- render/tests/test-attributes.js | 37 ++++++++++++++++++++++++++++++++ test-utils/domMock.js | 16 ++++++++------ test-utils/tests/test-domMock.js | 16 ++++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/render/tests/test-attributes.js b/render/tests/test-attributes.js index 64a3fdfd..fcc7f150 100644 --- a/render/tests/test-attributes.js +++ b/render/tests/test-attributes.js @@ -288,6 +288,43 @@ o.spec("attributes", function() { // o(b.dom.value).equals("") }) + o("isn't set when equivalent to the previous value and focused", function() { + var $window = domMock({spy: o.spy}) + var root = $window.document.body + var render = vdom($window).render + + var a = {tag: "textarea"} + var b = {tag: "textarea", attrs: {value: "1"}} + var c = {tag: "textarea", attrs: {value: "1"}} + var d = {tag: "textarea", attrs: {value: 1}} + var e = {tag: "textarea", attrs: {value: 2}} + + render(root, [a]) + var spies = $window.__getSpies(a.dom) + a.dom.focus() + + o(spies.valueSetter.callCount).equals(0) + + render(root, [b]) + + o(b.dom.value).equals("1") + o(spies.valueSetter.callCount).equals(1) + + render(root, [c]) + + o(c.dom.value).equals("1") + o(spies.valueSetter.callCount).equals(1) + + render(root, [d]) + + o(d.dom.value).equals("1") + o(spies.valueSetter.callCount).equals(1) + + render(root, [e]) + + o(d.dom.value).equals("2") + o(spies.valueSetter.callCount).equals(2) + }) }) o.spec("link href", function() { o("when link href is true, attribute is present", function() { diff --git a/test-utils/domMock.js b/test-utils/domMock.js index ad0c669d..060ba4f7 100644 --- a/test-utils/domMock.js +++ b/test-utils/domMock.js @@ -345,18 +345,22 @@ module.exports = function(options) { if (element.nodeName === "TEXTAREA") { var wasNeverSet = true var value = "" + var valueSetter = spy(function(v) { + wasNeverSet = false + /*eslint-disable no-implicit-coercion*/ + value = v === null ? "" : "" + v + /*eslint-enable no-implicit-coercion*/ + }) Object.defineProperty(element, "value", { get: function() { return wasNeverSet && this.firstChild ? this.firstChild.nodeValue : value }, - set: function(v) { - wasNeverSet = false - /*eslint-disable no-implicit-coercion*/ - value = v === null ? "" : "" + v - /*eslint-enable no-implicit-coercion*/ - }, + set: valueSetter, enumerable: true, }) + registerSpies(element, { + valueSetter: valueSetter + }) } /* eslint-disable radix */ diff --git a/test-utils/tests/test-domMock.js b/test-utils/tests/test-domMock.js index e56b3c04..a5829458 100644 --- a/test-utils/tests/test-domMock.js +++ b/test-utils/tests/test-domMock.js @@ -1237,5 +1237,21 @@ o.spec("domMock", function() { o(spies.valueSetter.this).equals(option) o(spies.valueSetter.args[0]).equals("aaa") }) + o("textarea elements have spies on value setters", function() { + var textarea = $window.document.createElement("textarea") + + var spies = $window.__getSpies(textarea) + + o(typeof spies).equals("object") + o(spies).notEquals(null) + o(typeof spies.valueSetter).equals("function") + o(spies.valueSetter.callCount).equals(0) + + textarea.value = "aaa" + + o(spies.valueSetter.callCount).equals(1) + o(spies.valueSetter.this).equals(textarea) + o(spies.valueSetter.args[0]).equals("aaa") + }) }) }) From 60e999fa89b32ad661e7b80613294cf91957c699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Yves=20G=C3=A9rardy?= Date: Mon, 12 Jun 2017 18:42:46 +0200 Subject: [PATCH 54/68] [render/render] Add a special case for ; fix #1870 --- render/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render/render.js b/render/render.js index 0b8cad97..844d766a 100644 --- a/render/render.js +++ b/render/render.js @@ -483,7 +483,7 @@ module.exports = function($window) { if (key === "value") { var normalized = "" + value // eslint-disable-line no-implicit-coercion //setting input[value] to same value by typing on focused element moves cursor to end in Chrome - if (vnode.tag === "input" && vnode.dom.value === normalized && vnode.dom === $doc.activeElement) return + if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === normalized && vnode.dom === $doc.activeElement) return //setting select[value] to same value while having select open blinks select dropdown in Chrome if (vnode.tag === "select") { if (value === null) { From 38956e119be1d006fac59d88a486322b9ae8f8a3 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Tue, 13 Jun 2017 09:30:45 +0000 Subject: [PATCH 55/68] Bundled output for commit c9186bcdec2763dbe953f808de1a6cf62d58ad1a [skip ci] --- mithril.js | 2 +- mithril.min.js | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/mithril.js b/mithril.js index 2d820867..914b0d89 100644 --- a/mithril.js +++ b/mithril.js @@ -836,7 +836,7 @@ var coreRenderer = function($window) { if (key2 === "value") { var normalized0 = "" + value // eslint-disable-line no-implicit-coercion //setting input[value] to same value by typing on focused element moves cursor to end in Chrome - if (vnode.tag === "input" && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return + if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return //setting select[value] to same value while having select open blinks select dropdown in Chrome if (vnode.tag === "select") { if (value === null) { diff --git a/mithril.min.js b/mithril.min.js index c02bdaa5..289dc5b3 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -29,16 +29,16 @@ a[c].dom)return a[c].dom;return b}function k(a,c,b){b&&b.parentNode?a.insertBefo b)for(var e=a.dom;--b;){var g=e.nextSibling,f=g.parentNode;null!=f&&f.removeChild(g)}b=a.dom;e=b.parentNode;null!=e&&e.removeChild(b);if(b=null!=c&&null==a.domSize)b=a.attrs,b=!(null!=b&&(b.oncreate||b.onupdate||b.onbeforeremove||b.onremove));b&&"string"===typeof a.tag&&(c.pool?c.pool.push(a):c.pool=[a])}}var h=1,d=0;if(a.attrs&&"function"===typeof a.attrs.onbeforeremove){var e=a.attrs.onbeforeremove.call(a.state,a);null!=e&&"function"===typeof e.then&&(h++,e.then(b,b))}"string"!==typeof a.tag&&"function"=== typeof a._state.onbeforeremove&&(e=a._state.onbeforeremove.call(a.state,a),null!=e&&"function"===typeof e.then&&(h++,e.then(b,b)));b()}function B(a){a.attrs&&"function"===typeof a.attrs.onremove&&a.attrs.onremove.call(a.state,a);"string"!==typeof a.tag&&"function"===typeof a._state.onremove&&a._state.onremove.call(a.state,a);if(null!=a.instance)B(a.instance);else if(a=a.children,Array.isArray(a))for(var c=0;c Date: Wed, 14 Jun 2017 01:59:22 +0200 Subject: [PATCH 56/68] Take the namespace of the root node into account, fix other ns oversight #1872 --- render/render.js | 5 +++-- render/tests/test-render.js | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/render/render.js b/render/render.js index 844d766a..e24a76eb 100644 --- a/render/render.js +++ b/render/render.js @@ -146,7 +146,7 @@ module.exports = function($window) { //update function updateNodes(parent, old, vnodes, recycling, hooks, nextSibling, ns) { if (old === vnodes || old == null && vnodes == null) return - else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined) + else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns) else if (vnodes == null) removeNodes(old, 0, old.length, vnodes) else { if (old.length === vnodes.length) { @@ -613,12 +613,13 @@ module.exports = function($window) { if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.") var hooks = [] var active = $doc.activeElement + var namespace = dom.namespaceURI // First time rendering into a node clears it out if (dom.vnodes == null) dom.textContent = "" if (!Array.isArray(vnodes)) vnodes = [vnodes] - updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, undefined) + updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) dom.vnodes = vnodes for (var i = 0; i < hooks.length; i++) hooks[i]() if ($doc.activeElement !== active) active.focus() diff --git a/render/tests/test-render.js b/render/tests/test-render.js index 5ffecf02..292433a0 100644 --- a/render/tests/test-render.js +++ b/render/tests/test-render.js @@ -293,4 +293,10 @@ o.spec("render", function() { o(svg.dom.childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg") o(svg.dom.childNodes[1].namespaceURI).equals("http://www.w3.org/2000/svg") }) + o("the namespace of the root is passed to children", function() { + render(root, [{tag: "svg"}]) + o(root.childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg") + render(root.childNodes[0], [{tag: "g"}]) + o(root.childNodes[0].childNodes[0].namespaceURI).equals("http://www.w3.org/2000/svg") + }) }) From fbf8d1f8ca2d83e29667bd4cacd3cdc6e29e3f33 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Wed, 14 Jun 2017 03:30:24 +0000 Subject: [PATCH 57/68] Bundled output for commit 18d732bf578f705da8d0d1f63cd3c1e0892e8730 [skip ci] --- mithril.js | 5 +-- mithril.min.js | 86 +++++++++++++++++++++++++------------------------- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/mithril.js b/mithril.js index 914b0d89..5917f595 100644 --- a/mithril.js +++ b/mithril.js @@ -505,7 +505,7 @@ var coreRenderer = function($window) { //update function updateNodes(parent, old, vnodes, recycling, hooks, nextSibling, ns) { if (old === vnodes || old == null && vnodes == null) return - else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined) + else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns) else if (vnodes == null) removeNodes(old, 0, old.length, vnodes) else { if (old.length === vnodes.length) { @@ -962,10 +962,11 @@ var coreRenderer = function($window) { if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.") var hooks = [] var active = $doc.activeElement + var namespace = dom.namespaceURI // First time0 rendering into a node clears it out if (dom.vnodes == null) dom.textContent = "" if (!Array.isArray(vnodes)) vnodes = [vnodes] - updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, undefined) + updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) dom.vnodes = vnodes for (var i = 0; i < hooks.length; i++) hooks[i]() if ($doc.activeElement !== active) active.focus() diff --git a/mithril.min.js b/mithril.min.js index 289dc5b3..e8500e8e 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,44 +1,44 @@ -(function(){function z(b,d,e,f,g,n){return{tag:b,key:d,attrs:e,children:f,text:g,dom:n,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function A(b){var d,e=arguments[1],f=2;if(null==b||"string"!==typeof b&&"function"!==typeof b&&"function"!==typeof b.view)throw Error("The selector must be either a string or a component.");if("string"===typeof b&&!(d=M[b])){var g="div";for(var n=[],l={};d=Q.exec(b);){var r=d[1],p=d[2];""===r&&""!==p?g=p:"#"===r?l.id=p:"."===r?n.push(p): -"["===d[3][0]&&((r=d[6])&&(r=r.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===d[4]?n.push(r):l[d[4]]=""===r?r:r||!0)}0a.indexOf("?")?"?":"&";a+=e+d}return a}function l(a){try{return""!==a?JSON.parse(a):null}catch(w){throw Error(a); -}}function r(a){return a.responseText}function p(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;dk.status||304===k.status||T.test(a.url))d(p(a.type, -b));else{var h=Error(k.responseText),c;for(c in b)h[c]=b[c];e(h)}}catch(q){e(q)}};f&&null!=a.data?k.send(a.data):k.send()});return!0===a.background?w:m(w)},jsonp:function(a,l){var m=e();a=f(a,l);var r=new d(function(d,e){var f=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+k++,l=b.document.createElement("script");b[f]=function(e){l.parentNode.removeChild(l);d(p(a.type,e));delete b[f]};l.onerror=function(){l.parentNode.removeChild(l);e(Error("JSONP request failed"));delete b[f]};null== -a.data&&(a.data={});a.url=g(a.url,a.data);a.data[a.callbackKey||"callback"]=f;l.src=n(a.url,a.data);b.document.documentElement.appendChild(l)});return!0===a.background?r:m(r)},setCompletionCallback:function(a){m=a}}}(window,x),P=function(b){function d(h,c,q,a,b,d,f){for(;q=m&&F>=C;){var v=c[m];t=q[C];if(v!==t||b)if(null==v)m++;else if(null==t)C++;else if(v.key===t.key){var E=null!=w&&m>=c.length-w.length||null==w&&b;m++;C++;l(h,v,t,f,p(c,m,g),E,n);b&&v.tag===t.tag&&k(h,r(v),g)}else if(v=c[u],v!==t||b)if(null==v)u--;else if(null==t)C++;else if(v.key===t.key)E=null!=w&&u>=c.length-w.length||null==w&&b, -l(h,v,t,f,p(c,u+1,g),E,n),(b||C=m&&F>=C;){v=c[u];t=q[F];if(v!==t||b)if(null==v)u--;else{if(null!=t)if(v.key===t.key)E=null!=w&&u>=c.length-w.length||null==w&&b,l(h,v,t,f,p(c,u+1,g),E,n),b&&v.tag===t.tag&&k(h,r(v),g),null!=v.dom&&(g=v.dom),u--;else{if(!B){B=c;E=u;v={};var y;for(y=0;ya.indexOf("?")?"?":"&";a+=e+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(x){throw Error(a); +}}function r(a){return a.responseText}function p(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;dm.status||304===m.status||S.test(a.url))d(p(a.type, +b));else{var h=Error(m.responseText),c;for(c in b)h[c]=b[c];e(h)}}catch(q){e(q)}};f&&null!=a.data?m.send(a.data):m.send()});return!0===a.background?x:n(x)},jsonp:function(a,k){var n=e();a=f(a,k);var r=new d(function(d,e){var f=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+m++,k=b.document.createElement("script");b[f]=function(e){k.parentNode.removeChild(k);d(p(a.type,e));delete b[f]};k.onerror=function(){k.parentNode.removeChild(k);e(Error("JSONP request failed"));delete b[f]};null== +a.data&&(a.data={});a.url=g(a.url,a.data);a.data[a.callbackKey||"callback"]=f;k.src=l(a.url,a.data);b.document.documentElement.appendChild(k)});return!0===a.background?r:n(r)},setCompletionCallback:function(a){n=a}}}(window,y),O=function(b){function d(h,c,q,a,b,d,f){for(;q=n&&E>=B;){var t=c[n];u=q[B];if(t!==u||b)if(null==t)n++;else if(null==u)B++;else if(t.key===u.key){var w=null!=x&&n>=c.length-x.length||null==x&&b;n++;B++;k(h,t,u,f,p(c,n,g),w,l);b&&t.tag===u.tag&&m(h,r(t),g)}else if(t=c[v],t!==u||b)if(null==t)v--;else if(null==u)B++;else if(t.key===u.key)w=null!=x&&v>=c.length-x.length||null==x&&b, +k(h,t,u,f,p(c,v+1,g),w,l),(b||B=n&&E>=B;){t=c[v];u=q[E];if(t!==u||b)if(null==t)v--;else{if(null!=u)if(t.key===u.key)w=null!=x&&v>=c.length-x.length||null==x&&b,k(h,t,u,f,p(c,v+1,g),w,l),b&&t.tag===u.tag&&m(h,r(t),g),null!=t.dom&&(g=t.dom),v--;else{if(!H){H=c;w=v;t={};var C;for(C=0;C Date: Sun, 25 Jun 2017 11:54:11 -0600 Subject: [PATCH 58/68] Fix 'Github' typos --- docs/contributing.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/contributing.md b/docs/contributing.md index 795177af..189d0b99 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -4,7 +4,7 @@ ## How do I go about contributing ideas or new features? -Create an [issue thread on Github](https://github.com/MithrilJS/mithril.js/issues/new) to suggest your idea so the community can discuss it. +Create an [issue thread on GitHub](https://github.com/MithrilJS/mithril.js/issues/new) to suggest your idea so the community can discuss it. If the consensus is that it's a good idea, the fastest way to get it into a release is to send a pull request. Without a PR, the time to implement the feature will depend on the bandwidth of the development team and its list of priorities. @@ -20,12 +20,12 @@ Ideally, the best way to report bugs is to provide a small snippet of code where To send a pull request: -- fork the repo (button at the top right in Github) -- clone the forked repo to your computer (green button in Github) +- fork the repo (button at the top right in GitHub) +- clone the forked repo to your computer (green button in GitHub) - create a feature branch (run `git checkout -b the-feature-branch-name`) - make your changes - run the tests (run `npm t`) -- submit a pull request (go to the pull requests tab in Github, click the green button and select your feature branch) +- submit a pull request (go to the pull requests tab in GitHub, click the green button and select your feature branch) From 6a65ebc9afc41e6381691508aeab2db1366d223f Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 26 Jun 2017 16:11:57 -0600 Subject: [PATCH 59/68] Use manual path resolution in webpack.config.js Webpack doesn't support relative paths in the output.path config parameter, so this updated code uses the 'path' module to resolve the relative path. --- docs/es6.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/es6.md b/docs/es6.md index 7a453c5d..0192a6ca 100644 --- a/docs/es6.md +++ b/docs/es6.md @@ -70,10 +70,12 @@ Create a `.babelrc` file: Next, create a file called `webpack.config.js` ```javascript +const path = require('path') + module.exports = { entry: './src/index.js', output: { - path: './bin', + path: path.resolve(__dirname, './bin'), filename: 'app.js', }, module: { From f7c4284ffc361d3a3555d37592bbf97079bd4ed0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 26 Jun 2017 16:13:37 -0600 Subject: [PATCH 60/68] Update webpack.config.js (in JSX docs) Webpack doesn't support relative paths in the output.path config parameter, so this updated code uses the 'path' module to resolve the relative path. --- docs/jsx.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/jsx.md b/docs/jsx.md index b10d86eb..0b8cd7f9 100644 --- a/docs/jsx.md +++ b/docs/jsx.md @@ -112,10 +112,12 @@ Create a `.babelrc` file: Next, create a file called `webpack.config.js` ```javascript +const path = require('path') + module.exports = { entry: './src/index.js', output: { - path: './bin', + path: path.resolve(__dirname, './bin'), filename: 'app.js', }, module: { From a867754d6a3c2538a8f03f0ff8118d89f8cf344a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Yves=20G=C3=A9rardy?= Date: Wed, 5 Jul 2017 23:58:01 +0200 Subject: [PATCH 61/68] Don't overwrite the options object when redirecting from onmatch with m.route.set, fix #1857 --- api/router.js | 5 ++++- api/tests/test-router.js | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/api/router.js b/api/router.js index c182008f..93f2e074 100644 --- a/api/router.js +++ b/api/router.js @@ -39,7 +39,10 @@ module.exports = function($window, redrawService) { redrawService.subscribe(root, run) } route.set = function(path, data, options) { - if (lastUpdate != null) options = {replace: true} + if (lastUpdate != null) { + options = options || {} + options.replace = true + } lastUpdate = null routeService.setPath(path, data, options) } diff --git a/api/tests/test-router.js b/api/tests/test-router.js index 9406de70..f84019eb 100644 --- a/api/tests/test-router.js +++ b/api/tests/test-router.js @@ -665,7 +665,7 @@ o.spec("route", function() { route(root, "/a", { "/a" : { onmatch: function() { - route.set("/b") + route.set("/b", {}, {state: {a: 5}}) }, render: render }, @@ -684,6 +684,7 @@ o.spec("route", function() { o(view.callCount).equals(1) o(root.childNodes.length).equals(1) o(root.firstChild.nodeName).equals("DIV") + o($window.history.state).deepEquals({a: 5}) done() }) From 67b11f156a4f25dbb7de94c2fd2179d69dd49f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Yves=20G=C3=A9rardy?= Date: Thu, 6 Jul 2017 19:48:24 +0200 Subject: [PATCH 62/68] Add a test to ensure that e.redraw is cleared if it was false --- api/tests/test-mount.js | 3 +-- api/tests/test-router.js | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/tests/test-mount.js b/api/tests/test-mount.js index db2bee04..53a35640 100644 --- a/api/tests/test-mount.js +++ b/api/tests/test-mount.js @@ -5,7 +5,6 @@ var components = require("../../test-utils/components") var domMock = require("../../test-utils/domMock") var m = require("../../render/hyperscript") -var coreRenderer = require("../../render/render") var apiRedraw = require("../../api/redraw") var apiMounter = require("../../api/mount") @@ -20,7 +19,7 @@ o.spec("mount", function() { redrawService = apiRedraw($window) mount = apiMounter(redrawService) - render = coreRenderer($window).render + render = redrawService.render }) o("throws on invalid component", function() { diff --git a/api/tests/test-router.js b/api/tests/test-router.js index 9406de70..0b078e67 100644 --- a/api/tests/test-router.js +++ b/api/tests/test-router.js @@ -224,9 +224,11 @@ o.spec("route", function() { } }) + o(oninit.callCount).equals(1) + root.firstChild.dispatchEvent(e) - o(oninit.callCount).equals(1) + o(e.redraw).notEquals(false) // Wrapped to ensure no redraw fired callAsync(function() { From 198f9ca7a9aaf40441d88171044ce72aea24d519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Yves=20G=C3=A9rardy?= Date: Thu, 6 Jul 2017 00:04:38 +0200 Subject: [PATCH 63/68] Reset e.redraw when it was set to `false`. fixes #1850 --- api/redraw.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/redraw.js b/api/redraw.js index 1b22271b..a05b6c88 100644 --- a/api/redraw.js +++ b/api/redraw.js @@ -26,7 +26,8 @@ function throttle(callback) { module.exports = function($window) { var renderService = coreRenderer($window) renderService.setEventCallback(function(e) { - if (e.redraw !== false) redraw() + if (e.redraw === false) e.redraw = undefined + else redraw() }) var callbacks = [] From 218c29a011c0961fa0627c6623955fc5a1a5f072 Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Thu, 6 Jul 2017 19:12:21 +0000 Subject: [PATCH 64/68] Bundled output for commit 4df43499beffd407d6cce027515bbb5eaf11b142 [skip ci] --- mithril.js | 8 +++-- mithril.min.js | 80 +++++++++++++++++++++++------------------------ package-lock.json | 5 --- 3 files changed, 46 insertions(+), 47 deletions(-) diff --git a/mithril.js b/mithril.js index 5917f595..209d1eb5 100644 --- a/mithril.js +++ b/mithril.js @@ -996,7 +996,8 @@ function throttle(callback) { var _11 = function($window) { var renderService = coreRenderer($window) renderService.setEventCallback(function(e) { - if (e.redraw !== false) redraw() + if (e.redraw === false) e.redraw = undefined + else redraw() }) var callbacks = [] function subscribe(key1, callback) { @@ -1195,7 +1196,10 @@ var _20 = function($window, redrawService0) { redrawService0.subscribe(root, run1) } route.set = function(path, data, options) { - if (lastUpdate != null) options = {replace: true} + if (lastUpdate != null) { + options = options || {} + options.replace = true + } lastUpdate = null routeService.setPath(path, data, options) } diff --git a/mithril.min.js b/mithril.min.js index e8500e8e..b50b7876 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,44 +1,44 @@ -(function(){function z(b,d,e,f,g,l){return{tag:b,key:d,attrs:e,children:f,text:g,dom:l,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function A(b){var d,e=arguments[1],f=2;if(null==b||"string"!==typeof b&&"function"!==typeof b&&"function"!==typeof b.view)throw Error("The selector must be either a string or a component.");if("string"===typeof b&&!(d=M[b])){var g="div";for(var l=[],k={};d=P.exec(b);){var r=d[1],p=d[2];""===r&&""!==p?g=p:"#"===r?k.id=p:"."===r?l.push(p): -"["===d[3][0]&&((r=d[6])&&(r=r.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===d[4]?l.push(r):k[d[4]]=""===r?r:r||!0)}0a.indexOf("?")?"?":"&";a+=e+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(x){throw Error(a); -}}function r(a){return a.responseText}function p(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;da.indexOf("?")?"?":"&";a+=e+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(z){throw Error(a); +}}function r(a){return a.responseText}function p(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;dm.status||304===m.status||S.test(a.url))d(p(a.type, -b));else{var h=Error(m.responseText),c;for(c in b)h[c]=b[c];e(h)}}catch(q){e(q)}};f&&null!=a.data?m.send(a.data):m.send()});return!0===a.background?x:n(x)},jsonp:function(a,k){var n=e();a=f(a,k);var r=new d(function(d,e){var f=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+m++,k=b.document.createElement("script");b[f]=function(e){k.parentNode.removeChild(k);d(p(a.type,e));delete b[f]};k.onerror=function(){k.parentNode.removeChild(k);e(Error("JSONP request failed"));delete b[f]};null== -a.data&&(a.data={});a.url=g(a.url,a.data);a.data[a.callbackKey||"callback"]=f;k.src=l(a.url,a.data);b.document.documentElement.appendChild(k)});return!0===a.background?r:n(r)},setCompletionCallback:function(a){n=a}}}(window,y),O=function(b){function d(h,c,q,a,b,d,f){for(;q=n&&E>=B;){var t=c[n];u=q[B];if(t!==u||b)if(null==t)n++;else if(null==u)B++;else if(t.key===u.key){var w=null!=x&&n>=c.length-x.length||null==x&&b;n++;B++;k(h,t,u,f,p(c,n,g),w,l);b&&t.tag===u.tag&&m(h,r(t),g)}else if(t=c[v],t!==u||b)if(null==t)v--;else if(null==u)B++;else if(t.key===u.key)w=null!=x&&v>=c.length-x.length||null==x&&b, -k(h,t,u,f,p(c,v+1,g),w,l),(b||B=n&&E>=B;){t=c[v];u=q[E];if(t!==u||b)if(null==t)v--;else{if(null!=u)if(t.key===u.key)w=null!=x&&v>=c.length-x.length||null==x&&b,k(h,t,u,f,p(c,v+1,g),w,l),b&&t.tag===u.tag&&m(h,r(t),g),null!=t.dom&&(g=t.dom),v--;else{if(!H){H=c;w=v;t={};var C;for(C=0;C=n&&E>=B;){var t=c[n];u=q[B];if(t!==u||b)if(null==t)n++;else if(null==u)B++;else if(t.key===u.key){var x=null!=z&&n>=c.length-z.length||null==z&&b;n++;B++;k(h,t,u,g,p(c,n,f),x,l);b&&t.tag===u.tag&&m(h,r(t),f)}else if(t=c[v],t!==u||b)if(null==t)v--;else if(null==u)B++;else if(t.key===u.key)x=null!=z&&v>=c.length-z.length||null==z&&b, +k(h,t,u,g,p(c,v+1,f),x,l),(b||B=n&&E>=B;){t=c[v];u=q[E];if(t!==u||b)if(null==t)v--;else{if(null!=u)if(t.key===u.key)x=null!=z&&v>=c.length-z.length||null==z&&b,k(h,t,u,g,p(c,v+1,f),x,l),b&&t.tag===u.tag&&m(h,r(t),f),null!=t.dom&&(f=t.dom),v--;else{if(!H){H=c;x=v;t={};var D;for(D=0;D Date: Tue, 11 Jul 2017 09:39:46 +0200 Subject: [PATCH 66/68] Move "use strict" directive inside the stream IIFE #1831 --- stream/stream.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stream/stream.js b/stream/stream.js index 18c7e608..fd2dcffb 100644 --- a/stream/stream.js +++ b/stream/stream.js @@ -1,6 +1,7 @@ -"use strict" - +/* eslint-disable */ ;(function() { +"use strict" +/* eslint-enable */ var guid = 0, HALT = {} function createStream() { From fbcefe3180aca6472bd97f28832328bf7b2143cb Mon Sep 17 00:00:00 2001 From: Gandalf-the-Bot Date: Tue, 11 Jul 2017 08:15:38 +0000 Subject: [PATCH 67/68] Bundled output for commit c7d33c7b81c2003cc739443dbca8035f4d78178b [skip ci] --- mithril.min.js | 66 +++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/mithril.min.js b/mithril.min.js index b50b7876..85b9ebb1 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,44 +1,44 @@ -(function(){function y(b,d,e,f,g,l){return{tag:b,key:d,attrs:e,children:f,text:g,dom:l,domSize:void 0,state:void 0,_state:void 0,events:void 0,instance:void 0,skip:!1}}function A(b){var d,e=arguments[1],f=2;if(null==b||"string"!==typeof b&&"function"!==typeof b&&"function"!==typeof b.view)throw Error("The selector must be either a string or a component.");if("string"===typeof b&&!(d=M[b])){var g="div";for(var l=[],k={};d=P.exec(b);){var r=d[1],p=d[2];""===r&&""!==p?g=p:"#"===r?k.id=p:"."===r?l.push(p): -"["===d[3][0]&&((r=d[6])&&(r=r.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),"class"===d[4]?l.push(r):k[d[4]]=""===r?r:r||!0)}0a.indexOf("?")?"?":"&";a+=e+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(z){throw Error(a); -}}function r(a){return a.responseText}function p(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;da.indexOf("?")?"?":"&";a+=e+d}return a}function k(a){try{return""!==a?JSON.parse(a):null}catch(x){throw Error(a); +}}function r(a){return a.responseText}function p(a,b){if("function"===typeof a)if(Array.isArray(b))for(var d=0;dm.status||304===m.status||S.test(a.url))d(p(a.type, -b));else{var h=Error(m.responseText),c;for(c in b)h[c]=b[c];e(h)}}catch(q){e(q)}};f&&null!=a.data?m.send(a.data):m.send()});return!0===a.background?z:n(z)},jsonp:function(a,k){var n=e();a=f(a,k);var r=new d(function(d,e){var f=a.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+m++,k=b.document.createElement("script");b[f]=function(e){k.parentNode.removeChild(k);d(p(a.type,e));delete b[f]};k.onerror=function(){k.parentNode.removeChild(k);e(Error("JSONP request failed"));delete b[f]};null== -a.data&&(a.data={});a.url=g(a.url,a.data);a.data[a.callbackKey||"callback"]=f;k.src=l(a.url,a.data);b.document.documentElement.appendChild(k)});return!0===a.background?r:n(r)},setCompletionCallback:function(a){n=a}}}(window,w),O=function(b){function d(h,c,q,a,b,d,g){for(;q=n&&E>=B;){var t=c[n];u=q[B];if(t!==u||b)if(null==t)n++;else if(null==u)B++;else if(t.key===u.key){var x=null!=z&&n>=c.length-z.length||null==z&&b;n++;B++;k(h,t,u,g,p(c,n,f),x,l);b&&t.tag===u.tag&&m(h,r(t),f)}else if(t=c[v],t!==u||b)if(null==t)v--;else if(null==u)B++;else if(t.key===u.key)x=null!=z&&v>=c.length-z.length||null==z&&b, -k(h,t,u,g,p(c,v+1,f),x,l),(b||B=n&&E>=B;){t=c[v];u=q[E];if(t!==u||b)if(null==t)v--;else{if(null!=u)if(t.key===u.key)x=null!=z&&v>=c.length-z.length||null==z&&b,k(h,t,u,g,p(c,v+1,f),x,l),b&&t.tag===u.tag&&m(h,r(t),f),null!=t.dom&&(f=t.dom),v--;else{if(!H){H=c;x=v;t={};var D;for(D=0;D=n&&E>=B;){var t=c[n];u=q[B];if(t!==u||b)if(null==t)n++;else if(null==u)B++;else if(t.key===u.key){var w=null!=x&&n>=c.length-x.length||null==x&&b;n++;B++;k(h,t,u,g,p(c,n,f),w,l);b&&t.tag===u.tag&&m(h,r(t),f)}else if(t=c[v],t!==u||b)if(null==t)v--;else if(null==u)B++;else if(t.key===u.key)w=null!=x&&v>=c.length-x.length||null==x&&b, +k(h,t,u,g,p(c,v+1,f),w,l),(b||B=n&&E>=B;){t=c[v];u=q[E];if(t!==u||b)if(null==t)v--;else{if(null!=u)if(t.key===u.key)w=null!=x&&v>=c.length-x.length||null==x&&b,k(h,t,u,g,p(c,v+1,f),w,l),b&&t.tag===u.tag&&m(h,r(t),f),null!=t.dom&&(f=t.dom),v--;else{if(!H){H=c;w=v;t={};var C;for(C=0;C Date: Tue, 11 Jul 2017 10:38:23 +0200 Subject: [PATCH 68/68] v1.1.2 change log --- docs/change-log.md | 124 +++++++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 48 deletions(-) diff --git a/docs/change-log.md b/docs/change-log.md index eedac49b..35654a4b 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -1,5 +1,6 @@ # Change log +- [v1.1.2](#v112) - [v1.1.1](#v111) - [v1.1.0](#v110) - [v1.0.1](#v101) @@ -8,6 +9,33 @@ --- +### v1.1.2 + +#### Bug fixes: + +- core: Namespace fixes [#1819](https://github.com/MithrilJS/mithril.js/issues/1819), ([#1825](https://github.com/MithrilJS/mithril.js/pull/1825) [@SamuelTilly](https://github.com/SamuelTilly)), [#1820](https://github.com/MithrilJS/mithril.js/issues/1820) ([#1864](https://github.com/MithrilJS/mithril.js/pull/1864)), [#1872](https://github.com/MithrilJS/mithril.js/issues/1872) ([#1873](https://github.com/MithrilJS/mithril.js/pull/1873)) +- core: Fix select option to allow empty string value [#1814](https://github.com/MithrilJS/mithril.js/issues/1814) ([#1828](https://github.com/MithrilJS/mithril.js/pull/1828) [@spacejack](https://github.com/spacejack)) +- core: Reset e.redraw when it was set to `false` [#1850](https://github.com/MithrilJS/mithril.js/issues/1850) ([#1890](https://github.com/MithrilJS/mithril.js/pull/1890)) +- core: differentiate between `{ value: "" }` and `{ value: 0 }` for form elements [#1595 comment](https://github.com/MithrilJS/mithril.js/pull/1595#issuecomment-304071453) ([#1862](https://github.com/MithrilJS/mithril.js/pull/1862)) +- core: Don't reset the cursor of textareas in IE10 when setting an identical `value` [#1870](https://github.com/MithrilJS/mithril.js/issues/1870) (([#1871](https://github.com/MithrilJS/mithril.js/pull/1871))) +- hypertext: Correct handling of `[value=""]` ([#1843](https://github.com/MithrilJS/mithril.js/issues/1843), [@CreaturesInUnitards](https://github.com/CreaturesInUnitards)) +- router: Don't overwrite the options object when redirecting from `onmatch with m.route.set()` [#1857](https://github.com/MithrilJS/mithril.js/issues/1857) ([#1889](https://github.com/MithrilJS/mithril.js/pull/1889)) +- stream: Move the "use strict" directive inside the IIFE [#1831](https://github.com/MithrilJS/mithril.js/issues/1831) ([#1893](https://github.com/MithrilJS/mithril.js/pull/1893)) + +#### Ospec improvements: + +- Shell command: Ignore hidden directories and files ([#1855](https://github.com/MithrilJS/mithril.js/pull/1855) [@pdfernhout)](https://github.com/pdfernhout)) +- Library: Add the possibility to name new test suites ([#1529](https://github.com/MithrilJS/mithril.js/pull/1529)) + +#### Docs / Repo maintenance: + +Our thanks to [@0joshuaolson1](https://github.com/0joshuaolson1), [@ACXgit](https://github.com/ACXgit), [@cavemansspa](https://github.com/cavemansspa), [@CreaturesInUnitards](https://github.com/CreaturesInUnitards), [@dlepaux](https://github.com/dlepaux), [@isaaclyman](https://github.com/isaaclyman), [@kevinkace](https://github.com/kevinkace), [@micellius](https://github.com/micellius), [@spacejack](https://github.com/spacejack) and [@yurivish](https://github.com/yurivish) + +#### Other: + +- Addition of a performance regression test suite (#) + + ### v1.1.1 #### Bug fixes @@ -155,7 +183,7 @@ m("div", { // Called after the node is updated onupdate : function(vnode) { /*...*/ }, // Called before the node is removed, return a Promise that resolves when - // ready for the node to be removed from the DOM + // ready for the node to be removed from the DOM onbeforeremove : function(vnode) { /*...*/ }, // Called before the node is removed, but after onbeforeremove calls done() onremove : function(vnode) { /*...*/ } @@ -472,9 +500,9 @@ In `v0.2.x` reading route params was entirely handled through `m.route.param()`. ```javascript m.route(document.body, "/booga", { "/:attr" : { - controller : function() { - m.route.param("attr") // "booga" - }, + controller : function() { + m.route.param("attr") // "booga" + }, view : function() { m.route.param("attr") // "booga" } @@ -489,11 +517,11 @@ m.route(document.body, "/booga", { "/:attr" : { oninit : function(vnode) { vnode.attrs.attr // "booga" - m.route.param("attr") // "booga" + m.route.param("attr") // "booga" }, view : function(vnode) { vnode.attrs.attr // "booga" - m.route.param("attr") // "booga" + m.route.param("attr") // "booga" } } }) @@ -531,14 +559,14 @@ It is no longer possible to prevent unmounting via `onunload`'s `e.preventDefaul ```javascript var Component = { - controller: function() { - this.onunload = function(e) { - if (condition) e.preventDefault() - } - }, - view: function() { - return m("a[href=/]", {config: m.route}) - } + controller: function() { + this.onunload = function(e) { + if (condition) e.preventDefault() + } + }, + view: function() { + return m("a[href=/]", {config: m.route}) + } } ``` @@ -546,9 +574,9 @@ var Component = { ```javascript var Component = { - view: function() { - return m("a", {onclick: function() {if (!condition) m.route.set("/")}}) - } + view: function() { + return m("a", {onclick: function() {if (!condition) m.route.set("/")}}) + } } ``` @@ -562,14 +590,14 @@ Components no longer call `this.onunload` when they are being removed. They now ```javascript var Component = { - controller: function() { - this.onunload = function(e) { - // ... - } - }, - view: function() { - // ... - } + controller: function() { + this.onunload = function(e) { + // ... + } + }, + view: function() { + // ... + } } ``` @@ -577,12 +605,12 @@ var Component = { ```javascript var Component = { - onremove : function() { - // ... - } - view: function() { - // ... - } + onremove : function() { + // ... + } + view: function() { + // ... + } } ``` @@ -598,13 +626,13 @@ In addition, requests no longer have `m.startComputation`/`m.endComputation` sem ```javascript var data = m.request({ - method: "GET", - url: "https://api.github.com/", - initialValue: [], + method: "GET", + url: "https://api.github.com/", + initialValue: [], }) setTimeout(function() { - console.log(data()) + console.log(data()) }, 1000) ``` @@ -613,15 +641,15 @@ setTimeout(function() { ```javascript var data = [] m.request({ - method: "GET", - url: "https://api.github.com/", + method: "GET", + url: "https://api.github.com/", }) .then(function (responseBody) { - data = responseBody + data = responseBody }) setTimeout(function() { - console.log(data) // note: not a getter-setter + console.log(data) // note: not a getter-setter }, 1000) ``` @@ -653,11 +681,11 @@ greetAsync() ```javascript var greetAsync = function() { - return new Promise(function(resolve){ - setTimeout(function() { - resolve("hello") - }, 1000) - }) + return new Promise(function(resolve){ + setTimeout(function() { + resolve("hello") + }, 1000) + }) } greetAsync() @@ -679,7 +707,7 @@ m.sync([ m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }), ]) .then(function (users) { - console.log("Contributors:", users[0].name, "and", users[1].name) + console.log("Contributors:", users[0].name, "and", users[1].name) }) ``` @@ -691,7 +719,7 @@ Promise.all([ m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }), ]) .then(function (users) { - console.log("Contributors:", users[0].name, "and", users[1].name) + console.log("Contributors:", users[0].name, "and", users[1].name) }) ``` @@ -706,7 +734,7 @@ In `v0.2.x`, the `xlink` namespace was the only supported attribute namespace, a ```javascript m("svg", // the `href` attribute is namespaced automatically - m("image[href='image.gif']") + m("image[href='image.gif']") ) ``` @@ -715,7 +743,7 @@ m("svg", ```javascript m("svg", // User-specified namespace on the `href` attribute - m("image[xlink:href='image.gif']") + m("image[xlink:href='image.gif']") ) ```