| import * as most from 'most'; | ||
| import { TestEnvironment } from '../src'; | ||
| import assert from 'assert'; | ||
| const lazilyPeriodic = t => most.periodic(t) | ||
| .concatMap( () => most.just(1).delay(t) ) | ||
| .timestamp() | ||
| .map( ts => ts.time ); | ||
| describe( 'class TestEnvironment', () => { | ||
| it( 'should comply to API', () => { | ||
| const env = new TestEnvironment(); | ||
| assert.equal( typeof env.now, 'number' ); | ||
| assert.equal( typeof env.tick, 'function' ); | ||
| assert.equal( typeof env.collect, 'function' ); | ||
| assert.equal( typeof env.results, 'function' ); | ||
| assert.equal( typeof env.reset, 'function' ); | ||
| }); | ||
| it( '.tick() increments .now', () => { | ||
| const env = new TestEnvironment(); | ||
| assert.strictEqual( env.now, 0 ); | ||
| return env.tick( 2 ) | ||
| .collect( most.empty() ) | ||
| .then( () => { | ||
| assert.strictEqual( env.now, 2 ); | ||
| }); | ||
| }); | ||
| it( '.reset() restarts the time', () => { | ||
| const env = new TestEnvironment(); | ||
| assert.strictEqual( env.now, 0 ); | ||
| return env.tick( 2 ) | ||
| .collect( most.empty() ) | ||
| .then( () => { | ||
| assert.strictEqual( env.now, 2 ); | ||
| return env.reset(); | ||
| }).then( () => { | ||
| assert.strictEqual( env.now, 0 ); | ||
| }); | ||
| }); | ||
| it( 'should emit the events expected with the specified time interval', () => { | ||
| const env = new TestEnvironment(); | ||
| const stream$ = lazilyPeriodic( 2 ); | ||
| return env.tick( 2 ) | ||
| .collect( stream$ ) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [2] ); | ||
| return env.tick().collect( stream$ ); | ||
| }) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [] ); | ||
| return env.tick( 5 ).collect( stream$ ); | ||
| }) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [4, 6, 8] ); | ||
| }); | ||
| }); | ||
| it( 'should emit events even with large tick periods', () => { | ||
| const LONG_TIME = 30000; | ||
| const env = new TestEnvironment(); | ||
| const stream$ = lazilyPeriodic( LONG_TIME ); | ||
| return env.tick( LONG_TIME * 2 ) | ||
| .collect( stream$ ) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [LONG_TIME, LONG_TIME * 2] ); | ||
| }); | ||
| }); | ||
| describe( 'shared stream', () => { | ||
| describe( 'without reset()', () => { | ||
| const env = new TestEnvironment(); | ||
| const _ = undefined; // dummy value | ||
| const stream$ = lazilyPeriodic( 2 ); | ||
| it( 'should emit the events expected with the specified time interval', () => { | ||
| return env.tick( 5 ) | ||
| .collect( stream$ ) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [2, 4] ); | ||
| }); | ||
| }); | ||
| it( 'should conflict with previous test', () => { | ||
| return env.tick( 5 ) | ||
| .collect( stream$ ) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [6, 8, 10] ); | ||
| }); | ||
| }); | ||
| }); | ||
| describe( 'with reset()', () => { | ||
| const env = new TestEnvironment(); | ||
| afterEach( () => env.reset() ); | ||
| const stream$ = lazilyPeriodic( 2 ); | ||
| it( 'should emit the events expected with the specified time interval', () => { | ||
| return env.tick( 5 ) | ||
| .collect( stream$ ) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [2, 4] ); | ||
| }); | ||
| }); | ||
| it( 'should emit the same events with the same timestamps', () => { | ||
| return env.tick( 5 ) | ||
| .collect( stream$ ) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [2, 4] ); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| describe( 'intermediate streams', () => { | ||
| const env = new TestEnvironment(); | ||
| afterEach( () => env.reset() ); | ||
| const stream$ = lazilyPeriodic( 2 ); | ||
| const delayed$ = stream$.delay( 1 ).timestamp().map( ts => ts.time ); | ||
| it( 'should emit events at both stages', () => { | ||
| env.track( stream$, delayed$ ); | ||
| return env.tick( 4 ).collect( stream$ ) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [2, 4] ); | ||
| return env.tick().collect( delayed$ ); | ||
| }) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [3, 5] ); | ||
| }); | ||
| }); | ||
| it( 'will not emit events at both stages without calling .track() first', () => { | ||
| // env.track( stream$, delayed$ ); | ||
| return env.tick( 4 ).collect( stream$ ) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [2, 4] ); | ||
| return env.tick().collect( delayed$ ); | ||
| }) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [] ); | ||
| }); | ||
| }); | ||
| it( 'should emit events even with large tick periods', () => { | ||
| const LONG_TIME = 30000; | ||
| const stream$ = lazilyPeriodic( LONG_TIME ); | ||
| const delayed$ = stream$.delay( LONG_TIME ).timestamp().map( ts => ts.time ); | ||
| env.track( stream$, delayed$ ); | ||
| return env.tick( LONG_TIME * 2 ).collect( stream$ ) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [LONG_TIME, LONG_TIME * 2] ); | ||
| return env.tick( LONG_TIME ).collect( delayed$ ); | ||
| }) | ||
| .then( ({ events }) => { | ||
| assert.deepEqual( events, [LONG_TIME * 2, LONG_TIME * 3] ); | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
+8
-5
@@ -9,7 +9,10 @@ 'use strict'; | ||
| Object.defineProperty(exports, 'run', { | ||
| enumerable: true, | ||
| get: function get() { | ||
| return _run.run; | ||
| } | ||
| Object.keys(_run).forEach(function (key) { | ||
| if (key === "default") return; | ||
| Object.defineProperty(exports, key, { | ||
| enumerable: true, | ||
| get: function get() { | ||
| return _run[key]; | ||
| } | ||
| }); | ||
| }); |
+124
-28
@@ -6,3 +6,3 @@ 'use strict'; | ||
| }); | ||
| exports.TestEnv = undefined; | ||
| exports.BasicTestEnvironment = exports.TestEnvironment = undefined; | ||
@@ -37,12 +37,14 @@ var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
| var TestEnv = exports.TestEnv = function () { | ||
| function TestEnv(timer, sink) { | ||
| _classCallCheck(this, TestEnv); | ||
| var TestEnvironment = exports.TestEnvironment = function () { | ||
| function TestEnvironment() { | ||
| _classCallCheck(this, TestEnvironment); | ||
| this._timer = timer; | ||
| this._sink = sink; | ||
| this._timer = new _virtualTimer2.default(); | ||
| this._t = 0; | ||
| this._cacheMap = new WeakMap(); | ||
| this._disposables = []; | ||
| this._tick = Promise.resolve(); | ||
| } | ||
| _createClass(TestEnv, [{ | ||
| _createClass(TestEnvironment, [{ | ||
| key: 'tick', | ||
@@ -54,5 +56,22 @@ value: function tick() { | ||
| return this._timer.tick(t).then(function () { | ||
| _this._t += t; | ||
| var bucket = _this._sink.next(_this._t); | ||
| this._tick = this._tick.then(function () { | ||
| return _this._timer.tick(t); | ||
| }).then(function () { | ||
| return _this._t += t; | ||
| }); | ||
| return this; | ||
| } | ||
| }, { | ||
| key: 'collect', | ||
| value: function collect(stream) { | ||
| var _this2 = this; | ||
| var _cache2 = this._cache(stream); | ||
| var sink = _cache2.sink; | ||
| var buckets = _cache2.buckets; | ||
| return this._tick.then(function () { | ||
| var bucket = sink.next(_this2._t); | ||
| buckets.push(bucket); | ||
| return bucket.toObject(); | ||
@@ -62,2 +81,65 @@ }); | ||
| }, { | ||
| key: 'results', | ||
| value: function results(stream) { | ||
| var _cache3 = this._cache(stream); | ||
| var buckets = _cache3.buckets; | ||
| return buckets.map(function (bucket) { | ||
| return bucket.toObject(); | ||
| }); | ||
| } | ||
| }, { | ||
| key: 'reset', | ||
| value: function reset() { | ||
| var _this3 = this; | ||
| return this._tick.then(function () { | ||
| _this3._t = 0; | ||
| _this3._timer._now = 0; | ||
| _this3._cacheMap = new WeakMap(); | ||
| _this3._disposables.forEach(function (disposable) { | ||
| return disposable.dispose(); | ||
| }); | ||
| _this3._disposables = []; | ||
| }); | ||
| } | ||
| }, { | ||
| key: 'track', | ||
| value: function track() { | ||
| var _this4 = this; | ||
| for (var _len = arguments.length, streams = Array(_len), _key = 0; _key < _len; _key++) { | ||
| streams[_key] = arguments[_key]; | ||
| } | ||
| streams.forEach(function (s) { | ||
| return _this4._cache(s); | ||
| }); | ||
| return this; | ||
| } | ||
| }, { | ||
| key: '_cache', | ||
| value: function _cache(stream) { | ||
| var cache = this._cacheMap.get(stream); | ||
| if (!cache) { | ||
| cache = this._buildCache(stream); | ||
| this._cacheMap.set(stream, cache); | ||
| this._disposables.push(cache.disposable); | ||
| } | ||
| return cache; | ||
| } | ||
| }, { | ||
| key: '_buildCache', | ||
| value: function _buildCache(_ref) { | ||
| var source = _ref.source; | ||
| var sink = new Sink(); | ||
| var disposable = new _SettableDisposable2.default(); | ||
| var observer = new _observer2.default(sink.event.bind(sink), sink.end.bind(sink), sink.error.bind(sink), disposable); | ||
| var scheduler = new _Scheduler2.default(this._timer, new _Timeline2.default()); | ||
| disposable.setDisposable(source.run(observer, scheduler)); | ||
| return { sink: sink, disposable: disposable, observer: observer, scheduler: scheduler, buckets: [] }; | ||
| } | ||
| }, { | ||
| key: 'now', | ||
@@ -67,14 +149,41 @@ get: function get() { | ||
| } | ||
| }]); | ||
| return TestEnvironment; | ||
| }(); | ||
| var BasicTestEnvironment = exports.BasicTestEnvironment = function () { | ||
| function BasicTestEnvironment(stream) { | ||
| _classCallCheck(this, BasicTestEnvironment); | ||
| this._env = new TestEnvironment(); | ||
| this._stream = stream; | ||
| } | ||
| _createClass(BasicTestEnvironment, [{ | ||
| key: 'tick', | ||
| value: function tick() { | ||
| var t = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0]; | ||
| return this._env.tick(t).collect(this._stream); | ||
| } | ||
| }, { | ||
| key: 'now', | ||
| get: function get() { | ||
| return this._env.now; | ||
| } | ||
| }, { | ||
| key: 'results', | ||
| get: function get() { | ||
| return this._sink.buckets.map(function (b) { | ||
| return b.toObject(); | ||
| }); | ||
| return this._env.results(this._stream); | ||
| } | ||
| }]); | ||
| return TestEnv; | ||
| return BasicTestEnvironment; | ||
| }(); | ||
| function run(stream) { | ||
| return new BasicTestEnvironment(stream); | ||
| } | ||
| var Bucket = function () { | ||
@@ -174,15 +283,2 @@ function Bucket() { | ||
| return Sink; | ||
| }(); | ||
| function run(_ref) { | ||
| var source = _ref.source; | ||
| var timer = new _virtualTimer2.default(); | ||
| var sink = new Sink(); | ||
| var testEnv = new TestEnv(timer, sink); | ||
| var disposable = new _SettableDisposable2.default(); | ||
| var observer = new _observer2.default(sink.event.bind(sink), sink.end.bind(sink), sink.error.bind(sink), disposable); | ||
| var scheduler = new _Scheduler2.default(timer, new _Timeline2.default()); | ||
| disposable.setDisposable(source.run(observer, scheduler)); | ||
| return testEnv; | ||
| } | ||
| }(); |
+3
-2
| { | ||
| "name": "most-test", | ||
| "version": "1.1.0", | ||
| "version": "1.2.0", | ||
| "description": "Unit testing tools for Most.js", | ||
@@ -40,4 +40,5 @@ "typings": "type-definitions/most-test.d.ts", | ||
| "mocha": "^2.5.3", | ||
| "most": "*" | ||
| "most": "*", | ||
| "rimraf": "^2.5.4" | ||
| } | ||
| } |
+1
-1
@@ -1,1 +0,1 @@ | ||
| export {run} from './run'; | ||
| export * from './run'; |
+85
-29
@@ -7,28 +7,99 @@ import Observer from './observer'; | ||
| export class TestEnv | ||
| { | ||
| constructor(timer, sink) { | ||
| this._timer = timer; | ||
| this._sink = sink; | ||
| export class TestEnvironment { | ||
| constructor() { | ||
| this._timer = new VirtualTimer(); | ||
| this._t = 0; | ||
| this._cacheMap = new WeakMap(); | ||
| this._disposables = []; | ||
| this._tick = Promise.resolve(); | ||
| } | ||
| tick(t = 1) { | ||
| return this._timer.tick(t) | ||
| .then(() => { | ||
| this._t += t; | ||
| const bucket = this._sink.next(this._t); | ||
| return bucket.toObject(); | ||
| }); | ||
| get now() { | ||
| return this._t; | ||
| } | ||
| tick( t = 1 ) { | ||
| this._tick = this._tick.then( () => this._timer.tick(t) ) | ||
| .then( () => this._t += t ); | ||
| return this; | ||
| } | ||
| collect( stream ) { | ||
| const { sink, buckets } = this._cache( stream ); | ||
| return this._tick.then( () => { | ||
| const bucket = sink.next( this._t ); | ||
| buckets.push( bucket ); | ||
| return bucket.toObject(); | ||
| }); | ||
| } | ||
| results( stream ) { | ||
| const { buckets } = this._cache( stream ); | ||
| return buckets.map( bucket => bucket.toObject() ); | ||
| } | ||
| reset() { | ||
| return this._tick.then( () => { | ||
| this._t = 0; | ||
| this._timer._now = 0; | ||
| this._cacheMap = new WeakMap(); | ||
| this._disposables.forEach( disposable => disposable.dispose() ); | ||
| this._disposables = []; | ||
| }); | ||
| } | ||
| track( ...streams ) { | ||
| streams.forEach( s => this._cache(s) ); | ||
| return this; | ||
| } | ||
| _cache( stream ) { | ||
| let cache = this._cacheMap.get( stream ); | ||
| if( !cache ) { | ||
| cache = this._buildCache( stream ); | ||
| this._cacheMap.set( stream, cache ); | ||
| this._disposables.push( cache.disposable ); | ||
| } | ||
| return cache; | ||
| } | ||
| _buildCache({ source }) { | ||
| const sink = new Sink(); | ||
| const disposable = new SettableDisposable(); | ||
| const observer = new Observer( | ||
| sink.event.bind(sink), | ||
| sink.end.bind(sink), | ||
| sink.error.bind(sink), | ||
| disposable ); | ||
| const scheduler = new Scheduler( this._timer, new Timeline() ); | ||
| disposable.setDisposable( source.run(observer, scheduler) ); | ||
| return { sink, disposable, observer, scheduler, buckets: [] }; | ||
| } | ||
| } | ||
| export class BasicTestEnvironment { | ||
| constructor( stream ) { | ||
| this._env = new TestEnvironment(); | ||
| this._stream = stream; | ||
| } | ||
| get now() { | ||
| return this._t; | ||
| return this._env.now; | ||
| } | ||
| tick( t = 1 ) { | ||
| return this._env.tick( t ).collect( this._stream ); | ||
| } | ||
| get results() { | ||
| return this._sink.buckets.map(b => b.toObject()); | ||
| return this._env.results( this._stream ); | ||
| } | ||
| } | ||
| export function run( stream ) { | ||
| return new BasicTestEnvironment( stream ); | ||
| } | ||
| class Bucket | ||
@@ -113,16 +184,1 @@ { | ||
| } | ||
| export function run({source}) { | ||
| const timer = new VirtualTimer(); | ||
| const sink = new Sink(); | ||
| const testEnv = new TestEnv(timer, sink); | ||
| const disposable = new SettableDisposable(); | ||
| const observer = new Observer( | ||
| sink.event.bind(sink), | ||
| sink.end.bind(sink), | ||
| sink.error.bind(sink), | ||
| disposable); | ||
| const scheduler = new Scheduler(timer, new Timeline()); | ||
| disposable.setDisposable(source.run(observer, scheduler)); | ||
| return testEnv; | ||
| } |
@@ -6,18 +6,53 @@ | ||
| export const run: <T>(stream: Stream<T>) => TestEnvironment<T>; | ||
| type Time = Number; | ||
| type Interval = Number; | ||
| export class TestEnvironment { | ||
| type TestEnvironment<T> = { | ||
| tick: (min?: Interval) => Promise<Result<T>>; | ||
| now: Time; | ||
| results: Result<T>[]; | ||
| readonly now: Time; | ||
| constructor(); | ||
| tick<T>( ms?: Interval ): this; | ||
| track( ...streams: Stream<any>[] ): this; | ||
| collect<T>( stream: Stream<T> ): Promise<Result<T>>; | ||
| results<T>( stream: Stream<T> ): Result<T>[]; | ||
| reset(): void; | ||
| } | ||
| type Result<T> = { | ||
| events: Array<T>; | ||
| end?: true; | ||
| events: T[]; | ||
| end?: true | { value: T }; | ||
| error?: Error; | ||
| } | ||
| export class BasicTestEnvironment<T> { | ||
| readonly now: Time; | ||
| readonly results: Result<T>[]; | ||
| constructor( stream: Stream<T> ); | ||
| tick<T>( ms?: Interval ): Promise<Result<T>>; | ||
| } | ||
| export function run<T>( stream: Stream<T> ): BasicTestEnvironment<T>; | ||
| class Sink<T> { | ||
| buckets: Bucket<T>[]; | ||
| t: Time; | ||
| events: [string, Time, any][]; | ||
| index: number; | ||
| next( t: Time ): Bucket<T>; | ||
| event( t: Time, x: T ); | ||
| end( t: Time, x: T ); | ||
| error( t: Time, err: Error ); | ||
| } | ||
| class Bucket<T> { | ||
| events: T[]; | ||
| end: true | { value: T }; | ||
| error: Error; | ||
| toObject(): Result<T>; | ||
| } | ||
| } |
35770
46.23%19
5.56%924
49.03%9
12.5%