如何使用 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 在幕后为您所做的,这只是一种稍微好一点的编写方式。有更好的错误消息也很好。
我意识到使用 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 在幕后为您所做的,这只是一种稍微好一点的编写方式。有更好的错误消息也很好。