如何使用 mockDOMSource 测试 Cycle.js 中的动作流?

How to use mockDOMSource to test a stream of actions in Cycle.js?

我意识到使用 cycle/time 可能有更好的方法,但我只是想了解基础知识。不知何故,我的 action$ 流似乎不是 运行;我尝试使用 xs.periodic 构建多个模拟 dom。测试框架是mocha

import 'mocha';
import {expect} from 'chai';
import xs from 'xstream';
import Stream from 'xstream';
import {mockDOMSource, DOMSource} from '@cycle/dom';
import {HTTPSource} from '@cycle/http';
import XStreamAdapter from '@cycle/xstream-adapter';

export interface Props {
  displayAbs: boolean
}

export interface ISources {
  DOM: DOMSource;
  http: HTTPSource;
}

function testIntent(sources: ISources):Stream<Props> {
  return  xs.merge<Props>(
      sources.DOM
          .select('.absShow').events('click')
          .mapTo( { displayAbs: true } ),
      sources.DOM
          .select('.absHide').events('click')
          .mapTo( { displayAbs: false } )
  ).startWith( {displayAbs: false } );
}

describe( 'Test', ()=>{

  describe( 'intent()', ()=>{

    it('should change on click to shows and hides', () => {
      let listenerGotEnd = false;

      const mDOM$: Stream<DOMSource> = xs.periodic(1000).take(6).map(ii => {
        if (ii % 2 == 0) {
          return mockDOMSource(XStreamAdapter, {
            '.absShow': {'click': xs.of({target: {}})}
          })
        }
        else {
          return mockDOMSource(XStreamAdapter, {
            '.absHide': {'click': xs.of({target: {}})}
          })
        }
      });

      const action$ = mDOM$.map(mDOM => testIntent({
        DOM: mDOM,
        http: {} as HTTPSource,
      })).flatten();


      action$.addListener({
        next: (x) => {
          console.log("x is " + x.displayAbs);
        },
        error: (err) => {
          console.log("error is:" + err);
            throw err;
        },
        complete: () => { listenerGotEnd = true; }
      });
      expect(listenerGotEnd).to.equal(true);
    });

  });/* end of describe intent */

});

测试不是 运行 的主要原因是因为它是异步的,所以在 mocha 中我们需要接受 done 回调,然后在测试完成时调用它。

在不使用 @cycle/time 的情况下,这就是我编写此测试的方式:

import 'mocha';
import {expect} from 'chai';
import xs, {Stream} from 'xstream';
import {mockDOMSource, DOMSource} from '@cycle/dom';
import XStreamAdapter from '@cycle/xstream-adapter';

export interface Props {
  displayAbs: boolean
}

export interface ISources {
  DOM: DOMSource;
}

function testIntent(sources: ISources):Stream<Props> {
  return  xs.merge<Props>(
      sources.DOM
          .select('.absShow').events('click')
          .mapTo( { displayAbs: true } ),
      sources.DOM
          .select('.absHide').events('click')
          .mapTo( { displayAbs: false } )
  ).startWith( {displayAbs: false } );
}

describe('Test', () => {
  describe('intent()', () => {
    it('should change on click to shows and hides', (done) => {
      const show$ = xs.create();
      const hide$ = xs.create();

      const DOM = mockDOMSource(XStreamAdapter, {
        '.absShow': {
          'click': show$
        },

        '.absHide': {
          'click': hide$
        }
      });

      const intent$ = testIntent({DOM});

      const expectedValues = [
        {displayAbs: false},
        {displayAbs: true},
        {displayAbs: false},
      ]

      intent$.take(expectedValues.length).addListener({
        next: (x) => {
          expect(x).to.deep.equal(expectedValues.shift());
        },
        error: done,
        complete: done
      });

      show$.shamefullySendNext({});
      hide$.shamefullySendNext({});
    });
  });
});

此测试运行时间为 11 毫秒,比使用 xs.periodic(1000).take(6)

快了很多

为了比较,下面是我用 @cycle/time 写的:

import {mockTimeSource} from '@cycle/time'

describe('Test', () => {
  describe('intent()', () => {
    it('should change on click to shows and hides', (done) => {
      const Time = mockTimeSource();

      const show$     = Time.diagram('---x-----');
      const hide$     = Time.diagram('------x--');
      const expected$ = Time.diagram('f--t--f--', {f: false, t: true});

      const DOM = mockDOMSource({
        '.absShow': {
          'click': show$
        },

        '.absHide': {
          'click': hide$
        }
      });

      const intent$ = testIntent({DOM}).map(intent => intent.displayAbs);

      Time.assertEqual(intent$, expected$);

      Time.run(done);
    });
  });
});

第一个版本实际上是@cycle/time 在幕后为您所做的,这只是一种稍微好一点的编写方式。有更好的错误消息也很好。