diff --git a/api/mount.js b/api/mount.js index 0465c4c7..13b19e17 100644 --- a/api/mount.js +++ b/api/mount.js @@ -18,7 +18,6 @@ module.exports = function(renderer, pubsub) { run() if (component === null) { - pubsub.unsubscribe(root.redraw) delete root.redraw } } diff --git a/api/router.js b/api/router.js index 396b688e..bd0c2295 100644 --- a/api/router.js +++ b/api/router.js @@ -9,15 +9,16 @@ module.exports = function($window, renderer, pubsub) { var route = function(root, defaultRoute, routes) { var current = {path: null, component: "div"} var replay = router.defineRoutes(routes, function(payload, args, path, route) { - if (typeof payload.view !== "function") { - if (typeof payload.render !== "function") payload.render = function(vnode) {return vnode} - var use = function(component) { + args.path = path, args.route = route + if (typeof payload.onmatch === "function") { + if (typeof payload.view !== "function") payload.view = function(vnode) {return vnode} + var resolve = function(component) { current.path = path, current.component = component - renderer.render(root, payload.render(Vnode(component, null, args, undefined, undefined, undefined))) + renderer.render(root, payload.view(Vnode(component, null, args, undefined, undefined, undefined))) } - if (typeof payload.resolve !== "function") payload.resolve = function() {use(current.component)} - if (path !== current.path) payload.resolve(use, args, path, route) - else use(current.component) + if (typeof payload.onmatch !== "function") payload.onmatch = function() {resolve(current.component)} + if (path !== current.path) payload.onmatch(Vnode(payload, null, args, undefined, undefined, undefined), resolve) + else resolve(current.component) } else { renderer.render(root, Vnode(payload, null, args, undefined, undefined, undefined)) diff --git a/api/tests/test-router.js b/api/tests/test-router.js index fdbc0054..a3867884 100644 --- a/api/tests/test-router.js +++ b/api/tests/test-router.js @@ -83,7 +83,7 @@ o.spec("route", function() { }) function init(vnode) { - o(vnode.attrs).deepEquals({}) + o(vnode.attrs.foo).equals(undefined) done() } @@ -231,8 +231,8 @@ o.spec("route", function() { }) }) - o("accepts object as payload", function(done) { - var resolveCount = 0 + o("accepts RouteResolver", function(done) { + var matchCount = 0 var renderCount = 0 var Component = { view: function() { @@ -243,19 +243,19 @@ o.spec("route", function() { $window.location.href = prefix + "/" route(root, "/abc", { "/:id" : { - resolve: function(use, args, path, route) { - resolveCount++ + onmatch: function(vnode, resolve) { + matchCount++ - o(args).deepEquals({id: "abc"}) - o(path).equals("/abc") - o(route).equals("/:id") + o(vnode.attrs.id).equals("abc") + o(vnode.attrs.path).equals("/abc") + o(vnode.attrs.route).equals("/:id") - use(Component) + resolve(Component) }, - render: function(vnode) { + view: function(vnode) { renderCount++ - o(vnode.attrs).deepEquals({id: "abc"}) + o(vnode.attrs.id).equals("abc") return vnode }, @@ -263,7 +263,7 @@ o.spec("route", function() { }) setTimeout(function() { - o(resolveCount).equals(1) + o(matchCount).equals(1) o(renderCount).equals(1) o(root.firstChild.nodeName).equals("DIV") @@ -271,8 +271,8 @@ o.spec("route", function() { }, FRAME_BUDGET) }) - o("accepts object without `render` method as payload", function(done) { - var resolveCount = 0 + o("accepts RouteResolver without `view` method as payload", function(done) { + var matchCount = 0 var Component = { view: function() { return m("div") @@ -282,20 +282,20 @@ o.spec("route", function() { $window.location.href = prefix + "/" route(root, "/abc", { "/:id" : { - resolve: function(use, args, path, route) { - resolveCount++ + onmatch: function(vnode, resolve) { + matchCount++ - o(args).deepEquals({id: "abc"}) - o(path).equals("/abc") - o(route).equals("/:id") + o(vnode.attrs.id).equals("abc") + o(vnode.attrs.path).equals("/abc") + o(vnode.attrs.route).equals("/:id") - use(Component) + resolve(Component) }, }, }) setTimeout(function() { - o(resolveCount).equals(1) + o(matchCount).equals(1) o(root.firstChild.nodeName).equals("DIV") @@ -303,7 +303,7 @@ o.spec("route", function() { }, FRAME_BUDGET) }) - o("accepts object without `resolve` method as payload", function(done) { + o("object without `onmatch` method acts as component", function(done) { var renderCount = 0 var Component = { view: function() { @@ -314,10 +314,10 @@ o.spec("route", function() { $window.location.href = prefix + "/" route(root, "/abc", { "/:id" : { - render: function(vnode) { + view: function(vnode) { renderCount++ - o(vnode.attrs).deepEquals({id: "abc"}) + o(vnode.attrs.id).equals("abc") return m(Component) }, @@ -331,8 +331,8 @@ o.spec("route", function() { }, FRAME_BUDGET) }) - o("calls resolve and render correct number of times", function(done) { - var resolveCount = 0 + o("calls onmatch and view correct number of times", function(done) { + var matchCount = 0 var renderCount = 0 var Component = { view: function() { @@ -343,11 +343,11 @@ o.spec("route", function() { $window.location.href = prefix + "/" route(root, "/", { "/" : { - resolve: function(use) { - resolveCount++ - use(Component) + onmatch: function(vnode, resolve) { + matchCount++ + resolve(Component) }, - render: function(vnode) { + view: function(vnode) { renderCount++ return vnode }, @@ -355,13 +355,13 @@ o.spec("route", function() { }) callAsync(function() { - o(resolveCount).equals(1) + o(matchCount).equals(1) o(renderCount).equals(1) redraw.publish() setTimeout(function() { - o(resolveCount).equals(1) + o(matchCount).equals(1) o(renderCount).equals(2) done() diff --git a/docs/components.md b/docs/components.md index db68927b..0b12f57a 100644 --- a/docs/components.md +++ b/docs/components.md @@ -239,7 +239,9 @@ m(Header, { #### Avoid component factories -Component diffing relies on strict equality checking, so you should avoid recreating components. Instead, consume components idiomatically. +If you create a component from within a `view` method (either directly inline or by calling a function that does so), each redraw will have a different clone of the component. When diffing component vnodes, if the component referenced by the new vnode is not strictly equal to the one referenced by the old component, the two are assumed to be different components even if they ultimately run equivalent code. This means components created dynamically via a factory will always be re-created from scratch. + +For that reason you should avoid recreating components. Instead, consume components idiomatically. ```javascript // AVOID diff --git a/docs/route.md b/docs/route.md index 2717b2a9..101c1c0e 100644 --- a/docs/route.md +++ b/docs/route.md @@ -6,15 +6,18 @@ - [route.get](#route-get) - [route.prefix](#route-prefix) - [route.link](#route-link) +- [RouteResolver](#routeresolver) + - [routeResolver.onmatch](#routeresolver-onmatch) + - [routeResolver.view](#routeresolver-view) - [How it works](#how-it-works) - [Typical usage](#typical-usage) - [Navigating to different routes](#navigating-to-different-routes) - [Routing parameters](#routing-parameters) - [Changing router prefix](#changing-router-prefix) -- [Advanced component resolution](#advanced-component-resolution) - [Wrapping a layout component](#wrapping-a-layout-component) +- [Advanced component resolution](#advanced-component-resolution) - [Authentication](#authentication) -- [Lazy loading](#lazy-loading) +- [Code splitting](#code-splitting) --- @@ -31,38 +34,6 @@ Argument | Type | Required | D [How to read signatures](signatures.md) -#### RouteResolver - -A RouterResolver is an object that contains a `resolve` and a `render` methods. Both methods are optional, but at least one must be defined. - -`routeResolver = {resolve, render}` - -##### routeResolver.resolve - -The `resolve` method is called when the router needs to find a component to render. It is called once when a router path changes, but not on subsequent redraws. It can be used to run logic before a component initializes (for example authentication logic) - -This method also allows you to asynchronously define what component will be rendered, making it suitable for code splitting and asynchronous module loading. - -`routeResolver.resolve(use, args, path, route)` - -Argument | Type | Required | Description ------------ | --------------------- | -------- | --- -`use` | `Function(Component)` | Yes | Call this function with a component as the first argument to use it as the route's component -`args` | `Object` | No | The [routing parameters](#routing-parameters) -`path` | `String` | No | The current router path, including interpolated routing parameter values, but without the prefix -`route` | `String` | No | The matched route -**returns** | | | Returns `undefined` - -##### routeResolver.render - -The `render` method is called on every redraw for a matching route. It is meant for functional composition of components, to avoid the need for repetitive component definitions - -`vnode = routeResolve.render(vnode)` - -Argument | Type | Required | Description ------------ | --------------------- | -------- | --- -`vnode` | `Vnode` | Yes | A [vnode](vnodes.md) whose attributes object contains routing parameters. If the routeResolver does not have a `resolve` method, the vnode defaults to a `div` -**returns** | `Vnode` | | Returns a vnode #### Static members @@ -109,6 +80,43 @@ Argument | Type | Required | Description `vnode` | `Vnode` | Yes | This method is meant to be used in conjunction with an `` [vnode](vnodes.md)'s [`oncreate` hook](lifecycle-methods.md) **returns** | Function(e) | | Returns an event handler that calls `m.route.set` with the link's `href` as the `path` +#### RouteResolver + +A RouterResolver is an object that contains an `onmatch` method, and optionally a `view` method. + +`routeResolver = {onmatch, view}` + +##### routeResolver.onmatch + +The `onmatch` hook is called when the router needs to find a component to render. It is called once when a router path changes, but not on subsequent redraws. It can be used to run logic before a component initializes (for example authentication logic) + +This method also allows you to asynchronously define what component will be rendered, making it suitable for code splitting and asynchronous module loading. + +`routeResolver.onmatch(vnode, resolve)` + +Argument | Type | Description +------------------- | --------------------- | --- +`vnode` | `Vnode` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If the routeResolver does not have a `resolve` method, the vnode's `tag` field defaults to a `div` +`vnode.attrs` | `Object` | The [routing parameters](#routing-parameters) +`vnode.attrs.path` | `String` | The current router path, including interpolated routing parameter values, but without the prefix. Same value as `m.route.get()` +`vnode.attrs.route` | `String` | The matched route +`resolve` | `Function(Component)` | Call this function with a component as the first argument to use it as the route's component +**returns** | | Returns `undefined` + +##### routeResolver.view + +The `view` method is called on every redraw for a matching route. It is similar to the `view` method in components and it exists to simplify [component composition](#wrapping-a-layout-component). + +`vnode = routeResolve.view(vnode)` + +Argument | Type | Required | Description +------------------- | --------------- | --- +`vnode` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If the routeResolver does not have a `resolve` method, the vnode's `tag` field defaults to a `div` +`vnode.attrs` | `Object` | A [vnode](vnodes.md) whose attributes object contains routing parameters. If the routeResolver does not have a `resolve` method, the vnode defaults to a `div` +`vnode.attrs.path` | `String` | The current router path, including interpolated routing parameter values, but without the prefix. Same value as `m.route.get()` +`vnode.attrs.route` | `String` | The matched route +**returns** | `Vnode` | Returns a vnode + --- #### How it works @@ -255,30 +263,9 @@ m.route.prefix("/my-app") --- -### Advanced component resolution - -Instead of mapping a component to a route, you can specify a RouteResolver object. A RouteResolver object contains a `resolve()` and a `render()` method. Both methods are optional, but at least one of them should be specified. - -```javascript -m.route(document.body, "/", { - "/": { - resolve: function(use) { - use(Home) - }, - render: function(vnode) { - return vnode - }, - } -}) -``` - -The RouteResolver can be used to implement a variety of advanced component initialization use cases. - ---- - ### Wrapping a layout component -The RouterResolver's `render` method can be used to wrap a layout around a component, or to pass parameters to a top level component +You can use anonymous components to wrap a layout around a component, or to pass parameters to a top level component ```javascript var Layout = { @@ -289,7 +276,7 @@ var Layout = { m.route(document.body, "/", { "/": { - render: function() { + view: function() { return m(Layout, Home) }, } @@ -298,9 +285,30 @@ m.route(document.body, "/", { --- +### Advanced component resolution + +Instead of mapping a component to a route, you can specify a RouteResolver object. A RouteResolver object contains a `onmatch()` method and a optionally a `view()` method. + +```javascript +m.route(document.body, "/", { + "/": { + onmatch: function(vnode, resolve) { + use(Home) + }, + view: function(vnode) { + return vnode + }, + } +}) +``` + +RouteResolvers are useful for implementing a variety of advanced routing use cases. + +--- + ### Authentication -The RouterResolver's `resolve` method can be used to run logic before component initialization (including asynchronous logic). The example below shows how to implement a login wall that prevents users from seeing the `/secret` page unless they login. +The RouterResolver's `onmatch` hook can be used to run logic before the top level component in a route is initializated. The example below shows how to implement a login wall that prevents users from seeing the `/secret` page unless they login. ```javascript var isLoggedIn = false @@ -320,8 +328,8 @@ var Login = { m.route(document.body, "/secret", { "/secret": { - resolve: function(use) { - if (isLoggedIn) use(Home) + onmatch: function(vnode, resolve) { + if (isLoggedIn) resolve(Home) else m.route.set("/login") }, }, @@ -329,15 +337,15 @@ m.route(document.body, "/secret", { }) ``` -When the application loads, `resolve` is called and since `isLoggedIn` is false, the application redirects to `/login`. Once the user pressed the login button, `isLoggedIn` would be set to true, and the application would redirect to `/secret`. The `resolve` method would run once again, and since `isLoggedIn` is true this time, the application would render the `Home` component. +When the application loads, `onmatch` is called and since `isLoggedIn` is false, the application redirects to `/login`. Once the user pressed the login button, `isLoggedIn` would be set to true, and the application would redirect to `/secret`. The `onmatch` hook would run once again, and since `isLoggedIn` is true this time, the application would render the `Home` component. For the sake of simplicity, in the example above, the user's logged in status is kept in a global variable, and that flag is merely toggled when the user clicks the login button. In a real life application, a user would obviously have to supply proper login credentials, and clicking the login button would trigger a request to a server to authenticate the user. --- -### Lazy loading +### Code splitting -One important feature of the `resolve` method in RouteResolvers is that the `use` callback can be triggered asynchronously. This allows components to be downloaded on demand. +In a large application, it may be desirable to download the code for each route on demand, rather than upfront. Dividing the codebase this way is known as code splitting or lazy loading. In Mithril, this can be accomplished by calling the `resolve` callback of the `onmatch` hook asynchronously: At its simplest form, one could do the following: @@ -368,8 +376,8 @@ function load(file, done) { m.route(document.body, "/", { "/": { - resolve: function(use) { - load("Home.js", use) + onmatch: function(vnode, resolve) { + load("Home.js", resolve) }, }, }) diff --git a/index.js b/index.js index 5c91f7db..9e290d80 100644 --- a/index.js +++ b/index.js @@ -1,22 +1,20 @@ "use strict" -var log = console.error.bind(console) -var defaultStream = require("./stream/index") var m = require("./render/hyperscript") -var renderService = require("./render/render")(window) -var requestService = require("./request/request")(window, log) -var redrawService = require("./api/pubsub")() +var renderService = require("./render") +var requestService = require("./request") +var redrawService = require("./redraw") requestService.setCompletionCallback(redrawService.publish) -m.route = require("./api/router")(window, renderService, redrawService) -m.mount = require("./api/mount")(renderService, redrawService) +m.route = require("./route") +m.mount = require("./mount") m.trust = require("./render/trust") m.withAttr = require("./util/withAttr") -m.prop = defaultStream +m.prop = require("./stream") m.render = renderService.render m.redraw = redrawService.publish -m.request = requestService.xhr +m.request = requestService.request m.jsonp = requestService.jsonp m.version = "bleeding-edge" diff --git a/mount.js b/mount.js new file mode 100644 index 00000000..e240835f --- /dev/null +++ b/mount.js @@ -0,0 +1,4 @@ +var renderService = require("./render") +var redrawService = require("./redraw") + +module.exports = require("./api/mount")(renderService, redrawService) \ No newline at end of file diff --git a/redraw.js b/redraw.js new file mode 100644 index 00000000..c2c1dee3 --- /dev/null +++ b/redraw.js @@ -0,0 +1 @@ +module.exports = require("./api/pubsub")() \ No newline at end of file diff --git a/render.js b/render.js new file mode 100644 index 00000000..5497c9bd --- /dev/null +++ b/render.js @@ -0,0 +1 @@ +module.exports = require("./render/render")(window) \ No newline at end of file diff --git a/request.js b/request.js new file mode 100644 index 00000000..b1525c25 --- /dev/null +++ b/request.js @@ -0,0 +1 @@ +module.exports = require("./request/request")(window, console.error.bind(console)) diff --git a/request/request.js b/request/request.js index 5fc87dc4..bb76c79e 100644 --- a/request/request.js +++ b/request/request.js @@ -10,8 +10,8 @@ module.exports = function($window, log) { var oncompletion function setCompletionCallback(callback) {oncompletion = callback} - function xhr(args) { - var stream = Stream.stream() + function request(args) { + var stream = Stream() if (args.initialValue !== undefined) stream(args.initialValue) var useBody = typeof args.useBody === "boolean" ? args.useBody : args.method !== "GET" && args.method !== "TRACE" @@ -63,7 +63,7 @@ module.exports = function($window, log) { } function jsonp(args) { - var stream = Stream.stream() + var stream = Stream() if (args.initialValue !== undefined) stream(args.initialValue) var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ @@ -130,5 +130,5 @@ module.exports = function($window, log) { return data } - return {xhr: xhr, jsonp: jsonp, setCompletionCallback: setCompletionCallback} + return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback} } diff --git a/request/tests/index.html b/request/tests/index.html index 4cc2e4fd..725501af 100644 --- a/request/tests/index.html +++ b/request/tests/index.html @@ -14,7 +14,7 @@ - + diff --git a/request/tests/test-xhr.js b/request/tests/test-request.js similarity index 99% rename from request/tests/test-xhr.js rename to request/tests/test-request.js index 9493b483..31bcb68a 100644 --- a/request/tests/test-xhr.js +++ b/request/tests/test-request.js @@ -9,7 +9,7 @@ o.spec("xhr", function() { o.beforeEach(function() { mock = xhrMock() spy = o.spy() - xhr = new Request(mock, spy).xhr + xhr = new Request(mock, spy).request }) o.spec("success", function() { diff --git a/route.js b/route.js new file mode 100644 index 00000000..94c7eec8 --- /dev/null +++ b/route.js @@ -0,0 +1,4 @@ +var renderService = require("./render") +var redrawService = require("./redraw") + +module.exports = require("./api/router")(window, renderService, redrawService) \ No newline at end of file diff --git a/stream.js b/stream.js new file mode 100644 index 00000000..9a68c091 --- /dev/null +++ b/stream.js @@ -0,0 +1,2 @@ +var StreamFactory = require("./util/stream") +module.exports = StreamFactory(console.log.bind(console)) \ No newline at end of file diff --git a/stream/index.js b/stream/index.js deleted file mode 100644 index 454a90fc..00000000 --- a/stream/index.js +++ /dev/null @@ -1,13 +0,0 @@ -"use strict" - -var log = console.error.bind(console) -var StreamFactory = require("../util/stream") -var Stream = StreamFactory(log) - -var defaultStream = Stream.stream -defaultStream.combine = Stream.combine -defaultStream.reject = Stream.reject -defaultStream.merge = Stream.merge -defaultStream.HALT = Stream.HALT - -module.exports = defaultStream diff --git a/util/stream.js b/util/stream.js index 27640abf..a3833526 100644 --- a/util/stream.js +++ b/util/stream.js @@ -193,6 +193,10 @@ module.exports = function(log) { return streams.map(function(s) {return s()}) }, streams) } + createStream.merge = merge + createStream.combine = combine + createStream.reject = reject + createStream.HALT = HALT - return {stream: createStream, merge: merge, combine: combine, reject: reject, HALT: HALT} + return createStream } diff --git a/util/tests/test-stream.js b/util/tests/test-stream.js index 4fec7dcc..f6fe70c7 100644 --- a/util/tests/test-stream.js +++ b/util/tests/test-stream.js @@ -13,7 +13,7 @@ o.spec("stream", function() { o.spec("stream", function() { o("works as getter/setter", function() { - var stream = Stream.stream(1) + var stream = Stream(1) var initialValue = stream() stream(2) var newValue = stream() @@ -22,25 +22,25 @@ o.spec("stream", function() { o(newValue).equals(2) }) o("has undefined value by default", function() { - var stream = Stream.stream() + var stream = Stream() o(stream()).equals(undefined) }) o("can update to undefined", function() { - var stream = Stream.stream(1) + var stream = Stream(1) stream(undefined) o(stream()).equals(undefined) }) o("can be stream of streams", function() { - var stream = Stream.stream(Stream.stream(1)) + var stream = Stream(Stream(1)) o(stream()()).equals(1) }) }) o.spec("combine", function() { o("transforms value", function() { - var stream = Stream.stream() + var stream = Stream() var doubled = Stream.combine(function(s) {return s() * 2}, [stream]) stream(2) @@ -48,14 +48,14 @@ o.spec("stream", function() { o(doubled()).equals(4) }) o("transforms default value", function() { - var stream = Stream.stream(2) + var stream = Stream(2) var doubled = Stream.combine(function(s) {return s() * 2}, [stream]) o(doubled()).equals(4) }) o("transforms multiple values", function() { - var s1 = Stream.stream() - var s2 = Stream.stream() + var s1 = Stream() + var s2 = Stream() var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) s1(2) @@ -64,15 +64,15 @@ o.spec("stream", function() { o(added()).equals(5) }) o("transforms multiple default values", function() { - var s1 = Stream.stream(2) - var s2 = Stream.stream(3) + var s1 = Stream(2) + var s2 = Stream(3) var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) o(added()).equals(5) }) o("transforms mixed default and late-bound values", function() { - var s1 = Stream.stream(2) - var s2 = Stream.stream() + var s1 = Stream(2) + var s2 = Stream() var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) s2(3) @@ -81,7 +81,7 @@ o.spec("stream", function() { }) o("combines atomically", function() { var count = 0 - var a = Stream.stream() + var a = Stream() var b = Stream.combine(function(a) {return a() * 2}, [a]) var c = Stream.combine(function(a) {return a() * a()}, [a]) var d = Stream.combine(function(b, c) { @@ -96,7 +96,7 @@ o.spec("stream", function() { }) o("combines default value atomically", function() { var count = 0 - var a = Stream.stream(3) + var a = Stream(3) var b = Stream.combine(function(a) {return a() * 2}, [a]) var c = Stream.combine(function(a) {return a() * a()}, [a]) var d = Stream.combine(function(b, c) { @@ -109,8 +109,8 @@ o.spec("stream", function() { }) o("combine lists only changed upstreams in last arg", function() { var streams = [] - var a = Stream.stream() - var b = Stream.stream() + var a = Stream() + var b = Stream() var c = Stream.combine(function(a, b, changed) { streams = changed }, [a, b]) @@ -123,8 +123,8 @@ o.spec("stream", function() { }) o("combine lists only changed upstreams in last arg with default value", function() { var streams = [] - var a = Stream.stream(3) - var b = Stream.stream(5) + var a = Stream(3) + var b = Stream(5) var c = Stream.combine(function(a, b, changed) { streams = changed }, [a, b]) @@ -135,7 +135,7 @@ o.spec("stream", function() { o(streams[0]).equals(a) }) o("combine can return undefined", function() { - var a = Stream.stream(1) + var a = Stream(1) var b = Stream.combine(function(a) { return undefined }, [a]) @@ -143,24 +143,24 @@ o.spec("stream", function() { o(b()).equals(undefined) }) o("combine can return stream", function() { - var a = Stream.stream(1) + var a = Stream(1) var b = Stream.combine(function(a) { - return Stream.stream(2) + return Stream(2) }, [a]) o(b()()).equals(2) }) o("combine can return pending stream", function() { - var a = Stream.stream(1) + var a = Stream(1) var b = Stream.combine(function(a) { - return Stream.stream() + return Stream() }, [a]) o(b()()).equals(undefined) }) o("combine can halt", function() { var count = 0 - var a = Stream.stream(1) + var a = Stream(1) var b = Stream.combine(function(a) { return Stream.HALT }, [a]) @@ -175,19 +175,19 @@ o.spec("stream", function() { o.spec("merge", function() { o("transforms an array of streams to an array of values", function() { var all = Stream.merge([ - Stream.stream(10), - Stream.stream("20"), - Stream.stream({value: 30}), + Stream(10), + Stream("20"), + Stream({value: 30}), ]) o(all()).deepEquals([10, "20", {value: 30}]) }) o("remains pending until all streams are active", function() { - var straggler = Stream.stream() + var straggler = Stream() var all = Stream.merge([ - Stream.stream(10), - Stream.stream("20"), + Stream(10), + Stream("20"), straggler, ]) @@ -199,7 +199,7 @@ o.spec("stream", function() { }) o.spec("end", function() { o("end stream works", function() { - var stream = Stream.stream() + var stream = Stream() var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream]) stream.end(true) @@ -209,7 +209,7 @@ o.spec("stream", function() { o(doubled()).equals(undefined) }) o("end stream works with default value", function() { - var stream = Stream.stream(2) + var stream = Stream(2) var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream]) stream.end(true) @@ -219,7 +219,7 @@ o.spec("stream", function() { o(doubled()).equals(4) }) o("cannot add downstream to ended stream", function() { - var stream = Stream.stream(2) + var stream = Stream(2) stream.end(true) var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream]) @@ -228,7 +228,7 @@ o.spec("stream", function() { o(doubled()).equals(undefined) }) o("upstream does not affect ended stream", function() { - var stream = Stream.stream(2) + var stream = Stream(2) var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream]) doubled.end(true) @@ -240,7 +240,7 @@ o.spec("stream", function() { }) o.spec("error", function() { o("error() works", function() { - var stream = Stream.stream() + var stream = Stream() var errored = Stream.combine(function(stream) {throw new Error("error")}, [stream]) stream(3) @@ -249,14 +249,14 @@ o.spec("stream", function() { o(errored.error().message).equals("error") }) o("error() works with default value", function() { - var stream = Stream.stream(3) + var stream = Stream(3) var errored = Stream.combine(function(stream) {throw new Error("error")}, [stream]) o(errored()).equals(undefined) o(errored.error().message).equals("error") }) o("error() removes error on valid value", function() { - var stream = Stream.stream("a") + var stream = Stream("a") var doubled = Stream.combine(function(stream) { if (typeof stream() !== "number") throw new Error("error") else return stream() * 2 @@ -269,7 +269,7 @@ o.spec("stream", function() { }) o("error() triggers catch", function() { var count = 0 - var stream = Stream.stream(1) + var stream = Stream(1) var handled = stream.catch(function() { count++ return 2 @@ -283,7 +283,7 @@ o.spec("stream", function() { }) o("thrown error propagates downstream", function() { var count = 0 - var stream = Stream.stream(1) + var stream = Stream(1) .map(function() {throw new Error("error")}) .map(function(value) { count++ @@ -300,7 +300,7 @@ o.spec("stream", function() { }) o("set error propagates downstream", function() { var count = 0 - var stream = Stream.stream() + var stream = Stream() var mapped = stream.map(function(value) { count++ return value * 2 @@ -316,7 +316,7 @@ o.spec("stream", function() { o(count).equals(0) }) o("error.map works", function() { - var stream = Stream.stream(1) + var stream = Stream(1) var mappedFromError = stream.error.map(function(value) { if (value) return "from" + value.message }) @@ -328,7 +328,7 @@ o.spec("stream", function() { o(mappedFromError()).equals("fromerror") }) o("error from error.map propagates", function() { - var stream = Stream.stream(1) + var stream = Stream(1) var mappedFromError = stream.error.map(function(value) { return "from" + value.message }) @@ -344,7 +344,7 @@ o.spec("stream", function() { }) o("error thrown from error.map propagates downstream", function() { var count = 0 - var stream = Stream.stream(1) + var stream = Stream(1) var mappedFromError = stream.error.map(function(value) { throw new Error("b") }) @@ -379,13 +379,13 @@ o.spec("stream", function() { o("error.map can return streams", function() { var stream = Stream.reject(new Error("error")) var error = stream.error.map(function(value) { - return Stream.stream(1) + return Stream(1) }) o(error()()).equals(1) }) o("combined stream of two errored streams adopts error from first", function() { - var a = Stream.stream(1) + var a = Stream(1) var b = Stream.combine(function(a) {throw new Error("error from b")}, [a]) var c = Stream.combine(function(a) {throw new Error("error from c")}, [a]) var d = Stream.combine(function(b, c) {return 2}, [b, c]) @@ -443,7 +443,7 @@ o.spec("stream", function() { }) o.spec("run", function() { o("works", function() { - var stream = Stream.stream() + var stream = Stream() var doubled = stream.run(function(value) {return value * 2}) stream(3) @@ -451,13 +451,13 @@ o.spec("stream", function() { o(doubled()).equals(6) }) o("works with default value", function() { - var stream = Stream.stream(3) + var stream = Stream(3) var doubled = stream.run(function(value) {return value * 2}) o(doubled()).equals(6) }) o("works with undefined value", function() { - var stream = Stream.stream() + var stream = Stream() var mapped = stream.run(function(value) {return String(value)}) stream(undefined) @@ -465,13 +465,13 @@ o.spec("stream", function() { o(mapped()).equals("undefined") }) o("does not run when initialized w/ HALT", function() { - var stream = Stream.stream(Stream.HALT) + var stream = Stream(Stream.HALT) var mapped = stream.run(function(value) {return 123}) o(mapped()).equals(undefined) }) o("does not run when set to HALT", function() { - var stream = Stream.stream() + var stream = Stream() var mapped = stream.run(function(value) {return 123}) stream(Stream.HALT) @@ -479,14 +479,14 @@ o.spec("stream", function() { o(mapped()).equals(undefined) }) o("works with default undefined value", function() { - var stream = Stream.stream(undefined) + var stream = Stream(undefined) var mapped = stream.run(function(value) {return String(value)}) o(mapped()).equals("undefined") }) o("works with stream that throws", function() { var count = 0 - var stream = Stream.stream(undefined) + var stream = Stream(undefined) var errored = stream.run(function(value) {throw new Error("error")}) var mapped = errored.map(function(value) { count++ @@ -501,8 +501,8 @@ o.spec("stream", function() { }) o("works with pending stream", function() { var count = 0 - var stream = Stream.stream(undefined) - var absorbed = Stream.stream() + var stream = Stream(undefined) + var absorbed = Stream() var absorber = stream.run(function(value) {return absorbed}) var mapped = absorber.map(function(value) { count++ @@ -518,14 +518,14 @@ o.spec("stream", function() { o(count).equals(1) }) o("works with active stream", function() { - var stream = Stream.stream(undefined) - var mapped = stream.run(function(value) {return Stream.stream(1)}) + var stream = Stream(undefined) + var mapped = stream.run(function(value) {return Stream(1)}) o(mapped()).equals(1) }) o("works with errored stream", function() { var rejected - var stream = Stream.stream(undefined) + var stream = Stream(undefined) var mapped = stream.run(function(value) { return Stream.reject(new Error("error")) }) @@ -534,9 +534,9 @@ o.spec("stream", function() { o(mapped.error().message).equals("error") }) o("works with ended stream", function() { - var stream = Stream.stream(1) + var stream = Stream(1) var mapped = stream.run(function(value) { - var ended = Stream.stream(2) + var ended = Stream(2) ended.end(true) return ended }) @@ -546,8 +546,8 @@ o.spec("stream", function() { o(mapped()).equals(2) }) o("works when active stream updates", function() { - var stream = Stream.stream(undefined) - var absorbed = Stream.stream(1) + var stream = Stream(undefined) + var absorbed = Stream(1) var mapped = stream.run(function(value) {return absorbed}) absorbed(2) @@ -560,8 +560,8 @@ o.spec("stream", function() { }) o("works when pending stream updates", function() { var count = 0 - var stream = Stream.stream(undefined) - var absorbed = Stream.stream() + var stream = Stream(undefined) + var absorbed = Stream() var mapped = stream.run(function(value) {return absorbed}) mapped.map(function (value) { @@ -577,8 +577,8 @@ o.spec("stream", function() { o(mapped()).equals(123) }) o("works when updating stream to errored state", function() { - var stream = Stream.stream(undefined) - var absorbed = Stream.stream(1) + var stream = Stream(undefined) + var absorbed = Stream(1) var mapped = stream.run(function(value) {return absorbed}) absorbed.error(new Error("error")) @@ -592,8 +592,8 @@ o.spec("stream", function() { o(mapped.error().message).equals("another error") }) o("works when updating pending stream to errored state", function() { - var stream = Stream.stream(undefined) - var absorbed = Stream.stream() + var stream = Stream(undefined) + var absorbed = Stream() var mapped = stream.run(function(value) {return absorbed}) absorbed.error(new Error("error")) @@ -602,8 +602,8 @@ o.spec("stream", function() { o(mapped.error().message).equals("error") }) o("works when updating stream to active state", function() { - var stream = Stream.stream(undefined) - var absorbed = Stream.stream(1) + var stream = Stream(undefined) + var absorbed = Stream(1) var mapped = stream.run(function(value) {return absorbed}) absorbed.error(new Error("error")) @@ -617,8 +617,8 @@ o.spec("stream", function() { o(mapped.error()).equals(undefined) }) o("throwing from absorbed propagates", function() { - var stream = Stream.stream(undefined) - var absorbedParent = Stream.stream() + var stream = Stream(undefined) + var absorbedParent = Stream() var absorbed = absorbedParent.map(function() {throw new Error("error")}) var mapped = stream.run(function(value) {return absorbed}) @@ -648,7 +648,7 @@ o.spec("stream", function() { }) o("catch works from combine", function() { var count = 0 - var stream = Stream.combine(function() {throw new Error("error")}, [Stream.stream(1)]).catch(function(e) { + var stream = Stream.combine(function() {throw new Error("error")}, [Stream(1)]).catch(function(e) { count++ return "no" + e.message }) @@ -662,7 +662,7 @@ o.spec("stream", function() { }) o("catch is not called if no error", function() { var count = 0 - var stream = Stream.stream() + var stream = Stream() var handled = stream.map(function(value) {return value + value}).catch(function(e) { count++ return "no" + e.message @@ -679,7 +679,7 @@ o.spec("stream", function() { }) o("catch is not called if no error with default value", function() { var count = 0 - var stream = Stream.stream("a").map(function(value) {return value + value}).catch(function(e) { + var stream = Stream("a").map(function(value) {return value + value}).catch(function(e) { count++ return "no" + e.message }) @@ -710,7 +710,7 @@ o.spec("stream", function() { }) o("catch absorbs pending stream", function() { var count = 0 - var stream = Stream.stream() + var stream = Stream() var mapped = Stream.reject(new Error("b")).catch(function(e) { return stream }) @@ -723,7 +723,7 @@ o.spec("stream", function() { o(count).equals(0) }) o("catch absorbs active stream", function() { - var stream = Stream.stream(1) + var stream = Stream(1) var mapped = Stream.reject(new Error("b")).catch(function(e) { return stream }) @@ -752,7 +752,7 @@ o.spec("stream", function() { }) o("catches wrapped rejected stream", function() { var caught - var stream = Stream.stream(1).map(function() { + var stream = Stream(1).map(function() { return Stream.reject(new Error("error")) }) .catch(function(value) { @@ -767,8 +767,8 @@ o.spec("stream", function() { }) o("catches nested wrapped rejected stream", function() { var caught - var stream = Stream.stream(1).map(function() { - return Stream.stream(2).map(function() { + var stream = Stream(1).map(function() { + return Stream(2).map(function() { return Stream.reject(new Error("error")) }) }) @@ -785,56 +785,56 @@ o.spec("stream", function() { }) o.spec("valueOf", function() { o("works", function() { - o(Stream.stream(1).valueOf()).equals(1) - o(Stream.stream("a").valueOf()).equals("a") - o(Stream.stream(true).valueOf()).equals(true) - o(Stream.stream(null).valueOf()).equals(null) - o(Stream.stream(undefined).valueOf()).equals(undefined) - o(Stream.stream({a: 1}).valueOf()).deepEquals({a: 1}) - o(Stream.stream([1, 2, 3]).valueOf()).deepEquals([1, 2, 3]) - o(Stream.stream().valueOf()).equals(undefined) + o(Stream(1).valueOf()).equals(1) + o(Stream("a").valueOf()).equals("a") + o(Stream(true).valueOf()).equals(true) + o(Stream(null).valueOf()).equals(null) + o(Stream(undefined).valueOf()).equals(undefined) + o(Stream({a: 1}).valueOf()).deepEquals({a: 1}) + o(Stream([1, 2, 3]).valueOf()).deepEquals([1, 2, 3]) + o(Stream().valueOf()).equals(undefined) }) o("allows implicit value access in mathematical operations", function() { - o(Stream.stream(1) + Stream.stream(1)).equals(2) + o(Stream(1) + Stream(1)).equals(2) }) }) o.spec("toString", function() { o("aliases valueOf", function() { - var stream = Stream.stream(1) + var stream = Stream(1) o(stream.toString).equals(stream.valueOf) }) o("allows implicit value access in string operations", function() { - o(Stream.stream("a") + Stream.stream("b")).equals("ab") + o(Stream("a") + Stream("b")).equals("ab") }) }) o.spec("toJSON", function() { o("works", function() { - o(Stream.stream(1).toJSON()).equals(1) - o(Stream.stream("a").toJSON()).equals("a") - o(Stream.stream(true).toJSON()).equals(true) - o(Stream.stream(null).toJSON()).equals(null) - o(Stream.stream(undefined).toJSON()).equals(undefined) - o(Stream.stream({a: 1}).toJSON()).deepEquals({a: 1}) - o(Stream.stream([1, 2, 3]).toJSON()).deepEquals([1, 2, 3]) - o(Stream.stream().toJSON()).equals(undefined) - o(Stream.stream(new Date(0)).toJSON()).equals(new Date(0).toJSON()) + o(Stream(1).toJSON()).equals(1) + o(Stream("a").toJSON()).equals("a") + o(Stream(true).toJSON()).equals(true) + o(Stream(null).toJSON()).equals(null) + o(Stream(undefined).toJSON()).equals(undefined) + o(Stream({a: 1}).toJSON()).deepEquals({a: 1}) + o(Stream([1, 2, 3]).toJSON()).deepEquals([1, 2, 3]) + o(Stream().toJSON()).equals(undefined) + o(Stream(new Date(0)).toJSON()).equals(new Date(0).toJSON()) }) o("works w/ JSON.stringify", function() { - o(JSON.stringify(Stream.stream(1))).equals(JSON.stringify(1)) - o(JSON.stringify(Stream.stream("a"))).equals(JSON.stringify("a")) - o(JSON.stringify(Stream.stream(true))).equals(JSON.stringify(true)) - o(JSON.stringify(Stream.stream(null))).equals(JSON.stringify(null)) - o(JSON.stringify(Stream.stream(undefined))).equals(JSON.stringify(undefined)) - o(JSON.stringify(Stream.stream({a: 1}))).deepEquals(JSON.stringify({a: 1})) - o(JSON.stringify(Stream.stream([1, 2, 3]))).deepEquals(JSON.stringify([1, 2, 3])) - o(JSON.stringify(Stream.stream())).equals(JSON.stringify(undefined)) - o(JSON.stringify(Stream.stream(new Date(0)))).equals(JSON.stringify(new Date(0))) + o(JSON.stringify(Stream(1))).equals(JSON.stringify(1)) + o(JSON.stringify(Stream("a"))).equals(JSON.stringify("a")) + o(JSON.stringify(Stream(true))).equals(JSON.stringify(true)) + o(JSON.stringify(Stream(null))).equals(JSON.stringify(null)) + o(JSON.stringify(Stream(undefined))).equals(JSON.stringify(undefined)) + o(JSON.stringify(Stream({a: 1}))).deepEquals(JSON.stringify({a: 1})) + o(JSON.stringify(Stream([1, 2, 3]))).deepEquals(JSON.stringify([1, 2, 3])) + o(JSON.stringify(Stream())).equals(JSON.stringify(undefined)) + o(JSON.stringify(Stream(new Date(0)))).equals(JSON.stringify(new Date(0))) }) }) o.spec("uncaught exception reporting", function() { o("reports thrown errors", function(done) { - Stream.stream(1).map(function() {throw new Error("error")}) + Stream(1).map(function() {throw new Error("error")}) setTimeout(function() { o(spy.callCount).equals(1) @@ -853,7 +853,7 @@ o.spec("stream", function() { }) o.spec("map", function() { o("works", function() { - var stream = Stream.stream() + var stream = Stream() var doubled = stream.map(function(value) {return value * 2}) stream(3) @@ -861,13 +861,13 @@ o.spec("stream", function() { o(doubled()).equals(6) }) o("works with default value", function() { - var stream = Stream.stream(3) + var stream = Stream(3) var doubled = stream.map(function(value) {return value * 2}) o(doubled()).equals(6) }) o("works with undefined value", function() { - var stream = Stream.stream() + var stream = Stream() var mapped = stream.map(function(value) {return String(value)}) stream(undefined) @@ -875,22 +875,22 @@ o.spec("stream", function() { o(mapped()).equals("undefined") }) o("works with default undefined value", function() { - var stream = Stream.stream(undefined) + var stream = Stream(undefined) var mapped = stream.map(function(value) {return String(value)}) o(mapped()).equals("undefined") }) o("works with pending stream", function() { - var stream = Stream.stream(undefined) - var mapped = stream.map(function(value) {return Stream.stream()}) + var stream = Stream(undefined) + var mapped = stream.map(function(value) {return Stream()}) o(mapped()()).equals(undefined) }) }) o.spec("ap", function() { o("works", function() { - var apply = Stream.stream(function(value) {return value * 2}) - var stream = Stream.stream(3) + var apply = Stream(function(value) {return value * 2}) + var stream = Stream(3) var applied = apply.ap(stream) o(applied()).equals(6) @@ -904,8 +904,8 @@ o.spec("stream", function() { o(applied()).equals(3) }) o("works with undefined value", function() { - var apply = Stream.stream(function(value) {return String(value)}) - var stream = Stream.stream(undefined) + var apply = Stream(function(value) {return String(value)}) + var stream = Stream(undefined) var applied = apply.ap(stream) o(applied()).equals("undefined") @@ -918,7 +918,7 @@ o.spec("stream", function() { o.spec("fantasy-land", function() { o.spec("functor", function() { o("identity", function() { - var stream = Stream.stream(3) + var stream = Stream(3) var mapped = stream.map(function(value) {return value}) o(stream()).equals(mapped()) @@ -927,7 +927,7 @@ o.spec("stream", function() { function f(x) {return x * 2} function g(x) {return x * x} - var stream = Stream.stream(3) + var stream = Stream(3) var mapped = stream.map(function(value) {return f(g(value))}) var composed = stream.map(g).map(f) @@ -938,9 +938,9 @@ o.spec("stream", function() { }) o.spec("apply", function() { o("composition", function() { - var a = Stream.stream(function(value) {return value * 2}) - var u = Stream.stream(function(value) {return value * 3}) - var v = Stream.stream(5) + var a = Stream(function(value) {return value * 2}) + var u = Stream(function(value) {return value * 3}) + var v = Stream(5) var mapped = a.map(function(f) { return function(g) { @@ -958,14 +958,14 @@ o.spec("stream", function() { }) o.spec("applicative", function() { o("identity", function() { - var a = Stream.stream().of(function(value) {return value}) - var v = Stream.stream(5) + var a = Stream().of(function(value) {return value}) + var v = Stream(5) o(a.ap(v)()).equals(5) o(a.ap(v)()).equals(v()) }) o("homomorphism", function() { - var a = Stream.stream(0) + var a = Stream(0) var f = function(value) {return value * 2} var x = 3 @@ -973,8 +973,8 @@ o.spec("stream", function() { o(a.of(f).ap(a.of(x))()).equals(a.of(f(x))()) }) o("interchange", function() { - var u = Stream.stream(function(value) {return value * 2}) - var a = Stream.stream() + var u = Stream(function(value) {return value * 2}) + var a = Stream() var y = 3 o(u.ap(a.of(y))()).equals(6)