├── .travis.yml ├── History.md ├── License.md ├── Readme.md ├── design-choices.md ├── event.js ├── example.js ├── index.js ├── keyboard.js ├── mouse.js ├── package.json ├── signal.js ├── test ├── common.js ├── index.js └── tap.js ├── time.js └── window.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.4 4 | - 0.5 5 | - 0.6 6 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 0.0.1 / 2013-09-20 4 | 5 | - Initial release -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | Copyright 2013 Irakli Gozalishvili. All rights reserved. 2 | Permission is hereby granted, free of charge, to any person obtaining a copy 3 | of this software and associated documentation files (the "Software"), to 4 | deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 6 | sell copies of the Software, and to permit persons to whom the Software is 7 | furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in 10 | all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 17 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 18 | IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # elmjs 2 | 3 | [![Build Status](https://secure.travis-ci.org/Gozala/elmjs.png)](http://travis-ci.org/Gozala/elmjs) 4 | 5 | 6 | [![Browser support](http://ci.testling.com/Gozala/elmjs.png)](http://ci.testling.com/Gozala/elmjs) 7 | 8 | 9 | Elm in JS 10 | 11 | ## Install 12 | 13 | npm install elmjs 14 | -------------------------------------------------------------------------------- /design-choices.md: -------------------------------------------------------------------------------- 1 | # Signals always have values 2 | 3 | ## Pros 4 | 5 | - Doing `map(f, xs, ys)` or `lift2(f, xs, ys)` is trivial both 6 | implementation vice and logic vice, since there's no chance 7 | some `x`s to be dropped while `y`s aren't received. Way to 8 | avoid loss is: 9 | 10 | ```js 11 | map(f, cons(initialX, xs), cons(initialY, ys)) 12 | ``` 13 | 14 | - Anything that does rendering can write initial state without 15 | waiting on things. 16 | 17 | ## Cons 18 | 19 | - GC issues, signal will hold reference to a last state, for 20 | example last open window, which maybe already closed, and 21 | could have being GC-ed otherwise. 22 | - Resumed connections need to recalculate it's value & receive 23 | it in case it's changed. (The problem is figureing out if 24 | it is changed as you need to recalculate the value). 25 | - If signal looses all it's connections it's value will become 26 | out of date. 27 | -------------------------------------------------------------------------------- /event.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Signal = require("./signal").Signal 4 | 5 | function Event(target, type, initial, options) { 6 | return new Signal(function(next) { 7 | function next(event) { 8 | event = event || window.event || {} 9 | if (Signal.Break === send(event)) { 10 | if (target.removeEventListener) 11 | target.removeEventListener(type, next, capture) 12 | else 13 | target.detachEvent("on" + type, next) 14 | } 15 | } 16 | 17 | if (target.addEventListener) 18 | target.addEventListener(type, handler, capture) 19 | else 20 | target.attachEvent("on" + type, handler) 21 | }, initial) 22 | } 23 | exports.Event = Event 24 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | // http://elm-lang.org/edit/examples/Elements/HelloWorld.elm 2 | 3 | var main = plainText("Hello, World") 4 | 5 | 6 | // http://elm-lang.org/edit/examples/Elements/Image.elm 7 | 8 | var main = image(472, 315, "/stack.jpg") 9 | 10 | 11 | // http://elm-lang.org/edit/examples/Elements/FittedImage.elm 12 | 13 | // fittedImage crops and resizes the image to fill the given area without 14 | // becoming deformed. 15 | 16 | var main = fittedImage(300, 300, "/book.jpg") 17 | 18 | 19 | // http://elm-lang.org/edit/examples/Elements/CroppedImage.elm 20 | 21 | 22 | // croppedImage cuts a rectangle that starts at the given 23 | // coordinates and has the given dimensions. It can later 24 | // be resized with width and height. 25 | 26 | main = croppedImage([10,10], 150, 150, "/yogi.jpg") 27 | 28 | 29 | // http://elm-lang.org/edit/examples/Elements/Size.elm 30 | 31 | 32 | /* 33 | You can set the width and height of the element with 34 | the following two functions: 35 | 36 | width, height : Int -> Element -> Element 37 | 38 | You can set both width and height at the same time 39 | with this function: 40 | 41 | size : Int -> Int -> Element -> Element 42 | 43 | Try them out on the car. 44 | */ 45 | 46 | main = width(300, image(472, 315, "/car.jpg")) 47 | main = height(200, image(472, 315, "/car.jpg")) 48 | main = size(300, 400, image(472, 315, "/car.jpg")) 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | -------------------------------------------------------------------------------- /keyboard.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Event = require("./event").Event 4 | var field = require("oops").field 5 | var signal = require("./signal") 6 | var map = signal.map 7 | var dropRepeats = signal.dropRepeats 8 | 9 | var keyCode = field("keycode") 10 | function Null() { return null } 11 | 12 | var down = map(keyCode, Event(document, "keydown")) 13 | exports.down = down 14 | 15 | var up = map(keycode, Event(document, "keyup")) 16 | exports.up = up 17 | 18 | var blur = map(Null, Event(document, "blur")) 19 | 20 | // The latest key that has been pressed. 21 | // lastPressed : Signal KeyCode 22 | var lastPressed = dropRepeats(down) 23 | exports.lastPressed = lastPressed 24 | 25 | // Whether an arbitrary key is pressed. 26 | // KeyCode -> Signal Bool 27 | function isDown(keyCode) { 28 | return map(is(keyCode), down) 29 | } 30 | exports.isDown = isDown 31 | 32 | // Whether the shift key is pressed. 33 | // shift : Signal Bool 34 | var shift = isDown(16) 35 | exports.shift = shift 36 | 37 | // Whether the control key is pressed. 38 | // ctrl : Signal Bool 39 | var ctrl = isDown(17) 40 | exports.ctrl = ctrl 41 | 42 | // Whether the space key is pressed. 43 | // space : Signal Bool 44 | var space = isDown(32) 45 | exports.space = space 46 | 47 | // Whether the enter key is pressed. 48 | // enter : Signal Bool 49 | var enter = isDown(13) 50 | exports.enter = enter 51 | 52 | function cons(head, tail) { 53 | return [head].concat(tail) 54 | } 55 | function remove(array, value) { 56 | var index = array.indexOf(value) 57 | if (index >= 0) array.splice(index, 1) 58 | return array 59 | } 60 | function has(array, value) { 61 | return array.indexOf(value) >= 0 62 | } 63 | 64 | // List of keys that are currently down. 65 | var keysDown = new Signal(function(next) { 66 | var value = [] 67 | down.spawn(function(keyCode) { 68 | return next(cons(keyCode, value)) 69 | }) 70 | up.spawn(function(keyCode) { 71 | return next(remove(value, keyCode)) 72 | }) 73 | blur.spawn(function() { 74 | return next([]) 75 | }) 76 | }, []) 77 | exports.keysDown = keysDown 78 | 79 | // Custom key directions so that you can support different locales. 80 | // The plan is to have a locale independent version of this function 81 | // that uses the physical location of keys, but I don't know how to do it. 82 | // directions : KeyCode -> KeyCode -> KeyCode -> KeyCode -> Signal { x:Int, y:Int } 83 | 84 | function directions(up, down, left, right) { 85 | return map(function(keys) { 86 | var x = has(keys, left) ? -1 : 87 | has(keys, right) ? 1 : 88 | 0 89 | var y = has(keys, down) ? -1 : 90 | has(keys, up) ? 1 : 91 | 0 92 | 93 | return {x:x, y:y} 94 | }, keysDown) 95 | } 96 | exports.directions = directions 97 | 98 | // A signal of records indicating which arrow keys are pressed. 99 | 100 | // `{ x = 0, y = 0 }` when pressing no arrows.
101 | // `{ x =-1, y = 0 }` when pressing the left arrow.
102 | // `{ x = 1, y = 1 }` when pressing the up and right arrows.
103 | // `{ x = 0, y =-1 }` when pressing the down, left, and right arrows. 104 | // arrows : Signal { x:Int, y:Int } 105 | var arrows = directions(38, 40, 37, 39) 106 | exports.arrows = arrows 107 | 108 | // Just like the arrows signal, but this uses keys w, a, s, and d, 109 | // which are common controls for many computer games. 110 | // wasd : Signal { x:Int, y:Int } 111 | var wasd = directions(87, 83, 65, 68) 112 | exports.wasd = wasd 113 | -------------------------------------------------------------------------------- /mouse.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Event = require("./event").Event 4 | var signal = require("./signal") 5 | var merge = signal.merge 6 | var map = signal.map 7 | 8 | var oops = require("oops") 9 | var field = oops.field 10 | 11 | 12 | var Tuple = Array 13 | 14 | // The current mouse position. 15 | // Signal [Int,Int] 16 | var position = map(function(event) { 17 | var x = 0; 18 | var y = 0; 19 | 20 | if (event.pageX || event.pageY) { 21 | x = e.pageX 22 | y = e.pageY 23 | } 24 | else if (event.clientX || event.clientY) { 25 | x = e.clientX + 26 | document.body.scrollLeft + 27 | document.documentElement.scrollLeft 28 | 29 | y = e.clientY + 30 | document.body.scrollTop + 31 | document.documentElement.scrollTop 32 | } 33 | 34 | return new Tuple(x, y) 35 | }, Event(document, "mousemove")) 36 | exports.position = position 37 | 38 | 39 | function Null() { return null } 40 | function True() { return true } 41 | function Flase() { return false } 42 | 43 | 44 | // The current x-coordinate of the mouse. 45 | // Signal Int 46 | var x = map(position, field(0)) 47 | exports.x = x 48 | 49 | // The current y-coordinate of the mouse. 50 | // Signal Int 51 | var y = map(position, field(1)) 52 | exports.y = y 53 | 54 | var down = Event(document, "mousedown") 55 | var up = Event(document, "mouseup") 56 | // The current state of the left mouse-button. 57 | // True when the button is down, and false otherwise. 58 | // Signal Bool 59 | var isDown = merge(map(True, down), map(False, up)) 60 | exports.isDown = isDown 61 | 62 | // Always equal to `null`. Event triggers on every mouse click. 63 | // Signal null 64 | var clicks = map(Null, Event(document, "click")) 65 | exports.clicks = clicks 66 | 67 | // True immediately after the left mouse-button has been clicked, and false otherwise. 68 | // Signal Bool 69 | var isClicked = new Signal(function(next) { 70 | spawn(function() { 71 | next(true) 72 | return next(false) 73 | }, clicks) 74 | }, false) 75 | exports.isClicked = isClicked 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elmjs", 3 | "id": "elmjs", 4 | "version": "0.0.1", 5 | "description": "Elm in JS", 6 | "keywords": [ "elmjs" ], 7 | "author": "Irakli Gozalishvili (http://jeditoolkit.com)", 8 | "homepage": "https://github.com/Gozala/elmjs", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/Gozala/elmjs.git", 12 | "web": "https://github.com/Gozala/elmjs" 13 | }, 14 | "bugs": { 15 | "url": "http://github.com/Gozala/elmjs/issues/" 16 | }, 17 | "devDependencies": { 18 | "test": "~0.x.0", 19 | "phantomify": "~0.x.0", 20 | "retape": "~0.x.0", 21 | "tape": "~0.1.5" 22 | }, 23 | "main": "./index.js", 24 | "scripts": { 25 | "test": "npm run test-node && npm run test-browser", 26 | "test-browser": "node ./node_modules/phantomify/bin/cmd.js ./test/common.js", 27 | "test-node": "node ./test/common.js", 28 | "test-tap": "node ./test/tap.js", 29 | "postinstall": "npm dedup" 30 | }, 31 | "testling": { 32 | "files": "test/tap.js", 33 | "browsers": { 34 | "iexplore": [ 35 | "9.0" 36 | ], 37 | "chrome": [ 38 | "20.0" 39 | ], 40 | "firefox": [ 41 | "10.0", 42 | "15.0" 43 | ], 44 | "safari": [ 45 | "5.1", 46 | "6.0" 47 | ], 48 | "opera": [ 49 | "12.0" 50 | ] 51 | } 52 | }, 53 | "licenses": [{ 54 | "type" : "MIT", 55 | "url" : "https://github.com/Gozala/elmjs/License.md" 56 | }] 57 | } 58 | -------------------------------------------------------------------------------- /signal.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | 4 | // The library for general signal manipulation. Includes `lift` function 5 | // (that supports up to 8 inputs), combinations, filters, and past-dependence. 6 | // 7 | // Signals are time-varying values. Lifted functions are reevaluated whenver 8 | // any of their input signals has an event. Signal events may be of the same 9 | // value as the previous value of the signal. Such signals are useful for 10 | // timing and past-dependence. 11 | // 12 | // Some useful functions for working with time (e.g. setting FPS) and combining 13 | // signals and time (e.g. delaying updates, getting timestamps) can be found in 14 | // the Time library. 15 | // 16 | // Module implements elm API: http://docs.elm-lang.org/library/Signal.elm 17 | 18 | 19 | var $source = "source@signal" 20 | var $sources = "sources@signal" 21 | var $outputs = "outputs@signal" 22 | var $connect = "connect@signal" 23 | var $disconnect = "disconnect@signal" 24 | var $receive = "receive@signal" 25 | var $error = "error@signal" 26 | var $end = "end@signal" 27 | var $start = "start@signal" 28 | var $stop = "stop@signal" 29 | var $state = "state@signal" 30 | var $pending = "pending@signal" 31 | 32 | function outputs(input) { return input[$outputs] } 33 | outputs.toString = function() { return $outputs } 34 | exports.outputs = outputs 35 | 36 | function start(input) { input[$start](input) } 37 | start.toString = function() { return $start } 38 | exports.start = start 39 | 40 | function stop(input) { input[$stop](input) } 41 | stop.toString = function() { return $stop } 42 | exports.stop = stop 43 | 44 | function connect(source, target) { source[$connect](source, target) } 45 | connect.toString = function() { return $connect } 46 | exports.connect = connect 47 | 48 | function disconnect(source, target) { source[$disconnect](source, target) } 49 | disconnect.toString = function() { return $disconnect } 50 | exports.disconnect = disconnect 51 | 52 | function receive(input, message) { input[$receive](input, message) } 53 | receive.toString = function() { return $receive } 54 | exports.receive = receive 55 | 56 | function error(input, message) { input[$error](input, message) } 57 | error.toString = function() { return $error } 58 | exports.error = error 59 | 60 | function end(input) { input[$end](input) } 61 | end.toString = function() { return $end } 62 | exports.end = end 63 | 64 | function stringify(input) { 65 | return input.name + "[" + (input[$outputs] || []).map(function(x) { return x.name }) + "]" 66 | } 67 | 68 | var stringifier = Object.prototype.toString 69 | function isError(message) { 70 | return stringifier.call(message) === "[object Error]" 71 | } 72 | 73 | function Return(value) { 74 | if (!(this instanceof Return)) 75 | return new Return(value) 76 | 77 | this.value = value 78 | } 79 | exports.Return = Return 80 | 81 | function send(input, message) { 82 | if (message instanceof Return) { 83 | input[$receive](input, message.value) 84 | input[$end](input) 85 | } 86 | else if (isError(message)) { 87 | input[$error](input, message) 88 | } 89 | else { 90 | input[$receive](input, message) 91 | } 92 | } 93 | exports.send = send 94 | 95 | function Break() {} 96 | exports.Break = Break 97 | 98 | 99 | function Input() {} 100 | exports.Input = Input 101 | 102 | // `Input.start` is invoked with an `input` whenever system is 103 | // ready to start receiving values. After this point `input` can 104 | // start sending messages. Generic behavior is to `connect` to 105 | // the `input[$source]` to start receiving messages. 106 | Input.start = function(input) { 107 | var source = input[$source] 108 | source[$connect](source, input) 109 | } 110 | 111 | // `Input.stop` is invoked with an `input` whenever it needs to 112 | // stop. After this point `input` should stop sending messages. 113 | // Generic `Input` behavior is to `disconnect` from the 114 | // `input[$source]` so no more `messages` will be received. 115 | Input.stop = function(input) { 116 | var source = input[$source] 117 | source[$disconnect](source, input) 118 | } 119 | 120 | // `Input.connect` is invoked with `input` and `output`. This 121 | // implementation put's `output` to it's `$output` ports to 122 | // delegate received `messages` to it. 123 | Input.connect = function(input, output) { 124 | var outputs = input[$outputs] 125 | if (outputs.indexOf(output) < 0) { 126 | outputs.push(output) 127 | if (outputs.length === 1) 128 | input[$start](input) 129 | } 130 | } 131 | 132 | // `Input.disconnect` is invoked with `input` and an `output` 133 | // connected to it. After this point `output` should not longer 134 | // receive messages from the `input`. If it's a last `output` 135 | // `input` will be stopped. 136 | Input.disconnect = function(input, output) { 137 | var outputs = input[$outputs] 138 | var index = outputs.indexOf(output) 139 | if (index >= 0) { 140 | outputs.splice(index, 1) 141 | if (outputs.length === 0) 142 | input[$stop](input) 143 | } 144 | } 145 | 146 | // `Input.Port` creates a message receiver port. `Input` instances support 147 | // `message`, `error`, `end` ports. 148 | Input.Port = function(port) { 149 | var isError = port === $error 150 | var isEnd = port === $end 151 | var isMessage = port === $receive 152 | 153 | // Function will write `message` to a given `input`. This means 154 | // it will delegeate messages to it's `input[$outputs]` ports. 155 | return function write(input, message) { 156 | var outputs = input[$outputs] 157 | var result = void(0) 158 | var count = outputs.length 159 | var index = 0 160 | 161 | // Note: dispatch loop decreases count or increases index as needed. 162 | // This makes sure that new connections will not receive messages 163 | // until next dispatch loop & intentionally so. 164 | while (index < outputs.length) { 165 | // Attempt to send a value to a connected `output`. If this is 166 | // `$end` `port` return `Break` to cause `output` to be 167 | // disconnected. If any other `port` just deliver a `message`. 168 | var output = outputs[index] 169 | try { 170 | result = isEnd ? output[port](output, input) : 171 | output[port](output, message, input) 172 | } 173 | catch (reason) { 174 | throw reason 175 | // If exception was thrown and `message` was send to `$error` 176 | // `port` give up and log error. 177 | if (isError) { 178 | console.error("Failed to receive an error message", 179 | message, 180 | reason) 181 | } 182 | // If exception was thrown when writing to a different `port` 183 | // attempt to write to an `$error` `port` of the `output`. 184 | else { 185 | try { 186 | result = output[$error](output, reason, input) 187 | } 188 | // If exception is still thrown when writing to an `$error` 189 | // `port` give up and log `error`. 190 | catch (error) { 191 | console.error("Failed to receive message & an error", 192 | message, 193 | reason, 194 | error); 195 | } 196 | } 197 | } 198 | 199 | // If result of sending `message` to an `output` was instance 200 | // of `Break`, disconnect that `output` so it no longer get's 201 | // messages. Note `index` is decremented as disconnect will 202 | // remove it from `outputs`. 203 | if (result instanceof Break || isEnd) { 204 | input[$disconnect](input, output) 205 | } 206 | // On any other `result` just move to a next output. 207 | else { 208 | index = index + 1 209 | } 210 | } 211 | 212 | // Once message was written to all outputs update `value` of 213 | // the input. 214 | if (isMessage) 215 | input.value = message 216 | } 217 | } 218 | 219 | // Inputs have `message`, `error` and `end` ports 220 | Input.receive = Input.Port($receive) 221 | Input.error = Input.Port($error) 222 | Input.end = Input.Port($end) 223 | 224 | // Same API functions are saved in the prototype in order to enable 225 | // polymorphic dispatch. 226 | Input.prototype[$start] = Input.start 227 | Input.prototype[$stop] = Input.stop 228 | Input.prototype[$connect] = Input.connect 229 | Input.prototype[$disconnect] = Input.disconnect 230 | Input.prototype[$receive] = Input.receive 231 | Input.prototype[$error] = Input.error 232 | Input.prototype[$end] = Input.end 233 | Input.prototype.toJSON = function() { 234 | return { value: this.value } 235 | } 236 | 237 | function Constant(value) { 238 | this.value = value 239 | } 240 | Constant.ignore = function() {} 241 | 242 | Constant.prototype = new Input() 243 | Constant.prototype.constructor = Constant 244 | Constant.prototype[$start] = Constant.ignore 245 | Constant.prototype[$stop] = Constant.ignore 246 | Constant.prototype[$connect] = Constant.ignore 247 | Constant.prototype[$disconnect] = Constant.ignore 248 | Constant.prototype[$receive] = Constant.ignore 249 | Constant.prototype[$error] = Constant.ignore 250 | Constant.prototype[$end] = Constant.ignore 251 | 252 | 253 | // Create a constant signal that never changes. 254 | 255 | // a -> Signal a 256 | 257 | function constant(value) { 258 | return new Constant(value) 259 | } 260 | exports.constant = constant 261 | 262 | 263 | function Merge(inputs) { 264 | this[$outputs] = [] 265 | this[$sources] = inputs 266 | this[$pending] = inputs.length 267 | this.value = inputs[0].value 268 | } 269 | Merge.start = function(input) { 270 | var sources = input[$sources] 271 | var count = sources.length 272 | var id = 0 273 | 274 | while (id < count) { 275 | var source = sources[id] 276 | source[$connect](source, input) 277 | id = id + 1 278 | } 279 | } 280 | Merge.stop = function(input) { 281 | var inputs = input[$sources] 282 | var count = inputs.length 283 | var id = 0 284 | while (id < count) { 285 | var source = inputs[id] 286 | source[$disconnect](source, input) 287 | id = id + 1 288 | } 289 | } 290 | Merge.end = function(input, source) { 291 | var sources = input[$sources] 292 | var id = sources.indexOf(source) 293 | if (id >= 0) { 294 | var pending = input[$pending] - 1 295 | input[$pending] = pending 296 | source[$disconnect](source, input) 297 | 298 | if (pending === 0) 299 | Input.end(input) 300 | } 301 | } 302 | 303 | Merge.prototype = new Input() 304 | Merge.prototype.constructor = Merge 305 | Merge.prototype[$start] = Merge.start 306 | Merge.prototype[$stop] = Merge.stop 307 | Merge.prototype[$end] = Merge.end 308 | 309 | // Merge two signals into one, biased towards the 310 | // first signal if both signals update at the same time. 311 | 312 | // Signal x -> Signal y -> ... -> Signal z 313 | function merge() { 314 | return new Merge(slicer.call(arguments, 0)) 315 | } 316 | exports.merge = merge 317 | 318 | 319 | // Merge many signals into one, biased towards the 320 | // left-most signal if multiple signals update simultaneously. 321 | function merges(inputs) { 322 | return new Merge(inputs) 323 | } 324 | exports.merges = merges 325 | 326 | 327 | // # Past-Dependence 328 | 329 | // Create a past-dependent signal. Each value given on the input signal 330 | // will be accumulated, producing a new output value. 331 | 332 | function FoldP(step, value, input) { 333 | this[$outputs] = [] 334 | this[$source] = input 335 | this.value = value 336 | this.step = step 337 | } 338 | FoldP.receive = function(input, message, source) { 339 | Input.receive(input, input.step(input.value, message)) 340 | } 341 | 342 | FoldP.prototype = new Input() 343 | FoldP.prototype.constructor = FoldP 344 | FoldP.prototype[$receive] = FoldP.receive 345 | 346 | 347 | function foldp(step, x, xs) { 348 | return new FoldP(step, x, xs) 349 | } 350 | exports.foldp = foldp 351 | 352 | 353 | // Optimized version that tracks single input. 354 | function Lift(step, input) { 355 | this.step = step 356 | this[$source] = input 357 | this[$outputs] = [] 358 | this.value = step(input.value) 359 | } 360 | Lift.receive = function(input, message) { 361 | Input.receive(input, input.step(message)) 362 | } 363 | 364 | Lift.prototype = new Input() 365 | Lift.prototype.constructor = Lift 366 | Lift.prototype[$receive] = Lift.receive 367 | 368 | function LiftN(step, inputs) { 369 | var count = inputs.length 370 | var id = 0 371 | var params = Array(count) 372 | while (id < count) { 373 | var input = inputs[id] 374 | params[id] = input.value 375 | id = id + 1 376 | } 377 | var value = step.apply(step, params) 378 | 379 | this.step = step 380 | this[$outputs] = [] 381 | this[$sources] = inputs 382 | this[$pending] = inputs.length 383 | this[$state] = params 384 | this.value = value 385 | } 386 | LiftN.start = Merge.start 387 | LiftN.stop = Merge.stop 388 | LiftN.end = Merge.end 389 | 390 | 391 | LiftN.receive = function(input, message, source) { 392 | var params = input[$state] 393 | var index = input[$sources].indexOf(source) 394 | var step = input.step 395 | params[index] = message 396 | return Input.receive(input, step.apply(step, params)) 397 | } 398 | 399 | LiftN.prototype = new Input() 400 | LiftN.prototype.constructor = LiftN 401 | LiftN.prototype[$start] = LiftN.start 402 | LiftN.prototype[$stop] = LiftN.stop 403 | LiftN.prototype[$end] = LiftN.end 404 | LiftN.prototype[$receive] = LiftN.receive 405 | 406 | var slicer = [].slice 407 | 408 | // Transform given signal(s) with a given `step` function. 409 | 410 | // (x -> y -> ...) -> Signal x -> Signal y -> ... -> Signal z 411 | // 412 | // xs :--x-----x-----x--- 413 | // lift(f, xs) :--f(x)--f(x)--f(x) 414 | // 415 | // xs :--x--------------------------x------- 416 | // ys :-----------y---------y--------------- 417 | // lift(f, xs, ys) :--f(x, y)--f(x, y)--f(x, y)--f(x, y)- 418 | function lift(step, xs, ys) { 419 | return ys ? new LiftN(step, slicer.call(arguments, 1)) : 420 | new Lift(step, xs) 421 | } 422 | exports.lift = lift 423 | exports.lift2 = lift 424 | exports.lift3 = lift 425 | exports.lift4 = lift 426 | exports.lift5 = lift 427 | exports.lift6 = lift 428 | exports.lift7 = lift 429 | exports.lift8 = lift 430 | exports.liftN = lift 431 | 432 | 433 | // Combine a array of signals into a signal of arrays. 434 | function combine(inputs) { 435 | return new LiftN(Array, inputs) 436 | } 437 | exports.combine = combine 438 | 439 | 440 | 441 | // Count the number of events that have occured. 442 | 443 | // Signal x -> Signal Int 444 | // 445 | // xs : --x--x----x--x------x 446 | // count(xs): --1--2----3--4------5 447 | function count(xs) { 448 | return foldp(function(x, y) { 449 | return x + 1 450 | }, 0, xs) 451 | } 452 | exports.count = count 453 | 454 | // Count the number of events that have occured that 455 | // satisfy a given predicate. 456 | 457 | // (x -> Bool) -> Signal x -> Signal Int 458 | function countIf(p, xs) { 459 | return count(keepIf(p, xs.value, xs)) 460 | } 461 | exports.countIf = countIf 462 | 463 | // # Filters 464 | 465 | function KeepIf(p, value, input) { 466 | this.p = p 467 | this.value = p(input.value) ? input.value : value 468 | this[$outputs] = [] 469 | this[$source] = input 470 | } 471 | KeepIf.receive = function(input, message) { 472 | if (input.p(message)) 473 | Input.receive(input, message) 474 | } 475 | KeepIf.prototype.constructor = KeepIf 476 | KeepIf.prototype = new Input() 477 | KeepIf.prototype[$receive] = KeepIf.receive 478 | 479 | // Keep only events that satisfy the given predicate. 480 | // Elm does not allow undefined signals, so a base case 481 | // must be provided in case the predicate is never satisfied. 482 | 483 | // (x -> Bool) -> x -> Signal x -> Signal x 484 | function keepIf(p, x, xs) { 485 | return new KeepIf(p, x, xs) 486 | } 487 | exports.keepIf = keepIf 488 | 489 | 490 | function DropIf(p, value, input) { 491 | this.p = p 492 | this.value = p(input.value) ? value : input.value 493 | this[$source] = input 494 | this[$outputs] = [] 495 | } 496 | DropIf.receive = function(input, message) { 497 | if (!input.p(message)) 498 | Input.receive(input, message) 499 | } 500 | DropIf.prototype = new Input() 501 | DropIf.prototype.constructor = DropIf 502 | DropIf.prototype[$receive] = DropIf.receive 503 | 504 | // Drop events that satisfy the given predicate. Elm does not allow 505 | // undefined signals, so a base case must be provided in case the 506 | // predicate is never satisfied. 507 | 508 | // (x -> Bool) -> x -> Signal x -> Signal x 509 | function dropIf(p, x, xs) { 510 | return new DropIf(p, x, xs) 511 | } 512 | exports.dropIf = dropIf 513 | 514 | 515 | // Keep events only when the first signal is true. When the first signal 516 | // becomes true, the most recent value of the second signal will be propagated. 517 | // Until the first signal becomes false again, all events will be propagated. 518 | // Elm does not allow undefined signals, so a base case must be provided in case 519 | // the first signal is never true. 520 | 521 | // Signal Bool -> x -> Signal x -> Signal x 522 | function Skip() { return Skip } 523 | function isSkip(x) { return x === Skip } 524 | function skipIfTrue(isTrue, x) { return isTrue ? Skip : x } 525 | function skipIfFalse(isTrue, x) { return isTrue ? x : Skip } 526 | 527 | function keepWhen(state, x, xs) { 528 | var input = lift(skipIfFalse, dropRepeats(state), xs) 529 | return dropIf(isSkip, x, input) 530 | } 531 | exports.keepWhen = keepWhen 532 | 533 | // Drop events when the first signal is true. When the first signal 534 | // becomes false, the most recent value of the second signal will be 535 | // propagated. Until the first signal becomes true again, all events 536 | // will be propagated. Elm does not allow undefined signals, so a base 537 | // case must be provided in case the first signal is always true. 538 | 539 | // Signal Bool -> x -> Signal x -> Signal x 540 | function dropWhen(state, x, xs) { 541 | var input = lift(skipIfTrue, dropRepeats(state), xs) 542 | return dropIf(isSkip, x, input) 543 | } 544 | exports.dropWhen = dropWhen 545 | 546 | // Drop sequential repeated values. For example, if a signal produces 547 | // the sequence [1,1,2,2,1], it becomes [1,2,1] by dropping the values 548 | // that are the same as the previous value. 549 | 550 | // Signal x -> Signal x 551 | function dropRepeats(xs) { 552 | return dropIf(function(x) { 553 | return xs.value === x 554 | }, xs.value, xs) 555 | } 556 | exports.dropRepeats = dropRepeats 557 | 558 | // Sample from the second input every time an event occurs on the first 559 | // input. For example, (sampleOn clicks (every second)) will give the 560 | // approximate time of the latest click. 561 | 562 | // Signal a -> Signal b -> Signal b 563 | function sampleOn(ticks, input) { 564 | return merge(dropIf(True, input.value, input), 565 | lift(function(_) { return input.value }, ticks)) 566 | } 567 | exports.sampleOn = sampleOn 568 | 569 | function True() { return true } 570 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | require("test").run(require("./index")) -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var signal = require("../signal") 4 | var Input = signal.Input 5 | var Break = signal.Break 6 | var Return = signal.Return 7 | var connect = signal.connect 8 | var disconnect = signal.disconnect 9 | var start = signal.start 10 | var stop = signal.stop 11 | var receive = signal.receive 12 | var error = signal.error 13 | var end = signal.end 14 | var send = signal.send 15 | 16 | function Subject(options) { 17 | var options = options || {} 18 | this[signal.outputs] = [] 19 | this.name = "subject" 20 | this.onStart = options.onStart 21 | this.onStop = options.onStop 22 | this.value = options.value 23 | this.started = 0 24 | this.stopped = 0 25 | } 26 | Subject.prototype = new Input() 27 | Subject.prototype[start] = function() { 28 | this.started = this.started + 1 29 | if (this.onStart) 30 | this.onStart() 31 | } 32 | Subject.prototype[stop] = function() { 33 | this.stopped = this.stopped + 1 34 | if (this.onStop) 35 | this.onStop() 36 | } 37 | Subject.prototype.toJSON = function() { 38 | return { 39 | started: this.started, 40 | stopped: this.stopped, 41 | value: this.value 42 | } 43 | } 44 | 45 | function Client(options) { 46 | options = options || {} 47 | this.messages = [] 48 | this.errors = [] 49 | this.ends = [] 50 | 51 | this.onNext = options.onNext 52 | this.onError = options.onError 53 | this.onEnd = options.onEnd 54 | 55 | } 56 | Client.prototype[receive] = function(input, message, source) { 57 | this.messages.push(message) 58 | return this.onNext && input.onNext(message, source) 59 | } 60 | Client.prototype[error] = function(input, message, source) { 61 | this.errors.push(message) 62 | return this.onError && input.onError(message, source) 63 | } 64 | Client.prototype[end] = function(input, source) { 65 | this.ends.push(true) 66 | return this.onEnd && input.onEnd(source) 67 | } 68 | Client.prototype.toJSON = function() { 69 | return { 70 | messages: this.messages, 71 | errors: this.errors, 72 | ends: this.ends 73 | } 74 | } 75 | 76 | exports["test 2 messages & end"] = function(assert) { 77 | var subject = new Subject({ value: null }) 78 | 79 | assert.deepEqual(subject.toJSON(), { 80 | started: 0, 81 | stopped: 0, 82 | value: null 83 | }, "nothing changed") 84 | 85 | var client = new Client() 86 | connect(subject, client) 87 | 88 | assert.deepEqual(subject.toJSON(), { 89 | started: 1, 90 | stopped: 0, 91 | value: null 92 | }, "subject started") 93 | 94 | assert.deepEqual(client.toJSON(), { 95 | messages: [], 96 | errors: [], 97 | ends: [] 98 | }, "nothing received yet") 99 | 100 | send(subject, 1) 101 | 102 | assert.deepEqual(subject.toJSON(), { 103 | value: 1, 104 | started: 1, 105 | stopped: 0 106 | }, "value changed to 1") 107 | 108 | assert.deepEqual(client.toJSON(), { 109 | messages: [1], 110 | errors: [], 111 | ends: [] 112 | }, "client received one message") 113 | 114 | receive(subject, 2) 115 | 116 | assert.deepEqual(subject.toJSON(), { 117 | value: 2, 118 | started: 1, 119 | stopped: 0 120 | }, "value changed to 2") 121 | 122 | assert.deepEqual(client.toJSON(), { 123 | messages: [1, 2], 124 | errors: [], 125 | ends: [] 126 | }, "client received second message") 127 | 128 | end(subject) 129 | 130 | assert.deepEqual(subject.toJSON(), { 131 | value: 2, 132 | started: 1, 133 | stopped: 1 134 | }, "value changed to 2") 135 | 136 | assert.deepEqual(client.toJSON(), { 137 | messages: [1, 2], 138 | errors: [], 139 | ends: [true] 140 | }, "client received second message") 141 | } 142 | 143 | exports["test multiple connections"] = function(assert) { 144 | var source = new Subject({ value: null }); 145 | var order = [] 146 | 147 | assert.deepEqual(source.toJSON(), { 148 | started: 0, 149 | stopped: 0, 150 | value: null 151 | }, "nothing happened yet") 152 | 153 | var client1 = new Client({ 154 | onNext: function() { 155 | order.push(1) 156 | } 157 | }); 158 | 159 | connect(source, client1) 160 | 161 | assert.deepEqual(source.toJSON(), { 162 | started: 1, 163 | stopped: 0, 164 | value: null 165 | }, "source was started") 166 | 167 | send(source, 1) 168 | 169 | assert.deepEqual(source.toJSON(), { 170 | started: 1, 171 | stopped: 0, 172 | value: 1 173 | }, "source.value changed to 1") 174 | 175 | assert.deepEqual(client1.toJSON(), { 176 | ends: [], 177 | messages: [1], 178 | errors: [] 179 | }, "one message received on clien1") 180 | 181 | var client2 = new Client({ 182 | onNext: function() { 183 | order.push(2) 184 | } 185 | }) 186 | 187 | connect(source, client2); 188 | 189 | send(source, 2) 190 | 191 | assert.deepEqual(source.toJSON(), { 192 | started: 1, 193 | stopped: 0, 194 | value: 2 195 | }, "source.value changed to 2"); 196 | 197 | assert.deepEqual(client1.toJSON(), { 198 | ends: [], 199 | messages: [1, 2], 200 | errors: [] 201 | }, "messagese received on client 1") 202 | 203 | assert.deepEqual(client2.toJSON(), { 204 | ends: [], 205 | messages: [2], 206 | errors: [] 207 | }, "message received on clien 2"); 208 | 209 | var client3 = new Client({ 210 | onNext: function() { 211 | order.push(3) 212 | return new Break() 213 | } 214 | }) 215 | 216 | connect(source, client3) 217 | 218 | send(source, 3) 219 | 220 | assert.deepEqual(source.toJSON(), { 221 | started: 1, 222 | stopped: 0, 223 | value: 3 224 | }, "source.value changed to 3") 225 | 226 | assert.deepEqual(client1.toJSON(), { 227 | messages: [1, 2, 3], 228 | errors: [], 229 | ends: [] 230 | }, "client1 received 3 messages") 231 | 232 | assert.deepEqual(client2.toJSON(), { 233 | messages: [2, 3], 234 | errors: [], 235 | ends: [] 236 | }, "client2 received 2 messages") 237 | 238 | assert.deepEqual(client3.toJSON(), { 239 | messages: [3], 240 | errors: [], 241 | ends: [] 242 | }, "client3 received 1 message") 243 | 244 | send(source, 4) 245 | 246 | assert.deepEqual(source.toJSON(), { 247 | started: 1, 248 | stopped: 0, 249 | value: 4 250 | }, "source.value changed to 4") 251 | 252 | assert.deepEqual(client1.toJSON(), { 253 | messages: [1, 2, 3, 4], 254 | errors: [], 255 | ends: [] 256 | }, "client1 received 4 messages") 257 | 258 | assert.deepEqual(client2.toJSON(), { 259 | messages: [2, 3, 4], 260 | errors: [], 261 | ends: [] 262 | }, "client2 received 3 messages") 263 | 264 | assert.deepEqual(client3.toJSON(), { 265 | messages: [3], 266 | errors: [], 267 | ends: [] 268 | }, "client3 did not got last message") 269 | 270 | end(source) 271 | 272 | assert.deepEqual(source.toJSON(), { 273 | started: 1, 274 | stopped: 1, 275 | value: 4 276 | }, "source stopped") 277 | 278 | assert.deepEqual(client1.toJSON(), { 279 | messages: [1, 2, 3, 4], 280 | errors: [], 281 | ends: [true] 282 | }, "client1 received 4 messages") 283 | 284 | assert.deepEqual(client2.toJSON(), { 285 | messages: [2, 3, 4], 286 | errors: [], 287 | ends: [true] 288 | }, "client2 received 3 messages") 289 | 290 | assert.deepEqual(client3.toJSON(), { 291 | messages: [3], 292 | errors: [], 293 | ends: [] 294 | }, "client3 did not got last message") 295 | 296 | assert.deepEqual(order, 297 | [1, 1, 2, 1, 2, 3, 1, 2], 298 | "order of received message is correct") 299 | } 300 | 301 | exports["test last disconnect stops"] = function(assert) { 302 | var source = new Subject({ value: 0 }) 303 | var client = new Client({ 304 | onNext: function(message) { 305 | return new Break() 306 | } 307 | }) 308 | 309 | assert.deepEqual(source.toJSON(), { 310 | value: 0, 311 | started: 0, 312 | stopped: 0 313 | }, "source is in initial state") 314 | 315 | connect(source, client) 316 | 317 | assert.deepEqual(source.toJSON(), { 318 | value: 0, 319 | started: 1, 320 | stopped: 0 321 | }, "source in started state") 322 | assert.deepEqual(client.toJSON(), { 323 | messages: [], 324 | errors: [], 325 | ends: [] 326 | }, "client got nothing so far") 327 | 328 | send(source, 1) 329 | 330 | assert.deepEqual(source.toJSON(), { 331 | value: 1, 332 | started: 1, 333 | stopped: 1 334 | }, "source was stopped") 335 | assert.deepEqual(client.toJSON(), { 336 | messages: [1], 337 | errors: [], 338 | ends: [] 339 | }, "client got one message") 340 | } 341 | 342 | exports["test same client can connect once"] = function(assert) { 343 | var source = new Subject({ value: null }) 344 | var client = new Client() 345 | 346 | connect(source, client) 347 | 348 | assert.deepEqual(source.toJSON(), { 349 | started: 1, 350 | stopped: 0, 351 | value: null 352 | }, "source started") 353 | 354 | send(source, 1) 355 | 356 | assert.deepEqual(source.toJSON(), { 357 | started: 1, 358 | stopped: 0, 359 | value: 1 360 | }, "source value changed to 1") 361 | assert.deepEqual(client.toJSON(), { 362 | messages: [1], 363 | errors: [], 364 | ends: [] 365 | }, "got one message") 366 | 367 | connect(source, client) 368 | send(source, 2) 369 | 370 | assert.deepEqual(source.toJSON(), { 371 | started: 1, 372 | stopped: 0, 373 | value: 2 374 | }, "source value changed to 2") 375 | assert.deepEqual(client.toJSON(), { 376 | messages: [1, 2], 377 | errors: [], 378 | ends: [] 379 | }, "got only one message") 380 | } 381 | 382 | exports["test manual disconnect stops"] = function(assert) { 383 | var source = new Subject({ value: 0 }) 384 | var a = new Client() 385 | var b = new Client() 386 | 387 | connect(source, a) 388 | connect(source, b) 389 | 390 | assert.deepEqual(source.toJSON(), { 391 | started: 1, 392 | stopped: 0, 393 | value: 0 394 | }, "input started") 395 | 396 | send(source, 1) 397 | 398 | assert.deepEqual(source.toJSON(), { 399 | started: 1, 400 | stopped: 0, 401 | value: 1 402 | }, "source.value is 1") 403 | 404 | assert.deepEqual(a.toJSON(), { 405 | messages: [1], 406 | errors: [], 407 | ends: [] 408 | }, "a got a message") 409 | 410 | assert.deepEqual(b.toJSON(), { 411 | messages: [1], 412 | errors: [], 413 | ends: [] 414 | }, "b got a message") 415 | 416 | disconnect(source, a) 417 | 418 | assert.deepEqual(source.toJSON(), { 419 | started: 1, 420 | stopped: 0, 421 | value: 1 422 | }, "source.value is 1") 423 | 424 | assert.deepEqual(a.toJSON(), { 425 | messages: [1], 426 | errors: [], 427 | ends: [] 428 | }, "disconnected a did not get a message") 429 | 430 | send(source, 2) 431 | 432 | assert.deepEqual(source.toJSON(), { 433 | started: 1, 434 | stopped: 0, 435 | value: 2 436 | }, "source.value is 2") 437 | 438 | assert.deepEqual(a.toJSON(), { 439 | messages: [1], 440 | errors: [], 441 | ends: [] 442 | }, "disconnected a did not get a message") 443 | 444 | assert.deepEqual(b.toJSON(), { 445 | messages: [1, 2], 446 | errors: [], 447 | ends: [] 448 | }, "b got a message") 449 | 450 | disconnect(source, b) 451 | 452 | assert.deepEqual(source.toJSON(), { 453 | started: 1, 454 | stopped: 1, 455 | value: 2 456 | }, "source was stopped") 457 | 458 | assert.deepEqual(a.toJSON(), { 459 | messages: [1], 460 | errors: [], 461 | ends: [] 462 | }, "a didn't change") 463 | 464 | assert.deepEqual(b.toJSON(), { 465 | messages: [1, 2], 466 | errors: [], 467 | ends: [] 468 | }, "b ended") 469 | } 470 | 471 | 472 | var constant = signal.constant 473 | exports["test constant"] = function(assert) { 474 | var one = constant(1) 475 | 476 | assert.equal(one.value, 1, "value is given one") 477 | 478 | var received = 0 479 | var errored = 0 480 | var ended = 0 481 | 482 | var client = new Client() 483 | connect(one, client) 484 | 485 | assert.deepEqual(client.toJSON(), { 486 | messages: [], 487 | errors: [], 488 | ends: [] 489 | }, "nothing received"); 490 | } 491 | 492 | var lift = signal.lift 493 | exports["test lift1"] = function(assert) { 494 | var order = [] 495 | var source = new Subject({ value: 0 }) 496 | 497 | assert.equal(source.value, 0, "value is 0") 498 | 499 | var xs = lift(function(x) { return x + 1 }, source) 500 | var ys = lift(function(x) { return x * 2 }, source) 501 | var zs = lift(function(y) { return y + 2 }, ys) 502 | 503 | assert.deepEqual(source.toJSON(), { 504 | started: 0, 505 | stopped: 0, 506 | value: 0 507 | }, "source isn't started yet") 508 | 509 | assert.equal(xs.value, 1, "xs.value is 1") 510 | assert.equal(ys.value, 0, "ys.value is 0") 511 | assert.equal(zs.value, 2, "zs.value is 2") 512 | 513 | var zclient = new Client({ onNext: function() { order.push("z") } }) 514 | connect(zs, zclient) 515 | 516 | assert.deepEqual(source.toJSON(), { 517 | started: 1, 518 | stopped: 0, 519 | value: 0 520 | }, "source started") 521 | 522 | send(source, 3) 523 | 524 | assert.deepEqual(source.toJSON(), { 525 | started: 1, 526 | stopped: 0, 527 | value: 3 528 | }, "source value is 3") 529 | 530 | assert.deepEqual(zclient.toJSON(), { 531 | messages: [8], 532 | errors: [], 533 | ends: [] 534 | }, "message received on the client") 535 | 536 | assert.equal(xs.value, 1, "xs.value didn't changed") 537 | assert.equal(ys.value, 6, "ys.value changed to 6") 538 | assert.equal(zs.value, 8, "zs.value changed to 8") 539 | 540 | var xclient = new Client({ onNext: function() { order.push("x") }}) 541 | connect(xs, xclient) 542 | 543 | assert.deepEqual(source.toJSON(), { 544 | started: 1, 545 | stopped: 0, 546 | value: 3 547 | }, "source didnt changed") 548 | 549 | assert.deepEqual(zclient.toJSON(), { 550 | messages: [8], 551 | errors: [], 552 | ends: [] 553 | }, "message received on the client") 554 | 555 | assert.deepEqual(xclient.toJSON(), { 556 | messages: [], 557 | errors: [], 558 | ends: [] 559 | }, "nothing happende yet") 560 | 561 | send(source, Return(4)) 562 | 563 | assert.deepEqual(source.toJSON(), { 564 | started: 1, 565 | stopped: 1, 566 | value: 4 567 | }, "source value is 4 and it's stopped") 568 | 569 | assert.deepEqual(zclient.toJSON(), { 570 | messages: [8, 10], 571 | errors: [], 572 | ends: [true] 573 | }, "z client received message & ended") 574 | 575 | assert.deepEqual(xclient.toJSON(), { 576 | messages: [5], 577 | errors: [], 578 | ends: [true] 579 | }, "x client received message & ended") 580 | 581 | assert.equal(xs.value, 5, "xs.value changed to 5") 582 | assert.equal(ys.value, 8, "ys.value changed to 8") 583 | assert.equal(zs.value, 10, "zs.value changed to 10") 584 | } 585 | 586 | exports["test liftN"] = function(assert) { 587 | var xs = new Subject({ value: 0 }) 588 | var ys = new Subject({ value: 5 }) 589 | var client = new Client(); 590 | 591 | var xys = lift(function(x, y) { 592 | return x + y 593 | }, xs, ys); 594 | 595 | assert.deepEqual(xs.toJSON(), { 596 | started: 0, 597 | stopped: 0, 598 | value: 0 599 | }, "xs has not started yet") 600 | 601 | assert.deepEqual(ys.toJSON(), { 602 | started: 0, 603 | stopped: 0, 604 | value: 5 605 | }, "ys has not started yet") 606 | 607 | assert.equal(xys.value, 5, "xys.value is 5") 608 | 609 | connect(xys, client); 610 | 611 | assert.deepEqual(xs.toJSON(), { 612 | started: 1, 613 | stopped: 0, 614 | value: 0 615 | }, "xs started") 616 | 617 | assert.deepEqual(ys.toJSON(), { 618 | started: 1, 619 | stopped: 0, 620 | value: 5 621 | }, "ys started") 622 | 623 | assert.equal(xys.value, 5, "xys.value is still 5") 624 | 625 | send(xs, 1) 626 | 627 | assert.deepEqual(xs.toJSON(), { 628 | started: 1, 629 | stopped: 0, 630 | value: 1 631 | }, "xs.value changed to 1") 632 | 633 | assert.deepEqual(ys.toJSON(), { 634 | started: 1, 635 | stopped: 0, 636 | value: 5 637 | }, "ys value didn't change") 638 | 639 | assert.equal(xys.value, 6, "xys.value changed to 6") 640 | 641 | send(ys, 6) 642 | 643 | assert.deepEqual(xs.toJSON(), { 644 | started: 1, 645 | stopped: 0, 646 | value: 1 647 | }, "xs.value is still 1") 648 | 649 | assert.deepEqual(ys.toJSON(), { 650 | started: 1, 651 | stopped: 0, 652 | value: 6 653 | }, "ys value changed to 6") 654 | 655 | assert.equal(xys.value, 7, "xys.value changed to 7") 656 | 657 | send(xs, 2) 658 | 659 | assert.deepEqual(xs.toJSON(), { 660 | started: 1, 661 | stopped: 0, 662 | value: 2 663 | }, "xs.value changed to 2") 664 | 665 | assert.deepEqual(ys.toJSON(), { 666 | started: 1, 667 | stopped: 0, 668 | value: 6 669 | }, "ys value didn't change") 670 | 671 | assert.equal(xys.value, 8, "xys.value changed to 8") 672 | 673 | send(ys, 8) 674 | 675 | assert.deepEqual(xs.toJSON(), { 676 | started: 1, 677 | stopped: 0, 678 | value: 2 679 | }, "xs.value didn't change") 680 | 681 | assert.deepEqual(ys.toJSON(), { 682 | started: 1, 683 | stopped: 0, 684 | value: 8 685 | }, "ys value changed to 8") 686 | 687 | assert.equal(xys.value, 10, "xys.value changed to 10") 688 | 689 | send(ys, Return(5)) 690 | 691 | assert.deepEqual(xs.toJSON(), { 692 | started: 1, 693 | stopped: 0, 694 | value: 2 695 | }, "xs.value didn't change") 696 | 697 | assert.deepEqual(ys.toJSON(), { 698 | started: 1, 699 | stopped: 1, 700 | value: 5 701 | }, "ys value changed to 5 & stopped") 702 | 703 | assert.equal(xys.value, 7, "xys.value changed to 7") 704 | 705 | send(xs, 5) 706 | 707 | assert.deepEqual(xs.toJSON(), { 708 | started: 1, 709 | stopped: 0, 710 | value: 5 711 | }, "xs.value changed to 5") 712 | 713 | assert.deepEqual(ys.toJSON(), { 714 | started: 1, 715 | stopped: 1, 716 | value: 5 717 | }, "ys value didn't change") 718 | 719 | assert.equal(xys.value, 10, "xys.value changed to 10") 720 | 721 | send(xs, Return(7)) 722 | 723 | assert.deepEqual(xs.toJSON(), { 724 | started: 1, 725 | stopped: 1, 726 | value: 7 727 | }, "stopped and changed xs.value to 7") 728 | 729 | assert.deepEqual(ys.toJSON(), { 730 | started: 1, 731 | stopped: 1, 732 | value: 5 733 | }, "ys value didn't change") 734 | 735 | assert.equal(xys.value, 12, "xys.value changed to 12") 736 | 737 | assert.deepEqual(client.toJSON(), { 738 | messages: [6, 7, 8, 10, 7, 10, 12], 739 | errors: [], 740 | ends: [true] 741 | }, "all messages received on the client") 742 | } 743 | 744 | 745 | 746 | var keepIf = signal.keepIf 747 | var isOdd = function(x) { return x % 2 } 748 | var isEven = function(x) { return !(x % 2) } 749 | exports["test keepIf (keep initial)"] = function(assert) { 750 | var xs = new Subject({ value: 1 }) 751 | var ys = keepIf(isOdd, 0, xs) 752 | 753 | assert.deepEqual(xs.toJSON(), { 754 | started: 0, 755 | stopped: 0, 756 | value: 1 757 | }, "xs is in initial state") 758 | 759 | assert.equal(ys.value, 1, "xs.value is kept since it's odd") 760 | 761 | var client = new Client() 762 | 763 | connect(ys, client) 764 | 765 | assert.deepEqual(xs.toJSON(), { 766 | started: 1, 767 | stopped: 0, 768 | value: 1 769 | }, "xs started") 770 | 771 | assert.deepEqual(client.toJSON(), { 772 | messages: [], 773 | errors: [], 774 | ends: [] 775 | }, "no messages received yet") 776 | 777 | assert.equal(ys.value, 1, "ys.value is 1") 778 | 779 | send(xs, 2) 780 | 781 | assert.deepEqual(xs.toJSON(), { 782 | started: 1, 783 | stopped: 0, 784 | value: 2 785 | }, "xs.value changed to 2") 786 | 787 | assert.deepEqual(client.toJSON(), { 788 | messages: [], 789 | errors: [], 790 | ends: [] 791 | }, "no messages were received") 792 | 793 | assert.equal(ys.value, 1, "ys.value is kept 1") 794 | 795 | send(xs, 3) 796 | 797 | assert.deepEqual(xs.toJSON(), { 798 | started: 1, 799 | stopped: 0, 800 | value: 3 801 | }, "xs.value changed to 3") 802 | 803 | assert.deepEqual(client.toJSON(), { 804 | messages: [3], 805 | errors: [], 806 | ends: [] 807 | }, "message was received") 808 | 809 | assert.equal(ys.value, 3, "ys.value updated to 3") 810 | 811 | send(xs, 4) 812 | 813 | assert.deepEqual(xs.toJSON(), { 814 | started: 1, 815 | stopped: 0, 816 | value: 4 817 | }, "xs.value changed to 4") 818 | 819 | assert.deepEqual(client.toJSON(), { 820 | messages: [3], 821 | errors: [], 822 | ends: [] 823 | }, "no messages were received") 824 | 825 | assert.equal(ys.value, 3, "ys.value is still 3") 826 | 827 | send(xs, new Return(5)) 828 | 829 | assert.deepEqual(xs.toJSON(), { 830 | started: 1, 831 | stopped: 1, 832 | value: 5 833 | }, "xs.value changed to 5 and stopped") 834 | 835 | assert.deepEqual(client.toJSON(), { 836 | messages: [3, 5], 837 | errors: [], 838 | ends: [true] 839 | }, "message & end was received") 840 | 841 | assert.equal(ys.value, 5, "ys.value updated to 5") 842 | } 843 | 844 | 845 | exports["test keepIf (update initial)"] = function(assert) { 846 | var xs = new Subject({ value: 1 }) 847 | var ys = keepIf(isEven, 0, xs) 848 | 849 | assert.deepEqual(xs.toJSON(), { 850 | started: 0, 851 | stopped: 0, 852 | value: 1 853 | }, "xs is in initial state") 854 | 855 | assert.equal(ys.value, 0, "ys.value updated since it's not even") 856 | 857 | var client = new Client() 858 | 859 | connect(ys, client) 860 | 861 | assert.deepEqual(xs.toJSON(), { 862 | started: 1, 863 | stopped: 0, 864 | value: 1 865 | }, "xs started") 866 | 867 | assert.deepEqual(client.toJSON(), { 868 | messages: [], 869 | errors: [], 870 | ends: [] 871 | }, "no messages received yet") 872 | 873 | send(xs, 2) 874 | 875 | assert.deepEqual(xs.toJSON(), { 876 | started: 1, 877 | stopped: 0, 878 | value: 2 879 | }, "xs.value changed to 2") 880 | 881 | assert.deepEqual(client.toJSON(), { 882 | messages: [2], 883 | errors: [], 884 | ends: [] 885 | }, "messages was received") 886 | 887 | assert.equal(ys.value, 2, "ys.value is updated to 2") 888 | 889 | send(xs, 3) 890 | 891 | assert.deepEqual(xs.toJSON(), { 892 | started: 1, 893 | stopped: 0, 894 | value: 3 895 | }, "xs.value changed to 3") 896 | 897 | assert.deepEqual(client.toJSON(), { 898 | messages: [2], 899 | errors: [], 900 | ends: [] 901 | }, "message wasn't received") 902 | 903 | assert.equal(ys.value, 2, "ys.value is 3") 904 | 905 | send(xs, 4) 906 | 907 | assert.deepEqual(xs.toJSON(), { 908 | started: 1, 909 | stopped: 0, 910 | value: 4 911 | }, "xs.value changed to 4") 912 | 913 | assert.deepEqual(client.toJSON(), { 914 | messages: [2, 4], 915 | errors: [], 916 | ends: [] 917 | }, "messages was received") 918 | 919 | assert.equal(ys.value, 4, "ys.value set to 4") 920 | 921 | send(xs, new Return(5)) 922 | 923 | assert.deepEqual(xs.toJSON(), { 924 | started: 1, 925 | stopped: 1, 926 | value: 5 927 | }, "xs.value changed to 5 and stopped") 928 | 929 | assert.deepEqual(client.toJSON(), { 930 | messages: [2, 4], 931 | errors: [], 932 | ends: [true] 933 | }, "ys ended, but nothing was received") 934 | 935 | assert.equal(ys.value, 4, "ys.value is still 4") 936 | } 937 | 938 | 939 | var dropIf = signal.dropIf 940 | exports["test dropIf (update initial)"] = function(assert) { 941 | var xs = new Subject({ value: 1 }) 942 | var ys = dropIf(isOdd, 0, xs) 943 | 944 | assert.deepEqual(xs.toJSON(), { 945 | started: 0, 946 | stopped: 0, 947 | value: 1 948 | }, "xs is in initial state") 949 | 950 | assert.equal(ys.value, 0, "ys.value updated since it's odd") 951 | 952 | var client = new Client() 953 | 954 | connect(ys, client) 955 | 956 | assert.deepEqual(xs.toJSON(), { 957 | started: 1, 958 | stopped: 0, 959 | value: 1 960 | }, "xs started") 961 | 962 | assert.deepEqual(client.toJSON(), { 963 | messages: [], 964 | errors: [], 965 | ends: [] 966 | }, "no messages received yet") 967 | 968 | send(xs, 2) 969 | 970 | assert.deepEqual(xs.toJSON(), { 971 | started: 1, 972 | stopped: 0, 973 | value: 2 974 | }, "xs.value changed to 2") 975 | 976 | assert.deepEqual(client.toJSON(), { 977 | messages: [2], 978 | errors: [], 979 | ends: [] 980 | }, "messages was received") 981 | 982 | assert.equal(ys.value, 2, "ys.value is updated to 2") 983 | 984 | send(xs, 3) 985 | 986 | assert.deepEqual(xs.toJSON(), { 987 | started: 1, 988 | stopped: 0, 989 | value: 3 990 | }, "xs.value changed to 3") 991 | 992 | assert.deepEqual(client.toJSON(), { 993 | messages: [2], 994 | errors: [], 995 | ends: [] 996 | }, "message wasn't received") 997 | 998 | assert.equal(ys.value, 2, "ys.value is 2") 999 | 1000 | send(xs, new Return(4)) 1001 | 1002 | assert.deepEqual(xs.toJSON(), { 1003 | started: 1, 1004 | stopped: 1, 1005 | value: 4 1006 | }, "xs.value changed to 4") 1007 | 1008 | assert.deepEqual(client.toJSON(), { 1009 | messages: [2, 4], 1010 | errors: [], 1011 | ends: [true] 1012 | }, "message received & end") 1013 | 1014 | assert.equal(ys.value, 4, "ys.value is 4") 1015 | } 1016 | 1017 | exports["test dropIf (keep initial)"] = function(assert) { 1018 | var xs = new Subject({ value: 1 }) 1019 | var ys = dropIf(isEven, 0, xs) 1020 | 1021 | assert.deepEqual(xs.toJSON(), { 1022 | started: 0, 1023 | stopped: 0, 1024 | value: 1 1025 | }, "xs is in initial state") 1026 | 1027 | assert.equal(ys.value, 1, "ys.value remained") 1028 | 1029 | var client = new Client() 1030 | 1031 | connect(ys, client) 1032 | 1033 | assert.deepEqual(xs.toJSON(), { 1034 | started: 1, 1035 | stopped: 0, 1036 | value: 1 1037 | }, "xs started") 1038 | 1039 | assert.deepEqual(client.toJSON(), { 1040 | messages: [], 1041 | errors: [], 1042 | ends: [] 1043 | }, "no messages received yet") 1044 | 1045 | send(xs, 2) 1046 | 1047 | assert.deepEqual(xs.toJSON(), { 1048 | started: 1, 1049 | stopped: 0, 1050 | value: 2 1051 | }, "xs.value changed to 2") 1052 | 1053 | assert.deepEqual(client.toJSON(), { 1054 | messages: [], 1055 | errors: [], 1056 | ends: [] 1057 | }, "messages wasn't received") 1058 | 1059 | assert.equal(ys.value, 1, "ys.value is stayed same") 1060 | 1061 | send(xs, 3) 1062 | 1063 | assert.deepEqual(xs.toJSON(), { 1064 | started: 1, 1065 | stopped: 0, 1066 | value: 3 1067 | }, "xs.value changed to 3") 1068 | 1069 | assert.deepEqual(client.toJSON(), { 1070 | messages: [3], 1071 | errors: [], 1072 | ends: [] 1073 | }, "message was received") 1074 | 1075 | assert.equal(ys.value, 3, "ys.value updated") 1076 | 1077 | send(xs, new Return(4)) 1078 | 1079 | assert.deepEqual(xs.toJSON(), { 1080 | started: 1, 1081 | stopped: 1, 1082 | value: 4 1083 | }, "xs.value changed") 1084 | 1085 | assert.deepEqual(client.toJSON(), { 1086 | messages: [3], 1087 | errors: [], 1088 | ends: [true] 1089 | }, "message received & end") 1090 | 1091 | assert.equal(ys.value, 3, "ys.value remained") 1092 | } 1093 | 1094 | 1095 | var foldp = signal.foldp 1096 | exports["test flodp"] = function(assert) { 1097 | var xs = new Subject({ value: 0 }) 1098 | var ys = foldp(function(p, x) { 1099 | return p + x 1100 | }, 5, xs) 1101 | 1102 | assert.deepEqual(xs.toJSON(), { 1103 | value: 0, 1104 | started: 0, 1105 | stopped: 0 1106 | }, "source is in inital state") 1107 | 1108 | assert.equal(ys.value, 5, "initial value is set") 1109 | 1110 | var client = new Client() 1111 | 1112 | connect(ys, client) 1113 | 1114 | assert.deepEqual(xs.toJSON(), { 1115 | value: 0, 1116 | started: 1, 1117 | stopped: 0 1118 | }, "source was started") 1119 | 1120 | assert.equal(ys.value, 5, "still in initial state") 1121 | 1122 | send(xs, 1) 1123 | 1124 | assert.deepEqual(xs.toJSON(), { 1125 | value: 1, 1126 | started: 1, 1127 | stopped: 0 1128 | }, "source was started") 1129 | 1130 | assert.deepEqual(client.toJSON(), { 1131 | messages: [6], 1132 | errors: [], 1133 | ends: [] 1134 | }, "message was received") 1135 | 1136 | assert.equal(ys.value, 6, "value changed") 1137 | 1138 | send(xs, 2) 1139 | 1140 | assert.deepEqual(xs.toJSON(), { 1141 | value: 2, 1142 | started: 1, 1143 | stopped: 0 1144 | }, "source was started") 1145 | 1146 | assert.deepEqual(client.toJSON(), { 1147 | messages: [6, 8], 1148 | errors: [], 1149 | ends: [] 1150 | }, "message was received") 1151 | 1152 | assert.equal(ys.value, 8, "value changed") 1153 | 1154 | send(xs, 3) 1155 | 1156 | assert.deepEqual(xs.toJSON(), { 1157 | value: 3, 1158 | started: 1, 1159 | stopped: 0 1160 | }, "source was started") 1161 | 1162 | assert.deepEqual(client.toJSON(), { 1163 | messages: [6, 8, 11], 1164 | errors: [], 1165 | ends: [] 1166 | }, "message was received") 1167 | 1168 | assert.equal(ys.value, 11, "value changed") 1169 | 1170 | send(xs, new Return(4)) 1171 | 1172 | assert.deepEqual(xs.toJSON(), { 1173 | value: 4, 1174 | started: 1, 1175 | stopped: 1 1176 | }, "source was stopped") 1177 | 1178 | assert.deepEqual(client.toJSON(), { 1179 | messages: [6, 8, 11, 15], 1180 | errors: [], 1181 | ends: [true] 1182 | }, "message & end was received") 1183 | 1184 | assert.equal(ys.value, 15, "value changed") 1185 | } 1186 | 1187 | 1188 | var merge = signal.merge 1189 | exports["test merge"] = function(assert) { 1190 | var xs = new Subject({ value: 0 }) 1191 | var ys = new Subject({ value: 5 }) 1192 | var xys = merge(xs, ys) 1193 | 1194 | assert.deepEqual(xs.toJSON(), { 1195 | value: 0, 1196 | started: 0, 1197 | stopped: 0 1198 | }, "source#1 is in inital state") 1199 | 1200 | assert.deepEqual(ys.toJSON(), { 1201 | value: 5, 1202 | started: 0, 1203 | stopped: 0 1204 | }, "source#2 is in inital state") 1205 | 1206 | assert.equal(xys.value, 0, "initial value is from source#1") 1207 | 1208 | var client = new Client() 1209 | connect(xys, client) 1210 | 1211 | assert.deepEqual(xs.toJSON(), { 1212 | value: 0, 1213 | started: 1, 1214 | stopped: 0 1215 | }, "source#1 is in started state") 1216 | 1217 | assert.deepEqual(ys.toJSON(), { 1218 | value: 5, 1219 | started: 1, 1220 | stopped: 0 1221 | }, "source#2 is in started state") 1222 | 1223 | assert.equal(xys.value, 0, "initial value is from source#1") 1224 | 1225 | send(xs, 1) 1226 | 1227 | assert.deepEqual(xs.toJSON(), { 1228 | value: 1, 1229 | started: 1, 1230 | stopped: 0 1231 | }, "source#1 value updated") 1232 | 1233 | assert.deepEqual(ys.toJSON(), { 1234 | value: 5, 1235 | started: 1, 1236 | stopped: 0 1237 | }, "source#2 value didn't change") 1238 | 1239 | assert.deepEqual(client.toJSON(), { 1240 | messages: [1], 1241 | errors: [], 1242 | ends: [] 1243 | }, "message received") 1244 | 1245 | assert.equal(xys.value, 1, "merged signal value updated") 1246 | 1247 | send(xs, 2) 1248 | 1249 | assert.deepEqual(xs.toJSON(), { 1250 | value: 2, 1251 | started: 1, 1252 | stopped: 0 1253 | }, "source#1 value updated") 1254 | 1255 | assert.deepEqual(ys.toJSON(), { 1256 | value: 5, 1257 | started: 1, 1258 | stopped: 0 1259 | }, "source#2 value didn't change") 1260 | 1261 | assert.deepEqual(client.toJSON(), { 1262 | messages: [1, 2], 1263 | errors: [], 1264 | ends: [] 1265 | }, "message received") 1266 | 1267 | assert.equal(xys.value, 2, "merged signal value updated") 1268 | 1269 | send(ys, 3) 1270 | 1271 | assert.deepEqual(xs.toJSON(), { 1272 | value: 2, 1273 | started: 1, 1274 | stopped: 0 1275 | }, "source#1 value stayed") 1276 | 1277 | assert.deepEqual(ys.toJSON(), { 1278 | value: 3, 1279 | started: 1, 1280 | stopped: 0 1281 | }, "source#2 value changed") 1282 | 1283 | assert.deepEqual(client.toJSON(), { 1284 | messages: [1, 2, 3], 1285 | errors: [], 1286 | ends: [] 1287 | }, "message received") 1288 | 1289 | assert.equal(xys.value, 3, "merged signal value updated") 1290 | 1291 | send(xs, new Return(4)) 1292 | 1293 | assert.deepEqual(xs.toJSON(), { 1294 | value: 4, 1295 | started: 1, 1296 | stopped: 1 1297 | }, "source#1 value changed & stopped") 1298 | 1299 | assert.deepEqual(ys.toJSON(), { 1300 | value: 3, 1301 | started: 1, 1302 | stopped: 0 1303 | }, "source#2 value didn't change") 1304 | 1305 | assert.deepEqual(client.toJSON(), { 1306 | messages: [1, 2, 3, 4], 1307 | errors: [], 1308 | ends: [] 1309 | }, "message received") 1310 | 1311 | assert.equal(xys.value, 4, "merged signal value updated") 1312 | 1313 | send(ys, 5) 1314 | 1315 | assert.deepEqual(xs.toJSON(), { 1316 | value: 4, 1317 | started: 1, 1318 | stopped: 1 1319 | }, "source#1 value changed & stopped") 1320 | 1321 | assert.deepEqual(ys.toJSON(), { 1322 | value: 5, 1323 | started: 1, 1324 | stopped: 0 1325 | }, "source#2 value changed") 1326 | 1327 | assert.deepEqual(client.toJSON(), { 1328 | messages: [1, 2, 3, 4, 5], 1329 | errors: [], 1330 | ends: [] 1331 | }, "message received") 1332 | 1333 | assert.equal(xys.value, 5, "merged signal value updated") 1334 | 1335 | send(ys, new Return(6)) 1336 | 1337 | assert.deepEqual(xs.toJSON(), { 1338 | value: 4, 1339 | started: 1, 1340 | stopped: 1 1341 | }, "source#1 value changed & stopped") 1342 | 1343 | assert.deepEqual(ys.toJSON(), { 1344 | value: 6, 1345 | started: 1, 1346 | stopped: 1 1347 | }, "source#2 value changed & stopped") 1348 | 1349 | assert.deepEqual(client.toJSON(), { 1350 | messages: [1, 2, 3, 4, 5, 6], 1351 | errors: [], 1352 | ends: [true] 1353 | }, "message & end received") 1354 | 1355 | assert.equal(xys.value, 6, "merged signal value updated") 1356 | } 1357 | 1358 | var merges = signal.merges 1359 | exports["test merges"] = function(assert) { 1360 | var xs = new Subject({ value: 0 }) 1361 | var ys = new Subject({ value: 5 }) 1362 | var xys = merges([xs, ys]) 1363 | 1364 | assert.deepEqual(xs.toJSON(), { 1365 | value: 0, 1366 | started: 0, 1367 | stopped: 0 1368 | }, "source#1 is in inital state") 1369 | 1370 | assert.deepEqual(ys.toJSON(), { 1371 | value: 5, 1372 | started: 0, 1373 | stopped: 0 1374 | }, "source#2 is in inital state") 1375 | 1376 | assert.equal(xys.value, 0, "initial value is from source#1") 1377 | 1378 | var client = new Client() 1379 | connect(xys, client) 1380 | 1381 | assert.deepEqual(xs.toJSON(), { 1382 | value: 0, 1383 | started: 1, 1384 | stopped: 0 1385 | }, "source#1 is in started state") 1386 | 1387 | assert.deepEqual(ys.toJSON(), { 1388 | value: 5, 1389 | started: 1, 1390 | stopped: 0 1391 | }, "source#2 is in started state") 1392 | 1393 | assert.equal(xys.value, 0, "initial value is from source#1") 1394 | 1395 | send(xs, 1) 1396 | 1397 | assert.deepEqual(xs.toJSON(), { 1398 | value: 1, 1399 | started: 1, 1400 | stopped: 0 1401 | }, "source#1 value updated") 1402 | 1403 | assert.deepEqual(ys.toJSON(), { 1404 | value: 5, 1405 | started: 1, 1406 | stopped: 0 1407 | }, "source#2 value didn't change") 1408 | 1409 | assert.deepEqual(client.toJSON(), { 1410 | messages: [1], 1411 | errors: [], 1412 | ends: [] 1413 | }, "message received") 1414 | 1415 | assert.equal(xys.value, 1, "merged signal value updated") 1416 | 1417 | send(xs, 2) 1418 | 1419 | assert.deepEqual(xs.toJSON(), { 1420 | value: 2, 1421 | started: 1, 1422 | stopped: 0 1423 | }, "source#1 value updated") 1424 | 1425 | assert.deepEqual(ys.toJSON(), { 1426 | value: 5, 1427 | started: 1, 1428 | stopped: 0 1429 | }, "source#2 value didn't change") 1430 | 1431 | assert.deepEqual(client.toJSON(), { 1432 | messages: [1, 2], 1433 | errors: [], 1434 | ends: [] 1435 | }, "message received") 1436 | 1437 | assert.equal(xys.value, 2, "merged signal value updated") 1438 | 1439 | send(ys, 3) 1440 | 1441 | assert.deepEqual(xs.toJSON(), { 1442 | value: 2, 1443 | started: 1, 1444 | stopped: 0 1445 | }, "source#1 value stayed") 1446 | 1447 | assert.deepEqual(ys.toJSON(), { 1448 | value: 3, 1449 | started: 1, 1450 | stopped: 0 1451 | }, "source#2 value changed") 1452 | 1453 | assert.deepEqual(client.toJSON(), { 1454 | messages: [1, 2, 3], 1455 | errors: [], 1456 | ends: [] 1457 | }, "message received") 1458 | 1459 | assert.equal(xys.value, 3, "merged signal value updated") 1460 | 1461 | send(xs, new Return(4)) 1462 | 1463 | assert.deepEqual(xs.toJSON(), { 1464 | value: 4, 1465 | started: 1, 1466 | stopped: 1 1467 | }, "source#1 value changed & stopped") 1468 | 1469 | assert.deepEqual(ys.toJSON(), { 1470 | value: 3, 1471 | started: 1, 1472 | stopped: 0 1473 | }, "source#2 value didn't change") 1474 | 1475 | assert.deepEqual(client.toJSON(), { 1476 | messages: [1, 2, 3, 4], 1477 | errors: [], 1478 | ends: [] 1479 | }, "message received") 1480 | 1481 | assert.equal(xys.value, 4, "merged signal value updated") 1482 | 1483 | send(ys, 5) 1484 | 1485 | assert.deepEqual(xs.toJSON(), { 1486 | value: 4, 1487 | started: 1, 1488 | stopped: 1 1489 | }, "source#1 value changed & stopped") 1490 | 1491 | assert.deepEqual(ys.toJSON(), { 1492 | value: 5, 1493 | started: 1, 1494 | stopped: 0 1495 | }, "source#2 value changed") 1496 | 1497 | assert.deepEqual(client.toJSON(), { 1498 | messages: [1, 2, 3, 4, 5], 1499 | errors: [], 1500 | ends: [] 1501 | }, "message received") 1502 | 1503 | assert.equal(xys.value, 5, "merged signal value updated") 1504 | 1505 | send(ys, new Return(6)) 1506 | 1507 | assert.deepEqual(xs.toJSON(), { 1508 | value: 4, 1509 | started: 1, 1510 | stopped: 1 1511 | }, "source#1 value changed & stopped") 1512 | 1513 | assert.deepEqual(ys.toJSON(), { 1514 | value: 6, 1515 | started: 1, 1516 | stopped: 1 1517 | }, "source#2 value changed & stopped") 1518 | 1519 | assert.deepEqual(client.toJSON(), { 1520 | messages: [1, 2, 3, 4, 5, 6], 1521 | errors: [], 1522 | ends: [true] 1523 | }, "message & end received") 1524 | 1525 | assert.equal(xys.value, 6, "merged signal value updated") 1526 | } 1527 | 1528 | var combine = signal.combine 1529 | exports["test combine"] = function(assert) { 1530 | var xs = new Subject({ value: 0 }) 1531 | var ys = new Subject({ value: 5 }) 1532 | var client = new Client(); 1533 | 1534 | var xys = combine([xs, ys]); 1535 | 1536 | assert.deepEqual(xs.toJSON(), { 1537 | started: 0, 1538 | stopped: 0, 1539 | value: 0 1540 | }, "xs has not started yet") 1541 | 1542 | assert.deepEqual(ys.toJSON(), { 1543 | started: 0, 1544 | stopped: 0, 1545 | value: 5 1546 | }, "ys has not started yet") 1547 | 1548 | assert.deepEqual(xys.value, [0, 5], "xys.value combined") 1549 | 1550 | connect(xys, client); 1551 | 1552 | assert.deepEqual(xs.toJSON(), { 1553 | started: 1, 1554 | stopped: 0, 1555 | value: 0 1556 | }, "xs started") 1557 | 1558 | assert.deepEqual(ys.toJSON(), { 1559 | started: 1, 1560 | stopped: 0, 1561 | value: 5 1562 | }, "ys started") 1563 | 1564 | assert.deepEqual(xys.value, [0, 5], "xys.value didn't change") 1565 | 1566 | send(xs, 1) 1567 | 1568 | assert.deepEqual(xs.toJSON(), { 1569 | started: 1, 1570 | stopped: 0, 1571 | value: 1 1572 | }, "xs.value changed to 1") 1573 | 1574 | assert.deepEqual(ys.toJSON(), { 1575 | started: 1, 1576 | stopped: 0, 1577 | value: 5 1578 | }, "ys value didn't change") 1579 | 1580 | assert.deepEqual(xys.value, [1, 5], "xys.value changed") 1581 | 1582 | send(ys, 6) 1583 | 1584 | assert.deepEqual(xs.toJSON(), { 1585 | started: 1, 1586 | stopped: 0, 1587 | value: 1 1588 | }, "xs.value is still 1") 1589 | 1590 | assert.deepEqual(ys.toJSON(), { 1591 | started: 1, 1592 | stopped: 0, 1593 | value: 6 1594 | }, "ys value changed to 6") 1595 | 1596 | assert.deepEqual(xys.value, [1, 6], "xys.value changed") 1597 | 1598 | send(xs, 2) 1599 | 1600 | assert.deepEqual(xs.toJSON(), { 1601 | started: 1, 1602 | stopped: 0, 1603 | value: 2 1604 | }, "xs.value changed to 2") 1605 | 1606 | assert.deepEqual(ys.toJSON(), { 1607 | started: 1, 1608 | stopped: 0, 1609 | value: 6 1610 | }, "ys value didn't change") 1611 | 1612 | assert.deepEqual(xys.value, [2, 6], "xys.value changed") 1613 | 1614 | send(ys, 8) 1615 | 1616 | assert.deepEqual(xs.toJSON(), { 1617 | started: 1, 1618 | stopped: 0, 1619 | value: 2 1620 | }, "xs.value didn't change") 1621 | 1622 | assert.deepEqual(ys.toJSON(), { 1623 | started: 1, 1624 | stopped: 0, 1625 | value: 8 1626 | }, "ys value changed to 8") 1627 | 1628 | assert.deepEqual(xys.value, [2, 8], "xys.value changed") 1629 | 1630 | send(ys, Return(5)) 1631 | 1632 | assert.deepEqual(xs.toJSON(), { 1633 | started: 1, 1634 | stopped: 0, 1635 | value: 2 1636 | }, "xs.value didn't change") 1637 | 1638 | assert.deepEqual(ys.toJSON(), { 1639 | started: 1, 1640 | stopped: 1, 1641 | value: 5 1642 | }, "ys value changed to 5 & stopped") 1643 | 1644 | assert.deepEqual(xys.value, [2, 5], "xys.value changed") 1645 | 1646 | send(xs, 5) 1647 | 1648 | assert.deepEqual(xs.toJSON(), { 1649 | started: 1, 1650 | stopped: 0, 1651 | value: 5 1652 | }, "xs.value changed to 5") 1653 | 1654 | assert.deepEqual(ys.toJSON(), { 1655 | started: 1, 1656 | stopped: 1, 1657 | value: 5 1658 | }, "ys value didn't change") 1659 | 1660 | assert.deepEqual(xys.value, [5, 5], "xys.value changed") 1661 | 1662 | send(xs, Return(7)) 1663 | 1664 | assert.deepEqual(xs.toJSON(), { 1665 | started: 1, 1666 | stopped: 1, 1667 | value: 7 1668 | }, "stopped and changed xs.value to 7") 1669 | 1670 | assert.deepEqual(ys.toJSON(), { 1671 | started: 1, 1672 | stopped: 1, 1673 | value: 5 1674 | }, "ys value didn't change") 1675 | 1676 | assert.deepEqual(xys.value, [7, 5], "xys.value changed") 1677 | 1678 | assert.deepEqual(client.toJSON(), { 1679 | messages: [ 1680 | [1, 5], 1681 | [1, 6], 1682 | [2, 6], 1683 | [2, 8], 1684 | [2, 5], 1685 | [5, 5], 1686 | [7, 5] 1687 | ], 1688 | errors: [], 1689 | ends: [true] 1690 | }, "all messages received on the client") 1691 | } 1692 | 1693 | 1694 | var count = signal.count 1695 | exports["test count"] = function(assert) { 1696 | var xs = new Subject({ value: null }) 1697 | var ys = count(xs) 1698 | 1699 | assert.deepEqual(xs.toJSON(), { 1700 | started: 0, 1701 | stopped: 0, 1702 | value: null 1703 | }, "source is in initial state") 1704 | assert.equal(ys.value, 0, "counter is at 0") 1705 | 1706 | var client = new Client() 1707 | connect(ys, client) 1708 | 1709 | assert.deepEqual(xs.toJSON(), { 1710 | started: 1, 1711 | stopped: 0, 1712 | value: null 1713 | }, "source is in initial state") 1714 | 1715 | assert.deepEqual(client.toJSON(), { 1716 | messages: [], 1717 | errors: [], 1718 | ends: [] 1719 | }, "counter hasn't recevied anything yet") 1720 | 1721 | assert.equal(ys.value, 0, "counter is at 0") 1722 | 1723 | send(xs, "a") 1724 | 1725 | assert.deepEqual(xs.toJSON(), { 1726 | started: 1, 1727 | stopped: 0, 1728 | value: "a" 1729 | }, "source vaule changed") 1730 | 1731 | assert.deepEqual(client.toJSON(), { 1732 | messages: [1], 1733 | errors: [], 1734 | ends: [] 1735 | }, "counter received message") 1736 | 1737 | assert.equal(ys.value, 1, "counter incremented") 1738 | 1739 | send(xs, "b") 1740 | 1741 | assert.deepEqual(xs.toJSON(), { 1742 | started: 1, 1743 | stopped: 0, 1744 | value: "b" 1745 | }, "source vaule changed") 1746 | 1747 | assert.deepEqual(client.toJSON(), { 1748 | messages: [1, 2], 1749 | errors: [], 1750 | ends: [] 1751 | }, "counter received message") 1752 | 1753 | assert.equal(ys.value, 2, "counter incremented") 1754 | 1755 | send(xs, "c") 1756 | 1757 | assert.deepEqual(xs.toJSON(), { 1758 | started: 1, 1759 | stopped: 0, 1760 | value: "c" 1761 | }, "source vaule changed") 1762 | 1763 | assert.deepEqual(client.toJSON(), { 1764 | messages: [1, 2, 3], 1765 | errors: [], 1766 | ends: [] 1767 | }, "counter received message") 1768 | 1769 | assert.equal(ys.value, 3, "counter incremented") 1770 | 1771 | send(xs, new Return("d")) 1772 | 1773 | assert.deepEqual(xs.toJSON(), { 1774 | started: 1, 1775 | stopped: 1, 1776 | value: "d" 1777 | }, "source vaule changed & stopped") 1778 | 1779 | assert.deepEqual(client.toJSON(), { 1780 | messages: [1, 2, 3, 4], 1781 | errors: [], 1782 | ends: [true] 1783 | }, "counter received message & end") 1784 | 1785 | assert.equal(ys.value, 4, "counter incremented") 1786 | } 1787 | 1788 | 1789 | var countIf = signal.countIf 1790 | exports["test countIf"] = function(assert) { 1791 | var isUpperCase = function(c) { 1792 | return c.toUpperCase() == c 1793 | } 1794 | 1795 | var xs = new Subject({ value: "B" }) 1796 | var ys = countIf(isUpperCase, xs) 1797 | 1798 | assert.deepEqual(xs.toJSON(), { 1799 | started: 0, 1800 | stopped: 0, 1801 | value: "B" 1802 | }, "source is in initial state") 1803 | 1804 | assert.equal(ys.value, 0, "counter value is at 0") 1805 | 1806 | var client = new Client() 1807 | connect(ys, client) 1808 | 1809 | assert.deepEqual(xs.toJSON(), { 1810 | started: 1, 1811 | stopped: 0, 1812 | value: "B" 1813 | }, "source is in start state") 1814 | 1815 | assert.deepEqual(client.toJSON(), { 1816 | messages: [], 1817 | errors: [], 1818 | ends: [] 1819 | }, "no messages received") 1820 | 1821 | assert.equal(ys.value, 0, "count is at 0") 1822 | 1823 | send(xs, "a") 1824 | 1825 | assert.deepEqual(xs.toJSON(), { 1826 | started: 1, 1827 | stopped: 0, 1828 | value: "a" 1829 | }, "source value changed") 1830 | 1831 | assert.deepEqual(client.toJSON(), { 1832 | messages: [], 1833 | errors: [], 1834 | ends: [] 1835 | }, "no messages received") 1836 | 1837 | assert.equal(ys.value, 0, "count is at 0") 1838 | 1839 | send(xs, "B") 1840 | 1841 | assert.deepEqual(xs.toJSON(), { 1842 | started: 1, 1843 | stopped: 0, 1844 | value: "B" 1845 | }, "source value changed") 1846 | 1847 | assert.deepEqual(client.toJSON(), { 1848 | messages: [1], 1849 | errors: [], 1850 | ends: [] 1851 | }, "message received") 1852 | 1853 | assert.equal(ys.value, 1, "counter incremented") 1854 | 1855 | send(xs, "C") 1856 | 1857 | assert.deepEqual(xs.toJSON(), { 1858 | started: 1, 1859 | stopped: 0, 1860 | value: "C" 1861 | }, "source value changed") 1862 | 1863 | assert.deepEqual(client.toJSON(), { 1864 | messages: [1, 2], 1865 | errors: [], 1866 | ends: [] 1867 | }, "message received") 1868 | 1869 | assert.equal(ys.value, 2, "counter incremented") 1870 | 1871 | send(xs, "d") 1872 | 1873 | assert.deepEqual(xs.toJSON(), { 1874 | started: 1, 1875 | stopped: 0, 1876 | value: "d" 1877 | }, "source value changed") 1878 | 1879 | assert.deepEqual(client.toJSON(), { 1880 | messages: [1, 2], 1881 | errors: [], 1882 | ends: [] 1883 | }, "message wasn't received") 1884 | 1885 | assert.equal(ys.value, 2, "counter didn't incremented") 1886 | 1887 | send(xs, new Return("D")) 1888 | 1889 | assert.deepEqual(xs.toJSON(), { 1890 | started: 1, 1891 | stopped: 1, 1892 | value: "D" 1893 | }, "source value changed & stopped") 1894 | 1895 | assert.deepEqual(client.toJSON(), { 1896 | messages: [1, 2, 3], 1897 | errors: [], 1898 | ends: [true] 1899 | }, "message & end received") 1900 | 1901 | assert.equal(ys.value, 3, "counter incremented") 1902 | } 1903 | 1904 | var dropRepeats = signal.dropRepeats 1905 | exports["test dropRepeats"] = function(assert) { 1906 | var xs = new Subject({ value: 0 }) 1907 | var ys = dropRepeats(xs) 1908 | 1909 | assert.deepEqual(xs.toJSON(), { 1910 | value: 0, 1911 | started: 0, 1912 | stopped: 0 1913 | }, "source in initial state") 1914 | 1915 | assert.equal(ys.value, 0, "filter is in initial state") 1916 | 1917 | var client = new Client() 1918 | connect(ys, client) 1919 | 1920 | assert.deepEqual(xs.toJSON(), { 1921 | value: 0, 1922 | started: 1, 1923 | stopped: 0 1924 | }, "source started") 1925 | 1926 | assert.deepEqual(client.toJSON(), { 1927 | messages: [], 1928 | errors: [], 1929 | ends: [] 1930 | }, "no messages recevied") 1931 | 1932 | assert.equal(ys.value, 0, "filter is in initial state") 1933 | 1934 | send(xs, 0) 1935 | 1936 | assert.deepEqual(xs.toJSON(), { 1937 | value: 0, 1938 | started: 1, 1939 | stopped: 0 1940 | }, "source started") 1941 | 1942 | assert.deepEqual(client.toJSON(), { 1943 | messages: [], 1944 | errors: [], 1945 | ends: [] 1946 | }, "no messages recevied") 1947 | 1948 | assert.equal(ys.value, 0, "filter is in initial state") 1949 | 1950 | send(xs, 1) 1951 | 1952 | assert.deepEqual(xs.toJSON(), { 1953 | value: 1, 1954 | started: 1, 1955 | stopped: 0 1956 | }, "source value changed") 1957 | 1958 | assert.deepEqual(client.toJSON(), { 1959 | messages: [1], 1960 | errors: [], 1961 | ends: [] 1962 | }, "messages recevied") 1963 | 1964 | assert.equal(ys.value, 1, "filter value changed") 1965 | 1966 | send(xs, 2) 1967 | 1968 | assert.deepEqual(xs.toJSON(), { 1969 | value: 2, 1970 | started: 1, 1971 | stopped: 0 1972 | }, "source value changed") 1973 | 1974 | assert.deepEqual(client.toJSON(), { 1975 | messages: [1, 2], 1976 | errors: [], 1977 | ends: [] 1978 | }, "messages recevied") 1979 | 1980 | assert.equal(ys.value, 2, "filter value changed") 1981 | 1982 | send(xs, 2) 1983 | 1984 | assert.deepEqual(xs.toJSON(), { 1985 | value: 2, 1986 | started: 1, 1987 | stopped: 0 1988 | }, "source value changed") 1989 | 1990 | assert.deepEqual(client.toJSON(), { 1991 | messages: [1, 2], 1992 | errors: [], 1993 | ends: [] 1994 | }, "messages isn't recevied") 1995 | 1996 | assert.equal(ys.value, 2, "filter value didn't changed") 1997 | 1998 | send(xs, 2) 1999 | 2000 | assert.deepEqual(xs.toJSON(), { 2001 | value: 2, 2002 | started: 1, 2003 | stopped: 0 2004 | }, "source value changed") 2005 | 2006 | assert.deepEqual(client.toJSON(), { 2007 | messages: [1, 2], 2008 | errors: [], 2009 | ends: [] 2010 | }, "messages isn't recevied") 2011 | 2012 | assert.equal(ys.value, 2, "filter value didn't changed") 2013 | 2014 | send(xs, 3) 2015 | 2016 | assert.deepEqual(xs.toJSON(), { 2017 | value: 3, 2018 | started: 1, 2019 | stopped: 0 2020 | }, "source value changed") 2021 | 2022 | assert.deepEqual(client.toJSON(), { 2023 | messages: [1, 2, 3], 2024 | errors: [], 2025 | ends: [] 2026 | }, "messages recevied") 2027 | 2028 | assert.equal(ys.value, 3, "filter value changed") 2029 | 2030 | send(xs, 3) 2031 | 2032 | assert.deepEqual(xs.toJSON(), { 2033 | value: 3, 2034 | started: 1, 2035 | stopped: 0 2036 | }, "source value changed") 2037 | 2038 | assert.deepEqual(client.toJSON(), { 2039 | messages: [1, 2, 3], 2040 | errors: [], 2041 | ends: [] 2042 | }, "messages isn't received") 2043 | 2044 | assert.equal(ys.value, 3, "filter value didn't change") 2045 | 2046 | send(xs, 4) 2047 | 2048 | assert.deepEqual(xs.toJSON(), { 2049 | value: 4, 2050 | started: 1, 2051 | stopped: 0 2052 | }, "source value changed") 2053 | 2054 | assert.deepEqual(client.toJSON(), { 2055 | messages: [1, 2, 3, 4], 2056 | errors: [], 2057 | ends: [] 2058 | }, "messages recevied") 2059 | 2060 | assert.equal(ys.value, 4, "filter value changed") 2061 | 2062 | send(xs, new Return(3)) 2063 | 2064 | assert.deepEqual(xs.toJSON(), { 2065 | value: 3, 2066 | started: 1, 2067 | stopped: 1 2068 | }, "source value changed") 2069 | 2070 | assert.deepEqual(client.toJSON(), { 2071 | messages: [1, 2, 3, 4, 3], 2072 | errors: [], 2073 | ends: [true] 2074 | }, "messages recevied") 2075 | 2076 | assert.equal(ys.value, 3, "filter value changed") 2077 | } 2078 | 2079 | var keepWhen = signal.keepWhen 2080 | exports["test keepWhen"] = function(assert) { 2081 | var setState = function(x) { send(state, x) } 2082 | var setX = function(x) { send(xs, x) } 2083 | 2084 | var state = new Subject({ value: false }) 2085 | var xs = new Subject({ value: 0 }) 2086 | 2087 | var ys = keepWhen(state, 10, xs) 2088 | 2089 | assert.deepEqual(state.toJSON(), { 2090 | started: 0, 2091 | stopped: 0, 2092 | value: false 2093 | }, "state in initial state") 2094 | 2095 | assert.deepEqual(xs.toJSON(), { 2096 | started: 0, 2097 | stopped: 0, 2098 | value: 0 2099 | }, "source is in initial state") 2100 | 2101 | assert.equal(ys.value, 10, "filter set in inital state") 2102 | 2103 | var client = new Client() 2104 | connect(ys, client) 2105 | setX(1) 2106 | 2107 | assert.deepEqual(state.toJSON(), { 2108 | started: 1, 2109 | stopped: 0, 2110 | value: false 2111 | }, "state in initial state") 2112 | 2113 | assert.deepEqual(xs.toJSON(), { 2114 | started: 1, 2115 | stopped: 0, 2116 | value: 1 2117 | }, "source value changed") 2118 | 2119 | assert.equal(ys.value, 10, "filter didn't change") 2120 | 2121 | setX(2) 2122 | 2123 | assert.deepEqual(state.toJSON(), { 2124 | started: 1, 2125 | stopped: 0, 2126 | value: false 2127 | }, "state in initial state") 2128 | 2129 | assert.deepEqual(xs.toJSON(), { 2130 | started: 1, 2131 | stopped: 0, 2132 | value: 2 2133 | }, "source value changed") 2134 | 2135 | assert.equal(ys.value, 10, "filter didn't change") 2136 | 2137 | setState(true) 2138 | 2139 | assert.deepEqual(state.toJSON(), { 2140 | started: 1, 2141 | stopped: 0, 2142 | value: true 2143 | }, "state in initial state") 2144 | 2145 | assert.deepEqual(xs.toJSON(), { 2146 | started: 1, 2147 | stopped: 0, 2148 | value: 2 2149 | }, "source value didn't change") 2150 | 2151 | assert.equal(ys.value, 2, "filter changed") 2152 | 2153 | 2154 | setX(3) 2155 | 2156 | assert.deepEqual(state.toJSON(), { 2157 | started: 1, 2158 | stopped: 0, 2159 | value: true 2160 | }, "state in initial state") 2161 | 2162 | assert.deepEqual(xs.toJSON(), { 2163 | started: 1, 2164 | stopped: 0, 2165 | value: 3 2166 | }, "source value changed") 2167 | 2168 | assert.equal(ys.value, 3, "filter value changed") 2169 | 2170 | setX(3) 2171 | 2172 | assert.deepEqual(state.toJSON(), { 2173 | started: 1, 2174 | stopped: 0, 2175 | value: true 2176 | }, "state is on") 2177 | 2178 | assert.deepEqual(xs.toJSON(), { 2179 | started: 1, 2180 | stopped: 0, 2181 | value: 3 2182 | }, "source value changed") 2183 | 2184 | assert.equal(ys.value, 3, "filter changed") 2185 | 2186 | setState(false) 2187 | 2188 | assert.deepEqual(state.toJSON(), { 2189 | started: 1, 2190 | stopped: 0, 2191 | value: false 2192 | }, "state is off") 2193 | 2194 | assert.deepEqual(xs.toJSON(), { 2195 | started: 1, 2196 | stopped: 0, 2197 | value: 3 2198 | }, "source value didn't change") 2199 | 2200 | assert.equal(ys.value, 3, "filter didn't change") 2201 | 2202 | setX(4) 2203 | 2204 | assert.deepEqual(state.toJSON(), { 2205 | started: 1, 2206 | stopped: 0, 2207 | value: false 2208 | }, "state is off") 2209 | 2210 | assert.deepEqual(xs.toJSON(), { 2211 | started: 1, 2212 | stopped: 0, 2213 | value: 4 2214 | }, "source value changed") 2215 | 2216 | assert.equal(ys.value, 3, "filter didn't changed") 2217 | 2218 | setState(false) 2219 | 2220 | assert.deepEqual(state.toJSON(), { 2221 | started: 1, 2222 | stopped: 0, 2223 | value: false 2224 | }, "state is off") 2225 | 2226 | assert.deepEqual(xs.toJSON(), { 2227 | started: 1, 2228 | stopped: 0, 2229 | value: 4 2230 | }, "source value changed") 2231 | 2232 | assert.equal(ys.value, 3, "filter changed") 2233 | 2234 | setState(true) 2235 | 2236 | assert.deepEqual(state.toJSON(), { 2237 | started: 1, 2238 | stopped: 0, 2239 | value: true 2240 | }, "state is on") 2241 | 2242 | assert.deepEqual(xs.toJSON(), { 2243 | started: 1, 2244 | stopped: 0, 2245 | value: 4 2246 | }, "source value didn't changed") 2247 | 2248 | assert.equal(ys.value, 4, "filter changed") 2249 | 2250 | setState(false) 2251 | 2252 | assert.deepEqual(state.toJSON(), { 2253 | started: 1, 2254 | stopped: 0, 2255 | value: false 2256 | }, "state is off") 2257 | 2258 | assert.deepEqual(xs.toJSON(), { 2259 | started: 1, 2260 | stopped: 0, 2261 | value: 4 2262 | }, "source value didn't changed") 2263 | 2264 | assert.equal(ys.value, 4, "filter didn't changed") 2265 | 2266 | setState(new Return(true)) 2267 | 2268 | assert.deepEqual(state.toJSON(), { 2269 | started: 1, 2270 | stopped: 1, 2271 | value: true 2272 | }, "state is on & stopped") 2273 | 2274 | assert.deepEqual(xs.toJSON(), { 2275 | started: 1, 2276 | stopped: 0, 2277 | value: 4 2278 | }, "source value didn't changed") 2279 | 2280 | assert.equal(ys.value, 4, "filter changed") 2281 | 2282 | assert.deepEqual(client.toJSON(), { 2283 | messages: [2, 3, 3, 4, 4], 2284 | errors: [], 2285 | ends: [] 2286 | }, "received all the messages") 2287 | 2288 | setX(5) 2289 | 2290 | assert.deepEqual(state.toJSON(), { 2291 | started: 1, 2292 | stopped: 1, 2293 | value: true 2294 | }, "state is on & stopped") 2295 | 2296 | assert.deepEqual(xs.toJSON(), { 2297 | started: 1, 2298 | stopped: 0, 2299 | value: 5 2300 | }, "source value changed") 2301 | 2302 | assert.equal(ys.value, 5, "filter changed") 2303 | 2304 | setX(Return(5)) 2305 | 2306 | assert.deepEqual(state.toJSON(), { 2307 | started: 1, 2308 | stopped: 1, 2309 | value: true 2310 | }, "state is on & stopped") 2311 | 2312 | assert.deepEqual(xs.toJSON(), { 2313 | started: 1, 2314 | stopped: 1, 2315 | value: 5 2316 | }, "source value changed & stopped") 2317 | 2318 | assert.equal(ys.value, 5, "filter changed") 2319 | 2320 | assert.deepEqual(client.toJSON(), { 2321 | messages: [2, 3, 3, 4, 4, 5, 5], 2322 | errors: [], 2323 | ends: [true] 2324 | }, "received all the messages & end") 2325 | } 2326 | 2327 | var dropWhen = signal.dropWhen 2328 | exports["test dropWhen"] = function(assert) { 2329 | var setState = function(x) { send(state, x) } 2330 | var setX = function(x) { send(xs, x) } 2331 | 2332 | var state = new Subject({ value: false }) 2333 | var xs = new Subject({ value: 0 }) 2334 | 2335 | var ys = dropWhen(state, 10, xs) 2336 | 2337 | assert.deepEqual(state.toJSON(), { 2338 | started: 0, 2339 | stopped: 0, 2340 | value: false 2341 | }, "state in initial state") 2342 | 2343 | assert.deepEqual(xs.toJSON(), { 2344 | started: 0, 2345 | stopped: 0, 2346 | value: 0 2347 | }, "source is in initial state") 2348 | 2349 | assert.equal(ys.value, 0, "filter inherts value as state is false") 2350 | 2351 | var client = new Client() 2352 | connect(ys, client) 2353 | setX(1) 2354 | 2355 | assert.deepEqual(state.toJSON(), { 2356 | started: 1, 2357 | stopped: 0, 2358 | value: false 2359 | }, "state in initial state") 2360 | 2361 | assert.deepEqual(xs.toJSON(), { 2362 | started: 1, 2363 | stopped: 0, 2364 | value: 1 2365 | }, "source value changed") 2366 | 2367 | assert.equal(ys.value, 1, "filter changed") 2368 | 2369 | setX(2) 2370 | 2371 | assert.deepEqual(state.toJSON(), { 2372 | started: 1, 2373 | stopped: 0, 2374 | value: false 2375 | }, "state in initial state") 2376 | 2377 | assert.deepEqual(xs.toJSON(), { 2378 | started: 1, 2379 | stopped: 0, 2380 | value: 2 2381 | }, "source value changed") 2382 | 2383 | assert.equal(ys.value, 2, "filter chnaged") 2384 | 2385 | setState(true) 2386 | 2387 | assert.deepEqual(state.toJSON(), { 2388 | started: 1, 2389 | stopped: 0, 2390 | value: true 2391 | }, "state signal changed") 2392 | 2393 | assert.deepEqual(xs.toJSON(), { 2394 | started: 1, 2395 | stopped: 0, 2396 | value: 2 2397 | }, "source value didn't change") 2398 | 2399 | assert.equal(ys.value, 2, "filter didn't change") 2400 | 2401 | 2402 | setX(3) 2403 | 2404 | assert.deepEqual(state.toJSON(), { 2405 | started: 1, 2406 | stopped: 0, 2407 | value: true 2408 | }, "state is true") 2409 | 2410 | assert.deepEqual(xs.toJSON(), { 2411 | started: 1, 2412 | stopped: 0, 2413 | value: 3 2414 | }, "source value changed") 2415 | 2416 | assert.equal(ys.value, 2, "filter didn't changed") 2417 | 2418 | setX(3) 2419 | 2420 | assert.deepEqual(state.toJSON(), { 2421 | started: 1, 2422 | stopped: 0, 2423 | value: true 2424 | }, "state is on") 2425 | 2426 | assert.deepEqual(xs.toJSON(), { 2427 | started: 1, 2428 | stopped: 0, 2429 | value: 3 2430 | }, "source value changed") 2431 | 2432 | assert.equal(ys.value, 2, "filter didn't change") 2433 | 2434 | setState(false) 2435 | 2436 | assert.deepEqual(state.toJSON(), { 2437 | started: 1, 2438 | stopped: 0, 2439 | value: false 2440 | }, "state is off") 2441 | 2442 | assert.deepEqual(xs.toJSON(), { 2443 | started: 1, 2444 | stopped: 0, 2445 | value: 3 2446 | }, "source value didn't change") 2447 | 2448 | assert.equal(ys.value, 3, "filter changed") 2449 | 2450 | setX(4) 2451 | 2452 | assert.deepEqual(state.toJSON(), { 2453 | started: 1, 2454 | stopped: 0, 2455 | value: false 2456 | }, "state is off") 2457 | 2458 | assert.deepEqual(xs.toJSON(), { 2459 | started: 1, 2460 | stopped: 0, 2461 | value: 4 2462 | }, "source value changed") 2463 | 2464 | assert.equal(ys.value, 4, "filter changed") 2465 | 2466 | setState(false) 2467 | 2468 | assert.deepEqual(state.toJSON(), { 2469 | started: 1, 2470 | stopped: 0, 2471 | value: false 2472 | }, "state is off") 2473 | 2474 | assert.deepEqual(xs.toJSON(), { 2475 | started: 1, 2476 | stopped: 0, 2477 | value: 4 2478 | }, "source value changed") 2479 | 2480 | assert.equal(ys.value, 4, "filter changed") 2481 | 2482 | setState(true) 2483 | 2484 | assert.deepEqual(state.toJSON(), { 2485 | started: 1, 2486 | stopped: 0, 2487 | value: true 2488 | }, "state is on") 2489 | 2490 | assert.deepEqual(xs.toJSON(), { 2491 | started: 1, 2492 | stopped: 0, 2493 | value: 4 2494 | }, "source value didn't changed") 2495 | 2496 | assert.equal(ys.value, 4, "filter didn't change") 2497 | 2498 | setState(false) 2499 | 2500 | assert.deepEqual(state.toJSON(), { 2501 | started: 1, 2502 | stopped: 0, 2503 | value: false 2504 | }, "state is off") 2505 | 2506 | assert.deepEqual(xs.toJSON(), { 2507 | started: 1, 2508 | stopped: 0, 2509 | value: 4 2510 | }, "source value didn't changed") 2511 | 2512 | assert.equal(ys.value, 4, "filter changed") 2513 | 2514 | setState(new Return(true)) 2515 | 2516 | assert.deepEqual(state.toJSON(), { 2517 | started: 1, 2518 | stopped: 1, 2519 | value: true 2520 | }, "state is on & stopped") 2521 | 2522 | assert.deepEqual(xs.toJSON(), { 2523 | started: 1, 2524 | stopped: 0, 2525 | value: 4 2526 | }, "source value didn't changed") 2527 | 2528 | assert.equal(ys.value, 4, "filter didn't changed") 2529 | 2530 | assert.deepEqual(client.toJSON(), { 2531 | messages: [1, 2, 3, 4, 4], 2532 | errors: [], 2533 | ends: [] 2534 | }, "received all the messages & end") 2535 | 2536 | setX(5) 2537 | 2538 | assert.deepEqual(state.toJSON(), { 2539 | started: 1, 2540 | stopped: 1, 2541 | value: true 2542 | }, "state is on & stopped") 2543 | 2544 | assert.deepEqual(xs.toJSON(), { 2545 | started: 1, 2546 | stopped: 0, 2547 | value: 5 2548 | }, "source value changed") 2549 | 2550 | assert.equal(ys.value, 4, "filter didn't changed") 2551 | 2552 | setX(new Return(6)) 2553 | 2554 | assert.deepEqual(state.toJSON(), { 2555 | started: 1, 2556 | stopped: 1, 2557 | value: true 2558 | }, "state is on & stopped") 2559 | 2560 | assert.deepEqual(xs.toJSON(), { 2561 | started: 1, 2562 | stopped: 1, 2563 | value: 6 2564 | }, "source value changed & source stopped") 2565 | 2566 | assert.equal(ys.value, 4, "filter didn't changed") 2567 | 2568 | assert.deepEqual(client.toJSON(), { 2569 | messages: [1, 2, 3, 4, 4], 2570 | errors: [], 2571 | ends: [true] 2572 | }, "received all the messages & end") 2573 | } 2574 | 2575 | 2576 | var sampleOn = signal.sampleOn 2577 | exports["test sampleOn"] = function(assert) { 2578 | var ticks = new Subject({ value: null }) 2579 | var xs = new Subject({ value: 0 }) 2580 | var ys = sampleOn(ticks, xs) 2581 | 2582 | assert.deepEqual(ticks.toJSON(), { 2583 | started: 0, 2584 | stopped: 0, 2585 | value: null 2586 | }, "ticks is in initial state") 2587 | 2588 | assert.deepEqual(xs.toJSON(), { 2589 | started: 0, 2590 | stopped: 0, 2591 | value: 0 2592 | }, "source is in initial state") 2593 | 2594 | assert.deepEqual(ys.value, 0, "sampler inherits initial value from source") 2595 | 2596 | var client = new Client() 2597 | 2598 | connect(ys, client) 2599 | 2600 | 2601 | assert.deepEqual(ticks.toJSON(), { 2602 | started: 1, 2603 | stopped: 0, 2604 | value: null 2605 | }, "ticks started") 2606 | 2607 | assert.deepEqual(xs.toJSON(), { 2608 | started: 1, 2609 | stopped: 0, 2610 | value: 0 2611 | }, "source started") 2612 | 2613 | assert.deepEqual(ys.value, 0, "sampler value didn't change") 2614 | 2615 | send(ticks, null) 2616 | 2617 | assert.deepEqual(client.toJSON(), { 2618 | messages: [0], 2619 | errors: [], 2620 | ends: [] 2621 | }, "client received message") 2622 | 2623 | send(xs, 2) 2624 | 2625 | assert.deepEqual(ticks.toJSON(), { 2626 | started: 1, 2627 | stopped: 0, 2628 | value: null 2629 | }, "ticks didn't change") 2630 | 2631 | assert.deepEqual(xs.toJSON(), { 2632 | started: 1, 2633 | stopped: 0, 2634 | value: 2 2635 | }, "source value changed") 2636 | 2637 | 2638 | assert.deepEqual(client.toJSON(), { 2639 | messages: [0], 2640 | errors: [], 2641 | ends: [] 2642 | }, "client didn't receive message") 2643 | 2644 | send(xs, 3) 2645 | 2646 | assert.deepEqual(ticks.toJSON(), { 2647 | started: 1, 2648 | stopped: 0, 2649 | value: null 2650 | }, "ticks didn't change") 2651 | 2652 | assert.deepEqual(xs.toJSON(), { 2653 | started: 1, 2654 | stopped: 0, 2655 | value: 3 2656 | }, "source value changed") 2657 | 2658 | 2659 | assert.deepEqual(client.toJSON(), { 2660 | messages: [0], 2661 | errors: [], 2662 | ends: [] 2663 | }, "client didn't received a message") 2664 | 2665 | send(ticks, 1) 2666 | 2667 | assert.deepEqual(ticks.toJSON(), { 2668 | started: 1, 2669 | stopped: 0, 2670 | value: 1 2671 | }, "ticks changed") 2672 | 2673 | assert.deepEqual(xs.toJSON(), { 2674 | started: 1, 2675 | stopped: 0, 2676 | value: 3 2677 | }, "source value didn't changed") 2678 | 2679 | 2680 | assert.deepEqual(client.toJSON(), { 2681 | messages: [0, 3], 2682 | errors: [], 2683 | ends: [] 2684 | }, "client received a message") 2685 | 2686 | send(xs, new Return(4)) 2687 | 2688 | assert.deepEqual(ticks.toJSON(), { 2689 | started: 1, 2690 | stopped: 0, 2691 | value: 1 2692 | }, "ticks didn't change") 2693 | 2694 | assert.deepEqual(xs.toJSON(), { 2695 | started: 1, 2696 | stopped: 1, 2697 | value: 4 2698 | }, "source value changed & stopped") 2699 | 2700 | 2701 | assert.deepEqual(client.toJSON(), { 2702 | messages: [0, 3], 2703 | errors: [], 2704 | ends: [] 2705 | }, "client didn't received message") 2706 | 2707 | send(ticks, 2) 2708 | 2709 | assert.deepEqual(ticks.toJSON(), { 2710 | started: 1, 2711 | stopped: 0, 2712 | value: 2 2713 | }, "ticks didn't change") 2714 | 2715 | assert.deepEqual(xs.toJSON(), { 2716 | started: 1, 2717 | stopped: 1, 2718 | value: 4 2719 | }, "source value didn't change") 2720 | 2721 | 2722 | assert.deepEqual(client.toJSON(), { 2723 | messages: [0, 3, 4], 2724 | errors: [], 2725 | ends: [] 2726 | }, "client received message") 2727 | 2728 | send(ticks, new Return(3)) 2729 | 2730 | assert.deepEqual(ticks.toJSON(), { 2731 | started: 1, 2732 | stopped: 1, 2733 | value: 3 2734 | }, "ticks changed") 2735 | 2736 | assert.deepEqual(xs.toJSON(), { 2737 | started: 1, 2738 | stopped: 1, 2739 | value: 4 2740 | }, "source value didn't change") 2741 | 2742 | 2743 | assert.deepEqual(client.toJSON(), { 2744 | messages: [0, 3, 4, 4], 2745 | errors: [], 2746 | ends: [true] 2747 | }, "client received message and ended") 2748 | } 2749 | -------------------------------------------------------------------------------- /test/tap.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | require("retape")(require("./index")) -------------------------------------------------------------------------------- /time.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Signal = require("./signal").Signal 4 | 5 | // Type alias to make it clearer when you are working with time values. 6 | // Using the `Time` constants instead of raw numbers is very highly recommended. 7 | var Time = Number 8 | exports.Time = Time 9 | 10 | // Units of time, making it easier to specify things like a 11 | // half-second `(500 * milliseconds)` without remembering Elm’s 12 | // underlying units of time. 13 | // millisecond : Time 14 | var millisecond = 1 15 | exports.millisecond = millisecond 16 | 17 | // second : Time 18 | var second = 1000 * millisecond 19 | exports.second = second 20 | 21 | // minute : Time 22 | var minute = 60 * second 23 | exports.minute = minute 24 | 25 | // hour : Time 26 | var hour = 60 * minute 27 | exports.hour = hour 28 | 29 | function inMilliseconds(time) { return time } 30 | exports.inMilliseconds = inMilliseconds 31 | 32 | function inSeconds(time) { return time / second } 33 | exports.inSeconds = inSeconds 34 | 35 | function inMinutes(time) { return time / minute } 36 | exports.inMinutes = inMinutes 37 | 38 | function inHours(time) { return time / hour } 39 | exports.inHours = inHours 40 | 41 | // Takes desired number of frames per second (fps). The 42 | // resulting signal gives a sequence of time deltas as 43 | // quickly as possible until it reaches the desired FPS. 44 | // A time delta is the time between the last frame and the 45 | // current frame. 46 | // int -> Signal Time 47 | var fps = function(n) { 48 | var ms = 1000 / n 49 | 50 | return new Signal(function(next) { 51 | var before = Date.now() 52 | 53 | function tick() { 54 | var now = Date.now() 55 | var diff = before - now 56 | before = now 57 | 58 | if (next(diff) !== Signal.Break) 59 | setTimeout(tick, ms) 60 | } 61 | 62 | setTimeout(tick, ms) 63 | }, 0) 64 | } 65 | exports.fps = fps 66 | 67 | // Same as the fps function, but you can turn it on and off. 68 | // Allows you to do brief animations based on user input without 69 | // major inefficiencies. The first time delta after a pause is always 70 | // zero, no matter how long the pause was. This way summing the deltas 71 | // will actually give the amount of time that the output signal has been 72 | // running. 73 | // fpsWhen : number -> Signal Bool -> Signal Time 74 | function fpsWhen(n, state) { 75 | return keepWhen(state, fps(n)) 76 | } 77 | exports.fpsWhen = fpsWhen 78 | 79 | function every(ms) { 80 | return new Signal(function(next) { 81 | function tick() { 82 | if (Signal.Break !== next(Date.now())) 83 | setTimeout(tick, ms) 84 | } 85 | 86 | setTimeout(tick, ms) 87 | }, Date.now()) 88 | } 89 | exports.every = every 90 | 91 | function fps(n) { 92 | var tickTimes = every(1000 / n) 93 | var states = foldp(function(result, time) { 94 | return [result, result[0] - time] 95 | }, [0, tickTimes.value], tickTimes) 96 | 97 | return map(field(1), states) 98 | } 99 | exports.fps = fps 100 | 101 | // Add a timestamp to any signal. Timestamps increase monotonically. 102 | // Each timestamp is related to a specfic event, so Mouse.x and Mouse.y 103 | // will always have the same timestamp because they both rely on the same 104 | // underlying event. 105 | // timestamp : Signal a -> Signal (Time, a) 106 | function timestamp(input) { 107 | return map(function(value) { 108 | return [Date.now(), value] 109 | }, input) 110 | } 111 | exports.timestamp = timestamp 112 | 113 | // Delay a signal by a certain amount of time. So (delay second Mouse.clicks) will 114 | // update one second later than any mouse click. 115 | // delay : Time -> Signal a -> Signal a 116 | function delay(ms, input) { 117 | return new Signal(function(next) { 118 | var result = void(0) 119 | spawn(function(value) { 120 | setTimeout(function() { result = next(value) }, ms) 121 | return result 122 | }, input) 123 | }, input.value) 124 | } 125 | exports.delay = delay 126 | 127 | function since(ms, input) { 128 | var on = map(True, input) 129 | var off = map(False, delay(ms, input)) 130 | return merge(on, off) 131 | } 132 | exports.since = since 133 | -------------------------------------------------------------------------------- /window.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Event = require("./event") 4 | var field = require("oops").field 5 | var map = require("./signal").map 6 | 7 | var root = document.documentElement 8 | var Tuple = Array 9 | 10 | 11 | // The current dimensions of the window (i.e. the area viewable 12 | // to the user, not including scroll bars). 13 | // dimensions : Signal [Int,Int] 14 | var dimensions = map(function() { 15 | return new Tuple(root.clientWidth, root.clientHeight) 16 | }, Event(window, "resize")) 17 | exports.dimensions = dimensions 18 | 19 | // The current width of the window. 20 | // width : Signal Int 21 | var width = map(field(0), dimensions) 22 | exports.width = width 23 | 24 | // The current height of the window. 25 | // height : Signal Int 26 | var height = map(field(1), dimensions) 27 | exports.height = height 28 | --------------------------------------------------------------------------------