rename routeresolver methods to {onmatch,view}

expose some piecemeal modules
rename internal xhr to request
mirror internal stream api to match public api
This commit is contained in:
Leo Horie 2016-08-17 00:10:47 -04:00
parent 8bb813155c
commit 80c25e3809
18 changed files with 276 additions and 264 deletions

View file

@ -18,7 +18,6 @@ module.exports = function(renderer, pubsub) {
run() run()
if (component === null) { if (component === null) {
pubsub.unsubscribe(root.redraw)
delete root.redraw delete root.redraw
} }
} }

View file

@ -9,15 +9,16 @@ module.exports = function($window, renderer, pubsub) {
var route = function(root, defaultRoute, routes) { var route = function(root, defaultRoute, routes) {
var current = {path: null, component: "div"} var current = {path: null, component: "div"}
var replay = router.defineRoutes(routes, function(payload, args, path, route) { var replay = router.defineRoutes(routes, function(payload, args, path, route) {
if (typeof payload.view !== "function") { args.path = path, args.route = route
if (typeof payload.render !== "function") payload.render = function(vnode) {return vnode} if (typeof payload.onmatch === "function") {
var use = function(component) { if (typeof payload.view !== "function") payload.view = function(vnode) {return vnode}
var resolve = function(component) {
current.path = path, current.component = 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 (typeof payload.onmatch !== "function") payload.onmatch = function() {resolve(current.component)}
if (path !== current.path) payload.resolve(use, args, path, route) if (path !== current.path) payload.onmatch(Vnode(payload, null, args, undefined, undefined, undefined), resolve)
else use(current.component) else resolve(current.component)
} }
else { else {
renderer.render(root, Vnode(payload, null, args, undefined, undefined, undefined)) renderer.render(root, Vnode(payload, null, args, undefined, undefined, undefined))

View file

@ -83,7 +83,7 @@ o.spec("route", function() {
}) })
function init(vnode) { function init(vnode) {
o(vnode.attrs).deepEquals({}) o(vnode.attrs.foo).equals(undefined)
done() done()
} }
@ -231,8 +231,8 @@ o.spec("route", function() {
}) })
}) })
o("accepts object as payload", function(done) { o("accepts RouteResolver", function(done) {
var resolveCount = 0 var matchCount = 0
var renderCount = 0 var renderCount = 0
var Component = { var Component = {
view: function() { view: function() {
@ -243,19 +243,19 @@ o.spec("route", function() {
$window.location.href = prefix + "/" $window.location.href = prefix + "/"
route(root, "/abc", { route(root, "/abc", {
"/:id" : { "/:id" : {
resolve: function(use, args, path, route) { onmatch: function(vnode, resolve) {
resolveCount++ matchCount++
o(args).deepEquals({id: "abc"}) o(vnode.attrs.id).equals("abc")
o(path).equals("/abc") o(vnode.attrs.path).equals("/abc")
o(route).equals("/:id") o(vnode.attrs.route).equals("/:id")
use(Component) resolve(Component)
}, },
render: function(vnode) { view: function(vnode) {
renderCount++ renderCount++
o(vnode.attrs).deepEquals({id: "abc"}) o(vnode.attrs.id).equals("abc")
return vnode return vnode
}, },
@ -263,7 +263,7 @@ o.spec("route", function() {
}) })
setTimeout(function() { setTimeout(function() {
o(resolveCount).equals(1) o(matchCount).equals(1)
o(renderCount).equals(1) o(renderCount).equals(1)
o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.nodeName).equals("DIV")
@ -271,8 +271,8 @@ o.spec("route", function() {
}, FRAME_BUDGET) }, FRAME_BUDGET)
}) })
o("accepts object without `render` method as payload", function(done) { o("accepts RouteResolver without `view` method as payload", function(done) {
var resolveCount = 0 var matchCount = 0
var Component = { var Component = {
view: function() { view: function() {
return m("div") return m("div")
@ -282,20 +282,20 @@ o.spec("route", function() {
$window.location.href = prefix + "/" $window.location.href = prefix + "/"
route(root, "/abc", { route(root, "/abc", {
"/:id" : { "/:id" : {
resolve: function(use, args, path, route) { onmatch: function(vnode, resolve) {
resolveCount++ matchCount++
o(args).deepEquals({id: "abc"}) o(vnode.attrs.id).equals("abc")
o(path).equals("/abc") o(vnode.attrs.path).equals("/abc")
o(route).equals("/:id") o(vnode.attrs.route).equals("/:id")
use(Component) resolve(Component)
}, },
}, },
}) })
setTimeout(function() { setTimeout(function() {
o(resolveCount).equals(1) o(matchCount).equals(1)
o(root.firstChild.nodeName).equals("DIV") o(root.firstChild.nodeName).equals("DIV")
@ -303,7 +303,7 @@ o.spec("route", function() {
}, FRAME_BUDGET) }, 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 renderCount = 0
var Component = { var Component = {
view: function() { view: function() {
@ -314,10 +314,10 @@ o.spec("route", function() {
$window.location.href = prefix + "/" $window.location.href = prefix + "/"
route(root, "/abc", { route(root, "/abc", {
"/:id" : { "/:id" : {
render: function(vnode) { view: function(vnode) {
renderCount++ renderCount++
o(vnode.attrs).deepEquals({id: "abc"}) o(vnode.attrs.id).equals("abc")
return m(Component) return m(Component)
}, },
@ -331,8 +331,8 @@ o.spec("route", function() {
}, FRAME_BUDGET) }, FRAME_BUDGET)
}) })
o("calls resolve and render correct number of times", function(done) { o("calls onmatch and view correct number of times", function(done) {
var resolveCount = 0 var matchCount = 0
var renderCount = 0 var renderCount = 0
var Component = { var Component = {
view: function() { view: function() {
@ -343,11 +343,11 @@ o.spec("route", function() {
$window.location.href = prefix + "/" $window.location.href = prefix + "/"
route(root, "/", { route(root, "/", {
"/" : { "/" : {
resolve: function(use) { onmatch: function(vnode, resolve) {
resolveCount++ matchCount++
use(Component) resolve(Component)
}, },
render: function(vnode) { view: function(vnode) {
renderCount++ renderCount++
return vnode return vnode
}, },
@ -355,13 +355,13 @@ o.spec("route", function() {
}) })
callAsync(function() { callAsync(function() {
o(resolveCount).equals(1) o(matchCount).equals(1)
o(renderCount).equals(1) o(renderCount).equals(1)
redraw.publish() redraw.publish()
setTimeout(function() { setTimeout(function() {
o(resolveCount).equals(1) o(matchCount).equals(1)
o(renderCount).equals(2) o(renderCount).equals(2)
done() done()

View file

@ -239,7 +239,9 @@ m(Header, {
#### Avoid component factories #### 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 ```javascript
// AVOID // AVOID

View file

@ -6,15 +6,18 @@
- [route.get](#route-get) - [route.get](#route-get)
- [route.prefix](#route-prefix) - [route.prefix](#route-prefix)
- [route.link](#route-link) - [route.link](#route-link)
- [RouteResolver](#routeresolver)
- [routeResolver.onmatch](#routeresolver-onmatch)
- [routeResolver.view](#routeresolver-view)
- [How it works](#how-it-works) - [How it works](#how-it-works)
- [Typical usage](#typical-usage) - [Typical usage](#typical-usage)
- [Navigating to different routes](#navigating-to-different-routes) - [Navigating to different routes](#navigating-to-different-routes)
- [Routing parameters](#routing-parameters) - [Routing parameters](#routing-parameters)
- [Changing router prefix](#changing-router-prefix) - [Changing router prefix](#changing-router-prefix)
- [Advanced component resolution](#advanced-component-resolution)
- [Wrapping a layout component](#wrapping-a-layout-component) - [Wrapping a layout component](#wrapping-a-layout-component)
- [Advanced component resolution](#advanced-component-resolution)
- [Authentication](#authentication) - [Authentication](#authentication)
- [Lazy loading](#lazy-loading) - [Code splitting](#code-splitting)
--- ---
@ -31,38 +34,6 @@ Argument | Type | Required | D
[How to read signatures](signatures.md) [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 #### Static members
@ -109,6 +80,43 @@ Argument | Type | Required | Description
`vnode` | `Vnode` | Yes | This method is meant to be used in conjunction with an `<a>` [vnode](vnodes.md)'s [`oncreate` hook](lifecycle-methods.md) `vnode` | `Vnode` | Yes | This method is meant to be used in conjunction with an `<a>` [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` **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 #### 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 ### 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 ```javascript
var Layout = { var Layout = {
@ -289,7 +276,7 @@ var Layout = {
m.route(document.body, "/", { m.route(document.body, "/", {
"/": { "/": {
render: function() { view: function() {
return m(Layout, Home) 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 ### 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 ```javascript
var isLoggedIn = false var isLoggedIn = false
@ -320,8 +328,8 @@ var Login = {
m.route(document.body, "/secret", { m.route(document.body, "/secret", {
"/secret": { "/secret": {
resolve: function(use) { onmatch: function(vnode, resolve) {
if (isLoggedIn) use(Home) if (isLoggedIn) resolve(Home)
else m.route.set("/login") 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. 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: At its simplest form, one could do the following:
@ -368,8 +376,8 @@ function load(file, done) {
m.route(document.body, "/", { m.route(document.body, "/", {
"/": { "/": {
resolve: function(use) { onmatch: function(vnode, resolve) {
load("Home.js", use) load("Home.js", resolve)
}, },
}, },
}) })

View file

@ -1,22 +1,20 @@
"use strict" "use strict"
var log = console.error.bind(console)
var defaultStream = require("./stream/index")
var m = require("./render/hyperscript") var m = require("./render/hyperscript")
var renderService = require("./render/render")(window) var renderService = require("./render")
var requestService = require("./request/request")(window, log) var requestService = require("./request")
var redrawService = require("./api/pubsub")() var redrawService = require("./redraw")
requestService.setCompletionCallback(redrawService.publish) requestService.setCompletionCallback(redrawService.publish)
m.route = require("./api/router")(window, renderService, redrawService) m.route = require("./route")
m.mount = require("./api/mount")(renderService, redrawService) m.mount = require("./mount")
m.trust = require("./render/trust") m.trust = require("./render/trust")
m.withAttr = require("./util/withAttr") m.withAttr = require("./util/withAttr")
m.prop = defaultStream m.prop = require("./stream")
m.render = renderService.render m.render = renderService.render
m.redraw = redrawService.publish m.redraw = redrawService.publish
m.request = requestService.xhr m.request = requestService.request
m.jsonp = requestService.jsonp m.jsonp = requestService.jsonp
m.version = "bleeding-edge" m.version = "bleeding-edge"

4
mount.js Normal file
View file

@ -0,0 +1,4 @@
var renderService = require("./render")
var redrawService = require("./redraw")
module.exports = require("./api/mount")(renderService, redrawService)

1
redraw.js Normal file
View file

@ -0,0 +1 @@
module.exports = require("./api/pubsub")()

1
render.js Normal file
View file

@ -0,0 +1 @@
module.exports = require("./render/render")(window)

1
request.js Normal file
View file

@ -0,0 +1 @@
module.exports = require("./request/request")(window, console.error.bind(console))

View file

@ -10,8 +10,8 @@ module.exports = function($window, log) {
var oncompletion var oncompletion
function setCompletionCallback(callback) {oncompletion = callback} function setCompletionCallback(callback) {oncompletion = callback}
function xhr(args) { function request(args) {
var stream = Stream.stream() var stream = Stream()
if (args.initialValue !== undefined) stream(args.initialValue) if (args.initialValue !== undefined) stream(args.initialValue)
var useBody = typeof args.useBody === "boolean" ? args.useBody : args.method !== "GET" && args.method !== "TRACE" 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) { function jsonp(args) {
var stream = Stream.stream() var stream = Stream()
if (args.initialValue !== undefined) stream(args.initialValue) if (args.initialValue !== undefined) stream(args.initialValue)
var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
@ -130,5 +130,5 @@ module.exports = function($window, log) {
return data return data
} }
return {xhr: xhr, jsonp: jsonp, setCompletionCallback: setCompletionCallback} return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback}
} }

View file

@ -14,7 +14,7 @@
<script src="../../querystring/build.js"></script> <script src="../../querystring/build.js"></script>
<script src="../../util/stream.js"></script> <script src="../../util/stream.js"></script>
<script src="../../request/request.js"></script> <script src="../../request/request.js"></script>
<script src="test-xhr.js"></script> <script src="test-request.js"></script>
<script src="test-jsonp.js"></script> <script src="test-jsonp.js"></script>
<script>require("../../ospec/ospec").run()</script> <script>require("../../ospec/ospec").run()</script>

View file

@ -9,7 +9,7 @@ o.spec("xhr", function() {
o.beforeEach(function() { o.beforeEach(function() {
mock = xhrMock() mock = xhrMock()
spy = o.spy() spy = o.spy()
xhr = new Request(mock, spy).xhr xhr = new Request(mock, spy).request
}) })
o.spec("success", function() { o.spec("success", function() {

4
route.js Normal file
View file

@ -0,0 +1,4 @@
var renderService = require("./render")
var redrawService = require("./redraw")
module.exports = require("./api/router")(window, renderService, redrawService)

2
stream.js Normal file
View file

@ -0,0 +1,2 @@
var StreamFactory = require("./util/stream")
module.exports = StreamFactory(console.log.bind(console))

View file

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

View file

@ -193,6 +193,10 @@ module.exports = function(log) {
return streams.map(function(s) {return s()}) return streams.map(function(s) {return s()})
}, streams) }, 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
} }

View file

@ -13,7 +13,7 @@ o.spec("stream", function() {
o.spec("stream", function() { o.spec("stream", function() {
o("works as getter/setter", function() { o("works as getter/setter", function() {
var stream = Stream.stream(1) var stream = Stream(1)
var initialValue = stream() var initialValue = stream()
stream(2) stream(2)
var newValue = stream() var newValue = stream()
@ -22,25 +22,25 @@ o.spec("stream", function() {
o(newValue).equals(2) o(newValue).equals(2)
}) })
o("has undefined value by default", function() { o("has undefined value by default", function() {
var stream = Stream.stream() var stream = Stream()
o(stream()).equals(undefined) o(stream()).equals(undefined)
}) })
o("can update to undefined", function() { o("can update to undefined", function() {
var stream = Stream.stream(1) var stream = Stream(1)
stream(undefined) stream(undefined)
o(stream()).equals(undefined) o(stream()).equals(undefined)
}) })
o("can be stream of streams", function() { o("can be stream of streams", function() {
var stream = Stream.stream(Stream.stream(1)) var stream = Stream(Stream(1))
o(stream()()).equals(1) o(stream()()).equals(1)
}) })
}) })
o.spec("combine", function() { o.spec("combine", function() {
o("transforms value", function() { o("transforms value", function() {
var stream = Stream.stream() var stream = Stream()
var doubled = Stream.combine(function(s) {return s() * 2}, [stream]) var doubled = Stream.combine(function(s) {return s() * 2}, [stream])
stream(2) stream(2)
@ -48,14 +48,14 @@ o.spec("stream", function() {
o(doubled()).equals(4) o(doubled()).equals(4)
}) })
o("transforms default value", function() { o("transforms default value", function() {
var stream = Stream.stream(2) var stream = Stream(2)
var doubled = Stream.combine(function(s) {return s() * 2}, [stream]) var doubled = Stream.combine(function(s) {return s() * 2}, [stream])
o(doubled()).equals(4) o(doubled()).equals(4)
}) })
o("transforms multiple values", function() { o("transforms multiple values", function() {
var s1 = Stream.stream() var s1 = Stream()
var s2 = Stream.stream() var s2 = Stream()
var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2])
s1(2) s1(2)
@ -64,15 +64,15 @@ o.spec("stream", function() {
o(added()).equals(5) o(added()).equals(5)
}) })
o("transforms multiple default values", function() { o("transforms multiple default values", function() {
var s1 = Stream.stream(2) var s1 = Stream(2)
var s2 = Stream.stream(3) var s2 = Stream(3)
var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2])
o(added()).equals(5) o(added()).equals(5)
}) })
o("transforms mixed default and late-bound values", function() { o("transforms mixed default and late-bound values", function() {
var s1 = Stream.stream(2) var s1 = Stream(2)
var s2 = Stream.stream() var s2 = Stream()
var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2]) var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2])
s2(3) s2(3)
@ -81,7 +81,7 @@ o.spec("stream", function() {
}) })
o("combines atomically", function() { o("combines atomically", function() {
var count = 0 var count = 0
var a = Stream.stream() var a = Stream()
var b = Stream.combine(function(a) {return a() * 2}, [a]) var b = Stream.combine(function(a) {return a() * 2}, [a])
var c = Stream.combine(function(a) {return a() * a()}, [a]) var c = Stream.combine(function(a) {return a() * a()}, [a])
var d = Stream.combine(function(b, c) { var d = Stream.combine(function(b, c) {
@ -96,7 +96,7 @@ o.spec("stream", function() {
}) })
o("combines default value atomically", function() { o("combines default value atomically", function() {
var count = 0 var count = 0
var a = Stream.stream(3) var a = Stream(3)
var b = Stream.combine(function(a) {return a() * 2}, [a]) var b = Stream.combine(function(a) {return a() * 2}, [a])
var c = Stream.combine(function(a) {return a() * a()}, [a]) var c = Stream.combine(function(a) {return a() * a()}, [a])
var d = Stream.combine(function(b, c) { 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() { o("combine lists only changed upstreams in last arg", function() {
var streams = [] var streams = []
var a = Stream.stream() var a = Stream()
var b = Stream.stream() var b = Stream()
var c = Stream.combine(function(a, b, changed) { var c = Stream.combine(function(a, b, changed) {
streams = changed streams = changed
}, [a, b]) }, [a, b])
@ -123,8 +123,8 @@ o.spec("stream", function() {
}) })
o("combine lists only changed upstreams in last arg with default value", function() { o("combine lists only changed upstreams in last arg with default value", function() {
var streams = [] var streams = []
var a = Stream.stream(3) var a = Stream(3)
var b = Stream.stream(5) var b = Stream(5)
var c = Stream.combine(function(a, b, changed) { var c = Stream.combine(function(a, b, changed) {
streams = changed streams = changed
}, [a, b]) }, [a, b])
@ -135,7 +135,7 @@ o.spec("stream", function() {
o(streams[0]).equals(a) o(streams[0]).equals(a)
}) })
o("combine can return undefined", function() { o("combine can return undefined", function() {
var a = Stream.stream(1) var a = Stream(1)
var b = Stream.combine(function(a) { var b = Stream.combine(function(a) {
return undefined return undefined
}, [a]) }, [a])
@ -143,24 +143,24 @@ o.spec("stream", function() {
o(b()).equals(undefined) o(b()).equals(undefined)
}) })
o("combine can return stream", function() { o("combine can return stream", function() {
var a = Stream.stream(1) var a = Stream(1)
var b = Stream.combine(function(a) { var b = Stream.combine(function(a) {
return Stream.stream(2) return Stream(2)
}, [a]) }, [a])
o(b()()).equals(2) o(b()()).equals(2)
}) })
o("combine can return pending stream", function() { o("combine can return pending stream", function() {
var a = Stream.stream(1) var a = Stream(1)
var b = Stream.combine(function(a) { var b = Stream.combine(function(a) {
return Stream.stream() return Stream()
}, [a]) }, [a])
o(b()()).equals(undefined) o(b()()).equals(undefined)
}) })
o("combine can halt", function() { o("combine can halt", function() {
var count = 0 var count = 0
var a = Stream.stream(1) var a = Stream(1)
var b = Stream.combine(function(a) { var b = Stream.combine(function(a) {
return Stream.HALT return Stream.HALT
}, [a]) }, [a])
@ -175,19 +175,19 @@ o.spec("stream", function() {
o.spec("merge", function() { o.spec("merge", function() {
o("transforms an array of streams to an array of values", function() { o("transforms an array of streams to an array of values", function() {
var all = Stream.merge([ var all = Stream.merge([
Stream.stream(10), Stream(10),
Stream.stream("20"), Stream("20"),
Stream.stream({value: 30}), Stream({value: 30}),
]) ])
o(all()).deepEquals([10, "20", {value: 30}]) o(all()).deepEquals([10, "20", {value: 30}])
}) })
o("remains pending until all streams are active", function() { o("remains pending until all streams are active", function() {
var straggler = Stream.stream() var straggler = Stream()
var all = Stream.merge([ var all = Stream.merge([
Stream.stream(10), Stream(10),
Stream.stream("20"), Stream("20"),
straggler, straggler,
]) ])
@ -199,7 +199,7 @@ o.spec("stream", function() {
}) })
o.spec("end", function() { o.spec("end", function() {
o("end stream works", function() { o("end stream works", function() {
var stream = Stream.stream() var stream = Stream()
var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream]) var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream])
stream.end(true) stream.end(true)
@ -209,7 +209,7 @@ o.spec("stream", function() {
o(doubled()).equals(undefined) o(doubled()).equals(undefined)
}) })
o("end stream works with default value", function() { 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]) var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream])
stream.end(true) stream.end(true)
@ -219,7 +219,7 @@ o.spec("stream", function() {
o(doubled()).equals(4) o(doubled()).equals(4)
}) })
o("cannot add downstream to ended stream", function() { o("cannot add downstream to ended stream", function() {
var stream = Stream.stream(2) var stream = Stream(2)
stream.end(true) stream.end(true)
var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream]) var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream])
@ -228,7 +228,7 @@ o.spec("stream", function() {
o(doubled()).equals(undefined) o(doubled()).equals(undefined)
}) })
o("upstream does not affect ended stream", function() { 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]) var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream])
doubled.end(true) doubled.end(true)
@ -240,7 +240,7 @@ o.spec("stream", function() {
}) })
o.spec("error", function() { o.spec("error", function() {
o("error() works", function() { o("error() works", function() {
var stream = Stream.stream() var stream = Stream()
var errored = Stream.combine(function(stream) {throw new Error("error")}, [stream]) var errored = Stream.combine(function(stream) {throw new Error("error")}, [stream])
stream(3) stream(3)
@ -249,14 +249,14 @@ o.spec("stream", function() {
o(errored.error().message).equals("error") o(errored.error().message).equals("error")
}) })
o("error() works with default value", function() { 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]) var errored = Stream.combine(function(stream) {throw new Error("error")}, [stream])
o(errored()).equals(undefined) o(errored()).equals(undefined)
o(errored.error().message).equals("error") o(errored.error().message).equals("error")
}) })
o("error() removes error on valid value", function() { o("error() removes error on valid value", function() {
var stream = Stream.stream("a") var stream = Stream("a")
var doubled = Stream.combine(function(stream) { var doubled = Stream.combine(function(stream) {
if (typeof stream() !== "number") throw new Error("error") if (typeof stream() !== "number") throw new Error("error")
else return stream() * 2 else return stream() * 2
@ -269,7 +269,7 @@ o.spec("stream", function() {
}) })
o("error() triggers catch", function() { o("error() triggers catch", function() {
var count = 0 var count = 0
var stream = Stream.stream(1) var stream = Stream(1)
var handled = stream.catch(function() { var handled = stream.catch(function() {
count++ count++
return 2 return 2
@ -283,7 +283,7 @@ o.spec("stream", function() {
}) })
o("thrown error propagates downstream", function() { o("thrown error propagates downstream", function() {
var count = 0 var count = 0
var stream = Stream.stream(1) var stream = Stream(1)
.map(function() {throw new Error("error")}) .map(function() {throw new Error("error")})
.map(function(value) { .map(function(value) {
count++ count++
@ -300,7 +300,7 @@ o.spec("stream", function() {
}) })
o("set error propagates downstream", function() { o("set error propagates downstream", function() {
var count = 0 var count = 0
var stream = Stream.stream() var stream = Stream()
var mapped = stream.map(function(value) { var mapped = stream.map(function(value) {
count++ count++
return value * 2 return value * 2
@ -316,7 +316,7 @@ o.spec("stream", function() {
o(count).equals(0) o(count).equals(0)
}) })
o("error.map works", function() { o("error.map works", function() {
var stream = Stream.stream(1) var stream = Stream(1)
var mappedFromError = stream.error.map(function(value) { var mappedFromError = stream.error.map(function(value) {
if (value) return "from" + value.message if (value) return "from" + value.message
}) })
@ -328,7 +328,7 @@ o.spec("stream", function() {
o(mappedFromError()).equals("fromerror") o(mappedFromError()).equals("fromerror")
}) })
o("error from error.map propagates", function() { o("error from error.map propagates", function() {
var stream = Stream.stream(1) var stream = Stream(1)
var mappedFromError = stream.error.map(function(value) { var mappedFromError = stream.error.map(function(value) {
return "from" + value.message return "from" + value.message
}) })
@ -344,7 +344,7 @@ o.spec("stream", function() {
}) })
o("error thrown from error.map propagates downstream", function() { o("error thrown from error.map propagates downstream", function() {
var count = 0 var count = 0
var stream = Stream.stream(1) var stream = Stream(1)
var mappedFromError = stream.error.map(function(value) { var mappedFromError = stream.error.map(function(value) {
throw new Error("b") throw new Error("b")
}) })
@ -379,13 +379,13 @@ o.spec("stream", function() {
o("error.map can return streams", function() { o("error.map can return streams", function() {
var stream = Stream.reject(new Error("error")) var stream = Stream.reject(new Error("error"))
var error = stream.error.map(function(value) { var error = stream.error.map(function(value) {
return Stream.stream(1) return Stream(1)
}) })
o(error()()).equals(1) o(error()()).equals(1)
}) })
o("combined stream of two errored streams adopts error from first", function() { 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 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 c = Stream.combine(function(a) {throw new Error("error from c")}, [a])
var d = Stream.combine(function(b, c) {return 2}, [b, c]) var d = Stream.combine(function(b, c) {return 2}, [b, c])
@ -443,7 +443,7 @@ o.spec("stream", function() {
}) })
o.spec("run", function() { o.spec("run", function() {
o("works", function() { o("works", function() {
var stream = Stream.stream() var stream = Stream()
var doubled = stream.run(function(value) {return value * 2}) var doubled = stream.run(function(value) {return value * 2})
stream(3) stream(3)
@ -451,13 +451,13 @@ o.spec("stream", function() {
o(doubled()).equals(6) o(doubled()).equals(6)
}) })
o("works with default value", function() { o("works with default value", function() {
var stream = Stream.stream(3) var stream = Stream(3)
var doubled = stream.run(function(value) {return value * 2}) var doubled = stream.run(function(value) {return value * 2})
o(doubled()).equals(6) o(doubled()).equals(6)
}) })
o("works with undefined value", function() { o("works with undefined value", function() {
var stream = Stream.stream() var stream = Stream()
var mapped = stream.run(function(value) {return String(value)}) var mapped = stream.run(function(value) {return String(value)})
stream(undefined) stream(undefined)
@ -465,13 +465,13 @@ o.spec("stream", function() {
o(mapped()).equals("undefined") o(mapped()).equals("undefined")
}) })
o("does not run when initialized w/ HALT", function() { 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}) var mapped = stream.run(function(value) {return 123})
o(mapped()).equals(undefined) o(mapped()).equals(undefined)
}) })
o("does not run when set to HALT", function() { o("does not run when set to HALT", function() {
var stream = Stream.stream() var stream = Stream()
var mapped = stream.run(function(value) {return 123}) var mapped = stream.run(function(value) {return 123})
stream(Stream.HALT) stream(Stream.HALT)
@ -479,14 +479,14 @@ o.spec("stream", function() {
o(mapped()).equals(undefined) o(mapped()).equals(undefined)
}) })
o("works with default undefined value", function() { 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)}) var mapped = stream.run(function(value) {return String(value)})
o(mapped()).equals("undefined") o(mapped()).equals("undefined")
}) })
o("works with stream that throws", function() { o("works with stream that throws", function() {
var count = 0 var count = 0
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var errored = stream.run(function(value) {throw new Error("error")}) var errored = stream.run(function(value) {throw new Error("error")})
var mapped = errored.map(function(value) { var mapped = errored.map(function(value) {
count++ count++
@ -501,8 +501,8 @@ o.spec("stream", function() {
}) })
o("works with pending stream", function() { o("works with pending stream", function() {
var count = 0 var count = 0
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var absorbed = Stream.stream() var absorbed = Stream()
var absorber = stream.run(function(value) {return absorbed}) var absorber = stream.run(function(value) {return absorbed})
var mapped = absorber.map(function(value) { var mapped = absorber.map(function(value) {
count++ count++
@ -518,14 +518,14 @@ o.spec("stream", function() {
o(count).equals(1) o(count).equals(1)
}) })
o("works with active stream", function() { o("works with active stream", function() {
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var mapped = stream.run(function(value) {return Stream.stream(1)}) var mapped = stream.run(function(value) {return Stream(1)})
o(mapped()).equals(1) o(mapped()).equals(1)
}) })
o("works with errored stream", function() { o("works with errored stream", function() {
var rejected var rejected
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var mapped = stream.run(function(value) { var mapped = stream.run(function(value) {
return Stream.reject(new Error("error")) return Stream.reject(new Error("error"))
}) })
@ -534,9 +534,9 @@ o.spec("stream", function() {
o(mapped.error().message).equals("error") o(mapped.error().message).equals("error")
}) })
o("works with ended stream", function() { o("works with ended stream", function() {
var stream = Stream.stream(1) var stream = Stream(1)
var mapped = stream.run(function(value) { var mapped = stream.run(function(value) {
var ended = Stream.stream(2) var ended = Stream(2)
ended.end(true) ended.end(true)
return ended return ended
}) })
@ -546,8 +546,8 @@ o.spec("stream", function() {
o(mapped()).equals(2) o(mapped()).equals(2)
}) })
o("works when active stream updates", function() { o("works when active stream updates", function() {
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var absorbed = Stream.stream(1) var absorbed = Stream(1)
var mapped = stream.run(function(value) {return absorbed}) var mapped = stream.run(function(value) {return absorbed})
absorbed(2) absorbed(2)
@ -560,8 +560,8 @@ o.spec("stream", function() {
}) })
o("works when pending stream updates", function() { o("works when pending stream updates", function() {
var count = 0 var count = 0
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var absorbed = Stream.stream() var absorbed = Stream()
var mapped = stream.run(function(value) {return absorbed}) var mapped = stream.run(function(value) {return absorbed})
mapped.map(function (value) { mapped.map(function (value) {
@ -577,8 +577,8 @@ o.spec("stream", function() {
o(mapped()).equals(123) o(mapped()).equals(123)
}) })
o("works when updating stream to errored state", function() { o("works when updating stream to errored state", function() {
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var absorbed = Stream.stream(1) var absorbed = Stream(1)
var mapped = stream.run(function(value) {return absorbed}) var mapped = stream.run(function(value) {return absorbed})
absorbed.error(new Error("error")) absorbed.error(new Error("error"))
@ -592,8 +592,8 @@ o.spec("stream", function() {
o(mapped.error().message).equals("another error") o(mapped.error().message).equals("another error")
}) })
o("works when updating pending stream to errored state", function() { o("works when updating pending stream to errored state", function() {
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var absorbed = Stream.stream() var absorbed = Stream()
var mapped = stream.run(function(value) {return absorbed}) var mapped = stream.run(function(value) {return absorbed})
absorbed.error(new Error("error")) absorbed.error(new Error("error"))
@ -602,8 +602,8 @@ o.spec("stream", function() {
o(mapped.error().message).equals("error") o(mapped.error().message).equals("error")
}) })
o("works when updating stream to active state", function() { o("works when updating stream to active state", function() {
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var absorbed = Stream.stream(1) var absorbed = Stream(1)
var mapped = stream.run(function(value) {return absorbed}) var mapped = stream.run(function(value) {return absorbed})
absorbed.error(new Error("error")) absorbed.error(new Error("error"))
@ -617,8 +617,8 @@ o.spec("stream", function() {
o(mapped.error()).equals(undefined) o(mapped.error()).equals(undefined)
}) })
o("throwing from absorbed propagates", function() { o("throwing from absorbed propagates", function() {
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var absorbedParent = Stream.stream() var absorbedParent = Stream()
var absorbed = absorbedParent.map(function() {throw new Error("error")}) var absorbed = absorbedParent.map(function() {throw new Error("error")})
var mapped = stream.run(function(value) {return absorbed}) var mapped = stream.run(function(value) {return absorbed})
@ -648,7 +648,7 @@ o.spec("stream", function() {
}) })
o("catch works from combine", function() { o("catch works from combine", function() {
var count = 0 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++ count++
return "no" + e.message return "no" + e.message
}) })
@ -662,7 +662,7 @@ o.spec("stream", function() {
}) })
o("catch is not called if no error", function() { o("catch is not called if no error", function() {
var count = 0 var count = 0
var stream = Stream.stream() var stream = Stream()
var handled = stream.map(function(value) {return value + value}).catch(function(e) { var handled = stream.map(function(value) {return value + value}).catch(function(e) {
count++ count++
return "no" + e.message return "no" + e.message
@ -679,7 +679,7 @@ o.spec("stream", function() {
}) })
o("catch is not called if no error with default value", function() { o("catch is not called if no error with default value", function() {
var count = 0 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++ count++
return "no" + e.message return "no" + e.message
}) })
@ -710,7 +710,7 @@ o.spec("stream", function() {
}) })
o("catch absorbs pending stream", function() { o("catch absorbs pending stream", function() {
var count = 0 var count = 0
var stream = Stream.stream() var stream = Stream()
var mapped = Stream.reject(new Error("b")).catch(function(e) { var mapped = Stream.reject(new Error("b")).catch(function(e) {
return stream return stream
}) })
@ -723,7 +723,7 @@ o.spec("stream", function() {
o(count).equals(0) o(count).equals(0)
}) })
o("catch absorbs active stream", function() { 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) { var mapped = Stream.reject(new Error("b")).catch(function(e) {
return stream return stream
}) })
@ -752,7 +752,7 @@ o.spec("stream", function() {
}) })
o("catches wrapped rejected stream", function() { o("catches wrapped rejected stream", function() {
var caught var caught
var stream = Stream.stream(1).map(function() { var stream = Stream(1).map(function() {
return Stream.reject(new Error("error")) return Stream.reject(new Error("error"))
}) })
.catch(function(value) { .catch(function(value) {
@ -767,8 +767,8 @@ o.spec("stream", function() {
}) })
o("catches nested wrapped rejected stream", function() { o("catches nested wrapped rejected stream", function() {
var caught var caught
var stream = Stream.stream(1).map(function() { var stream = Stream(1).map(function() {
return Stream.stream(2).map(function() { return Stream(2).map(function() {
return Stream.reject(new Error("error")) return Stream.reject(new Error("error"))
}) })
}) })
@ -785,56 +785,56 @@ o.spec("stream", function() {
}) })
o.spec("valueOf", function() { o.spec("valueOf", function() {
o("works", function() { o("works", function() {
o(Stream.stream(1).valueOf()).equals(1) o(Stream(1).valueOf()).equals(1)
o(Stream.stream("a").valueOf()).equals("a") o(Stream("a").valueOf()).equals("a")
o(Stream.stream(true).valueOf()).equals(true) o(Stream(true).valueOf()).equals(true)
o(Stream.stream(null).valueOf()).equals(null) o(Stream(null).valueOf()).equals(null)
o(Stream.stream(undefined).valueOf()).equals(undefined) o(Stream(undefined).valueOf()).equals(undefined)
o(Stream.stream({a: 1}).valueOf()).deepEquals({a: 1}) o(Stream({a: 1}).valueOf()).deepEquals({a: 1})
o(Stream.stream([1, 2, 3]).valueOf()).deepEquals([1, 2, 3]) o(Stream([1, 2, 3]).valueOf()).deepEquals([1, 2, 3])
o(Stream.stream().valueOf()).equals(undefined) o(Stream().valueOf()).equals(undefined)
}) })
o("allows implicit value access in mathematical operations", function() { 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.spec("toString", function() {
o("aliases valueOf", function() { o("aliases valueOf", function() {
var stream = Stream.stream(1) var stream = Stream(1)
o(stream.toString).equals(stream.valueOf) o(stream.toString).equals(stream.valueOf)
}) })
o("allows implicit value access in string operations", function() { 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.spec("toJSON", function() {
o("works", function() { o("works", function() {
o(Stream.stream(1).toJSON()).equals(1) o(Stream(1).toJSON()).equals(1)
o(Stream.stream("a").toJSON()).equals("a") o(Stream("a").toJSON()).equals("a")
o(Stream.stream(true).toJSON()).equals(true) o(Stream(true).toJSON()).equals(true)
o(Stream.stream(null).toJSON()).equals(null) o(Stream(null).toJSON()).equals(null)
o(Stream.stream(undefined).toJSON()).equals(undefined) o(Stream(undefined).toJSON()).equals(undefined)
o(Stream.stream({a: 1}).toJSON()).deepEquals({a: 1}) o(Stream({a: 1}).toJSON()).deepEquals({a: 1})
o(Stream.stream([1, 2, 3]).toJSON()).deepEquals([1, 2, 3]) o(Stream([1, 2, 3]).toJSON()).deepEquals([1, 2, 3])
o(Stream.stream().toJSON()).equals(undefined) o(Stream().toJSON()).equals(undefined)
o(Stream.stream(new Date(0)).toJSON()).equals(new Date(0).toJSON()) o(Stream(new Date(0)).toJSON()).equals(new Date(0).toJSON())
}) })
o("works w/ JSON.stringify", function() { o("works w/ JSON.stringify", function() {
o(JSON.stringify(Stream.stream(1))).equals(JSON.stringify(1)) o(JSON.stringify(Stream(1))).equals(JSON.stringify(1))
o(JSON.stringify(Stream.stream("a"))).equals(JSON.stringify("a")) o(JSON.stringify(Stream("a"))).equals(JSON.stringify("a"))
o(JSON.stringify(Stream.stream(true))).equals(JSON.stringify(true)) o(JSON.stringify(Stream(true))).equals(JSON.stringify(true))
o(JSON.stringify(Stream.stream(null))).equals(JSON.stringify(null)) o(JSON.stringify(Stream(null))).equals(JSON.stringify(null))
o(JSON.stringify(Stream.stream(undefined))).equals(JSON.stringify(undefined)) o(JSON.stringify(Stream(undefined))).equals(JSON.stringify(undefined))
o(JSON.stringify(Stream.stream({a: 1}))).deepEquals(JSON.stringify({a: 1})) o(JSON.stringify(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([1, 2, 3]))).deepEquals(JSON.stringify([1, 2, 3]))
o(JSON.stringify(Stream.stream())).equals(JSON.stringify(undefined)) o(JSON.stringify(Stream())).equals(JSON.stringify(undefined))
o(JSON.stringify(Stream.stream(new Date(0)))).equals(JSON.stringify(new Date(0))) o(JSON.stringify(Stream(new Date(0)))).equals(JSON.stringify(new Date(0)))
}) })
}) })
o.spec("uncaught exception reporting", function() { o.spec("uncaught exception reporting", function() {
o("reports thrown errors", function(done) { 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() { setTimeout(function() {
o(spy.callCount).equals(1) o(spy.callCount).equals(1)
@ -853,7 +853,7 @@ o.spec("stream", function() {
}) })
o.spec("map", function() { o.spec("map", function() {
o("works", function() { o("works", function() {
var stream = Stream.stream() var stream = Stream()
var doubled = stream.map(function(value) {return value * 2}) var doubled = stream.map(function(value) {return value * 2})
stream(3) stream(3)
@ -861,13 +861,13 @@ o.spec("stream", function() {
o(doubled()).equals(6) o(doubled()).equals(6)
}) })
o("works with default value", function() { o("works with default value", function() {
var stream = Stream.stream(3) var stream = Stream(3)
var doubled = stream.map(function(value) {return value * 2}) var doubled = stream.map(function(value) {return value * 2})
o(doubled()).equals(6) o(doubled()).equals(6)
}) })
o("works with undefined value", function() { o("works with undefined value", function() {
var stream = Stream.stream() var stream = Stream()
var mapped = stream.map(function(value) {return String(value)}) var mapped = stream.map(function(value) {return String(value)})
stream(undefined) stream(undefined)
@ -875,22 +875,22 @@ o.spec("stream", function() {
o(mapped()).equals("undefined") o(mapped()).equals("undefined")
}) })
o("works with default undefined value", function() { 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)}) var mapped = stream.map(function(value) {return String(value)})
o(mapped()).equals("undefined") o(mapped()).equals("undefined")
}) })
o("works with pending stream", function() { o("works with pending stream", function() {
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var mapped = stream.map(function(value) {return Stream.stream()}) var mapped = stream.map(function(value) {return Stream()})
o(mapped()()).equals(undefined) o(mapped()()).equals(undefined)
}) })
}) })
o.spec("ap", function() { o.spec("ap", function() {
o("works", function() { o("works", function() {
var apply = Stream.stream(function(value) {return value * 2}) var apply = Stream(function(value) {return value * 2})
var stream = Stream.stream(3) var stream = Stream(3)
var applied = apply.ap(stream) var applied = apply.ap(stream)
o(applied()).equals(6) o(applied()).equals(6)
@ -904,8 +904,8 @@ o.spec("stream", function() {
o(applied()).equals(3) o(applied()).equals(3)
}) })
o("works with undefined value", function() { o("works with undefined value", function() {
var apply = Stream.stream(function(value) {return String(value)}) var apply = Stream(function(value) {return String(value)})
var stream = Stream.stream(undefined) var stream = Stream(undefined)
var applied = apply.ap(stream) var applied = apply.ap(stream)
o(applied()).equals("undefined") o(applied()).equals("undefined")
@ -918,7 +918,7 @@ o.spec("stream", function() {
o.spec("fantasy-land", function() { o.spec("fantasy-land", function() {
o.spec("functor", function() { o.spec("functor", function() {
o("identity", function() { o("identity", function() {
var stream = Stream.stream(3) var stream = Stream(3)
var mapped = stream.map(function(value) {return value}) var mapped = stream.map(function(value) {return value})
o(stream()).equals(mapped()) o(stream()).equals(mapped())
@ -927,7 +927,7 @@ o.spec("stream", function() {
function f(x) {return x * 2} function f(x) {return x * 2}
function g(x) {return x * x} 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 mapped = stream.map(function(value) {return f(g(value))})
var composed = stream.map(g).map(f) var composed = stream.map(g).map(f)
@ -938,9 +938,9 @@ o.spec("stream", function() {
}) })
o.spec("apply", function() { o.spec("apply", function() {
o("composition", function() { o("composition", function() {
var a = Stream.stream(function(value) {return value * 2}) var a = Stream(function(value) {return value * 2})
var u = Stream.stream(function(value) {return value * 3}) var u = Stream(function(value) {return value * 3})
var v = Stream.stream(5) var v = Stream(5)
var mapped = a.map(function(f) { var mapped = a.map(function(f) {
return function(g) { return function(g) {
@ -958,14 +958,14 @@ o.spec("stream", function() {
}) })
o.spec("applicative", function() { o.spec("applicative", function() {
o("identity", function() { o("identity", function() {
var a = Stream.stream().of(function(value) {return value}) var a = Stream().of(function(value) {return value})
var v = Stream.stream(5) var v = Stream(5)
o(a.ap(v)()).equals(5) o(a.ap(v)()).equals(5)
o(a.ap(v)()).equals(v()) o(a.ap(v)()).equals(v())
}) })
o("homomorphism", function() { o("homomorphism", function() {
var a = Stream.stream(0) var a = Stream(0)
var f = function(value) {return value * 2} var f = function(value) {return value * 2}
var x = 3 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(a.of(f).ap(a.of(x))()).equals(a.of(f(x))())
}) })
o("interchange", function() { o("interchange", function() {
var u = Stream.stream(function(value) {return value * 2}) var u = Stream(function(value) {return value * 2})
var a = Stream.stream() var a = Stream()
var y = 3 var y = 3
o(u.ap(a.of(y))()).equals(6) o(u.ap(a.of(y))()).equals(6)