diff --git a/.gitattributes b/.gitattributes
index 574ffd50..6de5c647 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,4 +1,3 @@
* text=auto
/mithril.js binary
/mithril.min.js binary
-/stream/stream.js binary
diff --git a/docs/change-log.md b/docs/change-log.md
index ca5e695c..042b8008 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: 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
#### 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/docs/simple-application.md b/docs/simple-application.md
index 0fdab745..b226aa2c 100644
--- a/docs/simple-application.md
+++ b/docs/simple-application.md
@@ -477,7 +477,7 @@ module.exports = {
oninput: m.withAttr("value", function(value) {User.current.lastName = value}),
value: User.current.lastName
}),
- m("button.button[type=button]", "Save"),
+ m("button.button[type=submit]", "Save"),
])
}
}
diff --git a/render/hyperscript.js b/render/hyperscript.js
index 24151431..3d4b21e4 100644
--- a/render/hyperscript.js
+++ b/render/hyperscript.js
@@ -34,8 +34,8 @@ function execSelector(state, attrs, children) {
}
}
- if (className != null) {
- if (attrs.class != null) {
+ if (className !== undefined) {
+ if (attrs.class !== undefined) {
attrs.class = undefined
attrs.className = className
}
@@ -73,7 +73,7 @@ function hyperscript(selector) {
var cached = selectorCache[selector] || compileSelector(selector)
}
- if (!attrs) {
+ if (attrs == null) {
attrs = {}
} else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) {
attrs = {}
diff --git a/render/tests/index.html b/render/tests/index.html
index b978ae6f..eda51921 100644
--- a/render/tests/index.html
+++ b/render/tests/index.html
@@ -8,7 +8,7 @@
-
+
diff --git a/render/tests/test-hyperscript.js b/render/tests/test-hyperscript.js
index 2264fe18..3ef03358 100644
--- a/render/tests/test-hyperscript.js
+++ b/render/tests/test-hyperscript.js
@@ -16,6 +16,108 @@ 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({
+ class: undefined,
+ className: null
+ })
+ o(m('a', {
+ class: undefined
+ }).attrs).deepEquals({
+ class: undefined,
+ })
+ o(m('a', {
+ class: false
+ }).attrs).deepEquals({
+ class: undefined,
+ className: false
+ })
+ o(m('a', {
+ class: true
+ }).attrs).deepEquals({
+ 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({
+ class: undefined,
+ className: "x null"
+ })
+ o(m('a.x', {
+ class: undefined
+ }).attrs).deepEquals({
+ class: undefined,
+ className: "x"
+ })
+ o(m('a.x', {
+ class: false
+ }).attrs).deepEquals({
+ class: undefined,
+ className: "x false"
+ })
+ o(m('a.x', {
+ class: true
+ }).attrs).deepEquals({
+ class: undefined,
+ className: "x true"
+ })
+ o(m('a', {
+ className: null
+ }).attrs).deepEquals({
+ className: null
+ })
+ o(m('a', {
+ className: undefined
+ }).attrs).deepEquals({
+ className: undefined
+ })
+ o(m('a', {
+ className: false
+ }).attrs).deepEquals({
+ className: false
+ })
+ o(m('a', {
+ className: true
+ }).attrs).deepEquals({
+ className: true
+ })
+ o(m('a.x', {
+ className: null
+ }).attrs).deepEquals({
+ className: "x"
+ })
+ o(m('a.x', {
+ className: undefined
+ }).attrs).deepEquals({
+ className: "x"
+ })
+ o(m('a.x', {
+ className: false
+ }).attrs).deepEquals({
+ className: "x"
+ })
+ o(m('a.x', {
+ className: true
+ }).attrs).deepEquals({
+ className: "x true"
+ })
+ })
o("handles class in selector", function() {
var vnode = m(".a")
@@ -273,6 +375,11 @@ o.spec("hyperscript", function() {
o(vnode.children[0]).equals(null)
o(vnode.children[1]).equals(undefined)
})
+ o("handles falsy number single child without attrs", function() {
+ var vnode = m("div", 0)
+
+ o(vnode.text).equals(0)
+ })
})
o.spec("permutations", function() {
o("handles null attr and children", function() {
diff --git a/render/tests/test-onbeforeremove.js b/render/tests/test-onbeforeremove.js
index 2e23215b..0e83d4a0 100644
--- a/render/tests/test-onbeforeremove.js
+++ b/render/tests/test-onbeforeremove.js
@@ -199,9 +199,8 @@ o.spec("onbeforeremove", function() {
render(root, [{tag: component}])
render(root, [])
+ o(onremove.callCount).equals(0)
callAsync(function(){
- o(onremove.callCount).equals(0)
-
callAsync(function() {
o(onremove.callCount).equals(1)
done()
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() {