This mostly isolates the implementations for both of these. Now, everything
here calls the method itself, not any of the external methods.
Few driveby fixes as well:
1. Git now ignores archive/ again (it's a build artifact, and can be removed
when updating `master`)
2. Since I had to rewrite most of the Deferred implementation, the new version
passes one of the skipped tests, so it is now enabled.
This changes enough things to merit a new patch release. It changed a few
implementation details in the process, but it's at least much cleaner.
Be ready for every other currently outstanding PR for this file to have merge
conflicts.
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...
`MithrilVirtualElement` was recently converted to be generic over `<T extends
MithrilController>`. The element's `children` may then contain other
`MithrilVirtualElement<T>` or `MithrilComponent<T>` elements.
Unfortunately, in common usage of 'm()' this can cause problems with Mithril's type
inference system. If a type is not explicitly provided to `m()`, the "T" of the
returned MithrilVirtualElement<T> will be inferred from its children. The
candidates for T will be the concrete types of the child MithrilComponent<T>; however, if no
candidate type is a subset of *every* candidate type, then TypeScript will be
unable to infer a valid type, and the call to `m()` will not compile.
To improve this behavior, this commit makes MithrilVirtualElement non-generic;
it's child collection can now contain MithrilComponent<MithrilController>, which
matches all valid MithrilComponent. This the same underlying issue as the
previous commit, which made MithrilRoutes non-generic.
The motivation for fixing this is threefold:
1. Because `m()` has so many overloads, in the case where a call to `m()` will
not compile due to the above issue, the syntax error provided by TypeScript is
not helpful.
2. In many cases, users will be calling `m<MithrilController>()` in their code
simply to get it to compile, which provides no additional utility over a
non-generic type and adds significant boilerplate.
3. Explicitly specifying subtypes to `m()` calls provides little utility; at
best, it can check that contained components implement some common interface.
However, type assertions like that can be provided in other places without
having to attach the information to the MithrilVirtualElement.
The previous definition of MithrilRoutes was generic over <T extends
MithrilController>, with the MithrilRoutes containing a collection of
MithrilComponent<T>.
However, this presents problems with TypeScript's type inference in many common
situations. For example, consider this usage of m.route():
```
m.route(document.body, "/a", {
"/a": ComponentA,
"/b": ComponentB,
})
```
Both `ComponentA` and `ComponentB` implement `MithrilComponent<T extends
MithrilController>`, with each component having a different concrete controller
type (`ControllerA` and `ControllerB`).
However, unless ControllerA's type is a subset of ControllerB's type (or
vice-versa), TypeScript will be unable to infer the type of the MitrilRoutes<T>
returned by m.route(). To get this to compile, a third type which *is* a
subset of both ControllerA and ControllerB would need to be explicity provided:
```
// Both ControllerA and ControllerB implement MithrilController
m.route<MithrilController>(document.body, "/a", {
"/a": ComponentA,
"/b": ComponentB,
})
```
This commit makes MithrilRoute non-generic, and instead of containing
MithrilComponent<T extends MithrilRoute>, it contains
MithrilComponent<MithrilController>, which will match all valid
MithrilComponents.
The motivation for this change is twofold:
1. In the case where m.route() does not compile due to type-inference failure,
the resulting syntax error from Typescript is very unhelpful because m.route()
has a number of overloads. Leaving this as is will result in confusion for
downstream users.
2. An assumption that vanishingly little utility is provided by defining
`MithrilRoutes<T extends MithrilController>` over a type more specific than
MithrilController. At most, it could be used to assert that all of your routed
Components meet a more specialized interface, but that same type check could be
accomplished without having to augment MithrilRoutes with that information.
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.