diff --git a/docs/change-log.md b/docs/change-log.md index ca5e695c..2d3b03fe 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -1,5 +1,6 @@ # Change log +- [v1.1.1](#v111) - [v1.1.0](#v110) - [v1.0.1](#v101) - [Migrating from v0.2.x](#migrating-from-v02x) @@ -7,12 +8,21 @@ --- +### v1.1.1 + +#### 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: remove `attrs.class` after normalizing to `attrs.className` - [#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 #### News - support for ES6 class components - support for closure components +- improvements in build and release automation #### Bug fixes diff --git a/docs/framework-comparison.md b/docs/framework-comparison.md index a27ae135..cf8a3483 100644 --- a/docs/framework-comparison.md +++ b/docs/framework-comparison.md @@ -203,7 +203,7 @@ Vue | Mithril Vue is heavily inspired by Angular and has many things that Angular does (e.g. directives, filters, bi-directional bindings, `v-cloak`), but also has things inspired by React (e.g. components). As of Vue 2.0, it's also possible to write templates using hyperscript/JSX syntax (in addition to single-file components and the various webpack-based language transpilation plugins). Vue provides both bi-directional data binding and an optional Redux-like state management library, but unlike Angular, it provides no style guide. The many-ways-of-doing-one-thing approach can cause architectural fragmentation in long-lived projects. -Mithril has far less concepts and typically organizes applications in terms of components and a data layer. There are no different ways of defining components, and thus there's no need to install different sets of tools to make different flavors work. +Mithril has far less concepts and typically organizes applications in terms of components and a data layer. All component creation styles in Mithril output the same vnode structure using native Javascript features only. The direct consequence of leaning on the language is less tooling and a simpler project setup. #### Documentation diff --git a/docs/keys.md b/docs/keys.md index d6b16f8c..0ad0b0c5 100644 --- a/docs/keys.md +++ b/docs/keys.md @@ -96,21 +96,21 @@ users.map(function(u) { #### Avoid hiding keys in component root elements -If you refactor the code and put the button inside a component, the key must be moved out of the component and placed back where the component took the place of the button. +If you refactor the code and make a user component, the key must be moved out of the component and put on the component itself, since it is now the immediate child of the array. ```javascript // AVOID -var Button = { +var User = { view: function(vnode) { - return m("button", {key: vnode.attrs.id}, u.name) + return m("div", { key: vnode.attrs.user.id }, [ + m(Button, vnode.attrs.user.name) + ]) } } // PREFER users.map(function(u) { - return m("div", [ - m(Button, {key: u.id}, u.name) // key should be here, not in component - ]) + return m(User, { key: u.id, user: u }) // key should be here, not in component }) ``` @@ -195,12 +195,12 @@ users[0].key = 'c' // AVOID users.map(function(user){ // The component for John will be destroyed and recreated - return m(UserComponent, user) + return m(UserComponent, user) }) // PREFER users.map(function(user){ // Key is specifically extracted: data model is given its own property - return m(UserComponent, {key: user.id, model: user}) + return m(UserComponent, {key: user.id, model: user}) }) ``` diff --git a/test-utils/pushStateMock.js b/test-utils/pushStateMock.js index ebd963fd..c727f393 100644 --- a/test-utils/pushStateMock.js +++ b/test-utils/pushStateMock.js @@ -1,6 +1,18 @@ "use strict" var parseURL = require("../test-utils/parseURL") +var callAsync = require("../test-utils/callAsync.js") + +function debouncedAsync(f) { + var ref + return function() { + if (ref != null) return + ref = callAsync(function(){ + ref = null + f() + }) + } +} module.exports = function(options) { if (options == null) options = {} @@ -29,7 +41,9 @@ module.exports = function(options) { if (data.search != null && data.search !== search) search = data.search, isNew = true if (data.hash != null && data.hash !== hash) { hash = data.hash - if (!isNew) hashchange() + if (!isNew) { + hashchange() + } } return isNew } @@ -38,9 +52,10 @@ module.exports = function(options) { if (value === "") return "" return (value.charAt(0) !== prefix ? prefix : "") + value } - function hashchange() { + function _hashchange() { if (typeof $window.onhashchange === "function") $window.onhashchange({type: "hashchange"}) } + var hashchange = debouncedAsync(_hashchange) function popstate() { if (typeof $window.onpopstate === "function") $window.onpopstate({type: "popstate", state: $window.history.state}) } diff --git a/test-utils/tests/test-pushStateMock.js b/test-utils/tests/test-pushStateMock.js index 41efacab..b07ee1b4 100644 --- a/test-utils/tests/test-pushStateMock.js +++ b/test-utils/tests/test-pushStateMock.js @@ -2,7 +2,7 @@ var o = require("../../ospec/ospec") var pushStateMock = require("../../test-utils/pushStateMock") - +var callAsync = require("../../test-utils/callAsync") o.spec("pushStateMock", function() { var $window @@ -478,93 +478,160 @@ o.spec("pushStateMock", function() { }) }) o.spec("onhashchance", function() { - o("onhashchange triggers on location.href change", function() { + o("onhashchange triggers on location.href change", function(done) { $window.onhashchange = o.spy() $window.location.href = "http://localhost/#a" - o($window.onhashchange.callCount).equals(1) - o($window.onhashchange.args[0].type).equals("hashchange") + callAsync(function(){ + o($window.onhashchange.callCount).equals(1) + o($window.onhashchange.args[0].type).equals("hashchange") + done() + }) }) - o("onhashchange triggers on relative location.href change", function() { + o("onhashchange triggers on relative location.href change", function(done) { $window.onhashchange = o.spy() $window.location.href = "#a" - o($window.onhashchange.callCount).equals(1) + callAsync(function(){ + o($window.onhashchange.callCount).equals(1) + done() + }) }) - o("onhashchange triggers on location.hash change", function() { + o("onhashchange triggers on location.hash change", function(done) { $window.onhashchange = o.spy() $window.location.hash = "#a" - o($window.onhashchange.callCount).equals(1) + callAsync(function(){ + o($window.onhashchange.callCount).equals(1) + done() + }) }) - o("onhashchange does not trigger on page change", function() { + o("onhashchange does not trigger on page change", function(done) { $window.onhashchange = o.spy() $window.location.href = "http://localhost/a" - o($window.onhashchange.callCount).equals(0) + callAsync(function(){ + o($window.onhashchange.callCount).equals(0) + done() + }) }) - o("onhashchange does not trigger on page change with different hash", function() { + o("onhashchange does not trigger on page change with different hash", function(done) { $window.location.href = "http://localhost/#a" - $window.onhashchange = o.spy() - $window.location.href = "http://localhost/a#b" + callAsync(function(){ + $window.onhashchange = o.spy() + $window.location.href = "http://localhost/a#b" - o($window.onhashchange.callCount).equals(0) + callAsync(function(){ + o($window.onhashchange.callCount).equals(0) + done() + }) + }) }) - o("onhashchange does not trigger on page change with same hash", function() { + o("onhashchange does not trigger on page change with same hash", function(done) { $window.location.href = "http://localhost/#b" - $window.onhashchange = o.spy() - $window.location.href = "http://localhost/a#b" + callAsync(function(){ + $window.onhashchange = o.spy() + $window.location.href = "http://localhost/a#b" - o($window.onhashchange.callCount).equals(0) + callAsync(function(){ + o($window.onhashchange.callCount).equals(0) + done() + }) + }) }) - o("onhashchange triggers on history.back()", function() { + o("onhashchange triggers on history.back()", function(done) { $window.location.href = "#a" - $window.onhashchange = o.spy() - $window.history.back() + callAsync(function(){ + $window.onhashchange = o.spy() + $window.history.back() - o($window.onhashchange.callCount).equals(1) + callAsync(function(){ + o($window.onhashchange.callCount).equals(1) + done() + }) + }) }) - o("onhashchange triggers on history.forward()", function() { + o("onhashchange triggers on history.forward()", function(done) { $window.location.href = "#a" - $window.onhashchange = o.spy() - $window.history.back() - $window.history.forward() + callAsync(function(){ + $window.onhashchange = o.spy() + $window.history.back() + callAsync(function(){ + $window.history.forward() - o($window.onhashchange.callCount).equals(2) + callAsync(function(){ + o($window.onhashchange.callCount).equals(2) + done() + }) + }) + }) }) - o("onhashchange does not trigger on history.back() that causes page change with different hash", function() { + o("onhashchange triggers once when the hash changes twice in a single tick", function(done) { + $window.location.href = "#a" + callAsync(function(){ + $window.onhashchange = o.spy() + $window.history.back() + $window.history.forward() + + callAsync(function(){ + o($window.onhashchange.callCount).equals(1) + done() + }) + }) + }) + o("onhashchange does not trigger on history.back() that causes page change with different hash", function(done) { $window.location.href = "#a" $window.location.href = "a#b" - $window.onhashchange = o.spy() - $window.history.back() + callAsync(function(){ + $window.onhashchange = o.spy() + $window.history.back() - o($window.onhashchange.callCount).equals(0) + callAsync(function(){ + o($window.onhashchange.callCount).equals(0) + done() + }) + }) }) - o("onhashchange does not trigger on history.back() that causes page change with same hash", function() { + o("onhashchange does not trigger on history.back() that causes page change with same hash", function(done) { $window.location.href = "#a" $window.location.href = "a#a" - $window.onhashchange = o.spy() - $window.history.back() + callAsync(function(){ + $window.onhashchange = o.spy() + $window.history.back() - o($window.onhashchange.callCount).equals(0) + callAsync(function(){ + o($window.onhashchange.callCount).equals(0) + done() + }) + }) }) - o("onhashchange does not trigger on history.forward() that causes page change with different hash", function() { + o("onhashchange does not trigger on history.forward() that causes page change with different hash", function(done) { $window.location.href = "#a" $window.location.href = "a#b" - $window.onhashchange = o.spy() - $window.history.back() - $window.history.forward() + callAsync(function(){ + $window.onhashchange = o.spy() + $window.history.back() + $window.history.forward() - o($window.onhashchange.callCount).equals(0) + callAsync(function(){ + o($window.onhashchange.callCount).equals(0) + done() + }) + }) }) - o("onhashchange does not trigger on history.forward() that causes page change with same hash", function() { + o("onhashchange does not trigger on history.forward() that causes page change with same hash", function(done) { $window.location.href = "#a" $window.location.href = "a#b" - $window.onhashchange = o.spy() - $window.history.back() - $window.history.forward() + callAsync(function(){ + $window.onhashchange = o.spy() + $window.history.back() + $window.history.forward() - o($window.onhashchange.callCount).equals(0) + callAsync(function(){ + o($window.onhashchange.callCount).equals(0) + done() + }) + }) }) }) o.spec("onunload", function() {