import { prop, computed, stream, hashableStream } from '../src/index.js'; import { dirtyMock, hashSet } from '../src/testUtil.js'; describe('A stream', () => { const add = (a, b) => a + b; const square = a => a * a; async function delayAdd(a, b) { return new Promise(resolve => setTimeout(() => { resolve(a + b); }, 30) ); } async function delaySquare(a) { return new Promise(resolve => setTimeout(() => { resolve(a * a); }, 30) ); } it('accepts subscribable dependencies', async done => { const a = prop(0); const b = computed(square, [a]); const c = stream(delaySquare, [a]); const d = stream(delayAdd, [a, c]); const e = stream(delaySquare, [b]); expect(await c()).toEqual(0); expect(await d()).toEqual(0); expect(await e()).toEqual(0); a(1); expect(await c()).toEqual(1); expect(await d()).toEqual(2); expect(await e()).toEqual(1); a(2); expect(await c()).toEqual(4); expect(await d()).toEqual(6); expect(await e()).toEqual(16); a(3); expect(await c()).toEqual(9); expect(await d()).toEqual(12); expect(await e()).toEqual(81); done(); }); it('computes automatically when subscribed', async done => { let runCount = 0; let subRunCount = 0; let currentValue = 1; const a = prop(0); const b = stream( async val => { runCount += 1; expect(val).toEqual(currentValue); return new Promise(resolve => setTimeout(() => resolve(val * val), 30)); }, [a] ); // b does not evaluate a(1); expect(runCount).toEqual(0); // b evaluates expect(await b()).toEqual(1); expect(runCount).toEqual(1); // b does not evaluate expect(await b()).toEqual(1); expect(runCount).toEqual(1); const cancelSubscription = b.subscribe(val => { subRunCount += 1; expect(val).toEqual(currentValue * currentValue); }); currentValue = 3; // b evaluates a(3); // b is triggered to update but hasn't yet expect(runCount).toEqual(1); expect(subRunCount).toEqual(0); setTimeout(async () => { // b should have updated now expect(runCount).toEqual(2); expect(subRunCount).toEqual(1); // b does not evaluate expect(await b()).toEqual(9); expect(runCount).toEqual(2); expect(subRunCount).toEqual(1); done(); }, 40); }); it('only computes once for overlapping calls', async done => { let callCount = 0; async function delayRun(a) { return new Promise(resolve => setTimeout(() => { callCount += 1; resolve(callCount + a); }, 10) ); } const a = prop(0); const b = stream(delayRun, [a]); expect(await b()).toEqual(1); expect(callCount).toEqual(1); a(1); expect(await b()).toEqual(3); expect(callCount).toEqual(2); // Just calling sequentially should not re-evaluate expect(await b()).toEqual(3); expect(callCount).toEqual(2); // Set b.dirty flag a(2); Promise.all([b(), b()]) .then(([res_1, res_2]) => { expect(res_1).toEqual(5); expect(res_2).toEqual(5); expect(callCount).toEqual(3); }) .finally(done); }); });