'use strict'; const assert = require('assert'); const PromiseStream = require('../promise-stream'); function cloneJSON(x) { return JSON.parse(JSON.stringify(x)); } function afterMicrotasks() { return new Promise(function(resolve) { setTimeout(resolve); }); } describe('PromiseStream', function() { let stream; it('should finish constructing the PromiseStream', function() { stream = new PromiseStream; }); it('should have an undefined initial value', function() { assert.deepStrictEqual(stream.value, undefined); }); const onceVal = []; it('should allow setting a one-time callback', function() { stream.once((v) => { onceVal.push(v); }); }); const listenVal = []; let cancel; it('should allow setting an ongoing callback', function() { cancel = stream.listen((v) => { listenVal.push(v); }); }); it('should return a cancellation function', function() { assert.strictEqual(typeof cancel, 'function'); }); const then0Val = []; it('should provide access to the current promise', function() { stream.promise.then((v) => v.value + 3).then((v) => { then0Val.push(v); }); }); const next0Val = []; it('should provide access to the next promise', function() { stream.next_promise.then((v) => v.value + 7).then((v) => { next0Val.push(v); }); }); it('should wait for the first update before running callbacks', async function() { await afterMicrotasks(); assert.deepStrictEqual(onceVal, []); assert.deepStrictEqual(listenVal, []); assert.deepStrictEqual(then0Val, []); assert.deepStrictEqual(next0Val, []); }); let snapshot; it('should allow updates', function() { stream.update(10); snapshot = cloneJSON({ onceVal, listenVal, then0Val, next0Val }); }); it('should hold callbacks in the microtask queue', function() { assert.deepStrictEqual(snapshot.onceVal, []); assert.deepStrictEqual(snapshot.listenVal, []); assert.deepStrictEqual(snapshot.then0Val, []); assert.deepStrictEqual(snapshot.next0Val, []); }); it('should run callbacks after first update', async function() { await afterMicrotasks(); assert.deepStrictEqual(onceVal, [10]); assert.deepStrictEqual(listenVal, [10]); assert.deepStrictEqual(then0Val, [13]); assert.deepStrictEqual(next0Val, [17]); }); const then1Val = []; it('should provide access to current promise after update', async function() { stream.promise.then((v) => v.value + 4).then((v) => { then1Val.push(v); }); assert.deepStrictEqual(then1Val, []); await afterMicrotasks(); assert.deepStrictEqual(then1Val, [14]); }); const next1Val = []; it('should provide access to next promise after update', async function() { stream.next_promise.then((v) => v.value + 8).then((v) => { next1Val.push(v); }); await afterMicrotasks(); assert.deepStrictEqual(next1Val, []); }); it('should allow a second update', function() { stream.update(20); snapshot = cloneJSON({ onceVal, listenVal, then0Val, next0Val, then1Val, next1Val }); }); it('should hold callbacks in the microtask queue', function() { assert.deepStrictEqual(snapshot.onceVal, [10]); assert.deepStrictEqual(snapshot.listenVal, [10]); assert.deepStrictEqual(snapshot.then0Val, [13]); assert.deepStrictEqual(snapshot.next0Val, [17]); assert.deepStrictEqual(snapshot.then1Val, [14]); assert.deepStrictEqual(snapshot.next1Val, []); }); it('should run callbacks after second update', async function() { await afterMicrotasks(); assert.deepStrictEqual(onceVal, [10]); assert.deepStrictEqual(listenVal, [10, 20]); assert.deepStrictEqual(then0Val, [13]); assert.deepStrictEqual(next0Val, [17]); assert.deepStrictEqual(then1Val, [14]); assert.deepStrictEqual(next1Val, [28]); }); it('should allow listeners to be cancelled individually', async function() { const listen2Val = []; const cancel2 = stream.listen((v) => { listen2Val.push(v); }); cancel(); stream.update(30); await afterMicrotasks(); cancel2(); assert.deepStrictEqual(listenVal, [10, 20]); /* not updated */ assert.deepStrictEqual(listen2Val, [20, 30]); /* updated w/ current and new value */ }); });