├── .flowconfig
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .mocharc.json
├── .npmignore
├── .prettierrc
├── Kefir-with-bg.svg
├── Kefir.svg
├── LICENSE.txt
├── README.md
├── bacon-vs-kefir-api.md
├── bower.json
├── changelog.md
├── configs
├── docs.js
├── rollup.dev.js
├── rollup.esm.js
└── rollup.prod.js
├── deprecated-api-docs.md
├── docs-src
├── descriptions
│ ├── about-observables.pug
│ ├── active-state.pug
│ ├── convert.pug
│ ├── create.pug
│ ├── current-in-streams.pug
│ ├── emitter-object.pug
│ ├── errors.pug
│ ├── examples.pug
│ ├── interop.pug
│ ├── intro.pug
│ ├── main-methods.pug
│ ├── multiple-sources.pug
│ ├── one-source.pug
│ └── two-sources.pug
├── images
│ └── stream-and-property.png
├── includes
│ ├── mixins.pug
│ ├── side-menu.pug
│ └── stylesheet.css
└── index.pug
├── kefir.js.flow
├── package-lock.json
├── package.json
├── release.js
├── src
├── constants.js
├── dispatcher.js
├── emitter.js
├── index.js
├── interop
│ ├── from-es-observable.js
│ ├── from-promise.js
│ ├── static-land.js
│ ├── symbol.js
│ ├── to-es-observable.js
│ └── to-promise.js
├── many-sources
│ ├── abstract-pool.js
│ ├── combine.js
│ ├── concat.js
│ ├── flat-map-errors.js
│ ├── flat-map.js
│ ├── merge.js
│ ├── pool.js
│ ├── repeat.js
│ └── zip.js
├── observable.js
├── one-source
│ ├── before-end.js
│ ├── buffer-while.js
│ ├── buffer-with-count.js
│ ├── buffer-with-time-or-count.js
│ ├── changes.js
│ ├── debounce.js
│ ├── delay.js
│ ├── diff.js
│ ├── end-on-error.js
│ ├── errors-to-values.js
│ ├── filter-errors.js
│ ├── filter.js
│ ├── flatten.js
│ ├── ignore-end.js
│ ├── ignore-errors.js
│ ├── ignore-values.js
│ ├── last.js
│ ├── map-errors.js
│ ├── map.js
│ ├── scan.js
│ ├── skip-duplicates.js
│ ├── skip-end.js
│ ├── skip-while.js
│ ├── skip.js
│ ├── sliding-window.js
│ ├── take-errors.js
│ ├── take-while.js
│ ├── take.js
│ ├── throttle.js
│ ├── to-property.js
│ ├── transduce.js
│ ├── values-to-errors.js
│ └── with-handler.js
├── patterns
│ ├── one-source.js
│ ├── time-based.js
│ └── two-sources.js
├── primary
│ ├── constant-error.js
│ ├── constant.js
│ ├── from-callback.js
│ ├── from-events.js
│ ├── from-node-callback.js
│ ├── from-sub-unsub.js
│ ├── never.js
│ └── stream.js
├── property.js
├── stream.js
├── time-based
│ ├── from-poll.js
│ ├── interval.js
│ ├── later.js
│ ├── sequentially.js
│ └── with-interval.js
├── two-sources
│ ├── awaiting.js
│ ├── buffer-by.js
│ ├── buffer-while-by.js
│ ├── filter-by.js
│ ├── sampled-by.js
│ ├── skip-until-by.js
│ └── take-until-by.js
└── utils
│ ├── collections.js
│ ├── functions.js
│ ├── now.js
│ └── objects.js
└── test
├── flow
├── combine.js
├── diff.js
├── error.js
├── explicitType.js
├── filter.js
├── flatten.js
├── interop.js
├── merge.js
├── observe.js
├── pool.js
├── propType.js
├── sampledBy.js
├── scan.js
├── setName.js
├── thru.js
├── toProperty.js
├── transforms.js
├── vacuousObservables.js
└── zip.js
├── specs
├── before-end.js
├── buffer-by.js
├── buffer-while-by.js
├── buffer-while.js
├── buffer-with-count.js
├── buffer-with-time-or-count.js
├── changes.js
├── combine.js
├── concat.js
├── constant-error.js
├── constant.js
├── debounce.js
├── delay.js
├── diff.js
├── end-on-error.js
├── errors-to-values.js
├── es-observable.js
├── filter-by.js
├── filter-errors.js
├── filter.js
├── flat-map-concat.js
├── flat-map-errors.js
├── flat-map-first.js
├── flat-map-latest.js
├── flat-map-with-concurrency-limit.js
├── flat-map.js
├── flatten.js
├── from-callback.js
├── from-es-observable.js
├── from-event.js
├── from-node-callback.js
├── from-poll.js
├── from-promise.js
├── ignore-end.js
├── ignore-errors.js
├── ignore-values.js
├── interval.js
├── kefir-observable.js
├── kefir-stream.js
├── last.js
├── later.js
├── log.js
├── map-errors.js
├── map.js
├── merge.js
├── never.js
├── pool.js
├── property.js
├── repeat.js
├── sampled-by.js
├── scan.js
├── sequentially.js
├── skip-duplicates.js
├── skip-until-by.js
├── skip-while.js
├── skip.js
├── sliding-window.js
├── spy.js
├── static-land.js
├── stream.js
├── sugar.js
├── take-errors.js
├── take-until-by.js
├── take-while.js
├── take.js
├── throttle.js
├── thru.js
├── to-promise.js
├── to-property.js
├── transduce.js
├── values-to-errors.js
├── with-handler.js
├── with-interval.js
└── zip.js
└── test-helpers.js
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | .*/node_modules/.*/test/.*
3 |
4 | [include]
5 |
6 | [libs]
7 |
8 | [options]
9 | suppress_comment=\\(.\\|\n\\)*\\$ExpectError
10 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches: ['master']
6 | pull_request:
7 | branches: ['master']
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [14.x, 16.x, 18.x]
16 |
17 | steps:
18 | - name: Chekout
19 | uses: actions/checkout@v3
20 |
21 | - name: Setup Node ${{ matrix.node-version }}
22 | uses: actions/setup-node@v3
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | cache: 'npm'
26 |
27 | - name: Install dependencies
28 | run: npm ci
29 |
30 | - name: Build
31 | run: npm run build
32 |
33 | - name: Test
34 | run: npm test
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | tmp
3 | bower-packages
4 | dist
5 | test/in-browser/spec/KefirSpecs.js
6 | index.html
7 | test.html
8 | test.js
9 |
--------------------------------------------------------------------------------
/.mocharc.json:
--------------------------------------------------------------------------------
1 | {
2 | "reporter": "spec",
3 | "spec": "test/specs/*.js",
4 | "ui": "bdd"
5 | }
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | Gruntfile.coffee
2 | Kefir-with-bg.svg
3 | Kefir.svg
4 | bower-packages
5 | bower.json
6 | demos
7 | docs-src
8 | grunt-tasks
9 | index.html
10 | test
11 | .npmignore
12 | .travis.yml
13 | .jshintrc
14 | changelog.md
15 | tmp
16 | .babelrc
17 | .prettierrc
18 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": false,
3 | "bracketSpacing": false,
4 | "printWidth": 120,
5 | "singleQuote": true,
6 | "trailingComma": "es5"
7 | }
8 |
--------------------------------------------------------------------------------
/Kefir-with-bg.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/Kefir.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2015 Roman Pominov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
Kefir
2 |
3 | Kefir — is an Reactive Programming library for JavaScript
4 | inspired by [Bacon.js](https://github.com/baconjs/bacon.js)
5 | and [RxJS](https://github.com/Reactive-Extensions/RxJS)
6 | with focus on high performance and low memory usage.
7 |
8 | For docs visit [kefirjs.github.io/kefir](http://kefirjs.github.io/kefir).
9 | See also [Deprecated API docs](https://github.com/kefirjs/kefir/blob/master/deprecated-api-docs.md).
10 |
11 | [](https://github.com/kefirjs/kefir/blob/master/LICENSE.txt)
12 | [](https://www.npmjs.com/package/kefir)
13 | [](https://github.com/kefirjs/kefir/actions/workflows/node.js.yml)
14 | [](https://gitter.im/pozadi/kefir?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
15 |
16 | # Installation
17 |
18 | Kefir available as NPM and Bower packages, as well as simple files download.
19 |
20 | ### NPM
21 |
22 | ```sh
23 | npm install kefir
24 | ```
25 |
26 | ### Bower
27 |
28 | ```sh
29 | bower install kefir
30 | ```
31 |
32 | ### Download
33 |
34 | See [downloads](https://kefirjs.github.io/kefir/#downloads) section in the docs.
35 |
36 | Also available on [jsDelivr](http://www.jsdelivr.com/#!kefir).
37 |
38 | # Browsers support
39 |
40 | We don't support IE8 and below, aside from that Kefir should work in any browser.
41 |
42 | ## [Flow](https://flowtype.org/)
43 |
44 | The NPM package ships with Flow definitions. So you can do something like this if you use Flow:
45 |
46 | ```js
47 | // @flow
48 |
49 | import Kefir from 'kefir'
50 |
51 | function foo(numberStream: Kefir.Observable) {
52 | numberStream.onValue(x => {
53 | // Flow knows x is a number here
54 | })
55 | }
56 |
57 | const s = Kefir.constant(5)
58 | // Flow can automatically infer the type of values in the stream and determine
59 | // that `s` is of type Kefir.Observable here.
60 | foo(s)
61 | ```
62 |
63 | # Development
64 |
65 | ```sh
66 | npm run prettify # makes source code pretty (you must run it before a PR could be merged)
67 | npm run build-js # builds js bundlers
68 | npm run test # runs all the checks
69 | npm run test-only # runs only unit tests without other checks
70 | npm run test-debug # runs tests with a chrome inspector connected to the node process
71 | npm run build-docs # builds the documentation html file
72 | ```
73 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kefir",
3 | "version": "3.8.8",
4 | "homepage": "https://github.com/kefirjs/kefir",
5 | "authors": [
6 | "Roman Pominov "
7 | ],
8 | "description": "Reactive Programming library for JavaScript inspired by Bacon.js and RxJS with focus on high performance and low memory usage",
9 | "main": "dist/kefir.js",
10 | "moduleType": [
11 | "amd",
12 | "globals",
13 | "node"
14 | ],
15 | "keywords": [
16 | "frp",
17 | "bacon",
18 | "bacon.js",
19 | "kefir",
20 | "kefir.js",
21 | "functional",
22 | "reactive",
23 | "stream",
24 | "streams",
25 | "EventStream",
26 | "Rx",
27 | "RxJs",
28 | "Observable"
29 | ],
30 | "license": "MIT",
31 | "ignore": [
32 | "**",
33 | "!dist/**",
34 | "!LICENSE.txt"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/configs/docs.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const pug = require('pug')
3 | const pkg = require('../package.json')
4 |
5 | function escapehtml(block) {
6 | return block
7 | .replace(/&/g, '&')
8 | .replace(//g, '>')
10 | .replace(/"/g, '"')
11 | .replace(/'/g, ''')
12 | }
13 |
14 | const compiled = pug.compileFile('./docs-src/index.pug', {filters: {escapehtml}})
15 | fs.writeFileSync('./index.html', compiled({pkg}))
16 | console.log('Documentation rendered!')
17 |
--------------------------------------------------------------------------------
/configs/rollup.dev.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel'
2 | import nodeResolve from 'rollup-plugin-node-resolve'
3 | import commonjs from 'rollup-plugin-commonjs'
4 |
5 | const pkg = require('../package.json')
6 |
7 | const banner = `/*! Kefir.js v${pkg.version}
8 | * ${pkg.homepage}
9 | */
10 | `
11 |
12 | export default {
13 | moduleName: 'Kefir',
14 | entry: 'src/index.js',
15 | format: 'umd',
16 | banner: banner,
17 | plugins: [babel({presets: ['es2015-loose-rollup']}), nodeResolve({main: true}), commonjs()],
18 | dest: 'dist/kefir.js',
19 | }
20 |
--------------------------------------------------------------------------------
/configs/rollup.esm.js:
--------------------------------------------------------------------------------
1 | import base from './rollup.dev.js'
2 |
3 | export default Object.assign({}, base, {
4 | format: 'es',
5 | dest: 'dist/kefir.esm.js',
6 | })
7 |
--------------------------------------------------------------------------------
/configs/rollup.prod.js:
--------------------------------------------------------------------------------
1 | import base from './rollup.dev.js'
2 | import uglify from 'rollup-plugin-uglify'
3 |
4 | export default Object.assign({}, base, {
5 | plugins: base.plugins.concat([uglify({output: {comments: /\!\s\w/}})]),
6 | dest: 'dist/kefir.min.js',
7 | })
8 |
--------------------------------------------------------------------------------
/docs-src/descriptions/about-observables.pug:
--------------------------------------------------------------------------------
1 | h2#about-observables Intro to Streams and Properties
2 |
3 | p.
4 | Kefir supports two types of observables — streams and properties.
5 | Streams represent sequences of events made available over time. And properties represent
6 | values that change over time. The value of a property changes in response to events,
7 | which means that any stream may be easily converted to a property.
8 |
9 | figure
10 | img(src='docs-src/images/stream-and-property.png')
11 |
12 | p.
13 | In practice, the only difference between the two types of observables
14 | is that properties may have a current value. The process of subscribing
15 | to both types of observables is the same: you call the #[a(href='#on-value') onValue]
16 | method, passing a callback function to it. But when you subscribe
17 | to a property which has a current value, the callback is called
18 | immediately (synchronously) with the current value of the property.
19 |
--------------------------------------------------------------------------------
/docs-src/descriptions/active-state.pug:
--------------------------------------------------------------------------------
1 | h2#active-state Activation and deactivation of observables
2 |
3 | p.
4 | At the moment one create an observable it's not yet subscribed to its source.
5 | Observables subscribe to their sources only when they themselves get a first subscriber.
6 | In this docs this process is called #[b activation] of an observable.
7 | Also when the last subscriber is removed from an observable,
8 | the observable #[b deactivates] and unsubscribes from its source.
9 | Later it can be #[b activated] again, and so on.
10 |
11 | p.
12 | The #[i source] to which observable subscribe on #[b activation] may
13 | be an another observable (for example in #[tt .map]),
14 | several other observables (#[tt .combine]),
15 | or some external source (#[tt .fromEvents]).
16 |
17 | p.
18 | For example #[tt stream = Kefir.fromEvents(el, 'click')] won't
19 | immediately subscribe to the #[tt 'click'] event on #[tt el],
20 | it will subscribe only when the first listener will be added to the
21 | #[tt stream]. And it will automatically unsubscribe when the last listener
22 | will be removed from the #[tt stream].
23 |
24 | pre.javascript
25 | :escapehtml
26 | var stream = Kefir.fromEvents(el, 'click');
27 | // at this moment event listener to _el_ not added
28 |
29 | stream.onValue(someFn);
30 | // now 'click' listener is added to _el_
31 |
32 | stream.offValue(someFn);
33 | // and now it is removed again
34 |
35 | p.
36 | As you might already guess #[b activation] and #[b deactivation] propagates up the observables chain.
37 | For instance if one create a long chain like #[tt Kefir.fromEvents(...).map(...).filter(...).take(...)],
38 | the whole chain will be #[b inactive] until first subscriber is added,
39 | and then it will #[b activate] up to #[tt .fromEvents]. Same for #[b deactivation].
40 |
--------------------------------------------------------------------------------
/docs-src/descriptions/convert.pug:
--------------------------------------------------------------------------------
1 | h2#convert Convert observables
2 |
3 |
4 | +descr-method('to-property', 'toProperty', 'stream.toProperty([getCurrent])').
5 | Converts a stream to a property.
6 | Accepts an optional #[b getCurrent] callback, which will be called on
7 | each #[a(href='#active-state') activation] to get the current value at that moment.
8 |
9 | pre.javascript(title='example')
10 | :escapehtml
11 | var source = Kefir.sequentially(100, [1, 2, 3]);
12 | var result = source.toProperty(() => 0);
13 | result.log();
14 |
15 | pre(title='console output')
16 | :escapehtml
17 | > [sequentially.toProperty] 0
18 | > [sequentially.toProperty] 1
19 | > [sequentially.toProperty] 2
20 | > [sequentially.toProperty] 3
21 | > [sequentially.toProperty]
22 |
23 | pre(title='events in time').
24 | source: ----1----2----3X
25 | result: 0----1----2----3X
26 | div
27 |
28 |
29 |
30 | +descr-method('changes', 'changes', 'property.changes()').
31 | Converts a property to a stream.
32 | If the property has a current value (or error), it will be ignored
33 | (subscribers of the stream won't get it).
34 |
35 | p.
36 | If you call #[b changes] on a stream, it will return a new stream with
37 | #[a(href='#current-in-streams') current values/errors] removed.
38 |
39 | pre.javascript(title='example')
40 | :escapehtml
41 | var source = Kefir.sequentially(100, [1, 2, 3]);
42 | var property = source.toProperty(() => 0);
43 | var result = property.changes();
44 | result.log();
45 |
46 | pre(title='console output')
47 | :escapehtml
48 | > [sequentially.toProperty.changes] 1
49 | > [sequentially.toProperty.changes] 2
50 | > [sequentially.toProperty.changes] 3
51 | > [sequentially.toProperty.changes]
52 |
53 | pre(title='events in time').
54 | property: 0----1----2----3X
55 | result: ----1----2----3X
56 | div
57 |
--------------------------------------------------------------------------------
/docs-src/descriptions/emitter-object.pug:
--------------------------------------------------------------------------------
1 | h2#emitter-object Emitter
2 |
3 | p.
4 | Emitter is an object that has four methods for emitting events.
5 | It is used in several places in Kefir as a proxy to emit events from some observable.
6 |
7 | ul
8 | li #[tt emitter.value(value)] emits a value in the connected observable
9 | li #[tt emitter.error(error)] emits a error in the connected observable
10 | li #[tt emitter.end()] ends the connected observable
11 | li #[tt emitter.event(event)] emits an event (object with same format as in #[a(href='#on-any') onAny] method) in the connected observable
12 |
13 | pre.javascript(title='example')
14 | :escapehtml
15 | emitter.value(123);
16 | emitter.error('Oh, snap!');
17 | emitter.end();
18 | div
19 |
20 | p.
21 | All #[b emitter] methods are bound to their context,
22 | and can be passed as callbacks safely without binding:
23 |
24 | pre.javascript
25 | :escapehtml
26 | // instead of this
27 | el.addEventListener('click', emitter.value.bind(emitter));
28 |
29 | // you can do just this
30 | el.addEventListener('click', emitter.value);
31 | div
32 |
33 | p.
34 | There also exist legacy aliases to #[b emitter] methods:
35 |
36 | ul
37 | li #[tt emitter.emit === emitter.value]
38 | li #[tt emitter.emitEvent === emitter.event]
39 |
40 |
--------------------------------------------------------------------------------
/docs-src/descriptions/errors.pug:
--------------------------------------------------------------------------------
1 | h2#about-errors Errors
2 |
3 | p.
4 | Kefir supports an additional channel to pass data through observables — errors.
5 | Unlike values, errors normally just flow through the observable chain
6 | without any transformation. Consider this example:
7 |
8 | pre.javascript(title='example')
9 | :escapehtml
10 | var foo = Kefir.stream(emitter => {
11 | emitter.emit(0);
12 | emitter.emit(2);
13 | emitter.error(-1);
14 | emitter.emit(3);
15 | emitter.end();
16 | });
17 |
18 | var bar = foo.map(x => x + 2).filter(x => x > 3);
19 | bar.log();
20 |
21 |
22 | pre(title='console output')
23 | :escapehtml
24 | > [stream.map.filter] 4
25 | > [stream.map.filter] -1
26 | > [stream.map.filter] 5
27 | > [stream.map.filter]
28 |
29 | pre(title='events in time')
30 | :escapehtml
31 | foo: ---0---2---e---3---X
32 | -1
33 |
34 | bar: -------4---e---5---X
35 | -1
36 | div
37 |
38 | p.
39 | As you can see values are being mapped and filtered,
40 | but errors just flow unchanged.
41 | Also notice that observable doesn't end on an error by default,
42 | but you can use the #[a(href='#take-errors') takeErrors] method to make it happen.
43 | Consider a slight change to the above example:
44 |
45 | pre.javascript(title='example')
46 | :escapehtml
47 | var foo = Kefir.stream(emitter => {
48 | emitter.emit(0);
49 | emitter.emit(2);
50 | emitter.error(-1);
51 | emitter.emit(3);
52 | emitter.end();
53 | });
54 |
55 | var bar = foo.map(x => x + 2).filter(x => x > 3);
56 | bar.takeErrors(1).log();
57 |
58 | pre(title='console output')
59 | :escapehtml
60 | > [stream.map.filter.takeErrors] 4
61 | > [stream.map.filter.takeErrors] -1
62 | > [stream.map.filter.takeErrors]
63 |
64 | pre(title='events in time')
65 | :escapehtml
66 | foo: ---0---2---e---3---X
67 | -1
68 |
69 | bar: -------4---eX
70 | -1
71 | div
72 |
--------------------------------------------------------------------------------
/docs-src/descriptions/intro.pug:
--------------------------------------------------------------------------------
1 | h1
2 | |
Kefir.js
3 | = " "
4 | sup #{pkg.version} (changelog)
5 |
6 | p.
7 | Kefir — is a Reactive Programming
8 | library for JavaScript inspired by
9 | Bacon.js
10 | and
11 | RxJS,
12 | with focus on high performance and low memory usage.
13 |
14 |
15 | p.
16 | Kefir has a
17 |
18 |
GitHub repository, where you can
19 | send pull requests,
20 | report bugs,
21 | and have fun reading
22 | source code.
23 |
24 | p.
25 | See also
26 | #[a(href='https://github.com/kefirjs/kefir/blob/master/deprecated-api-docs.md') Deprecated API docs].
27 |
28 |
29 | h2#installation Installation
30 |
31 | p.
32 | Kefir is available as an NPM and a Bower package, as well as a simple file download.
33 |
34 | h3#npm NPM
35 |
36 | pre.
37 | npm install kefir
38 |
39 | h3#bower Bower
40 |
41 | pre.
42 | bower install kefir
43 |
44 | h3#downloads Downloads (#{pkg.version})
45 |
46 | table
47 | tr
48 | th(rowspan="1" valign="top") For Development
49 | td: a(href='dist/kefir.js') kefir.js
50 | td: i ~ 100 KB
51 | tr
52 | th(rowspan="1" valign="top") For Production
53 | td: a(href='dist/kefir.min.js') kefir.min.js
54 | td: i ~ 10 KB (when gzipped)
55 |
56 | tr
57 | td.rule(colspan="3")
58 | tr
59 | th All files
60 | td: a(href='https://github.com/kefirjs/kefir/archive/' + pkg.version + '.zip') kefir-#{pkg.version}.zip
61 | td ... including documentation, tests, source maps, etc.
62 |
63 | p.
64 | Kefir also available on #[a(href='http://www.jsdelivr.com/#!kefir') jsDelivr].
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/docs-src/images/stream-and-property.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kefirjs/kefir/d1c51fd56bb1997f66faed98c58f1701791ed1ed/docs-src/images/stream-and-property.png
--------------------------------------------------------------------------------
/docs-src/includes/mixins.pug:
--------------------------------------------------------------------------------
1 | mixin method(anchor, name)
2 | li
3 | a(href=anchor)= name
4 |
5 |
6 | mixin descr-method(anchor, name, code, alias, shorthand)
7 | p(id=anchor)
8 | a.header(href='#' + anchor) #{name}
9 | code #{code}
10 | if alias
11 | code.alias #{alias}
12 | if shorthand
13 | code.shorthand #{shorthand}
14 | if !alias && !shorthand
15 | br
16 | block
17 |
--------------------------------------------------------------------------------
/docs-src/index.pug:
--------------------------------------------------------------------------------
1 | include includes/mixins
2 |
3 | doctype html
4 | html(lang="en")
5 | head
6 | meta(charset='utf-8')
7 | meta(http-equiv='X-UA-Compatible', content='IE=edge')
8 | title Kefir.js — fast and light Reactive Programming library for JavaScript inspired by Bacon.js and RxJS
9 | meta(name='viewport', content='width=device-width, initial-scale=1')
10 | style(type='text/css')
11 | include includes/stylesheet.css
12 |
13 | script(src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js')
14 | script.
15 | window.module = {}
16 | script(src='https://unpkg.com/transducers-js@0.4.174')
17 | script.
18 | window.transducers = window.module.exports
19 | delete window.module
20 | script(src='dist/kefir.js')
21 |
22 | link(rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/styles/tomorrow.min.css")
23 | link(rel="stylesheet" href="https://code.cdn.mozilla.net/fonts/fira.css")
24 | style(type='text/css').
25 | .hljs {
26 | background: transparent;
27 | padding: 2px 2px 2px 12px;
28 | }
29 | script(src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.6/highlight.min.js")
30 |
31 |
32 | body
33 |
34 | .sidebar
35 | include includes/side-menu
36 |
37 | .container
38 | include descriptions/intro
39 | include descriptions/examples
40 | include descriptions/about-observables
41 | include descriptions/create
42 | include descriptions/convert
43 | include descriptions/main-methods
44 | include descriptions/one-source
45 | include descriptions/multiple-sources
46 | include descriptions/two-sources
47 | include descriptions/interop
48 | include descriptions/active-state
49 | include descriptions/emitter-object
50 | include descriptions/errors
51 | include descriptions/current-in-streams
52 |
53 | script.
54 | $.getJSON('https://api.github.com/emojis', function(emojis){
55 | $('[data-emoji]').each(function(){
56 | var name = $(this).data('emoji');
57 | $(this).attr({
58 | src: emojis[name],
59 | title: ':' + name + ':',
60 | alt: ':' + name + ':'
61 | });
62 | });
63 | });
64 |
65 | var $window = $(window);
66 | var $document = $(document);
67 |
68 | function getScrollLeft() {
69 | return $window.scrollLeft();
70 | }
71 |
72 | function getWinWidth() {
73 | return $window.width();
74 | }
75 |
76 | function getDocWidth() {
77 | return $document.width();
78 | }
79 |
80 | var scrolls = Kefir.fromEvents($window, 'scroll');
81 | var resizes = Kefir.fromEvents($window, 'resize');
82 |
83 | var scrollLeft = scrolls.map(getScrollLeft).toProperty(getScrollLeft).skipDuplicates();
84 | var winWidth = resizes.map(getWinWidth).toProperty(getWinWidth).skipDuplicates();
85 | var docWidth = resizes.map(getDocWidth).toProperty(getDocWidth).skipDuplicates();
86 |
87 | Kefir.combine([scrollLeft, winWidth, docWidth], function(scrollLeft, winWidth, docWidth) {
88 | return -Math.min(docWidth - winWidth, Math.max(0, scrollLeft));
89 | }).skipDuplicates().onValue(function(x) {$('.sidebar').css('left', x)});
90 |
91 | $('pre.javascript').each(function(_, block) {
92 | hljs.highlightBlock(block);
93 | });
94 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kefir",
3 | "version": "3.8.8",
4 | "description": "Reactive Programming library for JavaScript inspired by Bacon.js and RxJS with focus on high performance and low memory usage",
5 | "main": "dist/kefir.js",
6 | "module": "dist/kefir.esm.js",
7 | "scripts": {
8 | "prettify": "prettier --write '{src,configs,test}/**/*.js' '*.js'",
9 | "prettier-check": "prettier --list-different '{src,configs,test}/**/*.js' '*.js'",
10 | "build-js": "rollup -c ./configs/rollup.dev.js && rollup -c ./configs/rollup.esm.js && rollup -c ./configs/rollup.prod.js && cp kefir.js.flow dist/",
11 | "build-docs": "node configs/docs.js",
12 | "deploy-docs": "git checkout gh-pages && git merge master && npm run build && git add . && git commit -m 'build all' && git push && git checkout master",
13 | "clean": "rm -r dist index.html || true",
14 | "build": "npm run clean && npm run build-js && npm run build-docs",
15 | "test": "npm run prettier-check && rollup -c ./configs/rollup.dev.js && mocha && flow check",
16 | "test-only": "rollup -c ./configs/rollup.dev.js && mocha",
17 | "test-debug": "rollup -c ./configs/rollup.dev.js && mocha --inspect-brk"
18 | },
19 | "keywords": [
20 | "frp",
21 | "bacon",
22 | "bacon.js",
23 | "kefir",
24 | "kefir.js",
25 | "functional",
26 | "reactive",
27 | "stream",
28 | "streams",
29 | "EventStream",
30 | "Rx",
31 | "RxJs",
32 | "Observable"
33 | ],
34 | "author": "Roman Pominov ",
35 | "homepage": "https://github.com/kefirjs/kefir",
36 | "repository": {
37 | "type": "git",
38 | "url": "http://github.com/kefirjs/kefir.git"
39 | },
40 | "license": "MIT",
41 | "devDependencies": {
42 | "babel-preset-es2015-loose-rollup": "^7.0.0",
43 | "chai": "^4.2.0",
44 | "chai-kefir": "^2.0.5",
45 | "flow-bin": "^0.100.0",
46 | "inquirer": "^6.3.1",
47 | "mocha": "^10.0.0",
48 | "prettier": "^1.18.2",
49 | "pug": "^3.0.2",
50 | "rollup": "^0.41.6",
51 | "rollup-plugin-babel": "^2.7.1",
52 | "rollup-plugin-commonjs": "^8.4.1",
53 | "rollup-plugin-node-resolve": "^3.4.0",
54 | "rollup-plugin-uglify": "^1.0.2",
55 | "rxjs": "^6.5.2",
56 | "semver": "^6.1.1",
57 | "sinon": "^7.3.2",
58 | "sinon-chai": "^3.3.0",
59 | "symbol-observable": "^1.2.0",
60 | "transducers-js": "^0.4.174",
61 | "transducers.js": "^0.3.2",
62 | "zen-observable": "^0.8.14"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/release.js:
--------------------------------------------------------------------------------
1 | var child_process = require('child_process')
2 | var inquirer = require('inquirer')
3 | var semver = require('semver')
4 | var fs = require('fs')
5 |
6 | var pkg = require('./package.json')
7 | var bower = require('./bower.json')
8 |
9 | console.log('')
10 | console.log('Wellcome to Kefir release utility!')
11 | console.log('----------------------------------------------------------------')
12 | console.log('')
13 |
14 | var questions = [
15 | {
16 | type: 'list',
17 | name: 'version',
18 | message: 'Which version will it be? (current is ' + pkg.version + ')',
19 | choices: [
20 | semver.inc(pkg.version, 'patch'),
21 | semver.inc(pkg.version, 'minor'),
22 | semver.inc(pkg.version, 'major'),
23 | semver.inc(pkg.version, 'premajor', 'rc'),
24 | ],
25 | },
26 | {
27 | type: 'list',
28 | name: 'dryRun',
29 | message: 'Do you want to release, or just see what would happen if you do?',
30 | choices: ['Just see', 'Release!'],
31 | },
32 | ]
33 |
34 | inquirer.prompt(questions).then(function(answers) {
35 | var newVerison = answers.version
36 | var dryRun = answers.dryRun === 'Just see'
37 |
38 | bower.version = pkg.version = newVerison
39 |
40 | console.log('')
41 | if (dryRun) {
42 | console.log('Ok, here is what would happen:')
43 | } else {
44 | console.log('Doing actual release:')
45 | }
46 | console.log('')
47 |
48 | run('npm test', dryRun) &&
49 | bumpVersion('package.json', pkg, dryRun) &&
50 | bumpVersion('bower.json', bower, dryRun) &&
51 | run('npm run build', dryRun) &&
52 | run('git add .', dryRun) &&
53 | run('git add -f dist', dryRun) &&
54 | run('git add -f index.html', dryRun) &&
55 | run('git commit -m "' + newVerison + '"', dryRun) &&
56 | run('git push', dryRun) &&
57 | run('git tag -a ' + newVerison + ' -m "v' + newVerison + '"', dryRun) &&
58 | run('git push origin --tags', dryRun) &&
59 | run('npm publish', dryRun) &&
60 | run('git rm -r dist', dryRun) &&
61 | run('git rm index.html', dryRun) &&
62 | run('git commit -m "cleanup repository after release"', dryRun) &&
63 | run('git push', dryRun)
64 | })
65 |
66 | function bumpVersion(fileName, obj, dry) {
67 | console.log('Bumping version in `' + fileName + '` to ' + obj.version)
68 | if (!dry) {
69 | try {
70 | fs.writeFileSync(fileName, JSON.stringify(obj, null, ' ') + '\n')
71 | console.log('... ok')
72 | } catch (e) {
73 | console.error(e)
74 | return false
75 | }
76 | }
77 | return true
78 | }
79 |
80 | function run(cmd, dry) {
81 | console.log('Running `' + cmd + '`')
82 | if (!dry) {
83 | var proc = child_process.spawnSync(cmd, {
84 | stdio: 'inherit',
85 | shell: true,
86 | })
87 | if (proc.status === 0) {
88 | console.log('... ok')
89 | } else {
90 | console.error('... fail!')
91 | return false
92 | }
93 | }
94 | return true
95 | }
96 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const NOTHING = ['']
2 | export const END = 'end'
3 | export const VALUE = 'value'
4 | export const ERROR = 'error'
5 | export const ANY = 'any'
6 |
--------------------------------------------------------------------------------
/src/dispatcher.js:
--------------------------------------------------------------------------------
1 | import {extend} from './utils/objects'
2 | import {VALUE, ERROR, ANY} from './constants'
3 | import {concat, findByPred, remove, contains} from './utils/collections'
4 |
5 | function callSubscriber(type, fn, event) {
6 | if (type === ANY) {
7 | fn(event)
8 | } else if (type === event.type) {
9 | if (type === VALUE || type === ERROR) {
10 | fn(event.value)
11 | } else {
12 | fn()
13 | }
14 | }
15 | }
16 |
17 | function Dispatcher() {
18 | this._items = []
19 | this._spies = []
20 | this._inLoop = 0
21 | this._removedItems = null
22 | }
23 |
24 | extend(Dispatcher.prototype, {
25 | add(type, fn) {
26 | this._items = concat(this._items, [{type, fn}])
27 | return this._items.length
28 | },
29 |
30 | remove(type, fn) {
31 | const index = findByPred(this._items, x => x.type === type && x.fn === fn)
32 |
33 | // if we're currently in a notification loop,
34 | // remember this subscriber was removed
35 | if (this._inLoop !== 0 && index !== -1) {
36 | if (this._removedItems === null) {
37 | this._removedItems = []
38 | }
39 | this._removedItems.push(this._items[index])
40 | }
41 |
42 | this._items = remove(this._items, index)
43 | return this._items.length
44 | },
45 |
46 | addSpy(fn) {
47 | this._spies = concat(this._spies, [fn])
48 | return this._spies.length
49 | },
50 |
51 | // Because spies are only ever a function that perform logging as
52 | // their only side effect, we don't need the same complicated
53 | // removal logic like in remove()
54 | removeSpy(fn) {
55 | this._spies = remove(this._spies, this._spies.indexOf(fn))
56 | return this._spies.length
57 | },
58 |
59 | dispatch(event) {
60 | this._inLoop++
61 | for (let i = 0, spies = this._spies; this._spies !== null && i < spies.length; i++) {
62 | spies[i](event)
63 | }
64 |
65 | for (let i = 0, items = this._items; i < items.length; i++) {
66 | // cleanup was called
67 | if (this._items === null) {
68 | break
69 | }
70 |
71 | // this subscriber was removed
72 | if (this._removedItems !== null && contains(this._removedItems, items[i])) {
73 | continue
74 | }
75 |
76 | callSubscriber(items[i].type, items[i].fn, event)
77 | }
78 | this._inLoop--
79 | if (this._inLoop === 0) {
80 | this._removedItems = null
81 | }
82 | },
83 |
84 | cleanup() {
85 | this._items = null
86 | this._spies = null
87 | },
88 | })
89 |
90 | export {callSubscriber, Dispatcher}
91 |
--------------------------------------------------------------------------------
/src/emitter.js:
--------------------------------------------------------------------------------
1 | export default function emitter(obs) {
2 | function value(x) {
3 | obs._emitValue(x)
4 | return obs._active
5 | }
6 |
7 | function error(x) {
8 | obs._emitError(x)
9 | return obs._active
10 | }
11 |
12 | function end() {
13 | obs._emitEnd()
14 | return obs._active
15 | }
16 |
17 | function event(e) {
18 | obs._emit(e.type, e.value)
19 | return obs._active
20 | }
21 |
22 | return {
23 | value,
24 | error,
25 | end,
26 | event,
27 |
28 | // legacy
29 | emit: value,
30 | emitEvent: event,
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/interop/from-es-observable.js:
--------------------------------------------------------------------------------
1 | import stream from '../primary/stream'
2 | import $$observable from './symbol'
3 |
4 | export default function fromESObservable(_observable) {
5 | const observable = _observable[$$observable] ? _observable[$$observable]() : _observable
6 | return stream(function(emitter) {
7 | const unsub = observable.subscribe({
8 | error(error) {
9 | emitter.error(error)
10 | emitter.end()
11 | },
12 | next(value) {
13 | emitter.emit(value)
14 | },
15 | complete() {
16 | emitter.end()
17 | },
18 | })
19 |
20 | if (unsub.unsubscribe) {
21 | return function() {
22 | unsub.unsubscribe()
23 | }
24 | } else {
25 | return unsub
26 | }
27 | }).setName('fromESObservable')
28 | }
29 |
--------------------------------------------------------------------------------
/src/interop/from-promise.js:
--------------------------------------------------------------------------------
1 | import stream from '../primary/stream'
2 | import toProperty from '../one-source/to-property'
3 |
4 | export default function fromPromise(promise) {
5 | let called = false
6 |
7 | let result = stream(function(emitter) {
8 | if (!called) {
9 | let onValue = function(x) {
10 | emitter.emit(x)
11 | emitter.end()
12 | }
13 | let onError = function(x) {
14 | emitter.error(x)
15 | emitter.end()
16 | }
17 | let _promise = promise.then(onValue, onError)
18 |
19 | // prevent libraries like 'Q' or 'when' from swallowing exceptions
20 | if (_promise && typeof _promise.done === 'function') {
21 | _promise.done()
22 | }
23 |
24 | called = true
25 | }
26 | })
27 |
28 | return toProperty(result, null).setName('fromPromise')
29 | }
30 |
--------------------------------------------------------------------------------
/src/interop/static-land.js:
--------------------------------------------------------------------------------
1 | import constant from '../primary/constant'
2 | import never from '../primary/never'
3 | import combine from '../many-sources/combine'
4 |
5 | const Observable = {
6 | empty() {
7 | return never()
8 | },
9 |
10 | // Monoid based on merge() seems more useful than one based on concat().
11 | concat(a, b) {
12 | return a.merge(b)
13 | },
14 |
15 | of(x) {
16 | return constant(x)
17 | },
18 |
19 | map(fn, obs) {
20 | return obs.map(fn)
21 | },
22 |
23 | bimap(fnErr, fnVal, obs) {
24 | return obs.mapErrors(fnErr).map(fnVal)
25 | },
26 |
27 | // This ap strictly speaking incompatible with chain. If we derive ap from chain we get
28 | // different (not very useful) behavior. But spec requires that if method can be derived
29 | // it must have the same behavior as hand-written method. We intentionally violate the spec
30 | // in hope that it won't cause many troubles in practice. And in return we have more useful type.
31 | ap(obsFn, obsVal) {
32 | return combine([obsFn, obsVal], (fn, val) => fn(val))
33 | },
34 |
35 | chain(fn, obs) {
36 | return obs.flatMap(fn)
37 | },
38 | }
39 |
40 | export {Observable}
41 |
--------------------------------------------------------------------------------
/src/interop/symbol.js:
--------------------------------------------------------------------------------
1 | // this file contains some hot JS modules systems stuff
2 |
3 | import symbol from 'symbol-observable'
4 | export default symbol.default ? symbol.default : symbol
5 |
--------------------------------------------------------------------------------
/src/interop/to-es-observable.js:
--------------------------------------------------------------------------------
1 | import {extend} from '../utils/objects'
2 | import {VALUE, ERROR, END} from '../constants'
3 | import $$observable from './symbol'
4 |
5 | function ESObservable(observable) {
6 | this._observable = observable.takeErrors(1)
7 | }
8 |
9 | extend(ESObservable.prototype, {
10 | subscribe(observerOrOnNext, onError, onComplete) {
11 | const observer =
12 | typeof observerOrOnNext === 'function'
13 | ? {next: observerOrOnNext, error: onError, complete: onComplete}
14 | : observerOrOnNext
15 |
16 | const fn = event => {
17 | if (event.type === END) {
18 | closed = true
19 | }
20 |
21 | if (event.type === VALUE && observer.next) {
22 | observer.next(event.value)
23 | } else if (event.type === ERROR && observer.error) {
24 | observer.error(event.value)
25 | } else if (event.type === END && observer.complete) {
26 | observer.complete(event.value)
27 | }
28 | }
29 |
30 | this._observable.onAny(fn)
31 | let closed = false
32 |
33 | const subscription = {
34 | unsubscribe: () => {
35 | closed = true
36 | this._observable.offAny(fn)
37 | },
38 | get closed() {
39 | return closed
40 | },
41 | }
42 | return subscription
43 | },
44 | })
45 |
46 | // Need to assign directly b/c Symbols aren't enumerable.
47 | ESObservable.prototype[$$observable] = function() {
48 | return this
49 | }
50 |
51 | export default function toESObservable() {
52 | return new ESObservable(this)
53 | }
54 |
--------------------------------------------------------------------------------
/src/interop/to-promise.js:
--------------------------------------------------------------------------------
1 | import {VALUE, END} from '../constants'
2 |
3 | function getGlodalPromise() {
4 | if (typeof Promise === 'function') {
5 | return Promise
6 | } else {
7 | throw new Error("There isn't default Promise, use shim or parameter")
8 | }
9 | }
10 |
11 | export default function(obs, Promise = getGlodalPromise()) {
12 | let last = null
13 | return new Promise((resolve, reject) => {
14 | obs.onAny(event => {
15 | if (event.type === END && last !== null) {
16 | ;(last.type === VALUE ? resolve : reject)(last.value)
17 | last = null
18 | } else {
19 | last = event
20 | }
21 | })
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/src/many-sources/concat.js:
--------------------------------------------------------------------------------
1 | import repeat from './repeat'
2 |
3 | export default function concat(observables) {
4 | return repeat(function(index) {
5 | return observables.length > index ? observables[index] : false
6 | }).setName('concat')
7 | }
8 |
--------------------------------------------------------------------------------
/src/many-sources/flat-map-errors.js:
--------------------------------------------------------------------------------
1 | import {VALUE, ERROR, END} from '../constants'
2 | import {inherit} from '../utils/objects'
3 | import FlatMap from './flat-map'
4 |
5 | function FlatMapErrors(source, fn) {
6 | FlatMap.call(this, source, fn)
7 | }
8 |
9 | inherit(FlatMapErrors, FlatMap, {
10 | // Same as in FlatMap, only VALUE/ERROR flipped
11 | _handleMain(event) {
12 | if (event.type === ERROR) {
13 | let sameCurr = this._activating && this._hadNoEvSinceDeact && this._lastCurrent === event.value
14 | if (!sameCurr) {
15 | this._add(event.value, this._fn)
16 | }
17 | this._lastCurrent = event.value
18 | this._hadNoEvSinceDeact = false
19 | }
20 |
21 | if (event.type === VALUE) {
22 | this._emitValue(event.value)
23 | }
24 |
25 | if (event.type === END) {
26 | if (this._isEmpty()) {
27 | this._emitEnd()
28 | } else {
29 | this._mainEnded = true
30 | }
31 | }
32 | },
33 | })
34 |
35 | export default FlatMapErrors
36 |
--------------------------------------------------------------------------------
/src/many-sources/flat-map.js:
--------------------------------------------------------------------------------
1 | import {VALUE, ERROR, END} from '../constants'
2 | import {inherit} from '../utils/objects'
3 | import AbstractPool from './abstract-pool'
4 |
5 | function FlatMap(source, fn, options) {
6 | AbstractPool.call(this, options)
7 | this._source = source
8 | this._fn = fn
9 | this._mainEnded = false
10 | this._lastCurrent = null
11 | this._$handleMain = event => this._handleMain(event)
12 | }
13 |
14 | inherit(FlatMap, AbstractPool, {
15 | _onActivation() {
16 | AbstractPool.prototype._onActivation.call(this)
17 | if (this._active) {
18 | this._source.onAny(this._$handleMain)
19 | }
20 | },
21 |
22 | _onDeactivation() {
23 | AbstractPool.prototype._onDeactivation.call(this)
24 | this._source.offAny(this._$handleMain)
25 | this._hadNoEvSinceDeact = true
26 | },
27 |
28 | _handleMain(event) {
29 | if (event.type === VALUE) {
30 | // Is latest value before deactivation survived, and now is 'current' on this activation?
31 | // We don't want to handle such values, to prevent to constantly add
32 | // same observale on each activation/deactivation when our main source
33 | // is a `Kefir.conatant()` for example.
34 | let sameCurr = this._activating && this._hadNoEvSinceDeact && this._lastCurrent === event.value
35 | if (!sameCurr) {
36 | this._add(event.value, this._fn)
37 | }
38 | this._lastCurrent = event.value
39 | this._hadNoEvSinceDeact = false
40 | }
41 |
42 | if (event.type === ERROR) {
43 | this._emitError(event.value)
44 | }
45 |
46 | if (event.type === END) {
47 | if (this._isEmpty()) {
48 | this._emitEnd()
49 | } else {
50 | this._mainEnded = true
51 | }
52 | }
53 | },
54 |
55 | _onEmpty() {
56 | if (this._mainEnded) {
57 | this._emitEnd()
58 | }
59 | },
60 |
61 | _clear() {
62 | AbstractPool.prototype._clear.call(this)
63 | this._source = null
64 | this._lastCurrent = null
65 | this._$handleMain = null
66 | },
67 | })
68 |
69 | export default FlatMap
70 |
--------------------------------------------------------------------------------
/src/many-sources/merge.js:
--------------------------------------------------------------------------------
1 | import {inherit} from '../utils/objects'
2 | import AbstractPool from './abstract-pool'
3 | import never from '../primary/never'
4 |
5 | function Merge(sources) {
6 | AbstractPool.call(this)
7 | this._addAll(sources)
8 | this._initialised = true
9 | }
10 |
11 | inherit(Merge, AbstractPool, {
12 | _name: 'merge',
13 |
14 | _onEmpty() {
15 | if (this._initialised) {
16 | this._emitEnd()
17 | }
18 | },
19 | })
20 |
21 | export default function merge(observables) {
22 | return observables.length === 0 ? never() : new Merge(observables)
23 | }
24 |
--------------------------------------------------------------------------------
/src/many-sources/pool.js:
--------------------------------------------------------------------------------
1 | import {inherit} from '../utils/objects'
2 | import AbstractPool from './abstract-pool'
3 |
4 | function Pool() {
5 | AbstractPool.call(this)
6 | }
7 |
8 | inherit(Pool, AbstractPool, {
9 | _name: 'pool',
10 |
11 | plug(obs) {
12 | this._add(obs)
13 | return this
14 | },
15 |
16 | unplug(obs) {
17 | this._remove(obs)
18 | return this
19 | },
20 | })
21 |
22 | export default Pool
23 |
--------------------------------------------------------------------------------
/src/many-sources/repeat.js:
--------------------------------------------------------------------------------
1 | import {inherit} from '../utils/objects'
2 | import Stream from '../stream'
3 | import {END} from '../constants'
4 |
5 | function S(generator) {
6 | Stream.call(this)
7 | this._generator = generator
8 | this._source = null
9 | this._inLoop = false
10 | this._iteration = 0
11 | this._$handleAny = event => this._handleAny(event)
12 | }
13 |
14 | inherit(S, Stream, {
15 | _name: 'repeat',
16 |
17 | _handleAny(event) {
18 | if (event.type === END) {
19 | this._source = null
20 | this._getSource()
21 | } else {
22 | this._emit(event.type, event.value)
23 | }
24 | },
25 |
26 | _getSource() {
27 | if (!this._inLoop) {
28 | this._inLoop = true
29 | const generator = this._generator
30 | while (this._source === null && this._alive && this._active) {
31 | this._source = generator(this._iteration++)
32 | if (this._source) {
33 | this._source.onAny(this._$handleAny)
34 | } else {
35 | this._emitEnd()
36 | }
37 | }
38 | this._inLoop = false
39 | }
40 | },
41 |
42 | _onActivation() {
43 | if (this._source) {
44 | this._source.onAny(this._$handleAny)
45 | } else {
46 | this._getSource()
47 | }
48 | },
49 |
50 | _onDeactivation() {
51 | if (this._source) {
52 | this._source.offAny(this._$handleAny)
53 | }
54 | },
55 |
56 | _clear() {
57 | Stream.prototype._clear.call(this)
58 | this._generator = null
59 | this._source = null
60 | this._$handleAny = null
61 | },
62 | })
63 |
64 | export default function(generator) {
65 | return new S(generator)
66 | }
67 |
--------------------------------------------------------------------------------
/src/many-sources/zip.js:
--------------------------------------------------------------------------------
1 | import Stream from '../stream'
2 | import {VALUE, ERROR, END} from '../constants'
3 | import {inherit} from '../utils/objects'
4 | import {map, cloneArray} from '../utils/collections'
5 | import {spread} from '../utils/functions'
6 | import never from '../primary/never'
7 |
8 | const isArray =
9 | Array.isArray ||
10 | function(xs) {
11 | return Object.prototype.toString.call(xs) === '[object Array]'
12 | }
13 |
14 | function Zip(sources, combinator) {
15 | Stream.call(this)
16 |
17 | this._buffers = map(sources, source => (isArray(source) ? cloneArray(source) : []))
18 | this._sources = map(sources, source => (isArray(source) ? never() : source))
19 |
20 | this._combinator = combinator ? spread(combinator, this._sources.length) : x => x
21 | this._aliveCount = 0
22 |
23 | this._$handlers = []
24 | for (let i = 0; i < this._sources.length; i++) {
25 | this._$handlers.push(event => this._handleAny(i, event))
26 | }
27 | }
28 |
29 | inherit(Zip, Stream, {
30 | _name: 'zip',
31 |
32 | _onActivation() {
33 | // if all sources are arrays
34 | while (this._isFull()) {
35 | this._emit()
36 | }
37 |
38 | const length = this._sources.length
39 | this._aliveCount = length
40 | for (let i = 0; i < length && this._active; i++) {
41 | this._sources[i].onAny(this._$handlers[i])
42 | }
43 | },
44 |
45 | _onDeactivation() {
46 | for (let i = 0; i < this._sources.length; i++) {
47 | this._sources[i].offAny(this._$handlers[i])
48 | }
49 | },
50 |
51 | _emit() {
52 | let values = new Array(this._buffers.length)
53 | for (let i = 0; i < this._buffers.length; i++) {
54 | values[i] = this._buffers[i].shift()
55 | }
56 | const combinator = this._combinator
57 | this._emitValue(combinator(values))
58 | },
59 |
60 | _isFull() {
61 | for (let i = 0; i < this._buffers.length; i++) {
62 | if (this._buffers[i].length === 0) {
63 | return false
64 | }
65 | }
66 | return true
67 | },
68 |
69 | _handleAny(i, event) {
70 | if (event.type === VALUE) {
71 | this._buffers[i].push(event.value)
72 | if (this._isFull()) {
73 | this._emit()
74 | }
75 | }
76 | if (event.type === ERROR) {
77 | this._emitError(event.value)
78 | }
79 | if (event.type === END) {
80 | this._aliveCount--
81 | if (this._aliveCount === 0) {
82 | this._emitEnd()
83 | }
84 | }
85 | },
86 |
87 | _clear() {
88 | Stream.prototype._clear.call(this)
89 | this._sources = null
90 | this._buffers = null
91 | this._combinator = null
92 | this._$handlers = null
93 | },
94 | })
95 |
96 | export default function zip(observables, combinator /* Function | falsey */) {
97 | return observables.length === 0 ? never() : new Zip(observables, combinator)
98 | }
99 |
--------------------------------------------------------------------------------
/src/one-source/before-end.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({fn}) {
5 | this._fn = fn
6 | },
7 |
8 | _free() {
9 | this._fn = null
10 | },
11 |
12 | _handleEnd() {
13 | const fn = this._fn
14 | this._emitValue(fn())
15 | this._emitEnd()
16 | },
17 | }
18 |
19 | const S = createStream('beforeEnd', mixin)
20 | const P = createProperty('beforeEnd', mixin)
21 |
22 | export default function beforeEnd(obs, fn) {
23 | return new (obs._ofSameType(S, P))(obs, {fn})
24 | }
25 |
--------------------------------------------------------------------------------
/src/one-source/buffer-while.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({fn, flushOnEnd}) {
5 | this._fn = fn
6 | this._flushOnEnd = flushOnEnd
7 | this._buff = []
8 | },
9 |
10 | _free() {
11 | this._buff = null
12 | },
13 |
14 | _flush() {
15 | if (this._buff !== null && this._buff.length !== 0) {
16 | this._emitValue(this._buff)
17 | this._buff = []
18 | }
19 | },
20 |
21 | _handleValue(x) {
22 | this._buff.push(x)
23 | const fn = this._fn
24 | if (!fn(x)) {
25 | this._flush()
26 | }
27 | },
28 |
29 | _handleEnd() {
30 | if (this._flushOnEnd) {
31 | this._flush()
32 | }
33 | this._emitEnd()
34 | },
35 | }
36 |
37 | const S = createStream('bufferWhile', mixin)
38 | const P = createProperty('bufferWhile', mixin)
39 |
40 | const id = x => x
41 |
42 | export default function bufferWhile(obs, fn, {flushOnEnd = true} = {}) {
43 | return new (obs._ofSameType(S, P))(obs, {fn: fn || id, flushOnEnd})
44 | }
45 |
--------------------------------------------------------------------------------
/src/one-source/buffer-with-count.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({count, flushOnEnd}) {
5 | this._count = count
6 | this._flushOnEnd = flushOnEnd
7 | this._buff = []
8 | },
9 |
10 | _free() {
11 | this._buff = null
12 | },
13 |
14 | _flush() {
15 | if (this._buff !== null && this._buff.length !== 0) {
16 | this._emitValue(this._buff)
17 | this._buff = []
18 | }
19 | },
20 |
21 | _handleValue(x) {
22 | this._buff.push(x)
23 | if (this._buff.length >= this._count) {
24 | this._flush()
25 | }
26 | },
27 |
28 | _handleEnd() {
29 | if (this._flushOnEnd) {
30 | this._flush()
31 | }
32 | this._emitEnd()
33 | },
34 | }
35 |
36 | const S = createStream('bufferWithCount', mixin)
37 | const P = createProperty('bufferWithCount', mixin)
38 |
39 | export default function bufferWhile(obs, count, {flushOnEnd = true} = {}) {
40 | return new (obs._ofSameType(S, P))(obs, {count: count, flushOnEnd})
41 | }
42 |
--------------------------------------------------------------------------------
/src/one-source/buffer-with-time-or-count.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({wait, count, flushOnEnd}) {
5 | this._wait = wait
6 | this._count = count
7 | this._flushOnEnd = flushOnEnd
8 | this._intervalId = null
9 | this._$onTick = () => this._flush()
10 | this._buff = []
11 | },
12 |
13 | _free() {
14 | this._$onTick = null
15 | this._buff = null
16 | },
17 |
18 | _flush() {
19 | if (this._buff !== null) {
20 | this._emitValue(this._buff)
21 | this._buff = []
22 | }
23 | },
24 |
25 | _handleValue(x) {
26 | this._buff.push(x)
27 | if (this._buff.length >= this._count) {
28 | clearInterval(this._intervalId)
29 | this._flush()
30 | this._intervalId = setInterval(this._$onTick, this._wait)
31 | }
32 | },
33 |
34 | _handleEnd() {
35 | if (this._flushOnEnd && this._buff.length !== 0) {
36 | this._flush()
37 | }
38 | this._emitEnd()
39 | },
40 |
41 | _onActivation() {
42 | this._intervalId = setInterval(this._$onTick, this._wait)
43 | this._source.onAny(this._$handleAny) // copied from patterns/one-source
44 | },
45 |
46 | _onDeactivation() {
47 | if (this._intervalId !== null) {
48 | clearInterval(this._intervalId)
49 | this._intervalId = null
50 | }
51 | this._source.offAny(this._$handleAny) // copied from patterns/one-source
52 | },
53 | }
54 |
55 | const S = createStream('bufferWithTimeOrCount', mixin)
56 | const P = createProperty('bufferWithTimeOrCount', mixin)
57 |
58 | export default function bufferWithTimeOrCount(obs, wait, count, {flushOnEnd = true} = {}) {
59 | return new (obs._ofSameType(S, P))(obs, {wait, count, flushOnEnd})
60 | }
61 |
--------------------------------------------------------------------------------
/src/one-source/changes.js:
--------------------------------------------------------------------------------
1 | import {createStream} from '../patterns/one-source'
2 |
3 | const S = createStream('changes', {
4 | _handleValue(x) {
5 | if (!this._activating) {
6 | this._emitValue(x)
7 | }
8 | },
9 |
10 | _handleError(x) {
11 | if (!this._activating) {
12 | this._emitError(x)
13 | }
14 | },
15 | })
16 |
17 | export default function changes(obs) {
18 | return new S(obs)
19 | }
20 |
--------------------------------------------------------------------------------
/src/one-source/debounce.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 | import now from '../utils/now'
3 |
4 | const mixin = {
5 | _init({wait, immediate}) {
6 | this._wait = Math.max(0, wait)
7 | this._immediate = immediate
8 | this._lastAttempt = 0
9 | this._timeoutId = null
10 | this._laterValue = null
11 | this._endLater = false
12 | this._$later = () => this._later()
13 | },
14 |
15 | _free() {
16 | this._laterValue = null
17 | this._$later = null
18 | },
19 |
20 | _handleValue(x) {
21 | if (this._activating) {
22 | this._emitValue(x)
23 | } else {
24 | this._lastAttempt = now()
25 | if (this._immediate && !this._timeoutId) {
26 | this._emitValue(x)
27 | }
28 | if (!this._timeoutId) {
29 | this._timeoutId = setTimeout(this._$later, this._wait)
30 | }
31 | if (!this._immediate) {
32 | this._laterValue = x
33 | }
34 | }
35 | },
36 |
37 | _handleEnd() {
38 | if (this._activating) {
39 | this._emitEnd()
40 | } else {
41 | if (this._timeoutId && !this._immediate) {
42 | this._endLater = true
43 | } else {
44 | this._emitEnd()
45 | }
46 | }
47 | },
48 |
49 | _later() {
50 | let last = now() - this._lastAttempt
51 | if (last < this._wait && last >= 0) {
52 | this._timeoutId = setTimeout(this._$later, this._wait - last)
53 | } else {
54 | this._timeoutId = null
55 | if (!this._immediate) {
56 | const _laterValue = this._laterValue
57 | this._laterValue = null
58 | this._emitValue(_laterValue)
59 | }
60 | if (this._endLater) {
61 | this._emitEnd()
62 | }
63 | }
64 | },
65 | }
66 |
67 | const S = createStream('debounce', mixin)
68 | const P = createProperty('debounce', mixin)
69 |
70 | export default function debounce(obs, wait, {immediate = false} = {}) {
71 | return new (obs._ofSameType(S, P))(obs, {wait, immediate})
72 | }
73 |
--------------------------------------------------------------------------------
/src/one-source/delay.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const END_MARKER = {}
4 |
5 | const mixin = {
6 | _init({wait}) {
7 | this._wait = Math.max(0, wait)
8 | this._buff = []
9 | this._$shiftBuff = () => {
10 | const value = this._buff.shift()
11 | if (value === END_MARKER) {
12 | this._emitEnd()
13 | } else {
14 | this._emitValue(value)
15 | }
16 | }
17 | },
18 |
19 | _free() {
20 | this._buff = null
21 | this._$shiftBuff = null
22 | },
23 |
24 | _handleValue(x) {
25 | if (this._activating) {
26 | this._emitValue(x)
27 | } else {
28 | this._buff.push(x)
29 | setTimeout(this._$shiftBuff, this._wait)
30 | }
31 | },
32 |
33 | _handleEnd() {
34 | if (this._activating) {
35 | this._emitEnd()
36 | } else {
37 | this._buff.push(END_MARKER)
38 | setTimeout(this._$shiftBuff, this._wait)
39 | }
40 | },
41 | }
42 |
43 | const S = createStream('delay', mixin)
44 | const P = createProperty('delay', mixin)
45 |
46 | export default function delay(obs, wait) {
47 | return new (obs._ofSameType(S, P))(obs, {wait})
48 | }
49 |
--------------------------------------------------------------------------------
/src/one-source/diff.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 | import {NOTHING} from '../constants'
3 |
4 | const mixin = {
5 | _init({fn, seed}) {
6 | this._fn = fn
7 | this._prev = seed
8 | },
9 |
10 | _free() {
11 | this._prev = null
12 | this._fn = null
13 | },
14 |
15 | _handleValue(x) {
16 | if (this._prev !== NOTHING) {
17 | const fn = this._fn
18 | this._emitValue(fn(this._prev, x))
19 | }
20 | this._prev = x
21 | },
22 | }
23 |
24 | const S = createStream('diff', mixin)
25 | const P = createProperty('diff', mixin)
26 |
27 | function defaultFn(a, b) {
28 | return [a, b]
29 | }
30 |
31 | export default function diff(obs, fn, seed = NOTHING) {
32 | return new (obs._ofSameType(S, P))(obs, {fn: fn || defaultFn, seed})
33 | }
34 |
--------------------------------------------------------------------------------
/src/one-source/end-on-error.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _handleError(x) {
5 | this._emitError(x)
6 | this._emitEnd()
7 | },
8 | }
9 |
10 | const S = createStream('endOnError', mixin)
11 | const P = createProperty('endOnError', mixin)
12 |
13 | export default function endOnError(obs) {
14 | return new (obs._ofSameType(S, P))(obs)
15 | }
16 |
--------------------------------------------------------------------------------
/src/one-source/errors-to-values.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({fn}) {
5 | this._fn = fn
6 | },
7 |
8 | _free() {
9 | this._fn = null
10 | },
11 |
12 | _handleError(x) {
13 | const fn = this._fn
14 | const result = fn(x)
15 | if (result.convert) {
16 | this._emitValue(result.value)
17 | } else {
18 | this._emitError(x)
19 | }
20 | },
21 | }
22 |
23 | const S = createStream('errorsToValues', mixin)
24 | const P = createProperty('errorsToValues', mixin)
25 |
26 | const defFn = x => ({convert: true, value: x})
27 |
28 | export default function errorsToValues(obs, fn = defFn) {
29 | return new (obs._ofSameType(S, P))(obs, {fn})
30 | }
31 |
--------------------------------------------------------------------------------
/src/one-source/filter-errors.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({fn}) {
5 | this._fn = fn
6 | },
7 |
8 | _free() {
9 | this._fn = null
10 | },
11 |
12 | _handleError(x) {
13 | const fn = this._fn
14 | if (fn(x)) {
15 | this._emitError(x)
16 | }
17 | },
18 | }
19 |
20 | const S = createStream('filterErrors', mixin)
21 | const P = createProperty('filterErrors', mixin)
22 |
23 | const id = x => x
24 |
25 | export default function filterErrors(obs, fn = id) {
26 | return new (obs._ofSameType(S, P))(obs, {fn})
27 | }
28 |
--------------------------------------------------------------------------------
/src/one-source/filter.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({fn}) {
5 | this._fn = fn
6 | },
7 |
8 | _free() {
9 | this._fn = null
10 | },
11 |
12 | _handleValue(x) {
13 | const fn = this._fn
14 | if (fn(x)) {
15 | this._emitValue(x)
16 | }
17 | },
18 | }
19 |
20 | const S = createStream('filter', mixin)
21 | const P = createProperty('filter', mixin)
22 |
23 | const id = x => x
24 |
25 | export default function filter(obs, fn = id) {
26 | return new (obs._ofSameType(S, P))(obs, {fn})
27 | }
28 |
--------------------------------------------------------------------------------
/src/one-source/flatten.js:
--------------------------------------------------------------------------------
1 | import {createStream} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({fn}) {
5 | this._fn = fn
6 | },
7 |
8 | _free() {
9 | this._fn = null
10 | },
11 |
12 | _handleValue(x) {
13 | const fn = this._fn
14 | const xs = fn(x)
15 | for (let i = 0; i < xs.length; i++) {
16 | this._emitValue(xs[i])
17 | }
18 | },
19 | }
20 |
21 | const S = createStream('flatten', mixin)
22 |
23 | const id = x => x
24 |
25 | export default function flatten(obs, fn = id) {
26 | return new S(obs, {fn})
27 | }
28 |
--------------------------------------------------------------------------------
/src/one-source/ignore-end.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _handleEnd() {},
5 | }
6 |
7 | const S = createStream('ignoreEnd', mixin)
8 | const P = createProperty('ignoreEnd', mixin)
9 |
10 | export default function ignoreEnd(obs) {
11 | return new (obs._ofSameType(S, P))(obs)
12 | }
13 |
--------------------------------------------------------------------------------
/src/one-source/ignore-errors.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _handleError() {},
5 | }
6 |
7 | const S = createStream('ignoreErrors', mixin)
8 | const P = createProperty('ignoreErrors', mixin)
9 |
10 | export default function ignoreErrors(obs) {
11 | return new (obs._ofSameType(S, P))(obs)
12 | }
13 |
--------------------------------------------------------------------------------
/src/one-source/ignore-values.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _handleValue() {},
5 | }
6 |
7 | const S = createStream('ignoreValues', mixin)
8 | const P = createProperty('ignoreValues', mixin)
9 |
10 | export default function ignoreValues(obs) {
11 | return new (obs._ofSameType(S, P))(obs)
12 | }
13 |
--------------------------------------------------------------------------------
/src/one-source/last.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 | import {NOTHING} from '../constants'
3 |
4 | const mixin = {
5 | _init() {
6 | this._lastValue = NOTHING
7 | },
8 |
9 | _free() {
10 | this._lastValue = null
11 | },
12 |
13 | _handleValue(x) {
14 | this._lastValue = x
15 | },
16 |
17 | _handleEnd() {
18 | if (this._lastValue !== NOTHING) {
19 | this._emitValue(this._lastValue)
20 | }
21 | this._emitEnd()
22 | },
23 | }
24 |
25 | const S = createStream('last', mixin)
26 | const P = createProperty('last', mixin)
27 |
28 | export default function last(obs) {
29 | return new (obs._ofSameType(S, P))(obs)
30 | }
31 |
--------------------------------------------------------------------------------
/src/one-source/map-errors.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({fn}) {
5 | this._fn = fn
6 | },
7 |
8 | _free() {
9 | this._fn = null
10 | },
11 |
12 | _handleError(x) {
13 | const fn = this._fn
14 | this._emitError(fn(x))
15 | },
16 | }
17 |
18 | const S = createStream('mapErrors', mixin)
19 | const P = createProperty('mapErrors', mixin)
20 |
21 | const id = x => x
22 |
23 | export default function mapErrors(obs, fn = id) {
24 | return new (obs._ofSameType(S, P))(obs, {fn})
25 | }
26 |
--------------------------------------------------------------------------------
/src/one-source/map.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({fn}) {
5 | this._fn = fn
6 | },
7 |
8 | _free() {
9 | this._fn = null
10 | },
11 |
12 | _handleValue(x) {
13 | const fn = this._fn
14 | this._emitValue(fn(x))
15 | },
16 | }
17 |
18 | const S = createStream('map', mixin)
19 | const P = createProperty('map', mixin)
20 |
21 | const id = x => x
22 |
23 | export default function map(obs, fn = id) {
24 | return new (obs._ofSameType(S, P))(obs, {fn})
25 | }
26 |
--------------------------------------------------------------------------------
/src/one-source/scan.js:
--------------------------------------------------------------------------------
1 | import {createProperty} from '../patterns/one-source'
2 | import {ERROR, NOTHING} from '../constants'
3 |
4 | const P = createProperty('scan', {
5 | _init({fn, seed}) {
6 | this._fn = fn
7 | this._seed = seed
8 | if (seed !== NOTHING) {
9 | this._emitValue(seed)
10 | }
11 | },
12 |
13 | _free() {
14 | this._fn = null
15 | this._seed = null
16 | },
17 |
18 | _handleValue(x) {
19 | const fn = this._fn
20 | if (this._currentEvent === null || this._currentEvent.type === ERROR) {
21 | this._emitValue(this._seed === NOTHING ? x : fn(this._seed, x))
22 | } else {
23 | this._emitValue(fn(this._currentEvent.value, x))
24 | }
25 | },
26 | })
27 |
28 | export default function scan(obs, fn, seed = NOTHING) {
29 | return new P(obs, {fn, seed})
30 | }
31 |
--------------------------------------------------------------------------------
/src/one-source/skip-duplicates.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 | import {NOTHING} from '../constants'
3 |
4 | const mixin = {
5 | _init({fn}) {
6 | this._fn = fn
7 | this._prev = NOTHING
8 | },
9 |
10 | _free() {
11 | this._fn = null
12 | this._prev = null
13 | },
14 |
15 | _handleValue(x) {
16 | const fn = this._fn
17 | if (this._prev === NOTHING || !fn(this._prev, x)) {
18 | this._prev = x
19 | this._emitValue(x)
20 | }
21 | },
22 | }
23 |
24 | const S = createStream('skipDuplicates', mixin)
25 | const P = createProperty('skipDuplicates', mixin)
26 |
27 | const eq = (a, b) => a === b
28 |
29 | export default function skipDuplicates(obs, fn = eq) {
30 | return new (obs._ofSameType(S, P))(obs, {fn})
31 | }
32 |
--------------------------------------------------------------------------------
/src/one-source/skip-end.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _handleEnd() {},
5 | }
6 |
7 | const S = createStream('skipEnd', mixin)
8 | const P = createProperty('skipEnd', mixin)
9 |
10 | export default function skipEnd(obs) {
11 | return new (obs._ofSameType(S, P))(obs)
12 | }
13 |
--------------------------------------------------------------------------------
/src/one-source/skip-while.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({fn}) {
5 | this._fn = fn
6 | },
7 |
8 | _free() {
9 | this._fn = null
10 | },
11 |
12 | _handleValue(x) {
13 | const fn = this._fn
14 | if (this._fn !== null && !fn(x)) {
15 | this._fn = null
16 | }
17 | if (this._fn === null) {
18 | this._emitValue(x)
19 | }
20 | },
21 | }
22 |
23 | const S = createStream('skipWhile', mixin)
24 | const P = createProperty('skipWhile', mixin)
25 |
26 | const id = x => x
27 |
28 | export default function skipWhile(obs, fn = id) {
29 | return new (obs._ofSameType(S, P))(obs, {fn})
30 | }
31 |
--------------------------------------------------------------------------------
/src/one-source/skip.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({n}) {
5 | this._n = Math.max(0, n)
6 | },
7 |
8 | _handleValue(x) {
9 | if (this._n === 0) {
10 | this._emitValue(x)
11 | } else {
12 | this._n--
13 | }
14 | },
15 | }
16 |
17 | const S = createStream('skip', mixin)
18 | const P = createProperty('skip', mixin)
19 |
20 | export default function skip(obs, n) {
21 | return new (obs._ofSameType(S, P))(obs, {n})
22 | }
23 |
--------------------------------------------------------------------------------
/src/one-source/sliding-window.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 | import {slide} from '../utils/collections'
3 |
4 | const mixin = {
5 | _init({min, max}) {
6 | this._max = max
7 | this._min = min
8 | this._buff = []
9 | },
10 |
11 | _free() {
12 | this._buff = null
13 | },
14 |
15 | _handleValue(x) {
16 | this._buff = slide(this._buff, x, this._max)
17 | if (this._buff.length >= this._min) {
18 | this._emitValue(this._buff)
19 | }
20 | },
21 | }
22 |
23 | const S = createStream('slidingWindow', mixin)
24 | const P = createProperty('slidingWindow', mixin)
25 |
26 | export default function slidingWindow(obs, max, min = 0) {
27 | return new (obs._ofSameType(S, P))(obs, {min, max})
28 | }
29 |
--------------------------------------------------------------------------------
/src/one-source/take-errors.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({n}) {
5 | this._n = n
6 | if (n <= 0) {
7 | this._emitEnd()
8 | }
9 | },
10 |
11 | _handleError(x) {
12 | if (this._n === 0) {
13 | return
14 | }
15 | this._n--
16 | this._emitError(x)
17 | if (this._n === 0) {
18 | this._emitEnd()
19 | }
20 | },
21 | }
22 |
23 | const S = createStream('takeErrors', mixin)
24 | const P = createProperty('takeErrors', mixin)
25 |
26 | export default function takeErrors(obs, n) {
27 | return new (obs._ofSameType(S, P))(obs, {n})
28 | }
29 |
--------------------------------------------------------------------------------
/src/one-source/take-while.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({fn}) {
5 | this._fn = fn
6 | },
7 |
8 | _free() {
9 | this._fn = null
10 | },
11 |
12 | _handleValue(x) {
13 | const fn = this._fn
14 | if (fn(x)) {
15 | this._emitValue(x)
16 | } else {
17 | this._emitEnd()
18 | }
19 | },
20 | }
21 |
22 | const S = createStream('takeWhile', mixin)
23 | const P = createProperty('takeWhile', mixin)
24 |
25 | const id = x => x
26 |
27 | export default function takeWhile(obs, fn = id) {
28 | return new (obs._ofSameType(S, P))(obs, {fn})
29 | }
30 |
--------------------------------------------------------------------------------
/src/one-source/take.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({n}) {
5 | this._n = n
6 | if (n <= 0) {
7 | this._emitEnd()
8 | }
9 | },
10 |
11 | _handleValue(x) {
12 | if (this._n === 0) {
13 | return
14 | }
15 | this._n--
16 | this._emitValue(x)
17 | if (this._n === 0) {
18 | this._emitEnd()
19 | }
20 | },
21 | }
22 |
23 | const S = createStream('take', mixin)
24 | const P = createProperty('take', mixin)
25 |
26 | export default function take(obs, n) {
27 | return new (obs._ofSameType(S, P))(obs, {n})
28 | }
29 |
--------------------------------------------------------------------------------
/src/one-source/throttle.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 | import now from '../utils/now'
3 |
4 | const mixin = {
5 | _init({wait, leading, trailing}) {
6 | this._wait = Math.max(0, wait)
7 | this._leading = leading
8 | this._trailing = trailing
9 | this._trailingValue = null
10 | this._timeoutId = null
11 | this._endLater = false
12 | this._lastCallTime = 0
13 | this._$trailingCall = () => this._trailingCall()
14 | },
15 |
16 | _free() {
17 | this._trailingValue = null
18 | this._$trailingCall = null
19 | },
20 |
21 | _handleValue(x) {
22 | if (this._activating) {
23 | this._emitValue(x)
24 | } else {
25 | let curTime = now()
26 | if (this._lastCallTime === 0 && !this._leading) {
27 | this._lastCallTime = curTime
28 | }
29 | let remaining = this._wait - (curTime - this._lastCallTime)
30 | if (remaining <= 0) {
31 | this._cancelTrailing()
32 | this._lastCallTime = curTime
33 | this._emitValue(x)
34 | } else if (this._trailing) {
35 | this._cancelTrailing()
36 | this._trailingValue = x
37 | this._timeoutId = setTimeout(this._$trailingCall, remaining)
38 | }
39 | }
40 | },
41 |
42 | _handleEnd() {
43 | if (this._activating) {
44 | this._emitEnd()
45 | } else {
46 | if (this._timeoutId) {
47 | this._endLater = true
48 | } else {
49 | this._emitEnd()
50 | }
51 | }
52 | },
53 |
54 | _cancelTrailing() {
55 | if (this._timeoutId !== null) {
56 | clearTimeout(this._timeoutId)
57 | this._timeoutId = null
58 | }
59 | },
60 |
61 | _trailingCall() {
62 | this._emitValue(this._trailingValue)
63 | this._timeoutId = null
64 | this._trailingValue = null
65 | this._lastCallTime = !this._leading ? 0 : now()
66 | if (this._endLater) {
67 | this._emitEnd()
68 | }
69 | },
70 | }
71 |
72 | const S = createStream('throttle', mixin)
73 | const P = createProperty('throttle', mixin)
74 |
75 | export default function throttle(obs, wait, {leading = true, trailing = true} = {}) {
76 | return new (obs._ofSameType(S, P))(obs, {wait, leading, trailing})
77 | }
78 |
--------------------------------------------------------------------------------
/src/one-source/to-property.js:
--------------------------------------------------------------------------------
1 | import {createProperty} from '../patterns/one-source'
2 |
3 | const P = createProperty('toProperty', {
4 | _init({fn}) {
5 | this._getInitialCurrent = fn
6 | },
7 |
8 | _onActivation() {
9 | if (this._getInitialCurrent !== null) {
10 | const getInitial = this._getInitialCurrent
11 | this._emitValue(getInitial())
12 | }
13 | this._source.onAny(this._$handleAny) // copied from patterns/one-source
14 | },
15 | })
16 |
17 | export default function toProperty(obs, fn = null) {
18 | if (fn !== null && typeof fn !== 'function') {
19 | throw new Error('You should call toProperty() with a function or no arguments.')
20 | }
21 | return new P(obs, {fn})
22 | }
23 |
--------------------------------------------------------------------------------
/src/one-source/transduce.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | function xformForObs(obs) {
4 | return {
5 | '@@transducer/step'(res, input) {
6 | obs._emitValue(input)
7 | return null
8 | },
9 |
10 | '@@transducer/result'() {
11 | obs._emitEnd()
12 | return null
13 | },
14 | }
15 | }
16 |
17 | const mixin = {
18 | _init({transducer}) {
19 | this._xform = transducer(xformForObs(this))
20 | },
21 |
22 | _free() {
23 | this._xform = null
24 | },
25 |
26 | _handleValue(x) {
27 | if (this._xform['@@transducer/step'](null, x) !== null) {
28 | this._xform['@@transducer/result'](null)
29 | }
30 | },
31 |
32 | _handleEnd() {
33 | this._xform['@@transducer/result'](null)
34 | },
35 | }
36 |
37 | const S = createStream('transduce', mixin)
38 | const P = createProperty('transduce', mixin)
39 |
40 | export default function transduce(obs, transducer) {
41 | return new (obs._ofSameType(S, P))(obs, {transducer})
42 | }
43 |
--------------------------------------------------------------------------------
/src/one-source/values-to-errors.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 |
3 | const mixin = {
4 | _init({fn}) {
5 | this._fn = fn
6 | },
7 |
8 | _free() {
9 | this._fn = null
10 | },
11 |
12 | _handleValue(x) {
13 | const fn = this._fn
14 | let result = fn(x)
15 | if (result.convert) {
16 | this._emitError(result.error)
17 | } else {
18 | this._emitValue(x)
19 | }
20 | },
21 | }
22 |
23 | const S = createStream('valuesToErrors', mixin)
24 | const P = createProperty('valuesToErrors', mixin)
25 |
26 | const defFn = x => ({convert: true, error: x})
27 |
28 | export default function valuesToErrors(obs, fn = defFn) {
29 | return new (obs._ofSameType(S, P))(obs, {fn})
30 | }
31 |
--------------------------------------------------------------------------------
/src/one-source/with-handler.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/one-source'
2 | import emitter from '../emitter'
3 |
4 | const mixin = {
5 | _init({fn}) {
6 | this._handler = fn
7 | this._emitter = emitter(this)
8 | },
9 |
10 | _free() {
11 | this._handler = null
12 | this._emitter = null
13 | },
14 |
15 | _handleAny(event) {
16 | this._handler(this._emitter, event)
17 | },
18 | }
19 |
20 | const S = createStream('withHandler', mixin)
21 | const P = createProperty('withHandler', mixin)
22 |
23 | export default function withHandler(obs, fn) {
24 | return new (obs._ofSameType(S, P))(obs, {fn})
25 | }
26 |
--------------------------------------------------------------------------------
/src/patterns/one-source.js:
--------------------------------------------------------------------------------
1 | import Stream from '../stream'
2 | import Property from '../property'
3 | import {inherit} from '../utils/objects'
4 | import {VALUE, ERROR, END} from '../constants'
5 |
6 | function createConstructor(BaseClass, name) {
7 | return function AnonymousObservable(source, options) {
8 | BaseClass.call(this)
9 | this._source = source
10 | this._name = `${source._name}.${name}`
11 | this._init(options)
12 | this._$handleAny = event => this._handleAny(event)
13 | }
14 | }
15 |
16 | function createClassMethods(BaseClass) {
17 | return {
18 | _init() {},
19 | _free() {},
20 |
21 | _handleValue(x) {
22 | this._emitValue(x)
23 | },
24 | _handleError(x) {
25 | this._emitError(x)
26 | },
27 | _handleEnd() {
28 | this._emitEnd()
29 | },
30 |
31 | _handleAny(event) {
32 | switch (event.type) {
33 | case VALUE:
34 | return this._handleValue(event.value)
35 | case ERROR:
36 | return this._handleError(event.value)
37 | case END:
38 | return this._handleEnd()
39 | }
40 | },
41 |
42 | _onActivation() {
43 | this._source.onAny(this._$handleAny)
44 | },
45 | _onDeactivation() {
46 | this._source.offAny(this._$handleAny)
47 | },
48 |
49 | _clear() {
50 | BaseClass.prototype._clear.call(this)
51 | this._source = null
52 | this._$handleAny = null
53 | this._free()
54 | },
55 | }
56 | }
57 |
58 | function createStream(name, mixin) {
59 | const S = createConstructor(Stream, name)
60 | inherit(S, Stream, createClassMethods(Stream), mixin)
61 | return S
62 | }
63 |
64 | function createProperty(name, mixin) {
65 | const P = createConstructor(Property, name)
66 | inherit(P, Property, createClassMethods(Property), mixin)
67 | return P
68 | }
69 |
70 | export {createStream, createProperty}
71 |
--------------------------------------------------------------------------------
/src/patterns/time-based.js:
--------------------------------------------------------------------------------
1 | import {inherit} from '../utils/objects'
2 | import Stream from '../stream'
3 |
4 | export default function timeBased(mixin) {
5 | function AnonymousStream(wait, options) {
6 | Stream.call(this)
7 | this._wait = wait
8 | this._intervalId = null
9 | this._$onTick = () => this._onTick()
10 | this._init(options)
11 | }
12 |
13 | inherit(
14 | AnonymousStream,
15 | Stream,
16 | {
17 | _init() {},
18 | _free() {},
19 |
20 | _onTick() {},
21 |
22 | _onActivation() {
23 | this._intervalId = setInterval(this._$onTick, this._wait)
24 | },
25 |
26 | _onDeactivation() {
27 | if (this._intervalId !== null) {
28 | clearInterval(this._intervalId)
29 | this._intervalId = null
30 | }
31 | },
32 |
33 | _clear() {
34 | Stream.prototype._clear.call(this)
35 | this._$onTick = null
36 | this._free()
37 | },
38 | },
39 | mixin
40 | )
41 |
42 | return AnonymousStream
43 | }
44 |
--------------------------------------------------------------------------------
/src/patterns/two-sources.js:
--------------------------------------------------------------------------------
1 | import Stream from '../stream'
2 | import Property from '../property'
3 | import {inherit} from '../utils/objects'
4 | import {VALUE, ERROR, END, NOTHING} from '../constants'
5 |
6 | function createConstructor(BaseClass, name) {
7 | return function AnonymousObservable(primary, secondary, options) {
8 | BaseClass.call(this)
9 | this._primary = primary
10 | this._secondary = secondary
11 | this._name = `${primary._name}.${name}`
12 | this._lastSecondary = NOTHING
13 | this._$handleSecondaryAny = event => this._handleSecondaryAny(event)
14 | this._$handlePrimaryAny = event => this._handlePrimaryAny(event)
15 | this._init(options)
16 | }
17 | }
18 |
19 | function createClassMethods(BaseClass) {
20 | return {
21 | _init() {},
22 | _free() {},
23 |
24 | _handlePrimaryValue(x) {
25 | this._emitValue(x)
26 | },
27 | _handlePrimaryError(x) {
28 | this._emitError(x)
29 | },
30 | _handlePrimaryEnd() {
31 | this._emitEnd()
32 | },
33 |
34 | _handleSecondaryValue(x) {
35 | this._lastSecondary = x
36 | },
37 | _handleSecondaryError(x) {
38 | this._emitError(x)
39 | },
40 | _handleSecondaryEnd() {},
41 |
42 | _handlePrimaryAny(event) {
43 | switch (event.type) {
44 | case VALUE:
45 | return this._handlePrimaryValue(event.value)
46 | case ERROR:
47 | return this._handlePrimaryError(event.value)
48 | case END:
49 | return this._handlePrimaryEnd(event.value)
50 | }
51 | },
52 | _handleSecondaryAny(event) {
53 | switch (event.type) {
54 | case VALUE:
55 | return this._handleSecondaryValue(event.value)
56 | case ERROR:
57 | return this._handleSecondaryError(event.value)
58 | case END:
59 | this._handleSecondaryEnd(event.value)
60 | this._removeSecondary()
61 | }
62 | },
63 |
64 | _removeSecondary() {
65 | if (this._secondary !== null) {
66 | this._secondary.offAny(this._$handleSecondaryAny)
67 | this._$handleSecondaryAny = null
68 | this._secondary = null
69 | }
70 | },
71 |
72 | _onActivation() {
73 | if (this._secondary !== null) {
74 | this._secondary.onAny(this._$handleSecondaryAny)
75 | }
76 | if (this._active) {
77 | this._primary.onAny(this._$handlePrimaryAny)
78 | }
79 | },
80 | _onDeactivation() {
81 | if (this._secondary !== null) {
82 | this._secondary.offAny(this._$handleSecondaryAny)
83 | }
84 | this._primary.offAny(this._$handlePrimaryAny)
85 | },
86 |
87 | _clear() {
88 | BaseClass.prototype._clear.call(this)
89 | this._primary = null
90 | this._secondary = null
91 | this._lastSecondary = null
92 | this._$handleSecondaryAny = null
93 | this._$handlePrimaryAny = null
94 | this._free()
95 | },
96 | }
97 | }
98 |
99 | function createStream(name, mixin) {
100 | const S = createConstructor(Stream, name)
101 | inherit(S, Stream, createClassMethods(Stream), mixin)
102 | return S
103 | }
104 |
105 | function createProperty(name, mixin) {
106 | const P = createConstructor(Property, name)
107 | inherit(P, Property, createClassMethods(Property), mixin)
108 | return P
109 | }
110 |
111 | export {createStream, createProperty}
112 |
--------------------------------------------------------------------------------
/src/primary/constant-error.js:
--------------------------------------------------------------------------------
1 | import {inherit} from '../utils/objects'
2 | import Property from '../property'
3 |
4 | // HACK:
5 | // We don't call parent Class constructor, but instead putting all necessary
6 | // properties into prototype to simulate ended Property
7 | // (see Propperty and Observable classes).
8 |
9 | function P(value) {
10 | this._currentEvent = {type: 'error', value, current: true}
11 | }
12 |
13 | inherit(P, Property, {
14 | _name: 'constantError',
15 | _active: false,
16 | _activating: false,
17 | _alive: false,
18 | _dispatcher: null,
19 | _logHandlers: null,
20 | })
21 |
22 | export default function constantError(x) {
23 | return new P(x)
24 | }
25 |
--------------------------------------------------------------------------------
/src/primary/constant.js:
--------------------------------------------------------------------------------
1 | import {inherit} from '../utils/objects'
2 | import Property from '../property'
3 |
4 | // HACK:
5 | // We don't call parent Class constructor, but instead putting all necessary
6 | // properties into prototype to simulate ended Property
7 | // (see Propperty and Observable classes).
8 |
9 | function P(value) {
10 | this._currentEvent = {type: 'value', value, current: true}
11 | }
12 |
13 | inherit(P, Property, {
14 | _name: 'constant',
15 | _active: false,
16 | _activating: false,
17 | _alive: false,
18 | _dispatcher: null,
19 | _logHandlers: null,
20 | })
21 |
22 | export default function constant(x) {
23 | return new P(x)
24 | }
25 |
--------------------------------------------------------------------------------
/src/primary/from-callback.js:
--------------------------------------------------------------------------------
1 | import stream from './stream'
2 |
3 | export default function fromCallback(callbackConsumer) {
4 | let called = false
5 |
6 | return stream(function(emitter) {
7 | if (!called) {
8 | callbackConsumer(function(x) {
9 | emitter.emit(x)
10 | emitter.end()
11 | })
12 | called = true
13 | }
14 | }).setName('fromCallback')
15 | }
16 |
--------------------------------------------------------------------------------
/src/primary/from-events.js:
--------------------------------------------------------------------------------
1 | import fromSubUnsub from './from-sub-unsub'
2 |
3 | const pairs = [
4 | ['addEventListener', 'removeEventListener'],
5 | ['addListener', 'removeListener'],
6 | ['on', 'off'],
7 | ]
8 |
9 | export default function fromEvents(target, eventName, transformer) {
10 | let sub, unsub
11 |
12 | for (let i = 0; i < pairs.length; i++) {
13 | if (typeof target[pairs[i][0]] === 'function' && typeof target[pairs[i][1]] === 'function') {
14 | sub = pairs[i][0]
15 | unsub = pairs[i][1]
16 | break
17 | }
18 | }
19 |
20 | if (sub === undefined) {
21 | throw new Error(
22 | "target don't support any of " +
23 | 'addEventListener/removeEventListener, addListener/removeListener, on/off method pair'
24 | )
25 | }
26 |
27 | return fromSubUnsub(
28 | handler => target[sub](eventName, handler),
29 | handler => target[unsub](eventName, handler),
30 | transformer
31 | ).setName('fromEvents')
32 | }
33 |
--------------------------------------------------------------------------------
/src/primary/from-node-callback.js:
--------------------------------------------------------------------------------
1 | import stream from './stream'
2 |
3 | export default function fromNodeCallback(callbackConsumer) {
4 | let called = false
5 |
6 | return stream(function(emitter) {
7 | if (!called) {
8 | callbackConsumer(function(error, x) {
9 | if (error) {
10 | emitter.error(error)
11 | } else {
12 | emitter.emit(x)
13 | }
14 | emitter.end()
15 | })
16 | called = true
17 | }
18 | }).setName('fromNodeCallback')
19 | }
20 |
--------------------------------------------------------------------------------
/src/primary/from-sub-unsub.js:
--------------------------------------------------------------------------------
1 | import stream from './stream'
2 | import {apply} from '../utils/functions'
3 |
4 | export default function fromSubUnsub(sub, unsub, transformer /* Function | falsey */) {
5 | return stream(function(emitter) {
6 | let handler = transformer
7 | ? function() {
8 | emitter.emit(apply(transformer, this, arguments))
9 | }
10 | : x => {
11 | emitter.emit(x)
12 | }
13 |
14 | sub(handler)
15 | return () => unsub(handler)
16 | }).setName('fromSubUnsub')
17 | }
18 |
--------------------------------------------------------------------------------
/src/primary/never.js:
--------------------------------------------------------------------------------
1 | import Stream from '../stream'
2 |
3 | const neverS = new Stream()
4 | neverS._emitEnd()
5 | neverS._name = 'never'
6 |
7 | export default function never() {
8 | return neverS
9 | }
10 |
--------------------------------------------------------------------------------
/src/primary/stream.js:
--------------------------------------------------------------------------------
1 | import {inherit} from '../utils/objects'
2 | import Stream from '../stream'
3 | import emitter from '../emitter'
4 |
5 | function S(fn) {
6 | Stream.call(this)
7 | this._fn = fn
8 | this._unsubscribe = null
9 | }
10 |
11 | inherit(S, Stream, {
12 | _name: 'stream',
13 |
14 | _onActivation() {
15 | const fn = this._fn
16 | const unsubscribe = fn(emitter(this))
17 | this._unsubscribe = typeof unsubscribe === 'function' ? unsubscribe : null
18 |
19 | // fix https://github.com/kefirjs/kefir/issues/35
20 | if (!this._active) {
21 | this._callUnsubscribe()
22 | }
23 | },
24 |
25 | _callUnsubscribe() {
26 | if (this._unsubscribe !== null) {
27 | this._unsubscribe()
28 | this._unsubscribe = null
29 | }
30 | },
31 |
32 | _onDeactivation() {
33 | this._callUnsubscribe()
34 | },
35 |
36 | _clear() {
37 | Stream.prototype._clear.call(this)
38 | this._fn = null
39 | },
40 | })
41 |
42 | export default function stream(fn) {
43 | return new S(fn)
44 | }
45 |
--------------------------------------------------------------------------------
/src/property.js:
--------------------------------------------------------------------------------
1 | import {inherit} from './utils/objects'
2 | import {VALUE, ERROR, END} from './constants'
3 | import {callSubscriber} from './dispatcher'
4 | import Observable from './observable'
5 |
6 | function Property() {
7 | Observable.call(this)
8 | this._currentEvent = null
9 | }
10 |
11 | inherit(Property, Observable, {
12 | _name: 'property',
13 |
14 | _emitValue(value) {
15 | if (this._alive) {
16 | this._currentEvent = {type: VALUE, value}
17 | if (!this._activating) {
18 | this._dispatcher.dispatch({type: VALUE, value})
19 | }
20 | }
21 | },
22 |
23 | _emitError(value) {
24 | if (this._alive) {
25 | this._currentEvent = {type: ERROR, value}
26 | if (!this._activating) {
27 | this._dispatcher.dispatch({type: ERROR, value})
28 | }
29 | }
30 | },
31 |
32 | _emitEnd() {
33 | if (this._alive) {
34 | this._alive = false
35 | if (!this._activating) {
36 | this._dispatcher.dispatch({type: END})
37 | }
38 | this._clear()
39 | }
40 | },
41 |
42 | _on(type, fn) {
43 | if (this._alive) {
44 | this._dispatcher.add(type, fn)
45 | this._setActive(true)
46 | }
47 | if (this._currentEvent !== null) {
48 | callSubscriber(type, fn, this._currentEvent)
49 | }
50 | if (!this._alive) {
51 | callSubscriber(type, fn, {type: END})
52 | }
53 | return this
54 | },
55 |
56 | getType() {
57 | return 'property'
58 | },
59 | })
60 |
61 | export default Property
62 |
--------------------------------------------------------------------------------
/src/stream.js:
--------------------------------------------------------------------------------
1 | import {inherit} from './utils/objects'
2 | import Observable from './observable'
3 |
4 | function Stream() {
5 | Observable.call(this)
6 | }
7 |
8 | inherit(Stream, Observable, {
9 | _name: 'stream',
10 |
11 | getType() {
12 | return 'stream'
13 | },
14 | })
15 |
16 | export default Stream
17 |
--------------------------------------------------------------------------------
/src/time-based/from-poll.js:
--------------------------------------------------------------------------------
1 | import timeBased from '../patterns/time-based'
2 |
3 | const S = timeBased({
4 | _name: 'fromPoll',
5 |
6 | _init({fn}) {
7 | this._fn = fn
8 | },
9 |
10 | _free() {
11 | this._fn = null
12 | },
13 |
14 | _onTick() {
15 | const fn = this._fn
16 | this._emitValue(fn())
17 | },
18 | })
19 |
20 | export default function fromPoll(wait, fn) {
21 | return new S(wait, {fn})
22 | }
23 |
--------------------------------------------------------------------------------
/src/time-based/interval.js:
--------------------------------------------------------------------------------
1 | import timeBased from '../patterns/time-based'
2 |
3 | const S = timeBased({
4 | _name: 'interval',
5 |
6 | _init({x}) {
7 | this._x = x
8 | },
9 |
10 | _free() {
11 | this._x = null
12 | },
13 |
14 | _onTick() {
15 | this._emitValue(this._x)
16 | },
17 | })
18 |
19 | export default function interval(wait, x) {
20 | return new S(wait, {x})
21 | }
22 |
--------------------------------------------------------------------------------
/src/time-based/later.js:
--------------------------------------------------------------------------------
1 | import timeBased from '../patterns/time-based'
2 |
3 | const S = timeBased({
4 | _name: 'later',
5 |
6 | _init({x}) {
7 | this._x = x
8 | },
9 |
10 | _free() {
11 | this._x = null
12 | },
13 |
14 | _onTick() {
15 | this._emitValue(this._x)
16 | this._emitEnd()
17 | },
18 | })
19 |
20 | export default function later(wait, x) {
21 | return new S(wait, {x})
22 | }
23 |
--------------------------------------------------------------------------------
/src/time-based/sequentially.js:
--------------------------------------------------------------------------------
1 | import timeBased from '../patterns/time-based'
2 | import {cloneArray} from '../utils/collections'
3 | import never from '../primary/never'
4 |
5 | const S = timeBased({
6 | _name: 'sequentially',
7 |
8 | _init({xs}) {
9 | this._xs = cloneArray(xs)
10 | },
11 |
12 | _free() {
13 | this._xs = null
14 | },
15 |
16 | _onTick() {
17 | if (this._xs.length === 1) {
18 | this._emitValue(this._xs[0])
19 | this._emitEnd()
20 | } else {
21 | this._emitValue(this._xs.shift())
22 | }
23 | },
24 | })
25 |
26 | export default function sequentially(wait, xs) {
27 | return xs.length === 0 ? never() : new S(wait, {xs})
28 | }
29 |
--------------------------------------------------------------------------------
/src/time-based/with-interval.js:
--------------------------------------------------------------------------------
1 | import timeBased from '../patterns/time-based'
2 | import emitter from '../emitter'
3 |
4 | const S = timeBased({
5 | _name: 'withInterval',
6 |
7 | _init({fn}) {
8 | this._fn = fn
9 | this._emitter = emitter(this)
10 | },
11 |
12 | _free() {
13 | this._fn = null
14 | this._emitter = null
15 | },
16 |
17 | _onTick() {
18 | const fn = this._fn
19 | fn(this._emitter)
20 | },
21 | })
22 |
23 | export default function withInterval(wait, fn) {
24 | return new S(wait, {fn})
25 | }
26 |
--------------------------------------------------------------------------------
/src/two-sources/awaiting.js:
--------------------------------------------------------------------------------
1 | import merge from '../many-sources/merge'
2 | import map from '../one-source/map'
3 | import skipDuplicates from '../one-source/skip-duplicates'
4 | import toProperty from '../one-source/to-property'
5 |
6 | const f = () => false
7 | const t = () => true
8 |
9 | export default function awaiting(a, b) {
10 | let result = merge([map(a, t), map(b, f)])
11 | result = skipDuplicates(result)
12 | result = toProperty(result, f)
13 | return result.setName(a, 'awaiting')
14 | }
15 |
--------------------------------------------------------------------------------
/src/two-sources/buffer-by.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/two-sources'
2 |
3 | const mixin = {
4 | _init({flushOnEnd = true} = {}) {
5 | this._buff = []
6 | this._flushOnEnd = flushOnEnd
7 | },
8 |
9 | _free() {
10 | this._buff = null
11 | },
12 |
13 | _flush() {
14 | if (this._buff !== null) {
15 | this._emitValue(this._buff)
16 | this._buff = []
17 | }
18 | },
19 |
20 | _handlePrimaryEnd() {
21 | if (this._flushOnEnd) {
22 | this._flush()
23 | }
24 | this._emitEnd()
25 | },
26 |
27 | _onActivation() {
28 | this._primary.onAny(this._$handlePrimaryAny)
29 | if (this._alive && this._secondary !== null) {
30 | this._secondary.onAny(this._$handleSecondaryAny)
31 | }
32 | },
33 |
34 | _handlePrimaryValue(x) {
35 | this._buff.push(x)
36 | },
37 |
38 | _handleSecondaryValue() {
39 | this._flush()
40 | },
41 |
42 | _handleSecondaryEnd() {
43 | if (!this._flushOnEnd) {
44 | this._emitEnd()
45 | }
46 | },
47 | }
48 |
49 | const S = createStream('bufferBy', mixin)
50 | const P = createProperty('bufferBy', mixin)
51 |
52 | export default function bufferBy(primary, secondary, options /* optional */) {
53 | return new (primary._ofSameType(S, P))(primary, secondary, options)
54 | }
55 |
--------------------------------------------------------------------------------
/src/two-sources/buffer-while-by.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/two-sources'
2 | import {NOTHING} from '../constants'
3 |
4 | const mixin = {
5 | _init({flushOnEnd = true, flushOnChange = false} = {}) {
6 | this._buff = []
7 | this._flushOnEnd = flushOnEnd
8 | this._flushOnChange = flushOnChange
9 | },
10 |
11 | _free() {
12 | this._buff = null
13 | },
14 |
15 | _flush() {
16 | if (this._buff !== null) {
17 | this._emitValue(this._buff)
18 | this._buff = []
19 | }
20 | },
21 |
22 | _handlePrimaryEnd() {
23 | if (this._flushOnEnd) {
24 | this._flush()
25 | }
26 | this._emitEnd()
27 | },
28 |
29 | _handlePrimaryValue(x) {
30 | this._buff.push(x)
31 | if (this._lastSecondary !== NOTHING && !this._lastSecondary) {
32 | this._flush()
33 | }
34 | },
35 |
36 | _handleSecondaryEnd() {
37 | if (!this._flushOnEnd && (this._lastSecondary === NOTHING || this._lastSecondary)) {
38 | this._emitEnd()
39 | }
40 | },
41 |
42 | _handleSecondaryValue(x) {
43 | if (this._flushOnChange && !x) {
44 | this._flush()
45 | }
46 |
47 | // from default _handleSecondaryValue
48 | this._lastSecondary = x
49 | },
50 | }
51 |
52 | const S = createStream('bufferWhileBy', mixin)
53 | const P = createProperty('bufferWhileBy', mixin)
54 |
55 | export default function bufferWhileBy(primary, secondary, options /* optional */) {
56 | return new (primary._ofSameType(S, P))(primary, secondary, options)
57 | }
58 |
--------------------------------------------------------------------------------
/src/two-sources/filter-by.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/two-sources'
2 | import {NOTHING} from '../constants'
3 |
4 | const mixin = {
5 | _handlePrimaryValue(x) {
6 | if (this._lastSecondary !== NOTHING && this._lastSecondary) {
7 | this._emitValue(x)
8 | }
9 | },
10 |
11 | _handleSecondaryEnd() {
12 | if (this._lastSecondary === NOTHING || !this._lastSecondary) {
13 | this._emitEnd()
14 | }
15 | },
16 | }
17 |
18 | const S = createStream('filterBy', mixin)
19 | const P = createProperty('filterBy', mixin)
20 |
21 | export default function filterBy(primary, secondary) {
22 | return new (primary._ofSameType(S, P))(primary, secondary)
23 | }
24 |
--------------------------------------------------------------------------------
/src/two-sources/sampled-by.js:
--------------------------------------------------------------------------------
1 | import combine from '../many-sources/combine'
2 |
3 | const id2 = (_, x) => x
4 |
5 | export default function sampledBy(passive, active, combinator) {
6 | let _combinator = combinator ? (a, b) => combinator(b, a) : id2
7 | return combine([active], [passive], _combinator).setName(passive, 'sampledBy')
8 | }
9 |
--------------------------------------------------------------------------------
/src/two-sources/skip-until-by.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/two-sources'
2 | import {NOTHING} from '../constants'
3 |
4 | const mixin = {
5 | _handlePrimaryValue(x) {
6 | if (this._lastSecondary !== NOTHING) {
7 | this._emitValue(x)
8 | }
9 | },
10 |
11 | _handleSecondaryEnd() {
12 | if (this._lastSecondary === NOTHING) {
13 | this._emitEnd()
14 | }
15 | },
16 | }
17 |
18 | const S = createStream('skipUntilBy', mixin)
19 | const P = createProperty('skipUntilBy', mixin)
20 |
21 | export default function skipUntilBy(primary, secondary) {
22 | return new (primary._ofSameType(S, P))(primary, secondary)
23 | }
24 |
--------------------------------------------------------------------------------
/src/two-sources/take-until-by.js:
--------------------------------------------------------------------------------
1 | import {createStream, createProperty} from '../patterns/two-sources'
2 |
3 | const mixin = {
4 | _handleSecondaryValue() {
5 | this._emitEnd()
6 | },
7 | }
8 |
9 | const S = createStream('takeUntilBy', mixin)
10 | const P = createProperty('takeUntilBy', mixin)
11 |
12 | export default function takeUntilBy(primary, secondary) {
13 | return new (primary._ofSameType(S, P))(primary, secondary)
14 | }
15 |
--------------------------------------------------------------------------------
/src/utils/collections.js:
--------------------------------------------------------------------------------
1 | function concat(a, b) {
2 | let result, length, i, j
3 | if (a.length === 0) {
4 | return b
5 | }
6 | if (b.length === 0) {
7 | return a
8 | }
9 | j = 0
10 | result = new Array(a.length + b.length)
11 | length = a.length
12 | for (i = 0; i < length; i++, j++) {
13 | result[j] = a[i]
14 | }
15 | length = b.length
16 | for (i = 0; i < length; i++, j++) {
17 | result[j] = b[i]
18 | }
19 | return result
20 | }
21 |
22 | function circleShift(arr, distance) {
23 | let length = arr.length,
24 | result = new Array(length),
25 | i
26 | for (i = 0; i < length; i++) {
27 | result[(i + distance) % length] = arr[i]
28 | }
29 | return result
30 | }
31 |
32 | function find(arr, value) {
33 | let length = arr.length,
34 | i
35 | for (i = 0; i < length; i++) {
36 | if (arr[i] === value) {
37 | return i
38 | }
39 | }
40 | return -1
41 | }
42 |
43 | function findByPred(arr, pred) {
44 | let length = arr.length,
45 | i
46 | for (i = 0; i < length; i++) {
47 | if (pred(arr[i])) {
48 | return i
49 | }
50 | }
51 | return -1
52 | }
53 |
54 | function cloneArray(input) {
55 | let length = input.length,
56 | result = new Array(length),
57 | i
58 | for (i = 0; i < length; i++) {
59 | result[i] = input[i]
60 | }
61 | return result
62 | }
63 |
64 | function remove(input, index) {
65 | let length = input.length,
66 | result,
67 | i,
68 | j
69 | if (index >= 0 && index < length) {
70 | if (length === 1) {
71 | return []
72 | } else {
73 | result = new Array(length - 1)
74 | for (i = 0, j = 0; i < length; i++) {
75 | if (i !== index) {
76 | result[j] = input[i]
77 | j++
78 | }
79 | }
80 | return result
81 | }
82 | } else {
83 | return input
84 | }
85 | }
86 |
87 | function removeByPred(input, pred) {
88 | return remove(input, findByPred(input, pred))
89 | }
90 |
91 | function map(input, fn) {
92 | let length = input.length,
93 | result = new Array(length),
94 | i
95 | for (i = 0; i < length; i++) {
96 | result[i] = fn(input[i])
97 | }
98 | return result
99 | }
100 |
101 | function forEach(arr, fn) {
102 | let length = arr.length,
103 | i
104 | for (i = 0; i < length; i++) {
105 | fn(arr[i])
106 | }
107 | }
108 |
109 | function fillArray(arr, value) {
110 | let length = arr.length,
111 | i
112 | for (i = 0; i < length; i++) {
113 | arr[i] = value
114 | }
115 | }
116 |
117 | function contains(arr, value) {
118 | return find(arr, value) !== -1
119 | }
120 |
121 | function slide(cur, next, max) {
122 | let length = Math.min(max, cur.length + 1),
123 | offset = cur.length - length + 1,
124 | result = new Array(length),
125 | i
126 | for (i = offset; i < length; i++) {
127 | result[i - offset] = cur[i]
128 | }
129 | result[length - 1] = next
130 | return result
131 | }
132 |
133 | export {
134 | concat,
135 | circleShift,
136 | find,
137 | findByPred,
138 | cloneArray,
139 | remove,
140 | removeByPred,
141 | map,
142 | forEach,
143 | fillArray,
144 | contains,
145 | slide,
146 | }
147 |
--------------------------------------------------------------------------------
/src/utils/functions.js:
--------------------------------------------------------------------------------
1 | function spread(fn, length) {
2 | switch (length) {
3 | case 0:
4 | return function() {
5 | return fn()
6 | }
7 | case 1:
8 | return function(a) {
9 | return fn(a[0])
10 | }
11 | case 2:
12 | return function(a) {
13 | return fn(a[0], a[1])
14 | }
15 | case 3:
16 | return function(a) {
17 | return fn(a[0], a[1], a[2])
18 | }
19 | case 4:
20 | return function(a) {
21 | return fn(a[0], a[1], a[2], a[3])
22 | }
23 | default:
24 | return function(a) {
25 | return fn.apply(null, a)
26 | }
27 | }
28 | }
29 |
30 | function apply(fn, c, a) {
31 | let aLength = a ? a.length : 0
32 | if (c == null) {
33 | switch (aLength) {
34 | case 0:
35 | return fn()
36 | case 1:
37 | return fn(a[0])
38 | case 2:
39 | return fn(a[0], a[1])
40 | case 3:
41 | return fn(a[0], a[1], a[2])
42 | case 4:
43 | return fn(a[0], a[1], a[2], a[3])
44 | default:
45 | return fn.apply(null, a)
46 | }
47 | } else {
48 | switch (aLength) {
49 | case 0:
50 | return fn.call(c)
51 | default:
52 | return fn.apply(c, a)
53 | }
54 | }
55 | }
56 |
57 | export {spread, apply}
58 |
--------------------------------------------------------------------------------
/src/utils/now.js:
--------------------------------------------------------------------------------
1 | export default Date.now ? () => Date.now() : () => new Date().getTime()
2 |
--------------------------------------------------------------------------------
/src/utils/objects.js:
--------------------------------------------------------------------------------
1 | function createObj(proto) {
2 | let F = function() {}
3 | F.prototype = proto
4 | return new F()
5 | }
6 |
7 | function extend(target /*, mixin1, mixin2...*/) {
8 | let length = arguments.length,
9 | i,
10 | prop
11 | for (i = 1; i < length; i++) {
12 | for (prop in arguments[i]) {
13 | target[prop] = arguments[i][prop]
14 | }
15 | }
16 | return target
17 | }
18 |
19 | function inherit(Child, Parent /*, mixin1, mixin2...*/) {
20 | let length = arguments.length,
21 | i
22 | Child.prototype = createObj(Parent.prototype)
23 | Child.prototype.constructor = Child
24 | for (i = 2; i < length; i++) {
25 | extend(Child.prototype, arguments[i])
26 | }
27 | return Child
28 | }
29 |
30 | export {extend, inherit}
31 |
--------------------------------------------------------------------------------
/test/flow/diff.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | const source = Kefir.sequentially(10, [1, 2, 3, 4])
6 |
7 | function test_diff_noArgs() {
8 | const xs = source.diff()
9 |
10 | xs.onValue((x: [number, number]) => {})
11 |
12 | // $ExpectError
13 | xs.onValue((x: number) => {})
14 | }
15 |
16 | function test_diff_fn() {
17 | const xs = source.diff((x, y) => x - y)
18 |
19 | xs.onValue((x: number) => {})
20 |
21 | // $ExpectError
22 | xs.onValue((x: [number, number]) => {})
23 | }
24 |
25 | function test_diff_fnAndSeed() {
26 | const xs = source.diff((x, y) => x - y, 0)
27 |
28 | // $ExpectError
29 | source.diff((x, y) => x - y, 'zero')
30 |
31 | xs.onValue((x: number) => {})
32 |
33 | // $ExpectError
34 | xs.onValue((x: [number, number]) => {})
35 | }
36 |
37 | function test_diff_seed() {
38 | const xs = source.diff(null, 'zero')
39 |
40 | xs.onValue((x: [number | string, number]) => {})
41 |
42 | // $ExpectError
43 | xs.onValue((x: [number, number]) => {})
44 |
45 | // $ExpectError
46 | xs.onValue((x: number) => {})
47 | }
48 |
--------------------------------------------------------------------------------
/test/flow/error.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | // error type should be persisted through and unaffected by map call.
6 | const prop = Kefir.constantError(1)
7 | .merge(Kefir.constant(2))
8 | .map(x => String(x))
9 | .mapErrors(err => ({a: err}))
10 |
11 | prop
12 | .onValue(x => {
13 | const good: string = x
14 | // $ExpectError
15 | const bad: number = x
16 | })
17 | .onError(x => {
18 | const good: {a: number} = x
19 | // $ExpectError
20 | const bad: string = x
21 | })
22 |
--------------------------------------------------------------------------------
/test/flow/explicitType.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 | import type {Observable} from '../../kefir'
5 |
6 | const p1: Observable<{a: number, b: string}> = Kefir.constant({a: 1, b: 'b'})
7 | // Test that covariant casts are allowed.
8 | const p1c: Observable<{a: number}> = p1
9 | // $ExpectError
10 | const f1c: Observable = p1
11 |
12 | p1.onValue(v => {
13 | const good: {a: number, b: string} = v
14 | // $ExpectError
15 | const bad: number = v
16 | })
17 |
18 | // Check that "Kefir.Observable" refers to the type too.
19 | const p2: Kefir.Observable<{a: number, b: string}> = Kefir.constant({a: 2, b: 'b'})
20 | // $ExpectError
21 | const f2c: Observable = p1
22 |
23 | p2.onValue(v => {
24 | const good: {a: number, b: string} = v
25 | // $ExpectError
26 | const bad: number = v
27 | })
28 |
29 | // Check that the error type parameter works too.
30 |
31 | const p3: Observable = Kefir.constantError('foo')
32 | p3.observe(
33 | v => {
34 | const good: number = v
35 | // $ExpectError
36 | const bad: string = v
37 | },
38 | err => {
39 | const good: string = err
40 | // $ExpectError
41 | const bad: number = err
42 | }
43 | )
44 |
45 | // $ExpectError
46 | const bad: Observable = Kefir.constantError('foo')
47 |
--------------------------------------------------------------------------------
/test/flow/filter.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | Kefir.sequentially(10, [5, null])
6 | .onValue(x => {
7 | const good: ?number = x
8 | // $ExpectError
9 | const bad1: number = x
10 | // $ExpectError
11 | const bad2: null = x
12 | })
13 | .filter()
14 | .onValue(x => {
15 | const good: number = x
16 | // $ExpectError
17 | const bad: null = x
18 | })
19 |
20 | Kefir.sequentially(10, [5, null])
21 | .onValue(x => {
22 | const good: ?number = x
23 | // $ExpectError
24 | const bad1: number = x
25 | // $ExpectError
26 | const bad2: null = x
27 | })
28 | .filter(Boolean)
29 | .onValue(x => {
30 | const good: number = x
31 | // $ExpectError
32 | const bad: null = x
33 | })
34 |
35 | Kefir.sequentially(10, [5, null])
36 | .onValue(x => {
37 | const good: ?number = x
38 | // $ExpectError
39 | const bad1: number = x
40 | // $ExpectError
41 | const bad2: null = x
42 | })
43 | .filter(x => true)
44 | .onValue(x => {
45 | const good: ?number = x
46 | // $ExpectError
47 | const bad: number = x
48 | })
49 |
--------------------------------------------------------------------------------
/test/flow/flatten.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | function test_flatten() {
6 | const source = Kefir.sequentially(0, [[1], [2, 2], [3, 3, 3]])
7 | const xs = source.flatten()
8 |
9 | xs.onValue((x: number) => {})
10 | }
11 |
12 | function test_flatten_withTransformer() {
13 | const source = Kefir.sequentially(0, [1, 2, 3, 4])
14 | const xs = source.flatten(x => ['foo', 'bar'])
15 |
16 | xs.onValue((x: string) => {})
17 |
18 | // $ExpectError
19 | xs.onValue((x: number) => {})
20 | }
21 |
--------------------------------------------------------------------------------
/test/flow/interop.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | import EventEmitter from 'events'
6 |
7 | class MyEmitter extends EventEmitter {}
8 |
9 | function test_fromEvents() {
10 | const ee = new MyEmitter()
11 | const xs = Kefir.fromEvents(ee, 'somethingHappened', (x: string, y: number) => 'foo')
12 |
13 | xs.onValue((x: string) => {})
14 |
15 | // $ExpectError
16 | xs.onValue((x: number) => {})
17 | }
18 |
19 | function test_toPromise() {
20 | const xs = Kefir.constant('foo')
21 | const p = xs.toPromise()
22 |
23 | p.then((x: string) => {})
24 |
25 | // $ExpectError
26 | p.then((x: number) => {})
27 | }
28 |
29 | function test_fromPromise() {
30 | const p = Promise.resolve('foo')
31 | const xs = Kefir.fromPromise(p)
32 |
33 | xs.onValue((x: string) => {})
34 |
35 | // $ExpectError
36 | xs.onValue((x: number) => {})
37 | }
38 |
39 | function test_fromESObservable() {
40 | const myObservable = {
41 | subscribe({next, error, complete}) {
42 | next && next('foo')
43 | error && error(new Error())
44 | complete && complete()
45 | return {
46 | unsubscribe() {},
47 | }
48 | },
49 | }
50 |
51 | const xs = Kefir.fromESObservable(myObservable)
52 |
53 | xs.onValue((x: string) => {})
54 |
55 | // $ExpectError
56 | xs.onValue((x: number) => {})
57 |
58 | xs.onError((e: Error) => {})
59 |
60 | // $ExpectError
61 | xs.onError((e: number) => {})
62 | }
63 |
64 | function test_toESObservable() {
65 | const xs: Kefir.Observable = Kefir.constant('foo')
66 | const obs = xs.toESObservable()
67 |
68 | obs.subscribe({
69 | next: (x: string) => {},
70 | error: (e: Error) => {},
71 | complete: () => {},
72 | })
73 |
74 | obs.subscribe({
75 | // $ExpectError
76 | next: (x: number) => {},
77 | // $ExpectError
78 | error: (e: number) => {},
79 | complete: () => {},
80 | })
81 | }
82 |
--------------------------------------------------------------------------------
/test/flow/merge.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | const s1: Kefir.Observable = Kefir.constant(1)
6 | const s2: Kefir.Observable = Kefir.constant('two')
7 | const s3 = Kefir.constantError({foo: 1})
8 |
9 | const merged = Kefir.merge([s1, s2, s3])
10 |
11 | merged.observe(
12 | x => {
13 | const good: number | string = x
14 | // $ExpectError
15 | const bad1: number = x
16 | // $ExpectError
17 | const bad2: string = x
18 | },
19 | err => {
20 | const good: {foo: number} = err
21 | // $ExpectError
22 | const bad: number = err
23 | }
24 | )
25 |
--------------------------------------------------------------------------------
/test/flow/observe.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | const s1: Kefir.Observable = Kefir.constant(1)
6 |
7 | s1.observe({
8 | value(x) {
9 | const good: number = x
10 | // $ExpectError
11 | const bad: string = x
12 | },
13 | error: e => {},
14 | })
15 |
16 | class MyObserver {
17 | value(x) {
18 | const good: number = x
19 | // $ExpectError
20 | const bad: string = x
21 | }
22 | error(e) {}
23 | }
24 |
25 | s1.observe(new MyObserver())
26 |
27 | s1.observe(
28 | value => {},
29 | error => {},
30 | () => {}
31 | )
32 |
33 | s1.observe(value => {})
34 | s1.observe(null, error => {})
35 |
--------------------------------------------------------------------------------
/test/flow/pool.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | const pool: Kefir.Pool = Kefir.pool()
6 | pool.plug(Kefir.constant(1))
7 | // $ExpectError
8 | pool.plug(Kefir.constant('a'))
9 |
10 | pool.onValue(x => {
11 | const n: number = x
12 | // $ExpectError
13 | const s: string = x
14 | })
15 |
--------------------------------------------------------------------------------
/test/flow/propType.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | const prop = Kefir.constant(1)
6 |
7 | prop.onValue(x => {
8 | const n: number = x
9 | // $ExpectError
10 | const s: string = x
11 | })
12 |
--------------------------------------------------------------------------------
/test/flow/sampledBy.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | const source = Kefir.sequentially(0, [1, 2, 3, 4])
6 | const samples = Kefir.sequentially(0, ['one', 'one', 'one', 'one'])
7 |
8 | function test_sampledBy_noCombinator() {
9 | const xs = source.sampledBy(samples)
10 |
11 | xs.onValue((x: number) => {})
12 | }
13 |
14 | function test_sampledBy_withCombinator() {
15 | const xs = source.sampledBy(samples, (n: number, s: string) => s)
16 |
17 | // $ExpectError
18 | source.sampledBy(samples, (s: string, n: number) => s)
19 |
20 | xs.onValue((x: string) => {})
21 |
22 | // $ExpectError
23 | xs.onValue((x: number) => {})
24 | }
25 |
--------------------------------------------------------------------------------
/test/flow/scan.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | const source = Kefir.sequentially(10, [1, 2, 3, 4])
6 |
7 | function test_scan_noSeed() {
8 | const xs = source.scan((x, y) => x - y)
9 |
10 | xs.onValue((x: number) => {})
11 | }
12 |
13 | function test_scan_withSeed() {
14 | // $ExpectError
15 | source.scan((x, y) => x - y, 'foo')
16 |
17 | const xs = source.scan((x, y) => x + String(y), 'foo')
18 |
19 | xs.onValue((x: string) => {})
20 |
21 | // $ExpectError
22 | xs.onValue((x: number) => {})
23 | }
24 |
--------------------------------------------------------------------------------
/test/flow/setName.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 | import type {Observable} from '../../kefir'
5 |
6 | const s1: Observable = Kefir.constant(1)
7 |
8 | s1.setName('s1')
9 | // $ExpectError
10 | s1.setName()
11 |
12 | const s2: Observable = Kefir.constant('one')
13 | s1.setName(s2, 's1')
14 | // $ExpectError
15 | s1.setName(s2)
16 |
--------------------------------------------------------------------------------
/test/flow/thru.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 | import type {Observable} from '../../kefir'
5 |
6 | const obs: Observable = Kefir.constant('hello')
7 |
8 | const good: boolean = obs.thru((x: Observable): boolean => true)
9 |
10 | // $ExpectError
11 | const bad1: boolean = obs.thru((x: Observable): boolean => true)
12 |
13 | // $ExpectError
14 | const bad2: string = obs.thru((x: Observable): boolean => true)
15 |
--------------------------------------------------------------------------------
/test/flow/toProperty.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | function test_toProperty() {
6 | const xs = Kefir.sequentially(0, [1, 2, 3, 4])
7 | const p = xs.toProperty()
8 |
9 | p.onValue((x: number) => {})
10 |
11 | // $ExpectError
12 | p.onValue((x: string) => {})
13 | }
14 |
15 | function test_toProperty_getCurrent() {
16 | const xs = Kefir.sequentially(0, [1, 2, 3, 4])
17 | const p = xs.toProperty(() => 'foo')
18 |
19 | p.onValue((x: number | string) => {})
20 |
21 | // $ExpectError
22 | p.onValue((x: number) => {})
23 | }
24 |
--------------------------------------------------------------------------------
/test/flow/transforms.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | const prop = Kefir.constant(1)
6 | .map(x => {
7 | const n: number = x
8 | // $ExpectError
9 | const s: string = x
10 | return String(x)
11 | })
12 | .flatMap(x => {
13 | const s: string = x
14 | // $ExpectError
15 | const n: number = x
16 | return Kefir.later(10, parseInt(x))
17 | })
18 | .filter(x => {
19 | const n: number = x
20 | // $ExpectError
21 | const s: string = x
22 | return true
23 | })
24 | .flatMap(x => {
25 | if (Math.random() == 0) {
26 | return Kefir.never()
27 | } else {
28 | return Kefir.constant(String(x))
29 | }
30 | })
31 |
32 | prop.onValue(x => {
33 | const s: string = x
34 | // $ExpectError
35 | const n: number = x
36 | })
37 |
--------------------------------------------------------------------------------
/test/flow/vacuousObservables.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Some observables are guaranteed not to emit values or errors. For example,
3 | * `Kefir.constant(x)` produces an observable that never emits errors. In those
4 | * cases Kefir's type definitions use `empty` as a type parameter.
5 | *
6 | * @flow
7 | */
8 |
9 | import Kefir from '../../kefir'
10 |
11 | function test_Constant() {
12 | const c = Kefir.constant('foo')
13 |
14 | c.onValue((x: string) => {})
15 |
16 | // $ExpectError
17 | c.onValue((x: number) => {})
18 |
19 | // Error callbacks accept any argument type
20 | c.onError((e: Error) => {})
21 | }
22 |
23 | function testConstantError() {
24 | const c = Kefir.constantError(new Error('foo'))
25 |
26 | // Value callbacks accept any argument type
27 | c.onValue((x: string) => {})
28 | c.onValue((x: number) => {})
29 |
30 | c.onError((e: Error) => {})
31 |
32 | // $ExpectError
33 | c.onError((e: string) => {})
34 | }
35 |
36 | function testNever() {
37 | const never = Kefir.never()
38 |
39 | // Value and error callbacks accept any argument types
40 | never.onValue((x: string) => {})
41 | never.onValue((x: number) => {})
42 | never.onError((e: Error) => {})
43 |
44 | const merged = never.merge(Kefir.constant('foo'))
45 |
46 | merged.onValue((x: string) => {})
47 |
48 | // $ExpectError - the merged observable has `string` values, value callbacks must accept `string` arguments
49 | merged.onValue((x: number) => {})
50 |
51 | merged.onError((e: Error) => {})
52 | }
53 |
--------------------------------------------------------------------------------
/test/flow/zip.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import Kefir from '../../kefir'
4 |
5 | const as = Kefir.sequentially(0, [1, 2, 3, 4])
6 | const bs = Kefir.sequentially(0, ['foo', 'bar', 'baz'])
7 | const cs = Kefir.sequentially(0, [true, false, true])
8 |
9 | function test_zip_method_noCombinator() {
10 | const xs = as.zip(bs)
11 |
12 | xs.onValue((x: [number, string]) => {})
13 |
14 | // $ExpectError
15 | xs.onValue((x: Array<*>) => {})
16 | }
17 |
18 | function test_zip_method_withCombinator() {
19 | const xs = as.zip(bs, (n, s) => `${n}: ${s}`)
20 |
21 | xs.onValue((x: string) => {})
22 |
23 | // $ExpectError
24 | xs.onValue((x: [number, string]) => {})
25 | }
26 |
27 | function test_zip_noCombinator() {
28 | const xs = Kefir.zip([as, bs, cs])
29 |
30 | xs.onValue((x: [number, string, boolean]) => {})
31 |
32 | // $ExpectError
33 | xs.onValue((x: [boolean, string, number]) => {})
34 |
35 | xs.onValue((x: Array) => {})
36 |
37 | // $ExpectError
38 | xs.onValue((x: Array) => {})
39 | }
40 |
41 | function test_zip_withCombinator() {
42 | const xs = Kefir.zip([as, bs], (n, s) => `${n}: ${s}`)
43 |
44 | xs.onValue((x: string) => {})
45 | }
46 |
--------------------------------------------------------------------------------
/test/specs/before-end.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, end, expect} = require('../test-helpers')
2 |
3 | describe('beforeEnd', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().beforeEnd(() => {})).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.beforeEnd(() => {})).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () => {
15 | expect(send(stream(), [end()]).beforeEnd(() => 42)).to.emit([value(42, {current: true}), end({current: true})])
16 | })
17 |
18 | it('should handle events', () => {
19 | const a = stream()
20 | expect(a.beforeEnd(() => 42)).to.emit([value(1), value(2), value(42), end()], () =>
21 | send(a, [value(1), value(2), end()])
22 | )
23 | })
24 |
25 | it('errors should flow', () => {
26 | const a = stream()
27 | expect(a.beforeEnd(() => {})).to.flowErrors(a)
28 | })
29 | })
30 |
31 | describe('property', () => {
32 | it('should return property', () => {
33 | expect(prop().beforeEnd(() => {})).to.be.observable.property()
34 | })
35 |
36 | it('should activate/deactivate source', () => {
37 | const a = prop()
38 | expect(a.beforeEnd(() => {})).to.activate(a)
39 | })
40 |
41 | it('should be ended if source was ended', () => {
42 | expect(send(prop(), [end()]).beforeEnd(() => 42)).to.emit([value(42, {current: true}), end({current: true})])
43 | expect(send(prop(), [value(1), end()]).beforeEnd(() => 42)).to.emit([
44 | value(42, {current: true}),
45 | end({current: true}),
46 | ])
47 | })
48 |
49 | it('should handle events and current', () => {
50 | const a = send(prop(), [value(1)])
51 | expect(a.beforeEnd(() => 42)).to.emit([value(1, {current: true}), value(2), value(3), value(42), end()], () =>
52 | send(a, [value(2), value(3), end()])
53 | )
54 | })
55 |
56 | it('errors should flow', () => {
57 | const a = prop()
58 | expect(a.beforeEnd(() => {})).to.flowErrors(a)
59 | })
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/test/specs/buffer-while.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, end, expect} = require('../test-helpers')
2 |
3 | const not3 = x => x !== 3
4 |
5 | describe('bufferWhile', () => {
6 | describe('stream', () => {
7 | it('should stream', () => {
8 | expect(stream().bufferWhile(not3)).to.be.observable.stream()
9 | })
10 |
11 | it('should activate/deactivate source', () => {
12 | const a = stream()
13 | expect(a.bufferWhile(not3)).to.activate(a)
14 | })
15 |
16 | it('should be ended if source was ended', () =>
17 | expect(send(stream(), [end()]).bufferWhile(not3)).to.emit([end({current: true})]))
18 |
19 | it('should work correctly', () => {
20 | const a = stream()
21 | expect(a.bufferWhile(not3)).to.emit(
22 | [value([3]), value([1, 2, 3]), value([4, 3]), value([3]), value([5, 6]), end()],
23 | () => send(a, [value(3), value(1), value(2), value(3), value(4), value(3), value(3), value(5), value(6), end()])
24 | )
25 | })
26 |
27 | it('should not flush buffer on end if {flushOnEnd: false}', () => {
28 | const a = stream()
29 | expect(a.bufferWhile(not3, {flushOnEnd: false})).to.emit(
30 | [value([3]), value([1, 2, 3]), value([4, 3]), value([3]), end()],
31 | () => send(a, [value(3), value(1), value(2), value(3), value(4), value(3), value(3), value(5), value(6), end()])
32 | )
33 | })
34 |
35 | it('errors should flow', () => {
36 | const a = stream()
37 | expect(a.bufferWhile(not3)).to.flowErrors(a)
38 | })
39 | })
40 |
41 | describe('property', () => {
42 | it('should property', () => {
43 | expect(prop().bufferWhile(not3)).to.be.observable.property()
44 | })
45 |
46 | it('should activate/deactivate source', () => {
47 | const a = prop()
48 | expect(a.bufferWhile(not3)).to.activate(a)
49 | })
50 |
51 | it('should be ended if source was ended', () => {
52 | expect(send(prop(), [end()]).bufferWhile(not3)).to.emit([end({current: true})])
53 | expect(send(prop(), [value(3), end()]).bufferWhile(not3)).to.emit([
54 | value([3], {current: true}),
55 | end({current: true}),
56 | ])
57 | expect(send(prop(), [value(2), end()]).bufferWhile(not3)).to.emit([
58 | value([2], {current: true}),
59 | end({current: true}),
60 | ])
61 | expect(send(prop(), [value(3), end()]).bufferWhile(not3, {flushOnEnd: false})).to.emit([
62 | value([3], {current: true}),
63 | end({current: true}),
64 | ])
65 | expect(send(prop(), [value(2), end()]).bufferWhile(not3, {flushOnEnd: false})).to.emit([end({current: true})])
66 | })
67 |
68 | it('should work correctly', () => {
69 | let a = send(prop(), [value(3)])
70 | expect(a.bufferWhile(not3)).to.emit(
71 | [value([3], {current: true}), value([1, 2, 3]), value([4, 3]), value([3]), value([5, 6]), end()],
72 | () => send(a, [value(1), value(2), value(3), value(4), value(3), value(3), value(5), value(6), end()])
73 | )
74 | a = send(prop(), [value(1)])
75 | expect(a.bufferWhile(not3)).to.emit([value([1, 2, 3]), value([5, 6]), end()], () =>
76 | send(a, [value(2), value(3), value(5), value(6), end()])
77 | )
78 | })
79 |
80 | it('errors should flow', () => {
81 | const a = prop()
82 | expect(a.bufferWhile(not3)).to.flowErrors(a)
83 | })
84 | })
85 | })
86 |
--------------------------------------------------------------------------------
/test/specs/changes.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers')
2 |
3 | const streamWithCurrent = event => Kefir.stream(emitter => emitter.emitEvent(event))
4 |
5 | describe('changes', () => {
6 | describe('stream', () => {
7 | it('should stream', () => {
8 | expect(stream().changes()).to.be.observable.stream()
9 | })
10 |
11 | it('should activate/deactivate source', () => {
12 | const a = stream()
13 | expect(a.changes()).to.activate(a)
14 | })
15 |
16 | it('should be ended if source was ended', () => {
17 | expect(send(stream(), [end()]).changes()).to.emit([end({current: true})])
18 | })
19 |
20 | it('test `streamWithCurrent` helper', () => {
21 | expect(streamWithCurrent({type: 'value', value: 1})).to.emit([value(1, {current: true})])
22 | expect(streamWithCurrent({type: 'error', value: 1})).to.emit([error(1, {current: true})])
23 | })
24 |
25 | it('should handle events and current', () => {
26 | let a = streamWithCurrent({type: 'value', value: 1})
27 | expect(a.changes()).to.emit([value(value(2)), error(5), value(value(3)), end()], () =>
28 | send(a, [value(value(2)), error(5), value(value(3)), end()])
29 | )
30 | a = streamWithCurrent({type: 'error', value: 1})
31 | expect(a.changes()).to.emit([value(2), error(5), value(3), end()], () =>
32 | send(a, [value(2), error(5), value(3), end()])
33 | )
34 | })
35 | })
36 |
37 | describe('property', () => {
38 | it('should stream', () => {
39 | expect(prop().changes()).to.be.observable.stream()
40 | })
41 |
42 | it('should activate/deactivate source', () => {
43 | const a = prop()
44 | expect(a.changes()).to.activate(a)
45 | })
46 |
47 | it('should be ended if source was ended', () => {
48 | expect(send(prop(), [end()]).changes()).to.emit([end({current: true})])
49 | })
50 |
51 | it('should handle events and current', () => {
52 | const a = send(prop(), [value(1), error(4)])
53 | expect(a.changes()).to.emit([value(2), error(5), value(3), end()], () =>
54 | send(a, [value(2), error(5), value(3), end()])
55 | )
56 | })
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/test/specs/constant-error.js:
--------------------------------------------------------------------------------
1 | const {Kefir, error, end, expect} = require('../test-helpers')
2 |
3 | describe('constantError', () => {
4 | it('should return property', () => {
5 | expect(Kefir.constantError(1)).to.be.observable.property()
6 | })
7 |
8 | it('should be ended and has a current error', () => {
9 | expect(Kefir.constantError(1)).to.emit([error(1, {current: true}), end({current: true})])
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/test/specs/constant.js:
--------------------------------------------------------------------------------
1 | const {Kefir, expect, value, end} = require('../test-helpers')
2 |
3 | describe('constant', () => {
4 | it('should return property', () => {
5 | expect(Kefir.constant(1)).to.be.observable.property()
6 | })
7 |
8 | it('should be ended and has current', () => {
9 | expect(Kefir.constant(1)).to.emit([value(1, {current: true}), end({current: true})])
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/test/specs/delay.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, end, shakyTimeTest, expect} = require('../test-helpers')
2 |
3 | describe('delay', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().delay(100)).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.delay(100)).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () => {
15 | expect(send(stream(), [end()]).delay(100)).to.emit([end({current: true})])
16 | })
17 |
18 | it('should handle events', () => {
19 | const a = stream()
20 | expect(a.delay(100)).to.emitInTime(
21 | [
22 | [100, value(1)],
23 | [150, value(2)],
24 | [250, end()],
25 | ],
26 | tick => {
27 | send(a, [value(1)])
28 | tick(50)
29 | send(a, [value(2)])
30 | tick(100)
31 | send(a, [end()])
32 | }
33 | )
34 | })
35 |
36 | it('errors should flow', () => {
37 | const a = stream()
38 | expect(a.delay(100)).to.flowErrors(a)
39 | })
40 |
41 | // see https://github.com/kefirjs/kefir/issues/134
42 | describe('works with undependable setTimeout', () => {
43 | shakyTimeTest(expectToEmitOverShakyTime => {
44 | const a = stream()
45 | expectToEmitOverShakyTime(
46 | a.delay(10),
47 | [
48 | [10, value(1)],
49 | [15, value(4)],
50 | [15, end()],
51 | ],
52 | tick => {
53 | send(a, [value(1)])
54 | tick(5)
55 | send(a, [value(4)])
56 | send(a, [end()])
57 | }
58 | )
59 | })
60 | })
61 | })
62 |
63 | describe('property', () => {
64 | it('should return property', () => {
65 | expect(prop().delay(100)).to.be.observable.property()
66 | })
67 |
68 | it('should activate/deactivate source', () => {
69 | const a = prop()
70 | expect(a.delay(100)).to.activate(a)
71 | })
72 |
73 | it('should be ended if source was ended', () => {
74 | expect(send(prop(), [end()]).delay(100)).to.emit([end({current: true})])
75 | })
76 |
77 | it('should handle events and current', () => {
78 | const a = send(prop(), [value(1)])
79 | expect(a.delay(100)).to.emitInTime(
80 | [
81 | [0, value(1, {current: true})],
82 | [100, value(2)],
83 | [150, value(3)],
84 | [250, end()],
85 | ],
86 | tick => {
87 | send(a, [value(2)])
88 | tick(50)
89 | send(a, [value(3)])
90 | tick(100)
91 | send(a, [end()])
92 | }
93 | )
94 | })
95 |
96 | it('errors should flow', () => {
97 | const a = prop()
98 | expect(a.delay(100)).to.flowErrors(a)
99 | })
100 | })
101 | })
102 |
--------------------------------------------------------------------------------
/test/specs/diff.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, end, expect} = require('../test-helpers')
2 |
3 | const noop = () => {}
4 | const minus = (prev, next) => prev - next
5 |
6 | describe('diff', () => {
7 | describe('stream', () => {
8 | it('should return stream', () => {
9 | expect(stream().diff(noop, 0)).to.be.observable.stream()
10 | })
11 |
12 | it('should activate/deactivate source', () => {
13 | const a = stream()
14 | expect(a.diff(noop, 0)).to.activate(a)
15 | })
16 |
17 | it('should be ended if source was ended', () => {
18 | expect(send(stream(), [end()]).diff(noop, 0)).to.emit([end({current: true})])
19 | })
20 |
21 | it('should handle events', () => {
22 | const a = stream()
23 | expect(a.diff(minus, 0)).to.emit([value(-1), value(-2), end()], () => send(a, [value(1), value(3), end()]))
24 | })
25 |
26 | it('works without fn argument', () => {
27 | const a = stream()
28 | expect(a.diff(null, 0)).to.emit([value([0, 1]), value([1, 3]), end()], () => send(a, [value(1), value(3), end()]))
29 | })
30 |
31 | it('if no seed provided uses first value as seed', () => {
32 | let a = stream()
33 | expect(a.diff(minus)).to.emit([value(-1), value(-2), end()], () => send(a, [value(0), value(1), value(3), end()]))
34 | a = stream()
35 | expect(a.diff()).to.emit([value([0, 1]), value([1, 3]), end()], () =>
36 | send(a, [value(0), value(1), value(3), end()])
37 | )
38 | })
39 |
40 | it('errors should flow', () => {
41 | const a = stream()
42 | expect(a.diff()).to.flowErrors(a)
43 | })
44 | })
45 |
46 | describe('property', () => {
47 | it('should return property', () => {
48 | expect(prop().diff(noop, 0)).to.be.observable.property()
49 | })
50 |
51 | it('should activate/deactivate source', () => {
52 | const a = prop()
53 | expect(a.diff(noop, 0)).to.activate(a)
54 | })
55 |
56 | it('should be ended if source was ended', () => {
57 | expect(send(prop(), [end()]).diff(noop, 0)).to.emit([end({current: true})])
58 | })
59 |
60 | it('should handle events and current', () => {
61 | const a = send(prop(), [value(1)])
62 | expect(a.diff(minus, 0)).to.emit([value(-1, {current: true}), value(-2), value(-3), end()], () =>
63 | send(a, [value(3), value(6), end()])
64 | )
65 | })
66 |
67 | it('works without fn argument', () => {
68 | const a = send(prop(), [value(1)])
69 | expect(a.diff(null, 0)).to.emit([value([0, 1], {current: true}), value([1, 3]), value([3, 6]), end()], () =>
70 | send(a, [value(3), value(6), end()])
71 | )
72 | })
73 |
74 | it('if no seed provided uses first value as seed', () => {
75 | let a = send(prop(), [value(0)])
76 | expect(a.diff(minus)).to.emit([value(-1), value(-2), end()], () => send(a, [value(1), value(3), end()]))
77 | a = send(prop(), [value(0)])
78 | expect(a.diff()).to.emit([value([0, 1]), value([1, 3]), end()], () => send(a, [value(1), value(3), end()]))
79 | })
80 |
81 | it('errors should flow', () => {
82 | const a = prop()
83 | expect(a.diff()).to.flowErrors(a)
84 | })
85 | })
86 | })
87 |
--------------------------------------------------------------------------------
/test/specs/end-on-error.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers')
2 |
3 | describe('endOnError', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().endOnError()).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.endOnError()).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () => {
15 | expect(send(stream(), [end()]).endOnError()).to.emit([end({current: true})])
16 | })
17 |
18 | it('should handle events', () => {
19 | let a = stream()
20 | expect(a.endOnError()).to.emit([value(1), error(5), end()], () => send(a, [value(1), error(5), value(2)]))
21 | a = stream()
22 | expect(a.endOnError()).to.emit([value(1), value(2), end()], () => send(a, [value(1), value(2), end()]))
23 | })
24 | })
25 |
26 | describe('property', () => {
27 | it('should return property', () => {
28 | expect(prop().endOnError()).to.be.observable.property()
29 | })
30 |
31 | it('should activate/deactivate source', () => {
32 | const a = prop()
33 | expect(a.endOnError()).to.activate(a)
34 | })
35 |
36 | it('should be ended if source was ended', () => {
37 | expect(send(prop(), [end()]).endOnError()).to.emit([end({current: true})])
38 | })
39 |
40 | it('should handle events and current', () => {
41 | let a = send(prop(), [value(1)])
42 | expect(a.endOnError()).to.emit([value(1, {current: true}), error(5), end()], () => send(a, [error(5), value(2)]))
43 | a = send(prop(), [value(1)])
44 | expect(a.endOnError()).to.emit([value(1, {current: true}), value(2), end()], () => send(a, [value(2), end()]))
45 | })
46 |
47 | it('should handle currents', () => {
48 | let a = send(prop(), [error(-1, {current: true})])
49 | expect(a.endOnError()).to.emit([error(-1, {current: true}), end({current: true})])
50 | a = send(prop(), [error(-1, {current: true}), end()])
51 | expect(a.endOnError()).to.emit([error(-1, {current: true}), end({current: true})])
52 | a = send(prop(), [value(1)])
53 | expect(a.endOnError()).to.emit([value(1, {current: true})])
54 | a = send(prop(), [value(1), end()])
55 | expect(a.endOnError()).to.emit([value(1, {current: true}), end({current: true})])
56 | })
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/test/specs/errors-to-values.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers')
2 |
3 | const handler = x => ({
4 | convert: x >= 0,
5 | value: x * 3,
6 | })
7 |
8 | describe('errorsToValues', () => {
9 | describe('stream', () => {
10 | it('should return stream', () => {
11 | expect(stream().errorsToValues(() => {})).to.be.observable.stream()
12 | })
13 |
14 | it('should activate/deactivate source', () => {
15 | const a = stream()
16 | expect(a.errorsToValues(() => {})).to.activate(a)
17 | })
18 |
19 | it('should be ended if source was ended', () => {
20 | expect(send(stream(), [end()]).errorsToValues(() => {})).to.emit([end({current: true})])
21 | })
22 |
23 | it('should handle events', () => {
24 | const a = stream()
25 | expect(a.errorsToValues(handler)).to.emit([value(1), value(6), error(-1), value(9), value(4), end()], () =>
26 | send(a, [value(1), error(2), error(-1), error(3), value(4), end()])
27 | )
28 | })
29 |
30 | it('default handler should convert all errors', () => {
31 | const a = stream()
32 | expect(a.errorsToValues()).to.emit([value(1), value(2), value(-1), value(3), value(4), end()], () =>
33 | send(a, [value(1), error(2), error(-1), error(3), value(4), end()])
34 | )
35 | })
36 | })
37 |
38 | describe('property', () => {
39 | it('should return property', () => {
40 | expect(prop().errorsToValues(() => {})).to.be.observable.property()
41 | })
42 |
43 | it('should activate/deactivate source', () => {
44 | const a = prop()
45 | expect(a.errorsToValues(() => {})).to.activate(a)
46 | })
47 |
48 | it('should be ended if source was ended', () => {
49 | expect(send(prop(), [end()]).errorsToValues(() => {})).to.emit([end({current: true})])
50 | })
51 |
52 | it('should handle events', () => {
53 | const a = send(prop(), [value(1)])
54 | expect(a.errorsToValues(handler)).to.emit(
55 | [value(1, {current: true}), value(6), error(-1), value(9), value(4), end()],
56 | () => send(a, [error(2), error(-1), error(3), value(4), end()])
57 | )
58 | })
59 |
60 | it('should handle currents', () => {
61 | let a = send(prop(), [error(-2)])
62 | expect(a.errorsToValues(handler)).to.emit([error(-2, {current: true})])
63 | a = send(prop(), [error(2)])
64 | expect(a.errorsToValues(handler)).to.emit([value(6, {current: true})])
65 | a = send(prop(), [value(1)])
66 | expect(a.errorsToValues(handler)).to.emit([value(1, {current: true})])
67 | })
68 | })
69 | })
70 |
--------------------------------------------------------------------------------
/test/specs/es-observable.js:
--------------------------------------------------------------------------------
1 | const $$observable = require('symbol-observable').default
2 | const Observable = require('zen-observable')
3 | const {stream, send, value, error, end, expect} = require('../test-helpers')
4 |
5 | describe('[Symbol.observable]', () => {
6 | it('outputs a compatible Observable', done => {
7 | const a = stream()
8 | const values = []
9 | const observable = Observable.from(a)
10 | observable.subscribe({
11 | next(x) {
12 | values.push(x)
13 | },
14 | complete() {
15 | expect(values).to.deep.equal([1, 2, 3])
16 | done()
17 | },
18 | })
19 | send(a, [value(1), value(2), value(3), end()])
20 | })
21 |
22 | it('unsubscribes stream after an error', () => {
23 | const a = stream()
24 | const values = []
25 | const observable = a[$$observable]()
26 | observable.subscribe({
27 | next(x) {
28 | values.push(x)
29 | },
30 | })
31 | send(a, [value(1), error(2), value(3)])
32 | expect(values).to.deep.equal([1])
33 | })
34 |
35 | it('subscribe() returns an subscribtion object with unsubscribe method', () => {
36 | const a = stream()
37 | const values = []
38 | const observable = a[$$observable]()
39 | const subscribtion = observable.subscribe({
40 | next(x) {
41 | values.push(x)
42 | },
43 | })
44 | send(a, [value(1)])
45 | subscribtion.unsubscribe()
46 | send(a, [value(2)])
47 | expect(values).to.deep.equal([1])
48 | })
49 |
50 | it('subscribtion object has `closed` property', () => {
51 | const a = stream()
52 | const observable = a[$$observable]()
53 | const subscribtion = observable.subscribe({next() {}})
54 | expect(subscribtion.closed).to.deep.equal(false)
55 | subscribtion.unsubscribe()
56 | expect(subscribtion.closed).to.deep.equal(true)
57 | })
58 |
59 | it('supports subscribe(onNext, onError, onCompete) format', () => {
60 | const a = stream()
61 | const values = []
62 | const errors = []
63 | const completes = []
64 | const onValue = x => values.push(x)
65 | const onError = x => errors.push(x)
66 | const onComplete = x => completes.push(x)
67 | const observable = a[$$observable]()
68 | observable.subscribe(onValue, onError, onComplete)
69 | send(a, [value(1), error(2)])
70 | expect(values).to.deep.equal([1])
71 | expect(errors).to.deep.equal([2])
72 | expect(completes).to.deep.equal([undefined])
73 | })
74 |
75 | it('closed=true after end', () => {
76 | const a = stream()
77 | const observable = a[$$observable]()
78 | const subscribtion = observable.subscribe(() => {})
79 | expect(subscribtion.closed).to.deep.equal(false)
80 | send(a, [end()])
81 | expect(subscribtion.closed).to.deep.equal(true)
82 | })
83 | })
84 |
--------------------------------------------------------------------------------
/test/specs/filter-errors.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers')
2 |
3 | describe('filterErrors', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().filterErrors(() => {})).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.filterErrors(() => {})).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () =>
15 | expect(send(stream(), [end()]).filterErrors(() => {})).to.emit([end({current: true})]))
16 |
17 | it('should handle events', () => {
18 | const a = stream()
19 | expect(a.filterErrors(x => x > 3)).to.emit([value(-1), error(4), value(-2), error(5), error(6), end()], () =>
20 | send(a, [value(-1), error(1), error(2), error(3), error(4), value(-2), error(5), error(0), error(6), end()])
21 | )
22 | })
23 |
24 | it('shoud use id as default predicate', () => {
25 | const a = stream()
26 | expect(a.filterErrors()).to.emit([value(-1), error(4), value(-2), error(5), value(false), error(6), end()], () =>
27 | send(a, [
28 | value(-1),
29 | error(0),
30 | error(false),
31 | error(null),
32 | error(4),
33 | value(-2),
34 | error(5),
35 | error(''),
36 | value(false),
37 | error(6),
38 | end(),
39 | ])
40 | )
41 | })
42 | })
43 |
44 | describe('property', () => {
45 | it('should return property', () => {
46 | expect(prop().filterErrors(() => {})).to.be.observable.property()
47 | })
48 |
49 | it('should activate/deactivate source', () => {
50 | const a = prop()
51 | expect(a.filterErrors(() => {})).to.activate(a)
52 | })
53 |
54 | it('should be ended if source was ended', () =>
55 | expect(send(prop(), [end()]).filterErrors(() => {})).to.emit([end({current: true})]))
56 |
57 | it('should handle events and current', () => {
58 | const a = send(prop(), [error(5)])
59 | expect(a.filterErrors(x => x > 3)).to.emit(
60 | [error(5, {current: true}), error(4), value(-2), error(6), end()],
61 | () => send(a, [error(1), error(2), error(3), error(4), value(-2), error(0), error(6), end()])
62 | )
63 | })
64 |
65 | it('should handle current (not pass)', () => {
66 | const a = send(prop(), [error(0)])
67 | expect(a.filterErrors(x => x > 2)).to.emit([])
68 | })
69 |
70 | it('shoud use id as default predicate', () => {
71 | let a = send(prop(), [error(5)])
72 | expect(a.filterErrors()).to.emit([error(5, {current: true}), error(4), value(-2), error(6), end()], () =>
73 | send(a, [error(0), error(false), error(null), error(4), value(-2), error(undefined), error(6), end()])
74 | )
75 | a = send(prop(), [error(0)])
76 | expect(a.filterErrors()).to.emit([])
77 | })
78 | })
79 | })
80 |
--------------------------------------------------------------------------------
/test/specs/filter.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers')
2 |
3 | describe('filter', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().filter(() => {})).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.filter(() => {})).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () => {
15 | expect(send(stream(), [end()]).filter(() => {})).to.emit([end({current: true})])
16 | })
17 |
18 | it('should handle events', () => {
19 | const a = stream()
20 | expect(a.filter(x => x > 3)).to.emit([value(4), value(5), error(7), value(6), end()], () =>
21 | send(a, [value(1), value(2), value(4), value(5), value(0), error(7), value(6), end()])
22 | )
23 | })
24 |
25 | it('shoud use id as default predicate', () => {
26 | const a = stream()
27 | expect(a.filter()).to.emit([value(4), value(5), error(7), value(6), end()], () =>
28 | send(a, [value(0), value(0), value(4), value(5), value(0), error(7), value(6), end()])
29 | )
30 | })
31 | })
32 |
33 | describe('property', () => {
34 | it('should return property', () => {
35 | expect(prop().filter(() => {})).to.be.observable.property()
36 | })
37 |
38 | it('should activate/deactivate source', () => {
39 | const a = prop()
40 | expect(a.filter(() => {})).to.activate(a)
41 | })
42 |
43 | it('should be ended if source was ended', () => {
44 | expect(send(prop(), [end()]).filter(() => {})).to.emit([end({current: true})])
45 | })
46 |
47 | it('should handle events and current', () => {
48 | let a = send(prop(), [value(5)])
49 | expect(a.filter(x => x > 2)).to.emit([value(5, {current: true}), value(4), error(7), value(3), end()], () =>
50 | send(a, [value(4), error(7), value(3), value(2), value(1), end()])
51 | )
52 | a = send(prop(), [error(0)])
53 | expect(a.filter(x => x > 2)).to.emit([error(0, {current: true}), value(4), error(7), value(3), end()], () =>
54 | send(a, [value(4), error(7), value(3), value(2), value(1), end()])
55 | )
56 | })
57 |
58 | it('should handle current (not pass)', () => {
59 | const a = send(prop(), [value(1), error(0)])
60 | expect(a.filter(x => x > 2)).to.emit([error(0, {current: true})])
61 | })
62 |
63 | it('shoud use id as default predicate', () => {
64 | let a = send(prop(), [value(0)])
65 | expect(a.filter()).to.emit([value(4), error(-value(2)), value(5), value(6), end()], () =>
66 | send(a, [value(0), value(4), error(-value(2)), value(5), value(0), value(6), end()])
67 | )
68 | a = send(prop(), [value(1)])
69 | expect(a.filter()).to.emit(
70 | [value(1, {current: true}), value(4), error(-value(2)), value(5), value(6), end()],
71 | () => send(a, [value(0), value(4), error(-value(2)), value(5), value(0), value(6), end()])
72 | )
73 | })
74 | })
75 | })
76 |
--------------------------------------------------------------------------------
/test/specs/flatten.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('flatten', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().flatten(() => {})).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.flatten(() => {})).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () =>
15 | expect(send(stream(), [end()]).flatten(() => {})).to.emit([end({current: true})]))
16 |
17 | it('should handle events', () => {
18 | const a = stream()
19 | expect(
20 | a.flatten(x => {
21 | if (x > 1) {
22 | return __range__(1, x, true)
23 | } else {
24 | return []
25 | }
26 | })
27 | ).to.emit([value(1), value(2), error(4), value(1), value(2), value(3), end()], () =>
28 | send(a, [value(1), value(2), error(4), value(3), end()])
29 | )
30 | })
31 |
32 | it('if no `fn` provided should use the `id` function by default', () => {
33 | const a = stream()
34 | expect(a.flatten()).to.emit([value(1), value(2), value(3), end()], () =>
35 | send(a, [value([1]), value([]), value([2, 3]), end()])
36 | )
37 | })
38 | })
39 |
40 | describe('property', () => {
41 | it('should return stream', () => {
42 | expect(prop().flatten(() => {})).to.be.observable.stream()
43 | })
44 |
45 | it('should activate/deactivate source', () => {
46 | const a = prop()
47 | expect(a.flatten(() => {})).to.activate(a)
48 | })
49 |
50 | it('should be ended if source was ended', () =>
51 | expect(send(prop(), [end()]).flatten(() => {})).to.emit([end({current: true})]))
52 |
53 | it('should handle events (handler skips current)', () => {
54 | const a = send(prop(), [value(1)])
55 | expect(
56 | a.flatten(x => {
57 | if (x > 1) {
58 | return __range__(1, x, true)
59 | } else {
60 | return []
61 | }
62 | })
63 | ).to.emit([value(1), value(2), error(4), value(1), value(2), value(3), end()], () =>
64 | send(a, [value(2), error(4), value(3), end()])
65 | )
66 | })
67 |
68 | it('should handle current correctly', () => {
69 | expect(send(prop(), [value(1)]).flatten(x => [x])).to.emit([value(1, {current: true})])
70 | expect(send(prop(), [error(0)]).flatten(() => {})).to.emit([error(0, {current: true})])
71 | })
72 |
73 | it('should handle multiple currents correctly', () =>
74 | expect(send(prop(), [value(2)]).flatten(x => __range__(1, x, true))).to.emit([
75 | value(1, {current: true}),
76 | value(2, {current: true}),
77 | ]))
78 | })
79 | })
80 |
81 | function __range__(left, right, inclusive) {
82 | let range = []
83 | let ascending = left < right
84 | let end = !inclusive ? right : ascending ? right + 1 : right - 1
85 | for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) {
86 | range.push(i)
87 | }
88 | return range
89 | }
90 |
--------------------------------------------------------------------------------
/test/specs/from-callback.js:
--------------------------------------------------------------------------------
1 | const {activate, deactivate, Kefir, value, end, expect} = require('../test-helpers')
2 |
3 | describe('fromCallback', () => {
4 | it('should return stream', () => {
5 | expect(Kefir.fromCallback(() => {})).to.be.observable.stream()
6 | })
7 |
8 | it('should not be ended', () => {
9 | expect(Kefir.fromCallback(() => {})).to.emit([])
10 | })
11 |
12 | it('should call `callbackConsumer` on first activation, and only on first', () => {
13 | let count = 0
14 | const s = Kefir.fromCallback(() => count++)
15 | expect(count).to.equal(0)
16 | activate(s)
17 | expect(count).to.equal(1)
18 | deactivate(s)
19 | activate(s)
20 | deactivate(s)
21 | activate(s)
22 | expect(count).to.equal(1)
23 | })
24 |
25 | it('should emit first result and end after that', () => {
26 | let cb = null
27 | expect(Kefir.fromCallback(_cb => (cb = _cb))).to.emit([value(1), end()], () => cb(1))
28 | })
29 |
30 | it('should work after deactivation/activate cicle', () => {
31 | let cb = null
32 | const s = Kefir.fromCallback(_cb => (cb = _cb))
33 | activate(s)
34 | deactivate(s)
35 | activate(s)
36 | deactivate(s)
37 | expect(s).to.emit([value(1), end()], () => cb(1))
38 | })
39 |
40 | it('should emit a current, if `callback` is called immediately in `callbackConsumer`', () => {
41 | expect(Kefir.fromCallback(cb => cb(1))).to.emit([value(1, {current: true}), end({current: true})])
42 | })
43 | })
44 |
--------------------------------------------------------------------------------
/test/specs/from-es-observable.js:
--------------------------------------------------------------------------------
1 | const Observable = require('zen-observable')
2 | const {of: observableOf} = require('rxjs')
3 | const {activate, deactivate, Kefir, expect} = require('../test-helpers')
4 |
5 | describe('fromESObservable', () => {
6 | it('turns an ES7 observable into a stream', () => {
7 | expect(Kefir.fromESObservable(Observable.of(1, 2))).to.be.observable.stream()
8 | })
9 |
10 | it('emits events from observable to stream', done => {
11 | const stream = Kefir.fromESObservable(Observable.of(1, 2))
12 | const values = []
13 | stream.onValue(value => values.push(value))
14 | return stream.onEnd(() => {
15 | expect(values).to.deep.equal([1, 2])
16 | return done()
17 | })
18 | })
19 |
20 | it('ends stream after an error', done => {
21 | const observable = new Observable(observer => {
22 | observer.next(1)
23 | return observer.error()
24 | })
25 | return Kefir.fromESObservable(observable).onEnd(() => done())
26 | })
27 |
28 | it('turns an RxJS observable into a Kefir stream', done => {
29 | const stream = Kefir.fromESObservable(observableOf('hello world'))
30 | const values = []
31 | stream.onValue(value => values.push(value))
32 | return stream.onEnd(() => {
33 | expect(values).to.deep.equal(['hello world'])
34 | return done()
35 | })
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/test/specs/from-node-callback.js:
--------------------------------------------------------------------------------
1 | const {activate, deactivate, Kefir, value, error, end, expect} = require('../test-helpers')
2 |
3 | describe('fromNodeCallback', () => {
4 | it('should return stream', () => {
5 | expect(Kefir.fromNodeCallback(() => {})).to.be.observable.stream()
6 | })
7 |
8 | it('should not be ended', () => {
9 | expect(Kefir.fromNodeCallback(() => {})).to.emit([])
10 | })
11 |
12 | it('should call `callbackConsumer` on first activation, and only on first', () => {
13 | let count = 0
14 | const s = Kefir.fromNodeCallback(() => count++)
15 | expect(count).to.equal(0)
16 | activate(s)
17 | expect(count).to.equal(1)
18 | deactivate(s)
19 | activate(s)
20 | deactivate(s)
21 | activate(s)
22 | expect(count).to.equal(1)
23 | })
24 |
25 | it('should emit first result and end after that', () => {
26 | let cb = null
27 | expect(Kefir.fromNodeCallback(_cb => (cb = _cb))).to.emit([value(1), end()], () => cb(null, 1))
28 | })
29 |
30 | it('should emit first error and end after that', () => {
31 | let cb = null
32 | expect(Kefir.fromNodeCallback(_cb => (cb = _cb))).to.emit([error(-1), end()], () => cb(-1))
33 | })
34 |
35 | it('should work after deactivation/activate cicle', () => {
36 | let cb = null
37 | const s = Kefir.fromNodeCallback(_cb => (cb = _cb))
38 | activate(s)
39 | deactivate(s)
40 | activate(s)
41 | deactivate(s)
42 | expect(s).to.emit([value(1), end()], () => cb(null, 1))
43 | })
44 |
45 | it('should emit a current, if `callback` is called immediately in `callbackConsumer`', () => {
46 | expect(Kefir.fromNodeCallback(cb => cb(null, 1))).to.emit([value(1, {current: true}), end({current: true})])
47 |
48 | expect(Kefir.fromNodeCallback(cb => cb(-1))).to.emit([error(-1, {current: true}), end({current: true})])
49 | })
50 | })
51 |
--------------------------------------------------------------------------------
/test/specs/from-poll.js:
--------------------------------------------------------------------------------
1 | const {value, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('fromPoll', () => {
4 | it('should return stream', () => {
5 | expect(Kefir.fromPoll(100, () => {})).to.be.observable.stream()
6 | })
7 |
8 | it('should emit whatever fn returns at certain time', () => {
9 | let i = 0
10 | expect(Kefir.fromPoll(100, () => ++i)).to.emitInTime(
11 | [
12 | [100, value(1)],
13 | [200, value(2)],
14 | [300, value(3)],
15 | ],
16 | undefined,
17 | {timeLimit: 350}
18 | )
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/test/specs/from-promise.js:
--------------------------------------------------------------------------------
1 | const {activate, deactivate, value, error, end, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('fromPromise', () => {
4 | const inProgress = {
5 | then() {},
6 | }
7 |
8 | const fulfilledSync = {
9 | then(onSuccess) {
10 | return onSuccess(1)
11 | },
12 | }
13 |
14 | const failedSync = {
15 | then(onSuccess, onError) {
16 | return onError(1)
17 | },
18 | }
19 |
20 | const fulfilledAsync = {
21 | then(onSuccess) {
22 | const fulfill = () => onSuccess(1)
23 | return setTimeout(fulfill, 1000)
24 | },
25 | }
26 |
27 | const failedAsync = {
28 | then(onSuccess, onError) {
29 | const fail = () => onError(1)
30 | return setTimeout(fail, 1000)
31 | },
32 | }
33 |
34 | it('should return property', () => {
35 | expect(Kefir.fromPromise(inProgress)).to.be.observable.property()
36 | })
37 |
38 | it('should call `property.then` on first activation, and only on first', () => {
39 | let count = 0
40 | const s = Kefir.fromPromise({
41 | then() {
42 | return count++
43 | },
44 | })
45 | expect(count).to.equal(0)
46 | activate(s)
47 | expect(count).to.equal(1)
48 | deactivate(s)
49 | activate(s)
50 | deactivate(s)
51 | activate(s)
52 | expect(count).to.equal(1)
53 | })
54 |
55 | it('should call `property.done`', () => {
56 | let count = 0
57 | const s = Kefir.fromPromise({
58 | then() {
59 | return this
60 | },
61 | done() {
62 | return count++
63 | },
64 | })
65 | expect(count).to.equal(0)
66 | activate(s)
67 | expect(count).to.equal(1)
68 | deactivate(s)
69 | activate(s)
70 | deactivate(s)
71 | activate(s)
72 | expect(count).to.equal(1)
73 | })
74 |
75 | it('should work correctly with inProgress property', () => {
76 | expect(Kefir.fromPromise(inProgress)).to.emitInTime([])
77 | })
78 |
79 | it('... with fulfilledSync property', () => {
80 | expect(Kefir.fromPromise(fulfilledSync)).to.emit([value(1, {current: true}), end({current: true})])
81 | })
82 |
83 | it('... with failedSync property', () =>
84 | expect(Kefir.fromPromise(failedSync)).to.emit([error(1, {current: true}), end({current: true})]))
85 |
86 | it('... with fulfilledAsync property', () => {
87 | const a = Kefir.fromPromise(fulfilledAsync)
88 | expect(a).to.emitInTime([
89 | [1000, value(1)],
90 | [1000, end()],
91 | ])
92 | expect(a).to.emit([value(1, {current: true}), end({current: true})])
93 | })
94 |
95 | it('... with failedAsync property', () => {
96 | const a = Kefir.fromPromise(failedAsync)
97 | expect(a).to.emitInTime([
98 | [1000, error(1)],
99 | [1000, end()],
100 | ])
101 | expect(a).to.emit([error(1, {current: true}), end({current: true})])
102 | })
103 | })
104 |
--------------------------------------------------------------------------------
/test/specs/ignore-end.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, end, expect} = require('../test-helpers')
2 |
3 | describe('ignoreEnd', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().ignoreEnd()).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.ignoreEnd()).to.activate(a)
12 | })
13 |
14 | it('should not be ended if source was ended', () => {
15 | expect(send(stream(), [end()]).ignoreEnd()).to.emit([])
16 | })
17 |
18 | it('should handle events', () => {
19 | const a = stream()
20 | expect(a.ignoreEnd()).to.emit([value(1), value(2)], () => send(a, [value(1), value(2), end()]))
21 | })
22 |
23 | it('errors should flow', () => {
24 | const a = stream()
25 | expect(a.ignoreEnd()).to.flowErrors(a)
26 | })
27 | })
28 |
29 | describe('property', () => {
30 | it('should return property', () => {
31 | expect(prop().ignoreEnd()).to.be.observable.property()
32 | })
33 |
34 | it('should activate/deactivate source', () => {
35 | const a = prop()
36 | expect(a.ignoreEnd()).to.activate(a)
37 | })
38 |
39 | it('should not be ended if source was ended', () => {
40 | expect(send(prop(), [end()]).ignoreEnd()).to.emit([])
41 | expect(send(prop(), [value(1), end()]).ignoreEnd()).to.emit([value(1, {current: true})])
42 | })
43 |
44 | it('should handle events and current', () => {
45 | const a = send(prop(), [value(1)])
46 | expect(a.ignoreEnd()).to.emit([value(1, {current: true}), value(2), value(3)], () =>
47 | send(a, [value(2), value(3), end()])
48 | )
49 | })
50 |
51 | it('errors should flow', () => {
52 | const a = prop()
53 | expect(a.ignoreEnd()).to.flowErrors(a)
54 | })
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/test/specs/ignore-errors.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers')
2 |
3 | describe('ignoreErrors', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().ignoreErrors()).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.ignoreErrors()).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () =>
15 | expect(send(stream(), [end()]).ignoreErrors()).to.emit([end({current: true})]))
16 |
17 | it('should handle events', () => {
18 | const a = stream()
19 | expect(a.ignoreErrors()).to.emit([value(1), value(2), end()], () =>
20 | send(a, [value(1), error(-1), value(2), error(-2), end()])
21 | )
22 | })
23 | })
24 |
25 | describe('property', () => {
26 | it('should return property', () => {
27 | expect(prop().ignoreErrors()).to.be.observable.property()
28 | })
29 |
30 | it('should activate/deactivate source', () => {
31 | const a = prop()
32 | expect(a.ignoreErrors()).to.activate(a)
33 | })
34 |
35 | it('should be ended if source was ended', () =>
36 | expect(send(prop(), [end()]).ignoreErrors()).to.emit([end({current: true})]))
37 |
38 | it('should handle events and current', () => {
39 | let a = send(prop(), [error(-1)])
40 | expect(a.ignoreErrors()).to.emit([value(2), value(3), end()], () =>
41 | send(a, [value(2), error(-2), value(3), error(-3), end()])
42 | )
43 | a = send(prop(), [value(1)])
44 | expect(a.ignoreErrors()).to.emit([value(1, {current: true}), value(2), value(3), end()], () =>
45 | send(a, [value(2), error(-2), value(3), error(-3), end()])
46 | )
47 | })
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/test/specs/ignore-values.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('ignoreValues', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().ignoreValues()).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.ignoreValues()).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () =>
15 | expect(send(stream(), [end()]).ignoreValues()).to.emit([end({current: true})]))
16 |
17 | it('should handle events', () => {
18 | const a = stream()
19 | expect(a.ignoreValues()).to.emit([error(-1), error(-2), end()], () =>
20 | send(a, [value(1), error(-1), value(2), error(-2), end()])
21 | )
22 | })
23 | })
24 |
25 | describe('property', () => {
26 | it('should return property', () => {
27 | expect(prop().ignoreValues()).to.be.observable.property()
28 | })
29 |
30 | it('should activate/deactivate source', () => {
31 | const a = prop()
32 | expect(a.ignoreValues()).to.activate(a)
33 | })
34 |
35 | it('should be ended if source was ended', () =>
36 | expect(send(prop(), [end()]).ignoreValues()).to.emit([end({current: true})]))
37 |
38 | it('should handle events and current', () => {
39 | const a = send(prop(), [value(1), error(-1)])
40 | expect(a.ignoreValues()).to.emit([error(-1, {current: true}), error(-2), error(-3), end()], () =>
41 | send(a, [value(2), error(-2), value(3), error(-3), end()])
42 | )
43 | })
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/test/specs/interval.js:
--------------------------------------------------------------------------------
1 | const {value, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('interval', () => {
4 | it('should return stream', () => {
5 | expect(Kefir.interval(100, 1)).to.be.observable.stream()
6 | })
7 |
8 | it('should repeat same value at certain time', () => {
9 | expect(Kefir.interval(100, 1)).to.emitInTime(
10 | [
11 | [100, value(1)],
12 | [200, value(1)],
13 | [300, value(1)],
14 | ],
15 | undefined,
16 | {
17 | timeLimit: 350,
18 | }
19 | )
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/test/specs/kefir-observable.js:
--------------------------------------------------------------------------------
1 | let {Kefir, expect} = require('../test-helpers')
2 |
3 | describe('Kefir.Observable', () => {
4 | describe('observe', () => {
5 | let em, count, obs, sub
6 |
7 | beforeEach(() => {
8 | em = null
9 | count = 0
10 | obs = Kefir.stream(_em => {
11 | em = _em
12 | })
13 | sub = obs.observe({value: () => count++, error: () => count--, end: () => (count = 0)})
14 | })
15 |
16 | it('should return a Subscription', () => {
17 | expect(sub.closed).to.equal(false)
18 | expect(typeof sub.unsubscribe).to.equal('function')
19 | })
20 |
21 | it('should call Observer methods', () => {
22 | expect(count).to.equal(0)
23 |
24 | em.emit(1)
25 | expect(count).to.equal(1)
26 |
27 | em.emit(1)
28 | expect(count).to.equal(2)
29 |
30 | em.error(1)
31 | expect(count).to.equal(1)
32 |
33 | em.end()
34 | expect(count).to.equal(0)
35 | expect(sub.closed).to.equal(true)
36 | })
37 |
38 | it('should unsubcribe early', () => {
39 | expect(count).to.equal(0)
40 |
41 | em.emit(1)
42 | expect(count).to.equal(1)
43 |
44 | sub.unsubscribe()
45 |
46 | em.emit(1)
47 | expect(count).to.equal(1)
48 | expect(sub.closed).to.equal(true)
49 | })
50 |
51 | it('closed=true after end (w/o end handler)', () => {
52 | obs = Kefir.stream(_em => {
53 | em = _em
54 | })
55 | sub = obs.observe(() => {})
56 | em.end()
57 | expect(sub.closed).to.equal(true)
58 | })
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/test/specs/last.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('last', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().last()).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.last()).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () => {
15 | expect(send(stream(), [end()]).last()).to.emit([end({current: true})])
16 | })
17 |
18 | it('should handle events', () => {
19 | const a = stream()
20 | expect(a.last()).to.emit([error(5), error(6), value(3), end()], () =>
21 | send(a, [value(1), error(5), error(6), value(2), value(3), end()])
22 | )
23 | })
24 | })
25 |
26 | describe('property', () => {
27 | it('should return property', () => {
28 | expect(prop().last()).to.be.observable.property()
29 | })
30 |
31 | it('should activate/deactivate source', () => {
32 | const a = prop()
33 | expect(a.last()).to.activate(a)
34 | })
35 |
36 | it('should be ended if source was ended', () => {
37 | expect(send(prop(), [end()]).last()).to.emit([end({current: true})])
38 | expect(send(prop(), [value(1), end()]).last()).to.emit([value(1, {current: true}), end({current: true})])
39 | })
40 |
41 | it('should handle events and current', () => {
42 | let a = send(prop(), [value(1)])
43 | expect(a.last()).to.emit([error(5), value(1), end()], () => send(a, [error(5), end()]))
44 |
45 | a = send(prop(), [error(0)])
46 | expect(a.last()).to.emit([error(0, {current: true}), error(5), value(3), end()], () =>
47 | send(a, [value(2), error(5), value(3), end()])
48 | )
49 | })
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/test/specs/later.js:
--------------------------------------------------------------------------------
1 | const {value, end, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('later', () => {
4 | it('should return stream', () => {
5 | expect(Kefir.later(100, 1)).to.be.observable.stream()
6 | })
7 |
8 | it('should emmit value after interval then end', () => {
9 | expect(Kefir.later(100, 1)).to.emitInTime([
10 | [100, value(1)],
11 | [100, end()],
12 | ])
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/test/specs/log.js:
--------------------------------------------------------------------------------
1 | const {stream, send, value, error, end, expect} = require('../test-helpers')
2 | const sinon = require('sinon')
3 |
4 | describe('log', () => {
5 | describe('adding', () => {
6 | it('should return the stream', () => {
7 | expect(stream().log()).to.be.observable.stream()
8 | })
9 |
10 | it('should activate the stream', () => {
11 | const a = stream().log()
12 | expect(a).to.be.active()
13 | })
14 | })
15 |
16 | describe('removing', () => {
17 | it('should return the stream', () => {
18 | expect(
19 | stream()
20 | .log()
21 | .offLog()
22 | ).to.be.observable.stream()
23 | })
24 |
25 | it('should deactivate the stream', () => {
26 | const a = stream()
27 | .log()
28 | .offLog()
29 | expect(a).not.to.be.active()
30 | })
31 | })
32 |
33 | describe('console', () => {
34 | let stub
35 | beforeEach(() => (stub = sinon.stub(console, 'log')))
36 |
37 | afterEach(() => stub.restore())
38 |
39 | it('should have a default name', () => {
40 | const a = stream()
41 | a.log()
42 | expect(a).to.emit([value(1), value(2), value(3)], () => {
43 | send(a, [value(1), value(2), value(3)])
44 | expect(console.log).to.have.been.calledWith('[stream]', '', 1)
45 | expect(console.log).to.have.been.calledWith('[stream]', '', 2)
46 | expect(console.log).to.have.been.calledWith('[stream]', '', 3)
47 | })
48 | })
49 |
50 | it('should use the name', () => {
51 | const a = stream()
52 | a.log('logged')
53 | expect(a).to.emit([value(1), value(2), value(3)], () => {
54 | send(a, [value(1), value(2), value(3)])
55 | expect(console.log).to.have.been.calledWith('logged', '', 1)
56 | expect(console.log).to.have.been.calledWith('logged', '', 2)
57 | expect(console.log).to.have.been.calledWith('logged', '', 3)
58 | })
59 | })
60 |
61 | it('should not log if the log has been removed', () => {
62 | const a = stream()
63 | a.log()
64 | a.offLog()
65 | expect(a).to.emit([value(1), value(2), value(3)], () => {
66 | send(a, [value(1), value(2), value(3)])
67 | expect(console.log).not.to.have.been.called
68 | })
69 | })
70 | })
71 | })
72 |
--------------------------------------------------------------------------------
/test/specs/map-errors.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers')
2 |
3 | describe('mapErrors', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().mapErrors(() => {})).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.mapErrors(() => {})).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () =>
15 | expect(send(stream(), [end()]).mapErrors(() => {})).to.emit([end({current: true})]))
16 |
17 | it('should handle events', () => {
18 | const a = stream()
19 | expect(a.mapErrors(x => x * 2)).to.emit([value(1), error(-2), value(2), error(-4), end()], () =>
20 | send(a, [value(1), error(-1, {current: true}), value(2), error(-2), end()])
21 | )
22 | })
23 | })
24 |
25 | describe('property', () => {
26 | it('should return property', () => {
27 | expect(prop().mapErrors(() => {})).to.be.observable.property()
28 | })
29 |
30 | it('should activate/deactivate source', () => {
31 | const a = prop()
32 | expect(a.mapErrors(() => {})).to.activate(a)
33 | })
34 |
35 | it('should be ended if source was ended', () =>
36 | expect(send(prop(), [end()]).mapErrors(() => {})).to.emit([end({current: true})]))
37 |
38 | it('should handle events and current', () => {
39 | let a = send(prop(), [value(1)])
40 | expect(a.mapErrors(x => x * 2)).to.emit(
41 | [value(1, {current: true}), value(2), error(-4), value(3), error(-6), end()],
42 | () => send(a, [value(2), error(-2), value(3), error(-3), end()])
43 | )
44 | a = send(prop(), [error(-1, {current: true})])
45 | expect(a.mapErrors(x => x * 2)).to.emit(
46 | [error(-2, {current: true}), value(2), error(-4), value(3), error(-6), end()],
47 | () => send(a, [value(2), error(-2), value(3), error(-3), end()])
48 | )
49 | })
50 | })
51 | })
52 |
--------------------------------------------------------------------------------
/test/specs/map.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers')
2 |
3 | describe('map', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().map(() => {})).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.map(() => {})).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () =>
15 | expect(send(stream(), [end()]).map(() => {})).to.emit([end({current: true})]))
16 |
17 | it('should handle events', () => {
18 | const a = stream()
19 | expect(a.map(x => x * 2)).to.emit([value(2), error(5), value(4), end()], () =>
20 | send(a, [value(1), error(5), value(2), end()])
21 | )
22 | })
23 |
24 | it('should work with default `fn`', () => {
25 | const a = stream()
26 | expect(a.map()).to.emit([value(1), error(5), value(2), end()], () =>
27 | send(a, [value(1), error(5), value(2), end()])
28 | )
29 | })
30 | })
31 |
32 | describe('property', () => {
33 | it('should return property', () => {
34 | expect(prop().map(() => {})).to.be.observable.property()
35 | })
36 |
37 | it('should activate/deactivate source', () => {
38 | const a = prop()
39 | expect(a.map(() => {})).to.activate(a)
40 | })
41 |
42 | it('should be ended if source was ended', () =>
43 | expect(send(prop(), [end()]).map(() => {})).to.emit([end({current: true})]))
44 |
45 | it('should handle events and current', () => {
46 | let a = send(prop(), [value(1)])
47 | expect(a.map(x => x * 2)).to.emit([value(2, {current: true}), value(4), error(5), value(6), end()], () =>
48 | send(a, [value(2), error(5), value(3), end()])
49 | )
50 | a = send(prop(), [error(0)])
51 | expect(a.map(x => x * 2)).to.emit([error(0, {current: true}), value(4), error(5), value(6), end()], () =>
52 | send(a, [value(2), error(5), value(3), end()])
53 | )
54 | })
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/test/specs/merge.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, end, activate, deactivate, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('merge', () => {
4 | it('should return stream', () => {
5 | expect(Kefir.merge([])).to.be.observable.stream()
6 | expect(Kefir.merge([stream(), prop()])).to.be.observable.stream()
7 | expect(stream().merge(stream())).to.be.observable.stream()
8 | expect(prop().merge(prop())).to.be.observable.stream()
9 | })
10 |
11 | it('should be ended if empty array provided', () => {
12 | expect(Kefir.merge([])).to.emit([end({current: true})])
13 | })
14 |
15 | it('should be ended if array of ended observables provided', () => {
16 | const a = send(stream(), [end()])
17 | const b = send(prop(), [end()])
18 | const c = send(stream(), [end()])
19 | expect(Kefir.merge([a, b, c])).to.emit([end({current: true})])
20 | expect(a.merge(b)).to.emit([end({current: true})])
21 | })
22 |
23 | it('should activate sources', () => {
24 | const a = stream()
25 | const b = prop()
26 | const c = stream()
27 | expect(Kefir.merge([a, b, c])).to.activate(a, b, c)
28 | expect(a.merge(b)).to.activate(a, b)
29 | })
30 |
31 | it('should deliver events from observables, then end when all of them end', () => {
32 | let a = stream()
33 | let b = send(prop(), [value(0)])
34 | const c = stream()
35 | expect(Kefir.merge([a, b, c])).to.emit(
36 | [value(0, {current: true}), value(1), value(2), value(3), value(4), value(5), value(6), end()],
37 | () => {
38 | send(a, [value(1)])
39 | send(b, [value(2)])
40 | send(c, [value(3)])
41 | send(a, [end()])
42 | send(b, [value(4), end()])
43 | send(c, [value(5), value(6), end()])
44 | }
45 | )
46 | a = stream()
47 | b = send(prop(), [value(0)])
48 | expect(a.merge(b)).to.emit([value(0, {current: true}), value(1), value(2), value(3), end()], () => {
49 | send(a, [value(1)])
50 | send(b, [value(2)])
51 | send(a, [end()])
52 | send(b, [value(3), end()])
53 | })
54 | })
55 |
56 | it('should deliver currents from all source properties, but only to first subscriber on each activation', () => {
57 | const a = send(prop(), [value(0)])
58 | const b = send(prop(), [value(1)])
59 | const c = send(prop(), [value(2)])
60 |
61 | let merge = Kefir.merge([a, b, c])
62 | expect(merge).to.emit([value(0, {current: true}), value(1, {current: true}), value(2, {current: true})])
63 |
64 | merge = Kefir.merge([a, b, c])
65 | activate(merge)
66 | expect(merge).to.emit([])
67 |
68 | merge = Kefir.merge([a, b, c])
69 | activate(merge)
70 | deactivate(merge)
71 | expect(merge).to.emit([value(0, {current: true}), value(1, {current: true}), value(2, {current: true})])
72 | })
73 |
74 | it('errors should flow', () => {
75 | let a = stream()
76 | let b = prop()
77 | let c = stream()
78 | expect(Kefir.merge([a, b, c])).to.flowErrors(a)
79 | a = stream()
80 | b = prop()
81 | c = stream()
82 | expect(Kefir.merge([a, b, c])).to.flowErrors(b)
83 | a = stream()
84 | b = prop()
85 | c = stream()
86 | expect(Kefir.merge([a, b, c])).to.flowErrors(c)
87 | })
88 |
89 | it('should work correctly when unsuscribing after one sync event', () => {
90 | const a = Kefir.constant(1)
91 | const b = Kefir.interval(1000, 1)
92 | const c = a.merge(b)
93 | activate(c.take(1))
94 | expect(b).not.to.be.active()
95 | })
96 | })
97 |
--------------------------------------------------------------------------------
/test/specs/never.js:
--------------------------------------------------------------------------------
1 | const {end, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('never', () => {
4 | it('should return stream', () => {
5 | expect(Kefir.never()).to.be.observable.stream()
6 | })
7 |
8 | it('should be ended', () => {
9 | expect(Kefir.never()).to.emit([end({current: true})])
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/test/specs/repeat.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, activate, deactivate, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('repeat', () => {
4 | it('should return stream', () => {
5 | expect(Kefir.repeat()).to.be.observable.stream()
6 | })
7 |
8 | it('should work correctly (with .constant)', () => {
9 | const a = Kefir.repeat(i => Kefir[i === 2 ? 'constantError' : 'constant'](i))
10 | expect(a.take(3)).to.emit([
11 | value(0, {current: true}),
12 | value(1, {current: true}),
13 | error(2, {current: true}),
14 | value(3, {current: true}),
15 | end({current: true}),
16 | ])
17 | })
18 |
19 | it('should work correctly (with .later)', () => {
20 | const a = Kefir.repeat(i => Kefir.later(100, i))
21 | expect(a.take(3)).to.emitInTime([
22 | [100, value(0)],
23 | [200, value(1)],
24 | [300, value(2)],
25 | [300, end()],
26 | ])
27 | })
28 |
29 | it('should work correctly (with .sequentially)', () => {
30 | const a = Kefir.repeat(i => Kefir.sequentially(100, [1, 2, 3]))
31 | expect(a.take(5)).to.emitInTime([
32 | [100, value(1)],
33 | [200, value(2)],
34 | [300, value(3)],
35 | [400, value(1)],
36 | [500, value(2)],
37 | [500, end()],
38 | ])
39 | })
40 |
41 | it('should not cause stack overflow', () => {
42 | const sum = (a, b) => a + b
43 | const genConstant = () => Kefir.constant(1)
44 |
45 | const a = Kefir.repeat(genConstant)
46 | .take(3000)
47 | .scan(sum, 0)
48 | .last()
49 | expect(a).to.emit([value(3000, {current: true}), end({current: true})])
50 | })
51 |
52 | it('should get new source only if previous one ended', () => {
53 | let a = stream()
54 |
55 | let callsCount = 0
56 | const b = Kefir.repeat(() => {
57 | callsCount++
58 | if (!a._alive) {
59 | a = stream()
60 | }
61 | return a
62 | })
63 |
64 | expect(callsCount).to.equal(0)
65 | activate(b)
66 | expect(callsCount).to.equal(1)
67 | deactivate(b)
68 | activate(b)
69 | expect(callsCount).to.equal(1)
70 | send(a, [end()])
71 | expect(callsCount).to.equal(2)
72 | })
73 |
74 | it('should unsubscribe from source', () => {
75 | const a = stream()
76 | const b = Kefir.repeat(() => a)
77 | expect(b).to.activate(a)
78 | })
79 |
80 | it('should end when falsy value returned from generator', () => {
81 | const a = Kefir.repeat(i => {
82 | if (i < 3) {
83 | return Kefir.constant(i)
84 | } else {
85 | return false
86 | }
87 | })
88 | expect(a).to.emit([
89 | value(0, {current: true}),
90 | value(1, {current: true}),
91 | value(2, {current: true}),
92 | end({current: true}),
93 | ])
94 | })
95 | })
96 |
--------------------------------------------------------------------------------
/test/specs/sampled-by.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, activate, deactivate, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('sampledBy', () => {
4 | it('should return stream', () => {
5 | expect(prop().sampledBy(stream())).to.be.observable.stream()
6 | expect(stream().sampledBy(prop())).to.be.observable.stream()
7 | })
8 |
9 | it('should be ended if array of ended observables provided', () => {
10 | const a = send(stream(), [end()])
11 | expect(prop().sampledBy(a)).to.emit([end({current: true})])
12 | })
13 |
14 | it('should be ended and emmit current (once) if array of ended properties provided and each of them has current', () => {
15 | const a = send(prop(), [value(1), end()])
16 | const b = send(prop(), [value(2), end()])
17 | const s2 = a.sampledBy(b)
18 | expect(s2).to.emit([value(1, {current: true}), end({current: true})])
19 | expect(s2).to.emit([end({current: true})])
20 | })
21 |
22 | it('should activate sources', () => {
23 | const a = stream()
24 | const b = prop()
25 | expect(a.sampledBy(b)).to.activate(a, b)
26 | })
27 |
28 | it('should handle events and current from observables', () => {
29 | const a = stream()
30 | const b = send(prop(), [value(0)])
31 | expect(a.sampledBy(b)).to.emit([value(2), value(4), value(4), end()], () => {
32 | send(b, [value(1)])
33 | send(a, [value(2)])
34 | send(b, [value(3)])
35 | send(a, [value(4)])
36 | send(b, [value(5), value(6), end()])
37 | })
38 | })
39 |
40 | it('should accept optional combinator function', () => {
41 | const join = (...args) => args.join('+')
42 | const a = stream()
43 | const b = send(prop(), [value(0)])
44 | expect(a.sampledBy(b, join)).to.emit([value('2+3'), value('4+5'), value('4+6'), end()], () => {
45 | send(b, [value(1)])
46 | send(a, [value(2)])
47 | send(b, [value(3)])
48 | send(a, [value(4)])
49 | send(b, [value(5), value(6), end()])
50 | })
51 | })
52 |
53 | it('one sampledBy should remove listeners of another', () => {
54 | const a = send(prop(), [value(0)])
55 | const b = stream()
56 | const s1 = a.sampledBy(b)
57 | const s2 = a.sampledBy(b)
58 | activate(s1)
59 | activate(s2)
60 | deactivate(s2)
61 | expect(s1).to.emit([value(0)], () => send(b, [value(1)]))
62 | })
63 |
64 | // https://github.com/kefirjs/kefir/issues/98
65 | it('should work nice for emitating atomic updates', () => {
66 | const a = stream()
67 | const b = a.map(x => x + 2)
68 | const c = a.map(x => x * 2)
69 | expect(b.sampledBy(c, (x, y) => [x, y])).to.emit([value([3, 2]), value([4, 4]), value([5, 6])], () =>
70 | send(a, [value(1), value(2), value(3)])
71 | )
72 | })
73 | })
74 |
--------------------------------------------------------------------------------
/test/specs/sequentially.js:
--------------------------------------------------------------------------------
1 | const {value, end, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('sequentially', () => {
4 | it('should return stream', () => {
5 | expect(Kefir.sequentially(100, [1, 2, 3])).to.be.observable.stream()
6 | })
7 |
8 | it('should be ended if empty array provided', () => {
9 | expect(Kefir.sequentially(100, [])).to.emitInTime([[0, end({current: true})]])
10 | })
11 |
12 | it('should emmit values at certain time then end', () => {
13 | expect(Kefir.sequentially(100, [1, 2, 3])).to.emitInTime([
14 | [100, value(1)],
15 | [200, value(2)],
16 | [300, value(3)],
17 | [300, end()],
18 | ])
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/test/specs/skip-duplicates.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('skipDuplicates', () => {
4 | const roundlyEqual = (a, b) => Math.round(a) === Math.round(b)
5 |
6 | describe('stream', () => {
7 | it('should return stream', () => {
8 | expect(stream().skipDuplicates()).to.be.observable.stream()
9 | })
10 |
11 | it('should activate/deactivate source', () => {
12 | const a = stream()
13 | expect(a.skipDuplicates()).to.activate(a)
14 | })
15 |
16 | it('should be ended if source was ended', () =>
17 | expect(send(stream(), [end()]).skipDuplicates()).to.emit([end({current: true})]))
18 |
19 | it('should handle events (default comparator)', () => {
20 | const a = stream()
21 | expect(a.skipDuplicates()).to.emit([value(1), value(2), value(3), end()], () =>
22 | send(a, [value(1), value(1), value(2), value(3), value(3), end()])
23 | )
24 | })
25 |
26 | it('should handle events (custom comparator)', () => {
27 | const a = stream()
28 | expect(a.skipDuplicates(roundlyEqual)).to.emit([value(1), value(2), value(3.8), end()], () =>
29 | send(a, [value(1), value(1.1), value(2), value(3.8), value(4), end()])
30 | )
31 | })
32 |
33 | it('errors should flow', () => {
34 | const a = stream()
35 | expect(a.skipDuplicates()).to.flowErrors(a)
36 | })
37 |
38 | it('should help with creating circular dependencies', () => {
39 | // https://github.com/kefirjs/kefir/issues/42
40 |
41 | const a = stream()
42 | const b = Kefir.pool()
43 | b.plug(a)
44 | b.plug(b.map(x => x).skipDuplicates())
45 | expect(b).to.emit([value(1), value(1)], () => send(a, [value(1)]))
46 | })
47 | })
48 |
49 | describe('property', () => {
50 | it('should return property', () => {
51 | expect(prop().skipDuplicates()).to.be.observable.property()
52 | })
53 |
54 | it('should activate/deactivate source', () => {
55 | const a = prop()
56 | expect(a.skipDuplicates()).to.activate(a)
57 | })
58 |
59 | it('should be ended if source was ended', () =>
60 | expect(send(prop(), [end()]).skipDuplicates()).to.emit([end({current: true})]))
61 |
62 | it('should handle events and current (default comparator)', () => {
63 | const a = send(prop(), [value(1)])
64 | expect(a.skipDuplicates()).to.emit([value(1, {current: true}), value(2), value(3), end()], () =>
65 | send(a, [value(1), value(1), value(2), value(3), value(3), end()])
66 | )
67 | })
68 |
69 | it('should handle events and current (custom comparator)', () => {
70 | const a = send(prop(), [value(1)])
71 | expect(a.skipDuplicates(roundlyEqual)).to.emit([value(1, {current: true}), value(2), value(3), end()], () =>
72 | send(a, [value(1.1), value(1.2), value(2), value(3), value(3.2), end()])
73 | )
74 | })
75 |
76 | it('errors should flow', () => {
77 | const a = prop()
78 | expect(a.skipDuplicates()).to.flowErrors(a)
79 | })
80 | })
81 | })
82 |
--------------------------------------------------------------------------------
/test/specs/skip.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('skip', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().skip(3)).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.skip(3)).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () => {
15 | expect(send(stream(), [end()]).skip(3)).to.emit([end({current: true})])
16 | })
17 |
18 | it('should handle events (less than `n`)', () => {
19 | const a = stream()
20 | expect(a.skip(3)).to.emit([end()], () => send(a, [value(1), value(2), end()]))
21 | })
22 |
23 | it('should handle events (more than `n`)', () => {
24 | const a = stream()
25 | expect(a.skip(3)).to.emit([value(4), value(5), end()], () =>
26 | send(a, [value(1), value(2), value(3), value(4), value(5), end()])
27 | )
28 | })
29 |
30 | it('should handle events (n == 0)', () => {
31 | const a = stream()
32 | expect(a.skip(0)).to.emit([value(1), value(2), value(3), end()], () =>
33 | send(a, [value(1), value(2), value(3), end()])
34 | )
35 | })
36 |
37 | it('should handle events (n == -1)', () => {
38 | const a = stream()
39 | expect(a.skip(-1)).to.emit([value(1), value(2), value(3), end()], () =>
40 | send(a, [value(1), value(2), value(3), end()])
41 | )
42 | })
43 |
44 | it('errors should flow', () => {
45 | const a = stream()
46 | expect(a.skip(1)).to.flowErrors(a)
47 | })
48 | })
49 |
50 | describe('property', () => {
51 | it('should return property', () => {
52 | expect(prop().skip(3)).to.be.observable.property()
53 | })
54 |
55 | it('should activate/deactivate source', () => {
56 | const a = prop()
57 | expect(a.skip(3)).to.activate(a)
58 | })
59 |
60 | it('should be ended if source was ended', () =>
61 | expect(send(prop(), [end()]).skip(3)).to.emit([end({current: true})]))
62 |
63 | it('should handle events and current (less than `n`)', () => {
64 | const a = send(prop(), [value(1)])
65 | expect(a.skip(3)).to.emit([end()], () => send(a, [value(2), end()]))
66 | })
67 |
68 | it('should handle events and current (more than `n`)', () => {
69 | const a = send(prop(), [value(1)])
70 | expect(a.skip(3)).to.emit([value(4), value(5), end()], () =>
71 | send(a, [value(2), value(3), value(4), value(5), end()])
72 | )
73 | })
74 |
75 | it('should handle events and current (n == 0)', () => {
76 | const a = send(prop(), [value(1)])
77 | expect(a.skip(0)).to.emit([value(1, {current: true}), value(2), value(3), end()], () =>
78 | send(a, [value(2), value(3), end()])
79 | )
80 | })
81 |
82 | it('should handle events and current (n == -1)', () => {
83 | const a = send(prop(), [value(1)])
84 | expect(a.skip(-1)).to.emit([value(1, {current: true}), value(2), value(3), end()], () =>
85 | send(a, [value(2), value(3), end()])
86 | )
87 | })
88 |
89 | it('errors should flow', () => {
90 | const a = prop()
91 | expect(a.skip(1)).to.flowErrors(a)
92 | })
93 | })
94 | })
95 |
--------------------------------------------------------------------------------
/test/specs/spy.js:
--------------------------------------------------------------------------------
1 | const {stream, send, value, error, end, expect} = require('../test-helpers')
2 | const sinon = require('sinon')
3 |
4 | describe('spy', () => {
5 | describe('adding', () => {
6 | it('should return the stream', () => {
7 | expect(stream().spy()).to.be.observable.stream()
8 | })
9 |
10 | it('should not activate the stream', () => {
11 | const a = stream().spy()
12 | expect(a).not.to.be.active()
13 | })
14 | })
15 |
16 | describe('removing', () => {
17 | it('should return the stream', () => {
18 | expect(
19 | stream()
20 | .spy()
21 | .offSpy()
22 | ).to.be.observable.stream()
23 | })
24 |
25 | it('should not activate the stream', () => {
26 | const a = stream()
27 | .spy()
28 | .offSpy()
29 | expect(a).not.to.be.active()
30 | })
31 | })
32 |
33 | describe('console', () => {
34 | let stub
35 | beforeEach(() => (stub = sinon.stub(console, 'log')))
36 |
37 | afterEach(() => stub.restore())
38 |
39 | it('should have a default name', () => {
40 | const a = stream()
41 | a.spy()
42 | expect(a).to.emit([value(1), value(2), value(3)], () => {
43 | send(a, [value(1), value(2), value(3)])
44 | expect(console.log).to.have.been.calledWith('[stream]', '', 1)
45 | expect(console.log).to.have.been.calledWith('[stream]', '', 2)
46 | expect(console.log).to.have.been.calledWith('[stream]', '', 3)
47 | })
48 | })
49 |
50 | it('should use the name', () => {
51 | const a = stream()
52 | a.spy('spied')
53 | expect(a).to.emit([value(1), value(2), value(3)], () => {
54 | send(a, [value(1), value(2), value(3)])
55 | expect(console.log).to.have.been.calledWith('spied', '', 1)
56 | expect(console.log).to.have.been.calledWith('spied', '', 2)
57 | expect(console.log).to.have.been.calledWith('spied', '', 3)
58 | })
59 | })
60 |
61 | it('should not log if the spy has been removed', () => {
62 | const a = stream()
63 | a.spy()
64 | a.offSpy()
65 | expect(a).to.emit([value(1), value(2), value(3)], () => {
66 | send(a, [value(1), value(2), value(3)])
67 | expect(console.log).not.to.have.been.called
68 | })
69 | })
70 | })
71 | })
72 |
--------------------------------------------------------------------------------
/test/specs/static-land.js:
--------------------------------------------------------------------------------
1 | const {value, error, end, Kefir, expect} = require('../test-helpers')
2 | const {Observable} = Kefir.staticLand
3 |
4 | describe('Kefir.staticLand.Observable', () => {
5 | it('of works', () => {
6 | expect(Observable.of(2)).to.emit([value(2, {current: true}), end({current: true})])
7 | })
8 |
9 | it('empty works', () => {
10 | expect(Observable.empty()).to.emit([end({current: true})])
11 | })
12 |
13 | it('concat works', () => {
14 | expect(Observable.concat(Observable.of(2), Observable.empty())).to.emit([
15 | value(2, {current: true}),
16 | end({current: true}),
17 | ])
18 | })
19 |
20 | it('map works', () => {
21 | expect(Observable.map(x => x * 3, Observable.of(2))).to.emit([value(6, {current: true}), end({current: true})])
22 | })
23 |
24 | it('bimap works', () => {
25 | expect(
26 | Observable.bimap(
27 | x => x,
28 | x => x * 3,
29 | Observable.of(2)
30 | )
31 | ).to.emit([value(6, {current: true}), end({current: true})])
32 | expect(
33 | Observable.bimap(
34 | x => x * 3,
35 | x => x,
36 | Kefir.constantError(2)
37 | )
38 | ).to.emit([error(6, {current: true}), end({current: true})])
39 | })
40 |
41 | it('ap works', () => {
42 | expect(
43 | Observable.ap(
44 | Observable.of(x => x * 3),
45 | Observable.of(2)
46 | )
47 | ).to.emit([value(6, {current: true}), end({current: true})])
48 | })
49 |
50 | it('chain works', () => {
51 | expect(Observable.chain(x => Observable.of(x * 3), Observable.of(2))).to.emit([
52 | value(6, {current: true}),
53 | end({current: true}),
54 | ])
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/test/specs/sugar.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, expect} = require('../test-helpers')
2 |
3 | describe('setName', () => {
4 | it('should return same observable', () => {
5 | const a = stream()
6 | expect(a.setName('foo')).to.equal(a)
7 | expect(a.setName(stream(), 'foo')).to.equal(a)
8 | })
9 |
10 | it('should update observable name', () => {
11 | const a = stream()
12 | expect(a.toString()).to.equal('[stream]')
13 | a.setName('foo')
14 | expect(a.toString()).to.equal('[foo]')
15 | a.setName(stream().setName('foo'), 'bar')
16 | expect(a.toString()).to.equal('[foo.bar]')
17 | })
18 | })
19 |
20 | describe('awaiting', () => {
21 | it('stream and stream', () => {
22 | const a = stream()
23 | const b = stream()
24 | expect(a.awaiting(b)).to.emit([value(false, {current: true}), value(true), value(false), value(true)], () => {
25 | send(a, [value(1)])
26 | send(b, [value(1)])
27 | send(b, [value(1)])
28 | send(a, [value(1)])
29 | send(a, [value(1)])
30 | })
31 | })
32 |
33 | it('property and stream', () => {
34 | const a = send(prop(), [value(1)])
35 | const b = stream()
36 | expect(a.awaiting(b)).to.emit([value(true, {current: true}), value(false), value(true)], () => {
37 | send(a, [value(1)])
38 | send(b, [value(1)])
39 | send(b, [value(1)])
40 | send(a, [value(1)])
41 | send(a, [value(1)])
42 | })
43 | })
44 |
45 | it('property and property', () => {
46 | const a = send(prop(), [value(1)])
47 | const b = send(prop(), [value(1)])
48 | expect(a.awaiting(b)).to.emit([value(false, {current: true}), value(true), value(false), value(true)], () => {
49 | send(a, [value(1)])
50 | send(b, [value(1)])
51 | send(b, [value(1)])
52 | send(a, [value(1)])
53 | send(a, [value(1)])
54 | })
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/test/specs/take-errors.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, Kefir, pool, expect} = require('../test-helpers')
2 |
3 | describe('takeErrors', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().takeErrors(3)).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.takeErrors(3)).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () =>
15 | expect(send(stream(), [end()]).takeErrors(3)).to.emit([end({current: true})]))
16 |
17 | it('should be ended if `n` is 0', () => {
18 | expect(stream().takeErrors(0)).to.emit([end({current: true})])
19 | })
20 |
21 | it('should handle events (less than `n`)', () => {
22 | const a = stream()
23 | expect(a.takeErrors(3)).to.emit([error(1), error(2), end()], () => send(a, [error(1), error(2), end()]))
24 | })
25 |
26 | it('should handle events (more than `n`)', () => {
27 | const a = stream()
28 | expect(a.takeErrors(3)).to.emit([error(1), error(2), error(3), end()], () =>
29 | send(a, [error(1), error(2), error(3), error(4), error(5), end()])
30 | )
31 | })
32 |
33 | it('values should flow', () => {
34 | const a = stream()
35 | expect(a.takeErrors(1)).to.emit([value(1), value(2), value(3), end()], () =>
36 | send(a, [value(1), value(2), value(3), end()])
37 | )
38 | })
39 |
40 | it('should emit once on circular dependency', () => {
41 | const a = pool()
42 | const b = a.takeErrors(1).mapErrors(x => x + 1)
43 | a.plug(b)
44 |
45 | expect(b).to.emit([error(2), end()], () => send(a, [error(1), error(2), error(3), error(4), error(5)]))
46 | })
47 | })
48 |
49 | describe('property', () => {
50 | it('should return property', () => {
51 | expect(prop().takeErrors(3)).to.be.observable.property()
52 | })
53 |
54 | it('should activate/deactivate source', () => {
55 | const a = prop()
56 | expect(a.takeErrors(3)).to.activate(a)
57 | })
58 |
59 | it('should be ended if source was ended', () =>
60 | expect(send(prop(), [end()]).takeErrors(3)).to.emit([end({current: true})]))
61 |
62 | it('should be ended if `n` is 0', () => expect(prop().takeErrors(0)).to.emit([end({current: true})]))
63 |
64 | it('should handle events and current (less than `n`)', () => {
65 | const a = send(prop(), [error(1)])
66 | expect(a.takeErrors(3)).to.emit([error(1, {current: true}), error(2), end()], () => send(a, [error(2), end()]))
67 | })
68 |
69 | it('should handle events and current (more than `n`)', () => {
70 | const a = send(prop(), [error(1)])
71 | expect(a.takeErrors(3)).to.emit([error(1, {current: true}), error(2), error(3), end()], () =>
72 | send(a, [error(2), error(3), error(4), error(5), end()])
73 | )
74 | })
75 |
76 | it('should work correctly with .constant', () =>
77 | expect(Kefir.constantError(1).takeErrors(1)).to.emit([error(1, {current: true}), end({current: true})]))
78 |
79 | it('values should flow', () => {
80 | const a = send(prop(), [value(1)])
81 | expect(a.takeErrors(1)).to.emit([value(1, {current: true}), value(2), value(3), value(4), end()], () =>
82 | send(a, [value(2), value(3), value(4), end()])
83 | )
84 | })
85 | })
86 | })
87 |
--------------------------------------------------------------------------------
/test/specs/take-while.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('takeWhile', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().takeWhile(() => true)).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.takeWhile(() => true)).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () => {
15 | expect(send(stream(), [end()]).takeWhile(() => true)).to.emit([end({current: true})])
16 | })
17 |
18 | it('should handle events', () => {
19 | const a = stream()
20 | expect(a.takeWhile(x => x < 4)).to.emit([value(1), value(2), value(3), end()], () => {
21 | send(a, [value(1), value(2), value(3), value(4), value(5), end()])
22 | })
23 | })
24 |
25 | it('should handle events (natural end)', () => {
26 | const a = stream()
27 | expect(a.takeWhile(x => x < 4)).to.emit([value(1), value(2), end()], () => send(a, [value(1), value(2), end()]))
28 | })
29 |
30 | it('should handle events (with `-> false`)', () => {
31 | const a = stream()
32 | expect(a.takeWhile(() => false)).to.emit([end()], () => send(a, [value(1), value(2), end()]))
33 | })
34 |
35 | it('shoud use id as default predicate', () => {
36 | const a = stream()
37 | expect(a.takeWhile()).to.emit([value(1), value(2), end()], () =>
38 | send(a, [value(1), value(2), value(0), value(5), end()])
39 | )
40 | })
41 |
42 | it('errors should flow', () => {
43 | const a = stream()
44 | expect(a.takeWhile()).to.flowErrors(a)
45 | })
46 | })
47 |
48 | describe('property', () => {
49 | it('should return property', () => {
50 | expect(prop().takeWhile(() => true)).to.be.observable.property()
51 | })
52 |
53 | it('should activate/deactivate source', () => {
54 | const a = prop()
55 | expect(a.takeWhile(() => true)).to.activate(a)
56 | })
57 |
58 | it('should be ended if source was ended', () =>
59 | expect(send(prop(), [end()]).takeWhile(() => true)).to.emit([end({current: true})]))
60 |
61 | it('should be ended if calback was `-> false` and source has a current', () =>
62 | expect(send(prop(), [value(1)]).takeWhile(() => false)).to.emit([end({current: true})]))
63 |
64 | it('should handle events', () => {
65 | const a = send(prop(), [value(1)])
66 | expect(a.takeWhile(x => x < 4)).to.emit([value(1, {current: true}), value(2), value(3), end()], () =>
67 | send(a, [value(2), value(3), value(4), value(5), end()])
68 | )
69 | })
70 |
71 | it('should handle events (natural end)', () => {
72 | const a = send(prop(), [value(1)])
73 | expect(a.takeWhile(x => x < 4)).to.emit([value(1, {current: true}), value(2), end()], () =>
74 | send(a, [value(2), end()])
75 | )
76 | })
77 |
78 | it('should handle events (with `-> false`)', () => {
79 | const a = prop()
80 | expect(a.takeWhile(() => false)).to.emit([end()], () => send(a, [value(1), value(2), end()]))
81 | })
82 |
83 | it('shoud use id as default predicate', () => {
84 | let a = send(prop(), [value(1)])
85 | expect(a.takeWhile()).to.emit([value(1, {current: true}), value(2), end()], () =>
86 | send(a, [value(2), value(0), value(5), end()])
87 | )
88 | a = send(prop(), [value(0)])
89 | expect(a.takeWhile()).to.emit([end({current: true})], () => send(a, [value(2), value(0), value(5), end()]))
90 | })
91 |
92 | it('errors should flow', () => {
93 | const a = prop()
94 | expect(a.takeWhile()).to.flowErrors(a)
95 | })
96 | })
97 | })
98 |
--------------------------------------------------------------------------------
/test/specs/take.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, Kefir, pool, expect} = require('../test-helpers')
2 |
3 | describe('take', () => {
4 | describe('stream', () => {
5 | it('should return stream', () => {
6 | expect(stream().take(3)).to.be.observable.stream()
7 | })
8 |
9 | it('should activate/deactivate source', () => {
10 | const a = stream()
11 | expect(a.take(3)).to.activate(a)
12 | })
13 |
14 | it('should be ended if source was ended', () => {
15 | expect(send(stream(), [end()]).take(3)).to.emit([end({current: true})])
16 | })
17 |
18 | it('should be ended if `n` is 0', () => {
19 | expect(stream().take(0)).to.emit([end({current: true})])
20 | })
21 |
22 | it('should handle events (less than `n`)', () => {
23 | const a = stream()
24 | expect(a.take(3)).to.emit([value(1), value(2), end()], () => send(a, [value(1), value(2), end()]))
25 | })
26 |
27 | it('should handle events (more than `n`)', () => {
28 | const a = stream()
29 | expect(a.take(3)).to.emit([value(1), value(2), value(3), end()], () =>
30 | send(a, [value(1), value(2), value(3), value(4), value(5), end()])
31 | )
32 | })
33 |
34 | it('errors should flow', () => {
35 | const a = stream()
36 | expect(a.take(1)).to.flowErrors(a)
37 | })
38 |
39 | it('should emit once on circular dependency', () => {
40 | const a = pool()
41 | const b = a.take(1).map(x => x + 1)
42 | a.plug(b)
43 |
44 | expect(b).to.emit([value(2), end()], () => send(a, [value(1), value(2), value(3), value(4), value(5)]))
45 | })
46 | })
47 |
48 | describe('property', () => {
49 | it('should return property', () => expect(prop().take(3)).to.be.observable.property())
50 |
51 | it('should activate/deactivate source', () => {
52 | const a = prop()
53 | expect(a.take(3)).to.activate(a)
54 | })
55 |
56 | it('should be ended if source was ended', () => {
57 | expect(send(prop(), [end()]).take(3)).to.emit([end({current: true})])
58 | })
59 |
60 | it('should be ended if `n` is 0', () => {
61 | expect(prop().take(0)).to.emit([end({current: true})])
62 | })
63 |
64 | it('should handle events and current (less than `n`)', () => {
65 | const a = send(prop(), [value(1)])
66 | expect(a.take(3)).to.emit([value(1, {current: true}), value(2), end()], () => send(a, [value(2), end()]))
67 | })
68 |
69 | it('should handle events and current (more than `n`)', () => {
70 | const a = send(prop(), [value(1)])
71 | expect(a.take(3)).to.emit([value(1, {current: true}), value(2), value(3), end()], () =>
72 | send(a, [value(2), value(3), value(4), value(5), end()])
73 | )
74 | })
75 |
76 | it('should work correctly with .constant', () =>
77 | expect(Kefir.constant(1).take(1)).to.emit([value(1, {current: true}), end({current: true})]))
78 |
79 | it('errors should flow', () => {
80 | const a = prop()
81 | expect(a.take(1)).to.flowErrors(a)
82 | })
83 | })
84 | })
85 |
--------------------------------------------------------------------------------
/test/specs/thru.js:
--------------------------------------------------------------------------------
1 | const sinon = require('sinon')
2 | const {stream, expect} = require('../test-helpers')
3 |
4 | describe('thru', () => {
5 | it('should call function and return result', () => {
6 | const spy = sinon.spy(() => 0)
7 | const a = stream()
8 |
9 | expect(a.thru(spy)).to.equal(0)
10 | expect(spy.calledWith(a)).to.equal(true)
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/test/specs/values-to-errors.js:
--------------------------------------------------------------------------------
1 | const {stream, prop, send, value, error, end, Kefir, expect} = require('../test-helpers')
2 |
3 | const handler = x => ({
4 | convert: x < 0,
5 | error: x * 3,
6 | })
7 |
8 | describe('valuesToErrors', () => {
9 | describe('stream', () => {
10 | it('should return stream', () => {
11 | expect(stream().valuesToErrors(() => {})).to.be.observable.stream()
12 | })
13 |
14 | it('should activate/deactivate source', () => {
15 | const a = stream()
16 | expect(a.valuesToErrors(() => {})).to.activate(a)
17 | })
18 |
19 | it('should be ended if source was ended', () =>
20 | expect(send(stream(), [end()]).valuesToErrors(() => {})).to.emit([end({current: true})]))
21 |
22 | it('should handle events', () => {
23 | const a = stream()
24 | expect(a.valuesToErrors(handler)).to.emit([value(1), error(-6), error(-3), error(-12), value(5), end()], () =>
25 | send(a, [value(1), value(-2), error(-3), value(-4), value(5), end()])
26 | )
27 | })
28 |
29 | it('default handler should convert all values', () => {
30 | const a = stream()
31 | expect(a.valuesToErrors()).to.emit([error(1), error(-2), error(-3), error(-4), error(5), end()], () =>
32 | send(a, [value(1), value(-2), error(-3), value(-4), value(5), end()])
33 | )
34 | })
35 | })
36 |
37 | describe('property', () => {
38 | it('should return property', () => {
39 | expect(prop().valuesToErrors(() => {})).to.be.observable.property()
40 | })
41 |
42 | it('should activate/deactivate source', () => {
43 | const a = prop()
44 | expect(a.valuesToErrors(() => {})).to.activate(a)
45 | })
46 |
47 | it('should be ended if source was ended', () =>
48 | expect(send(prop(), [end()]).valuesToErrors(() => {})).to.emit([end({current: true})]))
49 |
50 | it('should handle events', () => {
51 | const a = send(prop(), [value(1)])
52 | expect(a.valuesToErrors(handler)).to.emit(
53 | [value(1, {current: true}), error(-6), error(-3), error(-12), value(5), end()],
54 | () => send(a, [value(-2), error(-3), value(-4), value(5), end()])
55 | )
56 | })
57 |
58 | it('should handle currents', () => {
59 | let a = send(prop(), [value(2)])
60 | expect(a.valuesToErrors(handler)).to.emit([value(2, {current: true})])
61 | a = send(prop(), [value(-2)])
62 | expect(a.valuesToErrors(handler)).to.emit([error(-6, {current: true})])
63 | a = send(prop(), [error(-2)])
64 | expect(a.valuesToErrors(handler)).to.emit([error(-2, {current: true})])
65 | })
66 | })
67 | })
68 |
--------------------------------------------------------------------------------
/test/specs/with-interval.js:
--------------------------------------------------------------------------------
1 | const {value, error, end, Kefir, expect} = require('../test-helpers')
2 |
3 | describe('withInterval', () => {
4 | it('should return stream', () => {
5 | expect(Kefir.withInterval(100, () => {})).to.be.observable.stream()
6 | })
7 |
8 | it('should work as expected', () => {
9 | let i = 0
10 | const fn = emitter => {
11 | i++
12 | if (i === 2) {
13 | emitter.error(-1)
14 | } else {
15 | emitter.emit(i)
16 | emitter.emit(i * 2)
17 | }
18 | if (i === 3) {
19 | return emitter.end()
20 | }
21 | }
22 | expect(Kefir.withInterval(100, fn)).to.emitInTime([
23 | [100, value(1)],
24 | [100, value(2)],
25 | [200, error(-1)],
26 | [300, value(3)],
27 | [300, value(6)],
28 | [300, end()],
29 | ])
30 | })
31 |
32 | it('should support emitter.emitEvent', () => {
33 | let i = 0
34 | const fn = emitter => {
35 | i++
36 | if (i === 2) {
37 | emitter.emitEvent({type: 'error', value: -1, current: false})
38 | } else {
39 | emitter.emitEvent({type: 'value', value: i, current: true}) // current should be ignored
40 | emitter.emitEvent({type: 'value', value: i * 2, current: false})
41 | }
42 | if (i === 3) {
43 | return emitter.emitEvent({type: 'end', value: undefined, current: false})
44 | }
45 | }
46 | expect(Kefir.withInterval(100, fn)).to.emitInTime([
47 | [100, value(1)],
48 | [100, value(2)],
49 | [200, error(-1)],
50 | [300, value(3)],
51 | [300, value(6)],
52 | [300, end()],
53 | ])
54 | })
55 | })
56 |
--------------------------------------------------------------------------------