3.0.0 • Published 7 years ago

@motorcycle/test v3.0.0

Weekly downloads
1
License
MIT
Repository
github
Last release
7 years ago

@motorcycle/test -- 2.1.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.

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
      )
    })
  }
}

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

Collects events for a given amount of time.

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>>
}

createTestScheduler(timeline?: Timeline): TestScheduler

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

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 }
}

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.

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 }
}
3.0.0

7 years ago

2.1.0

7 years ago

2.0.0

7 years ago

1.6.0

7 years ago

1.5.0

7 years ago

1.3.0

7 years ago

1.4.0

7 years ago

1.2.0

7 years ago

1.1.0

7 years ago

1.0.0

7 years ago