import {assert} from "chai"; import {spy, useFakeTimers} from "sinon"; import {map} from "../src/index"; import {placeholder} from "../src/placeholder"; import * as S from "../src/stream"; import { Stream, keepWhen, apply, filterApply, snapshot, snapshotWith, split, throttle, delay } from "../src/stream"; import * as B from "../src/behavior"; import {Behavior, at} from "../src/behavior"; const addTwo = (v: number): number => v + 2; const sum = (a: number, b: number): number => a + b; function publish(a: A, stream: Stream): void { stream.push(a); } describe("Stream", () => { describe("isStream", () => { it("should be true when Stream object", () => { assert.isTrue(S.isStream(S.empty())); }); it("should be true on placeholder", () => { assert.isTrue(S.isStream(placeholder())); }); it("should be false when not Stream object", () => { assert.isFalse(S.isStream([])); assert.isFalse(S.isStream({})); assert.isFalse(S.isStream("test")); assert.isFalse(S.isStream([S.empty()])); assert.isFalse(S.isStream(1234)); assert.isFalse(S.isStream(S.isStream)); }); }); describe("subscribe", () => { it("supports multiple listeners", () => { const s = S.empty(); const cb1 = spy(); const cb2 = spy(); s.subscribe(cb1); s.subscribe(cb2); publish(2, s); publish(3, s); assert.strictEqual(cb1.callCount, 2); assert.strictEqual(cb2.callCount, 2); }); it("single listeners can be removed", () => { const s = S.empty(); const cb1 = spy(); const cb2 = spy(); s.subscribe(cb1); const listener = s.subscribe(cb2); s.removeListener(listener); publish(2, s); publish(3, s); assert.strictEqual(cb1.callCount, 2); assert.strictEqual(cb2.callCount, 0); }); }); describe("publish", () => { it("should be a function", () => { assert.isFunction(publish); }); it("should call the subscribers", () => { const obs = S.empty(); const callback = spy(); S.subscribe(callback, obs); assert.equal(callback.callCount, 0); publish("value", obs); assert.equal(callback.callCount, 1); publish("value", obs); assert.equal(callback.callCount, 2); }); it("should pass the published value to subscribers", () => { const obs = S.empty(); const callback1 = spy(); const callback2 = spy(); S.subscribe(callback1, obs); S.subscribe(callback2, obs); const err1 = "Wrong or no value was recieved after publish."; publish("random value", obs); assert(callback1.calledWith("random value"), err1); assert(callback2.calledWith("random value"), err1); const err2 = "Wrong or no value was recieved after a second publish."; publish("another random value", obs); assert(callback1.calledWith("another random value"), err2); assert(callback2.calledWith("another random value"), err2); }); }); describe("combine", () => { it("should be a function", () => { assert.isFunction(S.combine); }); it("should combine two streams", () => { const stream1 = S.empty(); const stream2 = S.empty(); const callback = spy(); const combinedS = S.combine(stream1, stream2); S.subscribe(callback, combinedS); publish(1, stream1); publish("2", stream2); assert.deepEqual(callback.args, [[1], ["2"]]); }); }); describe("map", () => { it("should map the published values", () => { const obs = S.empty(); const callback = spy(); const mappedObs = map(addTwo, obs); S.subscribe(callback, mappedObs); for (let i = 0; i < 5; i++) { publish(i, obs); } assert.deepEqual(callback.args, [[2], [3], [4], [5], [6]]); }); it("maps to a constant with mapTo", () => { const stream = S.empty(); const callback = spy(); const mapped = stream.mapTo(7); S.subscribe(callback, mapped); publish(1, stream); publish(2, stream); publish(3, stream); assert.deepEqual(callback.args, [[7], [7], [7]]); }); it("works on placeholder", () => { let result = 0; const p = placeholder(); const mapped = p.map((s: number) => s + 1); mapped.subscribe((n: number) => result = n); const s = S.empty(); p.replaceWith(s); assert.strictEqual(result, 0); s.push(1) assert.strictEqual(result, 2); }); }); describe("apply", () => { it("at applies function in behavior", () => { const fnB = B.sink((n: number) => n * n); const origin = S.empty(); const applied = apply(fnB, origin); const callback = spy(); S.subscribe(callback, applied); publish(2, origin); publish(3, origin); fnB.push((n: number) => 2 * n) publish(4, origin); publish(5, origin); fnB.push((n: number) => n / 2); publish(4, origin); fnB.push(Math.sqrt); publish(25, origin); publish(36, origin); assert.deepEqual(callback.args, [ [4], [9], [8], [10], [2], [5], [6] ]); }); }); describe("filter", () => { it("should be a function", () => { assert.isFunction(S.filter); }); it("should filter the unwanted publishions", () => { const obs = S.empty(); const callback = spy(); const isEven = (v: number): boolean => !(v % 2); const filteredObs = S.filter(isEven, obs); S.subscribe(callback, filteredObs); for (let i = 0; i < 10; i++) { publish(i, obs); } assert.deepEqual(callback.args, [[0], [2], [4], [6], [8]], "Wrong or no value was recieved"); }); }); describe("split", () => { it("splits based on predicate", () => { const sink = S.empty(); const callbackA = spy(); const callbackB = spy(); const [a, b] = split((n) => n % 2 === 0, sink); a.subscribe(callbackA); b.subscribe(callbackB); sink.push(1); sink.push(4); sink.push(7); sink.push(10); assert.deepEqual(callbackA.args, [[4], [10]]); assert.deepEqual(callbackB.args, [[1], [7]]); }); }); describe("filterApply", () => { it("at applies filter from behavior", () => { const predB = B.sink((n: number) => n % 2 === 0); const origin = S.empty(); const filtered = filterApply(predB, origin); const callback = spy(); S.subscribe(callback, filtered); publish(2, origin); publish(3, origin); predB.push((n: number) => n % 3 === 0); publish(4, origin); publish(6, origin); predB.push((n: number) => n % 4 === 0); publish(6, origin); publish(12, origin); assert.deepEqual(callback.args, [ [2], [6], [12] ]); }); }); describe("scanS", () => { it("should scan the values to a stream", () => { const eventS = S.empty(); const callback = spy(); const sumF = (currSum: number, val: number) => currSum + val; const currentSumE = at(S.scanS(sumF, 0, eventS)); S.subscribe(callback, currentSumE); for (let i = 0; i < 10; i++) { publish(i, eventS); } assert.deepEqual(callback.args, [[0], [1], [3], [6], [10], [15], [21], [28], [36], [45]]); }); }); describe("keepWhen", () => { it("removes occurences when behavior is false", () => { let flag = true; const bool: Behavior = B.fromFunction(() => flag); const origin = S.empty(); const filtered = keepWhen(origin, bool); const callback = spy(); S.subscribe(callback, filtered); publish(0, origin); publish(1, origin); flag = false; publish(2, origin); publish(3, origin); flag = true; publish(4, origin); flag = false; publish(5, origin); flag = true; publish(6, origin); assert.deepEqual(callback.args, [ [0], [1], [4], [6] ]); }); }); describe("snapshot", () => { it("snapshots pull based Behavior", () => { let n = 0; const b: Behavior = B.fromFunction(() => n); const e: Stream = S.empty(); const shot = snapshot(b, e); const callback = spy(); S.subscribe(callback, shot); publish(0, e); publish(1, e); n = 1; publish(2, e); n = 2; publish(3, e); publish(4, e); assert.deepEqual(callback.args, [ [0], [0], [1], [2], [2] ]); }); it("applies function in snapshotWith to pull based Behavior", () => { let n = 0; const b: Behavior = B.fromFunction(() => n); const e: Stream = S.empty(); const shot = snapshotWith(sum, b, e); const callback = spy(); S.subscribe(callback, shot); publish(0, e); publish(1, e); n = 1; publish(2, e); n = 2; publish(3, e); publish(4, e); assert.deepEqual(callback.args, [ [0], [1], [3], [5], [6] ]); }); it("works with placeholder", () => { let result = 0; const b = Behavior.of(7); const p = placeholder(); const snap = snapshot(b, p); snap.subscribe((n: number) => result = n); const s = S.empty(); p.replaceWith(s); assert.strictEqual(result, 0); s.push(1); assert.strictEqual(result, 7); }); }); describe("timing operators", () => { let clock: any; beforeEach(() => { clock = useFakeTimers(); }); afterEach(() => { clock.restore(); }); describe("delay", () => { it("should delay every push", () => { let n = 0; const s = S.empty(); const delayedS = delay(50, s); delayedS.subscribe(() => n = 2); s.subscribe(() => n = 1); s.push(0); assert.strictEqual(n, 1); clock.tick(49); assert.strictEqual(n, 1); clock.tick(1); assert.strictEqual(n, 2) }); it("should work with placeholder", () => { let n = 0; const p = placeholder(); const delayedP = delay(50,p ); delayedP.subscribe(() => n = 2); p.subscribe(() => n = 1); const s = S.empty(); p.replaceWith(s); s.push(0); assert.strictEqual(n, 1); clock.tick(49); assert.strictEqual(n, 1); clock.tick(1); assert.strictEqual(n, 2) }); }); describe("throttle", () => { it("after an occurrence it should ignore", () => { let n = 0; const s = S.empty(); const throttleS = throttle(100, s); throttleS.subscribe((v) => n = v); assert.strictEqual(n, 0); s.push(1); assert.strictEqual(n, 1); clock.tick(80); s.push(2); assert.strictEqual(n, 1); clock.tick(19); s.push(3); assert.strictEqual(n, 1); clock.tick(1); s.push(4); assert.strictEqual(n, 4); }); it("should work with placeholder", () => { let n = 0; const p = placeholder(); const throttleP = throttle(100, p); throttleP.subscribe((v: number) => n = v); assert.strictEqual(n, 0); const s = S.empty(); p.replaceWith(s); s.push(1); clock.tick(99); s.push(2); assert.strictEqual(n, 1); clock.tick(1); s.push(3); assert.strictEqual(n, 3); }); }); describe("debounce", () => { it("holding the latest occurens until an amount of time has passed", () => { let n = 0; const s = S.empty(); const debouncedS = S.debounce(100, s); debouncedS.subscribe((v) => n = v); assert.strictEqual(n, 0); s.push(1); clock.tick(80); assert.strictEqual(n, 0); clock.tick(30); assert.strictEqual(n, 1); s.push(2); assert.strictEqual(n, 1); clock.tick(99); assert.strictEqual(n, 1); clock.tick(2); assert.strictEqual(n, 2); }); it("should work with placeholder", () => { let n = 0; const p = placeholder(); const debouncedP = S.debounce(100, p); debouncedP.subscribe((v: number) => n = v); const s = S.empty(); p.replaceWith(s); assert.strictEqual(n, 0); s.push(1); clock.tick(80); assert.strictEqual(n, 0); clock.tick(30); assert.strictEqual(n, 1); s.push(2); assert.strictEqual(n, 1); clock.tick(99); assert.strictEqual(n, 1); clock.tick(2); assert.strictEqual(n, 2); }); }); }); });