Skip to content

Latest commit

 

History

History

test

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

@motorcycle/test -- 3.0.0

Testing functions for Motorcycle.ts

Get it

yarn add @motorcycle/test
# or
npm install --save @motorcycle/test

API Documentation

All functions are curried!

TestScheduler

TestScheduler

export type TestScheduler = {
  readonly tick: (delay: Delay) => Promise<void>
  readonly scheduler: Scheduler
}

VirtualTimer

A Timer instance with control over how time progresses.

See an example
import { VirtualTimer } from '@motorcycle/test'

const timer = new VirtualTimer()

timer.setTimer(() => console.log('Hello'), 100)

timer.tick(100)
See the code
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
      )
    })
  }
}

collectEventsFor<A>(delay: Delay, stream: Stream<A>): Promise<ReadonlyArray<A>>

Collects events for a given amount of time.

See an example
// 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]))
})
See the code
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>>
}

createTestScheduler(timeline?: Timeline): TestScheduler

Creates a test scheduler. Using the test scheduler you are the master of time.

See an example
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)
See the code
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 }
}

run<Sources, Sinks>(Main: Component<Sources, Sinks>, IO: IOComponent<Sinks, Sources>)

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.

See an example
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)
See the code
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 }
}