网站搜索

javascript - 如何使用 mockDOMSource 在 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 */

});

答案#1

测试无法运行的主要原因是它是异步的,所以在 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 在后台为你所做的事情,这只是一个稍微更好的写法。拥有更好的错误信息也很不错。