xstream
Version:
An extremely intuitive, small, and fast functional reactive stream library for JavaScript
374 lines (337 loc) • 10.7 kB
text/typescript
/// <reference path="../typings/globals/mocha/index.d.ts" />
/// <reference path="../typings/globals/node/index.d.ts" />
import xs, {Producer, Listener, Stream} from '../src/index';
import * as assert from 'assert';
describe('Stream', () => {
it('should have all the core static operators', () => {
assert.equal(typeof xs.create, 'function');
assert.equal(typeof xs.createWithMemory, 'function');
assert.equal(typeof xs.never, 'function');
assert.equal(typeof xs.empty, 'function');
assert.equal(typeof xs.throw, 'function');
assert.equal(typeof xs.of, 'function');
assert.equal(typeof xs.fromArray, 'function');
assert.equal(typeof xs.fromPromise, 'function');
assert.equal(typeof xs.periodic, 'function');
assert.equal(typeof xs.merge, 'function');
assert.equal(typeof xs.combine, 'function');
});
it('should have all the core operators as methods, plus addListener and removeListener', () => {
const stream = xs.create();
assert.equal(typeof stream.addListener, 'function');
assert.equal(typeof stream.removeListener, 'function');
assert.equal(typeof stream.map, 'function');
assert.equal(typeof stream.mapTo, 'function');
assert.equal(typeof stream.filter, 'function');
assert.equal(typeof stream.take, 'function');
assert.equal(typeof stream.drop, 'function');
assert.equal(typeof stream.last, 'function');
assert.equal(typeof stream.startWith, 'function');
assert.equal(typeof stream.endWhen, 'function');
assert.equal(typeof stream.fold, 'function');
assert.equal(typeof stream.flatten, 'function');
assert.equal(typeof stream.compose, 'function');
assert.equal(typeof stream.remember, 'function');
assert.equal(typeof stream.debug, 'function');
assert.equal(typeof stream.imitate, 'function');
});
it('should be createable giving a custom producer object', (done) => {
const expected = [10, 20, 30];
let listenerGotEnd: boolean = false;
const producer: Producer<number> = {
start(listener: Listener<number>) {
listener.next(10);
listener.next(20);
listener.next(30);
listener.complete();
},
stop() {
done();
assert.equal(expected.length, 0);
assert.equal(listenerGotEnd, true);
},
};
const stream: Stream<number> = xs.create(producer);
stream.addListener({
next: (x: number) => {
assert.equal(x, expected.shift());
},
error: (err: any) => done(err),
complete: () => {
listenerGotEnd = true;
},
});
});
it('should allow using shamefullySend* methods', (done) => {
const expected = [10, 20, 30];
let listenerGotEnd: boolean = false;
const stream = xs.create();
stream.addListener({
next: (x: number) => {
assert.equal(x, expected.shift());
},
error: (err: any) => done(err),
complete: () => {
listenerGotEnd = true;
},
});
stream.shamefullySendNext(10);
stream.shamefullySendNext(20);
stream.shamefullySendNext(30);
stream.shamefullySendComplete();
assert.equal(expected.length, 0);
assert.equal(listenerGotEnd, true);
done();
});
it('should be possible to addListener and removeListener with 1 listener', (done) => {
const stream = xs.periodic(100);
const expected = [0, 1, 2];
let listener = {
next: (x: number) => {
assert.equal(x, expected.shift());
if (expected.length === 0) {
stream.removeListener(listener);
done();
}
},
error: (err: any) => done(err),
complete: () => done('should not call complete'),
};
stream.addListener(listener);
});
it('should broadcast events to two listeners', (done) => {
const stream = xs.periodic(100);
const expected1 = [0, 1, 2];
const expected2 = [1, 2];
let listener1 = {
next: (x: number) => {
assert.equal(x, expected1.shift());
},
error: (err: any) => done(err),
complete: () => done('should not call complete'),
};
stream.addListener(listener1);
let listener2 = {
next: (x: number) => {
assert.equal(x, expected2.shift());
},
error: (err: any) => done(err),
complete: () => done('should not call complete'),
};
setTimeout(() => {
stream.addListener(listener2);
}, 150);
setTimeout(() => {
stream.removeListener(listener1);
stream.removeListener(listener2);
assert.equal(expected1.length, 0);
assert.equal(expected2.length, 0);
done();
}, 400);
});
it('should not stop if listener is synchronously removed and re-added', (done) => {
const stream = xs.periodic(100);
const expected = [0, 1, 2];
let listener = {
next: (x: number) => {
assert.equal(x, expected.shift());
if (expected.length === 0) {
stream.removeListener(listener);
done();
}
},
error: (err: any) => done(err),
complete: () => done('should not call complete'),
};
stream.addListener(listener);
setTimeout(() => {
stream.removeListener(listener);
stream.addListener(listener);
}, 150);
});
it('should restart if listener is asynchronously removed and re-added', (done) => {
const stream = xs.periodic(100);
let expected = [0, 1, 2];
let listener = {
next: (x: number) => {
assert.equal(x, expected.shift());
if (expected.length === 0) {
stream.removeListener(listener);
done();
}
},
error: (err: any) => done(err),
complete: () => done('should not call complete'),
};
stream.addListener(listener);
setTimeout(() => {
stream.removeListener(listener);
}, 130);
setTimeout(() => {
assert.equal(expected.length, 2);
expected = [0, 1, 2];
stream.addListener(listener);
}, 180);
});
it('should synchronously stop producer when completed', (done) => {
let on = false;
const stream = xs.create({
start: (listener) => {
on = true;
listener.next(10);
listener.next(20);
listener.next(30);
listener.complete();
},
stop: () => {
on = false;
},
});
const expected1 = [10, 20, 30];
const expected2 = [10, 20, 30];
stream.addListener({
next: (x: number) => {
assert.equal(on, true);
assert.equal(x, expected1.shift());
},
error: (err: any) => done(err),
complete: () => {
assert.equal(on, true);
assert.equal(expected1.length, 0);
},
});
assert.equal(on, false);
assert.equal(expected1.length, 0);
stream.addListener({
next: (x: number) => {
assert.equal(on, true);
assert.equal(x, expected2.shift());
},
error: (err: any) => done(err),
complete: () => {
assert.equal(on, true);
assert.equal(expected2.length, 0);
},
});
assert.equal(on, false);
assert.equal(expected2.length, 0);
done();
});
it('should synchronously stop producer when error thrown', (done) => {
let on = false;
const stream = xs.create({
start: (listener) => {
on = true;
listener.next(10);
listener.next(20);
listener.next(30);
listener.error('oops');
},
stop: () => {
on = false;
},
});
const expected1 = [10, 20, 30];
const expected2 = [10, 20, 30];
stream.addListener({
next: (x: number) => {
assert.equal(on, true);
assert.equal(x, expected1.shift());
},
error: (err: any) => {
assert.equal(err, 'oops');
assert.equal(on, true);
assert.equal(expected1.length, 0);
},
complete: () => {
done('complete should not be called');
},
});
assert.equal(on, false);
assert.equal(expected1.length, 0);
stream.addListener({
next: (x: number) => {
assert.equal(on, true);
assert.equal(x, expected2.shift());
},
error: (err: any) => {
assert.equal(err, 'oops');
assert.equal(on, true);
assert.equal(expected2.length, 0);
},
complete: () => {
done('complete should not be called');
},
});
assert.equal(on, false);
assert.equal(expected2.length, 0);
done();
});
describe('create', () => {
it('throws a helpful error if you pass an incomplete producer', (done) => {
try {
const incompleteProducer = <Producer<any>> <any> {
start: () => {},
stop: undefined
};
xs.create(incompleteProducer);
} catch (e) {
assert.equal(e.message, 'producer requires both start and stop functions');
done();
}
});
});
describe('addListener', () => {
it('throws a helpful error if you forget the next function', (done) => {
const stream = xs.empty();
const listener = <Listener<Number>> <any> {};
try {
stream.addListener(listener);
} catch (e) {
assert.equal(e.message, 'stream.addListener() requires all three ' +
'next, error, and complete functions.');
done();
}
});
it('throws a helpful error if you forget the error function', (done) => {
const stream = xs.empty();
const listener = <Listener<Number>> <any> {
next: (x: any) => {}
};
try {
stream.addListener(listener);
} catch (e) {
assert.equal(e.message, 'stream.addListener() requires all three ' +
'next, error, and complete functions.');
done();
}
});
it('throws a helpful error if you forget the complete function', (done) => {
const stream = xs.empty();
const listener = <Listener<Number>> <any> {
next: (x: any) => {},
error: (err: any) => {}
};
try {
stream.addListener(listener);
} catch (e) {
assert.equal(e.message, 'stream.addListener() requires all three ' +
'next, error, and complete functions.');
done();
}
});
it('throws a helpful error if you pass a non function value as the next function', (done) => {
const stream = xs.empty();
const listener = <Listener<Number>> <any> {
next: undefined
};
try {
stream.addListener(listener);
} catch (e) {
assert.equal(e.message, 'stream.addListener() requires all three ' +
'next, error, and complete functions.');
done();
}
});
});
});