xstream
Version:
An extremely intuitive, small, and fast functional reactive stream library for JavaScript
288 lines (257 loc) • 8.54 kB
text/typescript
/// <reference path="../../typings/globals/mocha/index.d.ts" />
/// <reference path="../../typings/globals/node/index.d.ts" />
import xs, {Producer, Listener, Stream, MemoryStream} from '../../src/index';
import delay from '../../src/extra/delay';
import * as assert from 'assert';
describe('Stream.prototype.imitate', () => {
it('should be able to model a circular dependency in the stream graph', (done) => {
const secondMimic = xs.create<number>();
const first = secondMimic.map(x => x * 10).take(3);
const second = first.map(x => x + 1).startWith(1).compose(delay<number>(1));
secondMimic.imitate(second);
const expected = [1, 11, 111, 1111];
second.addListener({
next: (x: number) => {
assert.equal(x, expected.shift());
},
error: (err: any) => done(err),
complete: () => {
assert.equal(expected.length, 0);
done();
},
});
});
it('should be able to model a circular dependency, mimic subscribed', (done) => {
const secondMimic = xs.create<number>();
const first = secondMimic.map(x => x * 10).take(3);
const second = first.map(x => x + 1).startWith(1).compose(delay<number>(1));
secondMimic.imitate(second);
const expected = [1, 11, 111, 1111];
secondMimic.addListener({
next: (x: number) => {
assert.equal(x, expected.shift());
},
error: (err: any) => done(err),
complete: () => {
assert.equal(expected.length, 0);
done();
},
});
});
it('should broadcast the source stream to multiple listeners', (done) => {
const fakeSecond = xs.create<number>();
const first = fakeSecond.map(x => x * 10).take(3);
const second = first.map(x => x + 1).startWith(1).compose(delay<number>(100));
fakeSecond.imitate(second);
const expected1 = [1, 11, 111, 1111];
const expected2 = [11, 111, 1111];
let completed1 = false;
let completed2 = false;
let listener1 = {
next: (x: number) => {
assert.equal(x, expected1.shift());
},
error: (err: any) => done(err),
complete: () => {
completed1 = true;
}
};
fakeSecond.addListener(listener1);
second.addListener({ next: () => { }, error: () => { }, complete: () => { } });
let listener2 = {
next: (x: number) => {
assert.equal(x, expected2.shift());
},
error: (err: any) => done(err),
complete: () => {
completed2 = true;
}
};
setTimeout(() => {
fakeSecond.addListener(listener2);
}, 150);
setTimeout(() => {
fakeSecond.removeListener(listener1);
fakeSecond.removeListener(listener2);
assert.equal(expected1.length, 0);
assert.equal(expected2.length, 0);
assert.equal(completed1, true);
assert.equal(completed2, true);
done();
}, 600);
});
it('should not cause leaked cyclic executions (1)', (done) => {
const expectedProxy = [2, 4, 8, 16, 32 /* inertia due to stopping on next tick */];
const expectedResult = [2, 4, 8, 16];
const proxy$ = xs.create<number>();
const source$ = proxy$.startWith(1).map(x => x * 2)
.debug(x => {
try {
assert.equal(expectedProxy.length > 0, true,
'should be expecting the next value ' + x);
assert.equal(x, expectedProxy.shift());
} catch (err) {
done(err);
}
});
const result$ = source$.compose(delay(100)).compose(s => <Stream<number>> s);
proxy$.imitate(result$);
result$.take(4).addListener({
next: (x: number) => {
assert.equal(x, expectedResult.shift());
},
error: (err: any) => done(err),
complete: () => {
assert.equal(expectedProxy.length, 1); // still waiting for 32
assert.equal(expectedResult.length, 0);
setTimeout(() => {
done();
}, 1000);
},
});
});
it('should not cause leaked cyclic executions (2)', (done) => {
const expectedProxy = [2, 4, 8, 16, 32 /* inertia due to stopping on next tick */];
const expectedResult = [2, 4, 8, 16];
const proxy$ = xs.create<number>();
const source$ = proxy$.startWith(1).map(x => x * 2)
.debug(x => {
try {
assert.equal(expectedProxy.length > 0, true,
'should be expecting the next value ' + x);
assert.equal(x, expectedProxy.shift());
} catch (err) {
done(err);
}
});
const result$ = source$.compose(delay(100)).compose(s => <Stream<number>> s);
proxy$.imitate(result$)
source$.take(4).addListener({
next: (x: number) => {
assert.equal(x, expectedResult.shift());
},
error: (err: any) => done(err),
complete: () => {
assert.equal(expectedProxy.length, 1); // still waiting for 32
assert.equal(expectedResult.length, 0);
setTimeout(() => {
done();
}, 1000);
},
});
});
it('should not cause stack overflow while detecting cycles', (done) => {
const outside = xs.periodic(150);
const secondMimic = xs.create<number>();
const first = xs.merge(outside, secondMimic.map(x => x * 10));
const second = first.map(x => x + 1).compose(delay<number>(100));
secondMimic.imitate(second);
const expectedSecond1 = [1];
const expectedSecond4 = [1, 11, 2, 111];
const expectedOutside = [0, 1];
let completedSecond1 = false;
let completedSecond4 = false;
let completedOutside = false;
second.take(1).addListener({
next: (x: number) => {
assert.equal(x, expectedSecond1.shift());
},
error: (err: any) => done(err),
complete: () => {
completedSecond1 = true;
},
});
second.take(4).addListener({
next: (x: number) => {
assert.equal(x, expectedSecond4.shift());
},
error: (err: any) => done(err),
complete: () => {
completedSecond4 = true;
},
});
outside.take(2).addListener({
next: (x: number) => {
assert.equal(x, expectedOutside.shift());
},
error: (err: any) => done(err),
complete: () => {
completedOutside = true;
},
});
setTimeout(() => {
assert.equal(expectedSecond1.length, 0);
assert.equal(expectedSecond4.length, 0);
assert.equal(expectedOutside.length, 0);
assert.equal(completedSecond1, true);
assert.equal(completedSecond4, true);
assert.equal(completedOutside, true);
done();
}, 1000);
});
it('should not propagate errors in a cycle', (done) => {
const proxyAction$ = xs.create<number>();
const state$ = proxyAction$.fold((state, action) => state + action, 0);
const action$ = state$.map(state => {
if (state === 3) {
throw new Error(':(');
}
return xs.of(1).compose(delay<number>(20));
}).flatten();
proxyAction$.imitate(action$);
const expected = [0, 1, 2];
let errors: Array<any> = [];
state$.addListener({
next: (x: number) => {
assert.equal(x, expected.shift());
},
error: (err: any) => {
errors.push(err);
},
complete: () => {
done('complete should not be called');
},
});
setTimeout(() => {
assert.equal(errors.length, 1);
assert.equal(expected.length, 0);
done();
}, 150);
});
it('should not by itself start the target stream execution', (done) => {
let nextDelivered = false;
const stream = xs.periodic(50).take(3).debug(() => {
nextDelivered = true;
});
const proxyStream = xs.create<number>();
setTimeout(() => {
assert.equal(nextDelivered, false);
done();
}, 125);
proxyStream.imitate(stream);
});
it('should throw an error when given a MemoryStream', (done) => {
const stream = xs.periodic(50).take(3).remember();
assert.strictEqual(stream instanceof MemoryStream, true);
const proxyStream = xs.create<number>();
assert.throws(() => {
proxyStream.imitate(stream);
});
done();
});
it('should transfer existing listeners to imitation target', (done) => {
const mimic = xs.create<number>();
const expected = [0, 1, 2];
mimic.addListener({
next: (x: number) => {
assert.equal(x, expected.shift());
},
error: (err: any) => done(err),
complete: () => {
assert.equal(expected.length, 0);
done();
},
});
mimic.imitate(xs.periodic(50).take(3));
});
});