import { Behavior, switchTo, when } from "../src/behavior"; import { Future } from "../src/future"; import { async, Now, performStream, performStreamLatest, performStreamOrdered, plan, runNow, sample } from "../src/now"; import * as S from "../src/stream"; import { assert } from "chai"; import { lift } from "jabz/applicative"; import { Either } from "jabz/either"; import { callP, IO, withEffects, withEffectsP } from "jabz/io"; import { go } from "jabz/monad"; // A reference that can be mutated type Ref = { ref: A }; function createRef(a: A): Ref { return { ref: a }; } const mutateRef: (a: A, r: Ref) => IO<{}> = withEffects((a: any, r: Ref) => r.ref = a); describe("Now", () => { describe("async", () => { it("works with runNow", () => { let resolve: (n: number) => void; const promise = runNow( async(callP((n: number) => new Promise((res) => resolve = res), 0)) ); setTimeout(() => { resolve(12); }); return promise.then((result: Either) => { assert.deepEqual(result, 12); }); }); }); describe("sample", () => { it("samples constant behavior", () => { const b = Behavior.of(6); const comp = sample(b).chain((n) => Now.of(Future.of(n))); return runNow(comp).then((result: number) => { assert.strictEqual(result, 6); }); }); }); describe("plan", () => { it("executes plan asynchronously", () => { let resolve: (n: number) => void; let done = false; const fn = withEffectsP((n: number) => { return new Promise((res) => { resolve = res; }); }); function comp(n: number): Now { return Now.of(n * 2); } const prog = go(function* (): Iterator> { const e: Future = yield async(fn(1)); const e2 = yield plan(e.map((r) => comp(r))); return e2; }); setTimeout(() => { assert.strictEqual(done, false); resolve(11); }); return runNow(prog).then((res: number) => { done = true; assert.strictEqual(res, 22); }); }); }); describe("functor", () => { it("mapTo", () => { assert.strictEqual(Now.of(12).mapTo(4).run(), 4); }); }); describe("applicative", () => { it("lifts over constant now", () => { const n = Now.of(1); assert.strictEqual(lift((n) => n * n, n.of(3)).run(), 9); assert.strictEqual( lift((n, m) => n + m, n.of(1), n.of(3)).run(), 4 ); assert.strictEqual( lift((n, m, p) => n + m + p, n.of(1), n.of(3), n.of(5)).run(), 9 ); }); }); describe("monad", () => { it("executes several `async`s in succession", () => { const ref1 = createRef(1); const ref2 = createRef("Hello"); const comp = async(mutateRef(2, ref1)).chain( (_: any) => async(mutateRef("World", ref2)).chain( (__: any) => Now.of(Future.of(true)) ) ); return runNow(comp).then((result: boolean) => { assert.strictEqual(result, true); assert.strictEqual(ref1.ref, 2); assert.strictEqual(ref2.ref, "World"); }); }); it("can flatten pure nows", () => { assert.strictEqual(Now.of(Now.of(12)).flatten().run(), 12); }); }); it("handles recursively defined behavior", () => { let resolve: (n: number) => void; const getNextNr = withEffectsP((n: number) => { return new Promise((res) => { resolve = res; }); }); function loop(n: number): Now> { return go(function* (): Iterator> { const e = yield async(getNextNr(1)); const e1 = yield plan(e.map(loop)); return switchTo(Behavior.of(n), e1); }); } function main(): Now> { return go(function* (): Iterator> { const b: Behavior = yield loop(0); const e = yield sample(when(b.map((n: number) => { return n === 3; }))); return e; }); } setTimeout(() => { resolve(1); setTimeout(() => { resolve(2); setTimeout(() => { resolve(3); }); }); }); return runNow(main()); }); describe("performStream", () => { it("runs io actions", (done: Function) => { let actions: number[] = []; let results: number[] = []; const impure = withEffects((n: number) => { actions.push(n); return n + 2; }); const s = S.empty(); const mappedS = s.map(impure); performStream(mappedS).run().subscribe((n) => results.push(n)); s.push(1); setTimeout(() => { s.push(2); setTimeout(() => { s.push(3); setTimeout(() => { assert.deepEqual(actions, [1, 2, 3]); assert.deepEqual(results, [3, 4, 5]); done(); }); }); }); }); }); describe("performStreamLatest", () => { it("work with one occurrence", (done: Function) => { let results: any[] = []; const impure = withEffectsP((n: number) => new Promise((resolve, reject) => resolve(n))); const s = S.empty(); const mappedS = s.map(impure); performStreamLatest(mappedS).run().subscribe((n) => results.push(n)); s.push(60); setTimeout(() => { assert.deepEqual(results, [60]); done(); }); }); it("runs io actions and ignores outdated results", (done: Function) => { let results: any[] = []; const impure = withEffects((n: number) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(n); }, n); }); }); const s = S.empty(); const mappedS = s.map(impure); performStreamLatest(mappedS).run().subscribe((n) => results.push(n)); s.push(60); s.push(20); s.push(30); setTimeout(() => { assert.deepEqual(results, [20, 30]) done(); }, 100); }); }); describe("performStreamOrdered", () => { it("work with one occurrence", (done: Function) => { let results: any[] = []; const impure = withEffectsP((n: number) => new Promise((resolve, reject) => resolve(n))); const s = S.empty(); const mappedS = s.map(impure); performStreamOrdered(mappedS).run().subscribe((n) => results.push(n)); s.push(60); setTimeout(() => { assert.deepEqual(results, [60]); done(); }); }); it("runs io actions and makes sure to keep the results in the same order", (done: Function) => { let results: any[] = []; const impure = withEffectsP((n: number) => { return new Promise((resolve, reject) => { setTimeout(() => resolve(n), n); }); }); const s = S.empty(); const mappedS = s.map(impure); performStreamOrdered(mappedS).run().subscribe((n) => results.push(n)); s.push(60); s.push(20); s.push(30); s.push(undefined); s.push(50); s.push(40); setTimeout(() => { assert.deepEqual(results, [60, 20, 30, undefined, 50, 40]) done(); }, 100); }); it("should support `undefined` as result", (done: MochaDone) => { let results: any[] = []; const impure = withEffectsP((n: number) => new Promise((resolve, reject) => resolve(n))); const s = S.empty(); const mappedS = s.map(impure); performStreamOrdered(mappedS).run().subscribe((n) => results.push(n)); s.push(60); s.push(undefined); s.push(20); setTimeout(() => { assert.deepEqual(results, [60, undefined, 20]); done(); }); }); }); });