Merge branch 'next'

This commit is contained in:
Pierre-Yves Gerardy 2017-04-04 12:42:26 +02:00
commit 4772055629
11 changed files with 261 additions and 64 deletions

1
.gitattributes vendored
View file

@ -1,4 +1,3 @@
* text=auto
/mithril.js binary
/mithril.min.js binary
/stream/stream.js binary

View file

@ -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

View file

@ -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

View file

@ -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})
})
```

View file

@ -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"),
])
}
}

View file

@ -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 = {}

View file

@ -8,7 +8,7 @@
<script src="../../ospec/ospec.js"></script>
<script src="../../test-utils/callAsync.js"></script>
<script src="../../test-utils/domMock.js"></script>
<script src="../../test-utils/component.js"></script>
<script src="../../test-utils/components.js"></script>
<script src="../../render/vnode.js"></script>
<script src="../../render/trust.js"></script>

View file

@ -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() {

View file

@ -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()

View file

@ -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})
}

View file

@ -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() {