UNPKG

@motorcycle/test

Version:
348 lines (227 loc) 6.68 kB
# @motorcycle/test -- 2.1.0 Testing functions for Motorcycle.ts ## Get it ```sh yarn add @motorcycle/test # or npm install --save @motorcycle/test ``` ## API Documentation All functions are curried! #### TestScheduler <p> TestScheduler </p> ```typescript export type TestScheduler = { readonly tick: (delay: Delay) => Promise<void> readonly scheduler: Scheduler } ``` #### VirtualTimer <p> A Timer instance with control over how time progresses. </p> <details> <summary>See an example</summary> ```typescript import { VirtualTimer } from '@motorcycle/test' const timer = new VirtualTimer() timer.setTimer(() => console.log('Hello'), 100) timer.tick(100) ``` </details> <details> <summary>See the code</summary> ```typescript export class VirtualTimer implements Timer { protected time: Time = 0 protected targetTime: Time = 0 protected currentTime: Time = Infinity protected task: (() => any) | void = void 0 protected timer: Handle protected active: boolean = false protected running: boolean = false protected key: Handle = {} protected promise: Promise<void> = Promise.resolve() constructor() {} public now(): Time { return this.time } public setTimer(fn: () => any, delay: Delay): Handle { if (this.task !== void 0) throw new Error('Virtualtimer: Only supports one in-flight task') this.task = fn this.currentTime = this.time + Math.max(0, delay) if (this.active) this.run() return this.key } public clearTimer(handle: Handle) { if (handle !== this.key) return clearTimeout(this.timer) this.timer = void 0 this.currentTime = Infinity this.task = void 0 } public tick(delay: Delay) { if (delay <= 0) return this.promise this.targetTime = this.targetTime + delay return this.run() } protected run() { if (this.running) return this.promise this.running = true this.active = true return new Promise<void>((resolve, reject) => { this.timer = setTimeout(() => { this.step() .then(() => resolve()) .catch(reject) }, 0) }) } protected step() { return new Promise((resolve, reject) => { if (this.time >= this.targetTime) { this.time = this.targetTime this.currentTime = Infinity this.running = false return resolve() } const task = this.task this.task = void 0 this.time = this.currentTime this.currentTime = Infinity if (typeof task === 'function') task() this.timer = setTimeout( () => this.step() .then(() => resolve()) .catch(reject), 0 ) }) } } ``` </details> <hr /> #### collectEventsFor\<A\>(delay: Delay, stream: Stream\<A\>): Promise\<ReadonlyArray\<A\>\> <p> Collects events for a given amount of time. </p> <details> <summary>See an example</summary> ```typescript // Mocha style tests it('increasing value by one', () => { const stream = scan(x => x + 1, skip(1, periodic(10))) return collectEventsFor(30, stream).then(events => assert.deepEqual(events, [0, 1, 2, 3])) }) ``` </details> <details> <summary>See the code</summary> ```typescript export const collectEventsFor: CollectEventsFor = curry2(function collectEventsFor<A>( delay: Delay, stream: Stream<A> ) { const { tick, scheduler } = createTestScheduler() const eventList: Array<A> = [] runEffects(tap(a => eventList.push(a), stream), scheduler) return tick(delay).then(() => eventList.slice()) }) export interface CollectEventsFor { <A>(delay: Delay, stream: Stream<A>): Promise<ReadonlyArray<A>> (delay: Delay): <A>(stream: Stream<A>) => Promise<ReadonlyArray<A>> <A>(delay: Delay): (stream: Stream<A>) => Promise<ReadonlyArray<A>> } ``` </details> <hr /> #### createTestScheduler(timeline?: Timeline): TestScheduler <p> Creates a test scheduler. Using the test scheduler you are the master of time. </p> <details> <summary>See an example</summary> ```typescript import { createTestScheduler } from '@motorcycle/test' import { now, runEffects } from '@motorcycle/stream' const { tick, scheduler } createTestScheduler() const stream = now(100) runEffects(stream, scheduler).then(() => console.log('done!')) // manually tick forward in time // tick returns a Promise that resolves when all scheduled tasks have been run. tick(100) ``` </details> <details> <summary>See the code</summary> ```typescript export function createTestScheduler(timeline: Timeline = newTimeline()): TestScheduler { const timer = new VirtualTimer() const tick = (delay: Delay) => timer.tick(delay) const scheduler: Scheduler = newScheduler(timer, timeline) return { tick, scheduler } } ``` </details> <hr /> #### run\<Sources, Sinks\>(Main: Component\<Sources, Sinks\>, IO: IOComponent\<Sinks, Sources\>) <p> This is nearly identical to the `run` found inside of `@motorcycle/run`. The only difference is that it makes use of the test scheduler to create the application's event loop. An additional property is returned with the `tick` that allows you to control how time progresses. </p> <details> <summary>See an example</summary> ```typescript import { run } from '@motorcycle/test' import { makeDomComponent, div, button, h2, query, clickEvent } from '@motorcycle/dom' function Main(sources) { const { dom } = sources const click$ = clickEvent(query('button', dom)) const count$ = scan(x => x + 1, click$) const view$ = map(view, count$) return { view$ } } function view(count: number) { return div([ h2(`Clicked ${count} times`), button('Click Me'), ]) } const Dom = fakeDomComponent({ 'button': { click: now(fakeEvent()) } }) const { tick, dispose } = run(UI, Dom) tick(500).then(dispose) ``` </details> <details> <summary>See the code</summary> ```typescript export function run< Sources extends Readonly<Record<string, any>>, Sinks extends Readonly<Record<string, Stream<any>>> >(Main: Component<Sources, Sinks>, IO: IOComponent<Sinks, Sources>) { const { stream: endSignal } = createProxy<void>() const sinkProxies = {} as Record<keyof Sinks, ProxyStream<any>> const proxySinks: Sinks = createProxySinks(sinkProxies, endSignal) const sources: Sources = IO(proxySinks) const sinks: Sinks = createDisposableSinks(Main(sources), endSignal) const { disposable, tick } = replicateSinks(sinks, sinkProxies) function dispose() { endSignal.event(scheduler.currentTime(), void 0) disposable.dispose() disposeSources(sources) } return { sinks, sources, dispose, tick } } ``` </details> <hr />