UNPKG

computed-async-mobx

Version:

Define a computed by returning a Promise

278 lines (179 loc) 7.31 kB
import test from "blue-tape"; import { testStrictness, waitForLength, Obs } from "./util"; import { delay } from "./delay"; import { observable, runInAction, autorun } from "mobx" import { promisedComputed } from "../src/index" testStrictness("promisedComputed - can't be used outside of reactive contexts", async (assert: test.Test) => { const o = observable({ x: 1, y: 2 }); const r = promisedComputed(undefined, async () => { const result = o.x + o.y; await delay(100); return result; }); assert.throws(() => r.get(), /inside reactions/); }); testStrictness("promisedComputed - can use getNonReactive outside of reactive contexts", async (assert: test.Test) => { const o = observable({ x: 1, y: 2 }); const r = promisedComputed(undefined, async () => { const result = o.x + o.y; await delay(100); return result; }); assert.equal(r.getNonReactive(), undefined); const stop = autorun(() => r.get()); while (r.getNonReactive() === undefined) { await delay(5); } stop(); assert.equal(r.getNonReactive(), 3); }); testStrictness("promisedComputed - transitions to new values", async (assert: test.Test) => { const o = observable({ x: 1, y: 2 }); const r = promisedComputed(101, async () => { const result = o.x + o.y; await delay(10); return result; }); const trace: (number)[] = []; const stop = autorun(() => trace.push(r.get())); assert.deepEqual(trace, [101], "No new value until promise resolves"); await waitForLength(trace, 2); assert.deepEqual(trace, [101, 3], "First proper value appears"); runInAction(() => o.x = 5); assert.deepEqual(trace, [101, 3], "No value until promise resolves [2]"); await waitForLength(trace, 3); assert.deepEqual(trace, [101, 3, 7], "Second value appears"); stop(); }); testStrictness("promisedComputed - busy property works by itself", async (assert: test.Test) => { const o = observable({ x: 1, y: 2 }); const r = promisedComputed(undefined, async () => { const result = o.x + o.y; await delay(10); return result; }); const trace: boolean[] = []; const stop = autorun(() => trace.push(r.busy)); assert.deepEqual(trace, [true], "Is initially busy"); await waitForLength(trace, 2); assert.deepEqual(trace, [true, false], "Busy transitions to false"); runInAction(() => o.x = 5); assert.deepEqual(trace, [true, false, true], "Synchronously transitions to true"); await waitForLength(trace, 4); assert.deepEqual(trace, [true, false, true, false], "Second transition to false"); stop(); }); testStrictness("promisedComputed - busy property interleaves with value changes", async (assert: test.Test) => { const o = observable({ x: 1, y: 2 }); const r = promisedComputed(undefined, async () => { const result = o.x + o.y; await delay(10); return result; }); const trace: ({ value: number | undefined, busy: boolean })[] = []; const stop = autorun(() => trace.push({ value: r.get(), busy: r.busy })); assert.deepEqual(trace, [ {value: undefined, busy: true} ], "No value until promise resolves"); await waitForLength(trace, 2); assert.deepEqual(trace, [ {value: undefined, busy: true}, {value: 3, busy: false} ], "Initial value appears"); runInAction(() => o.x = 5); assert.deepEqual(trace, [ {value: undefined, busy: true}, {value: 3, busy: false}, {value: 3, busy: true} ], "No value until promise resolves [2]"); await waitForLength(trace, 4); assert.deepEqual(trace, [ {value: undefined, busy: true}, {value: 3, busy: false}, {value: 3, busy: true}, {value: 7, busy: false} ], "Second value appears"); stop(); }); testStrictness("promisedComputed - propagates exceptions", async (assert: test.Test) => { const o = new Obs(false); const r = promisedComputed(101, async () => { const shouldThrow = o.get(); await delay(10); if (shouldThrow) { throw new Error("Badness"); } return 1; }); const trace: (number | string)[] = []; const stop = autorun(() => { try { trace.push(r.get()); } catch(x) { trace.push(x.message); } }); assert.deepEqual(trace, [101]); await waitForLength(trace, 2); assert.deepEqual(trace, [101, 1]); runInAction(() => o.set(true)); assert.deepEqual(trace, [101, 1], "Reactive contexts don't seem immediate changes"); await waitForLength(trace, 3); assert.deepEqual(trace, [101, 1, "Badness"], "But do see delayed changes"); runInAction(() => o.set(false)); await waitForLength(trace, 4); assert.deepEqual(trace, [101, 1, "Badness", "Badness"], "Transition to busy triggers new exception"); await waitForLength(trace, 5); assert.deepEqual(trace, [101, 1, "Badness", "Badness", 1], "And reverts back to non-throwing"); stop(); }); testStrictness("promisedComputed - is fully synchronous if value is not a promise", async (assert: test.Test) => { const o = new Obs("sync"); const r = promisedComputed("never", () => { const v = o.get(); if (v === "throw") { throw new Error(v); } return v === "async" ? delay(10).then(() => v) : v; }); const trace: string[] = []; const stop = autorun(() => { try { trace.push(r.get()); } catch (x) { trace.push("error: " + x.message); } }); assert.deepEqual(trace, ["sync"], "Synchronously has value"); runInAction(() => o.set("sync2")); assert.deepEqual(trace, ["sync", "sync2"], "Synchronously transitions"); runInAction(() => o.set("async")); assert.deepEqual(trace, ["sync", "sync2"], "Does not immediately transition to promised value"); await waitForLength(trace, 3); assert.deepEqual(trace, ["sync", "sync2", "async"], "Eventually transitions"); runInAction(() => o.set("throw")); assert.deepEqual(trace, ["sync", "sync2", "async", "error: throw"], "Synchronously transitions to throwing"); runInAction(() => o.set("sync3")); assert.deepEqual(trace, ["sync", "sync2", "async", "error: throw", "sync3"], "Synchronously transitions to normal"); stop(); }); testStrictness("promisedComputed - can be refreshed", async (assert: test.Test) => { let counter = 0; const r = promisedComputed(0, async () => { await delay(10); return ++counter; }); const trace: (number)[] = []; const stop = autorun(() => trace.push(r.get())); assert.deepEqual(trace, [0], "No new value until promise resolves"); await waitForLength(trace, 2); assert.deepEqual(trace, [0, 1], "First proper value appears"); r.refresh(); assert.deepEqual(trace, [0, 1], "No value until promise resolves [2]"); await waitForLength(trace, 3); assert.deepEqual(trace, [0, 1, 2], "Second value appears"); stop(); });