Details:
1. All tests now live in `test`. All test dependencies that aren't from npm live
in `test-deps`.
2. The QUnit tests are gone, as well as their dependencies. Half of them
duplicated existing tests, and some of them depended on the real DOM to
properly test.
3. All tests are now using Mocha to run the tests, Chai for assertions, and
Sinon and Sinon Chai for testing some callbacks.
4. Tests are run through mocha-phantomjs. If you want to run just the tests,
run `grunt mocha_phantomjs` or fire up a server in the root and open
`http://localhost:<port>/test/index.html`, e.g. `python3 -m http.server`.
5. The linter I chose is ESLint. It is relatively easy to configure, but with a
lot of flexibility. The rules I chose mostly were in tune to the style the
project was already using. I'm not including a style guide in this commit,
but one will likely come. You can check out the `.eslintrc` in the root and
in `test/` for the two configs. The `.eslintignore` includes a TODO for
`mithril.js` itself targeted at me, in the root.
Other info:
- As a drive-by fix, I fixed line endings on a few of the files.
- I also took care of a few other files and linted them as I went:
- `Gruntfile.js`
- `test/input-cursor.html` (was in `tests/`)
- `test/svg.html` (was in `tests/`)
- `docs/layout/tools/template-converter.html`
- `docs/layout/tools/template-converter.js`
I didn't test the template converter after linting it, because it needs
further scrutiny to ensure it works with the latest version of Mithril. I
know the API has changed a little, which is why I want to be sure.
- I simplified the `.travis.yml` file because none of the tests are run directly
through Node anymore. They are always run in a browser of some kind.
Hopefully, this turned out all right...
Previously, the Typescript definition file did not support parameterized
components. The `component()` function did accept additional arguments, but the
`MithrilComponent<T>` interface did not allow components to have any additional
arguments in either their `controller()` or `view()` methods.
Properly supporting flexible variadic parameters in typescript is somewhat
challenging. Tuple types are close, but would only work if the signature of
`controller` and `view` accepted arrays of arguments:
```
interface MithrilStatic {
component<TController extend controller, TRest extends any[]>(
component: ParameterizedMithrilComponent<TController, TRest>,
...args:TRest
) : TController
}
interface ParameterizedMithrilComponent<TController extend controller, TRest
extends any[]> {
// Doesn't match mithril's component interface; we want to unpack the contents
// of TRest
controller: (args: TRest) => TController,
view: (ctrl: TController, args: TRest)
}
```
Therefore, I have gone with a more traditional method of defining several
overloads of m.component(), each with a different number of extra parameters.
Because of this, the first four parameters to m.component() will be correctly
type checked (i.e. if the Component's controller defines the parameter, it must
be supplied to m.component). Additional parameters beyond the first four are
allowed, but are caught via an `...args:any[]` and thus are not type checked.
Background:
In ES6 we now have `Object.setPrototypeOf` which makes prototype delegation an alternative to *sugary*, fake class coding patterns. This can be accomplished in ES5, but ES6 is much more elegant. Here is a basic example. `new` is never used and `this` is very easy to follow.
Working example with changes to mithril.js on lines 854, 858, 859 *only*.
http://jsbin.com/nopugo/1/edit?js,console,output
```javascript
'use strict'
const m = require('mithril')
const DataModel = {
data: [1,2,3],
getData(){
console.log('getting data from server')
return this.data
},
postData(val){
console.log('sending data to server')
this.data.push(val)
}
}
const ViewModel = {
filterEvenData: function() {
return this.getData().filter((val) => (val % 2 === 0) && (+val !== 0))
},
addNewData: function(val){
console.log('adding new data', val)
this.postData(val)
}
}
var Model = Object.setPrototypeOf(ViewModel, DataModel)
const App = {
controller(){},
view(ctrl){
return m('div', [
m('pre', [ 'EVEN NUMBERS IN DATA\n',
this.filterEvenData().map((key,i) =>`${i+1} = ${key}\n`)
]),
m('div', 'Enter a number'),
m('input[placeholder=number]', {
// addNewData is called with **this** === undefined unless we pass `this` to Function.prototype.call in m.withAttr
onchange: m.withAttr('value', App.addNewData, App),
value: null
}
)
])
}
}
Object.setPrototypeOf(App, Model)
m.mount(document.body, App)
```
When a null component is passed into m.mount(), remove references to the root DOM element from:
- roots
- cellCache
- nodeCache
And remove the associated entries from:
- controllers
- components