deno.land / x / solid@v1.5.6 / test / signals.spec.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707/** @jest-environment jsdom */import { createRoot, createSignal, createEffect, createRenderEffect, createComputed, createReaction, createDeferred, createMemo, createSelector, untrack, on, onMount, onCleanup, onError, createContext, useContext, getOwner, runWithOwner,} from "../src";
import "./MessageChannel";
describe("Create signals", () => { test("Create and read a Signal", () => { const [value] = createSignal(5); expect(value()).toBe(5); }); test("Create and read a Signal with comparator", () => { const [value] = createSignal(5, { equals: (a, b) => a === b }); expect(value()).toBe(5); }); test("Create and read a Memo", () => { createRoot(() => { const memo = createMemo(() => "Hello"); expect(memo()).toBe("Hello"); }); }); test("Create and read a Memo with initial value", () => { createRoot(() => { const memo = createMemo(i => `${i} John`, "Hello"); expect(memo()).toBe("Hello John"); }); }); test("Create onMount", () => { let temp: string; createRoot(() => { onMount(() => (temp = "impure")); }); expect(temp!).toBe("impure"); }); test("Create a Effect with explicit deps", () => { let temp: string; createRoot(() => { const [sign] = createSignal("thoughts"); const fn = on(sign, v => (temp = `impure ${v}`)); createEffect(fn); createEffect(on(sign, v => (temp = `impure ${v}`))); }); expect(temp!).toBe("impure thoughts"); }); test("Create a Effect with multiple explicit deps", () => { let temp: string; createRoot(() => { const [sign] = createSignal("thoughts"); const [num] = createSignal(3); const fn = on([sign, num], v => (temp = `impure ${v[1]}`)); createEffect(fn); }); expect(temp!).toBe("impure 3"); }); test("Create a Effect with explicit deps and lazy evaluation", () => { let temp: string; const [sign, set] = createSignal("thoughts"); createRoot(() => { const fn = on(sign, v => (temp = `impure ${v}`), { defer: true }); createEffect(fn); }); expect(temp!).toBeUndefined(); set("minds"); expect(temp!).toBe("impure minds"); });});
describe("Update signals", () => { test("Create and update a Signal", () => { const [value, setValue] = createSignal(5); setValue(10); expect(value()).toBe(10); }); test("Create and update a Signal with fn", () => { const [value, setValue] = createSignal(5); setValue(p => p + 5); expect(value()).toBe(10); }); test("Create Signal and set different value", () => { const [value, setValue] = createSignal(5); setValue(10); expect(value()).toBe(10); }); test("Create Signal and set equivalent value", () => { const [value, setValue] = createSignal(5, { equals: (a, b) => a > b }); setValue(3); expect(value()).toBe(5); }); test("Create and read a Signal with function value", () => { const [value, setValue] = createSignal<() => string>(() => "Hi"); expect(value()()).toBe("Hi"); setValue(() => () => "Hello"); expect(value()()).toBe("Hello"); }); test("Create and trigger a Memo", () => { createRoot(() => { const [name, setName] = createSignal("John"), memo = createMemo(() => `Hello ${name()}`); expect(memo()).toBe("Hello John"); setName("Jake"); expect(memo()).toBe("Hello Jake"); }); }); test("Create Signal and set equivalent value not trigger Memo", () => { createRoot(() => { const [name, setName] = createSignal("John", { equals: (a, b) => b.startsWith("J") }), memo = createMemo(() => `Hello ${name()}`); expect(name()).toBe("John"); expect(memo()).toBe("Hello John"); setName("Jake"); expect(name()).toBe("John"); expect(memo()).toBe("Hello John"); }); }); test("Create and trigger a Memo in an effect", done => { createRoot(() => { let temp: string; const [name, setName] = createSignal("John"), memo = createMemo(() => `Hello ${name()}`); createEffect(() => (temp = `${memo()}!!!`)); setTimeout(() => { expect(temp).toBe("Hello John!!!"); setName("Jake"); expect(temp).toBe("Hello Jake!!!"); done(); }); }); }); test("Create and trigger an Effect", done => { createRoot(() => { let temp: string; const [sign, setSign] = createSignal("thoughts"); createEffect(() => (temp = `unpure ${sign()}`)); setTimeout(() => { expect(temp).toBe("unpure thoughts"); setSign("mind"); expect(temp).toBe("unpure mind"); done(); }); }); }); test("Create and trigger an Effect with function signals", done => { createRoot(() => { let temp: string; const [sign, setSign] = createSignal<() => string>(() => "thoughts"); createEffect(() => (temp = `unpure ${sign()()}`)); setTimeout(() => { expect(temp).toBe("unpure thoughts"); setSign(() => () => "mind"); expect(temp).toBe("unpure mind"); done(); }); }); }); test("Set signal returns argument", () => { const [_, setValue] = createSignal<number>(); const res1: undefined = setValue(undefined); expect(res1).toBe(undefined); const res2: number = setValue(12); expect(res2).toBe(12); const res3 = setValue(Math.random() >= 0 ? 12 : undefined); expect(res3).toBe(12); const res4 = setValue(); expect(res4).toBe(undefined); });});
describe("Untrack signals", () => { test("Mute an effect", done => { createRoot(() => { let temp: string; const [sign, setSign] = createSignal("thoughts"); createEffect(() => (temp = `unpure ${untrack(sign)}`)); setTimeout(() => { expect(temp).toBe("unpure thoughts"); setSign("mind"); expect(temp).toBe("unpure thoughts"); done(); }); }); });});
describe("Batching signals", () => { test("Mute an effect", done => { createRoot(() => { let temp: string; const [sign, setSign] = createSignal("thoughts"); createEffect(() => (temp = `unpure ${untrack(sign)}`)); setTimeout(() => { expect(temp).toBe("unpure thoughts"); setSign("mind"); expect(temp).toBe("unpure thoughts"); done(); }); }); });});
describe("Effect grouping of signals", () => { test("Groups updates", done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(0); const [b, setB] = createSignal(0); createEffect(() => { setA(1); setB(1); }); createMemo(() => (count += a() + b())); setTimeout(() => { expect(count).toBe(2); done(); }); }); }); test("Groups updates with repeated sets", done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(0); createEffect(() => { setA(1); setA(4); }); createMemo(() => (count += a())); setTimeout(() => { expect(count).toBe(4); done(); }); }); }); test("Groups updates with fn setSignal", done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(0); const [b, setB] = createSignal(0); createEffect(() => { setA(a => a + 1); setB(b => b + 1); }); createMemo(() => (count += a() + b())); setTimeout(() => { expect(count).toBe(2); done(); }); }); }); test("Groups updates with fn setSignal with repeated sets", done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(0); createEffect(() => { setA(a => a + 1); setA(a => a + 2); }); createMemo(() => (count += a())); setTimeout(() => { expect(count).toBe(3); done(); }); }); }); test("Test cross setting in a effect update", done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(1); const [b, setB] = createSignal(0); createEffect(() => { setA(a => a + b()); }); createMemo(() => (count += a())); setTimeout(() => { setB(b => b + 1); setTimeout(() => { expect(count).toBe(3); done(); }); }); }); }); test("Handles errors gracefully", done => { createRoot(() => { let error: Error; const [a, setA] = createSignal(0); const [b, setB] = createSignal(0); createEffect(() => { try { setA(1); throw new Error("test"); setB(1); } catch (e) { error = e as Error; } }); createMemo(() => a() + b()); setTimeout(() => { expect(a()).toBe(1); expect(b()).toBe(0); setA(2); expect(a()).toBe(2); expect(error).toBeInstanceOf(Error); expect(error.message).toBe("test"); done(); }); }); });
test("Multiple sets", done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(0); createEffect(() => { setA(1); setA(0); }); createMemo(() => (count = a())); setTimeout(() => { expect(count).toBe(0); done(); }); }); });});
describe("Typecheck computed and effects", () => { test("No default value can return undefined", () => { createRoot(() => { let count = 0; const [sign, setSign] = createSignal("thoughts"); const fn = (arg?: number) => { count++; sign(); expect(arg).toBe(undefined); return arg; }; createComputed(fn); createRenderEffect(fn); createEffect(fn); setTimeout(() => { expect(count).toBe(3); setSign("update"); expect(count).toBe(6); }); }); }); test("Default value never receives undefined", () => { createRoot(() => { let count = 0; const [sign, setSign] = createSignal("thoughts"); const fn = (arg: number) => { count++; sign(); expect(arg).toBe(12); return arg; }; createComputed(fn, 12); createRenderEffect(fn, 12); createEffect(fn, 12); setTimeout(() => { expect(count).toBe(3); setSign("update"); expect(count).toBe(6); }); }); });});
describe("onCleanup", () => { test("Clean an effect", done => { createRoot(() => { let temp: string; const [sign, setSign] = createSignal("thoughts"); createEffect(() => { sign(); onCleanup(() => (temp = "after")); }); setTimeout(() => { expect(temp).toBeUndefined(); setSign("mind"); expect(temp).toBe("after"); done(); }); }); }); test("Explicit root disposal", () => { let temp: string | undefined, disposer: () => void; createRoot(dispose => { disposer = dispose; onCleanup(() => (temp = "disposed")); }); expect(temp).toBeUndefined(); disposer!(); expect(temp).toBe("disposed"); }); test("Failed Root disposal from arguments", () => { let temp: string | undefined, disposer: () => void; createRoot((...args) => { disposer = args[0]; onCleanup(() => (temp = "disposed")); }); expect(temp).toBeUndefined(); expect(disposer!).toThrow(); });});
describe("onError", () => { test("No Handler", () => { expect(() => createRoot(() => { throw "fail"; }) ).toThrow("fail"); }); test("Top level", () => { let errored = false; expect(() => createRoot(() => { onError(() => (errored = true)); throw "fail"; }) ).not.toThrow("fail"); expect(errored).toBe(true); });
test("In initial effect", () => { let errored = false; expect(() => createRoot(() => { createEffect(() => { onError(() => (errored = true)); throw "fail"; }); }) ).not.toThrow("fail"); expect(errored).toBe(true); });
test("With multiple error handlers", () => { let errored = false; let errored2 = false; expect(() => createRoot(() => { createEffect(() => { onError(() => (errored = true)); onError(() => (errored2 = true)); throw "fail"; }); }) ).not.toThrow("fail"); expect(errored).toBe(true); expect(errored2).toBe(true); });
test("In update effect", () => { let errored = false; expect(() => createRoot(() => { const [s, set] = createSignal(0); createEffect(() => { const v = s(); onError(() => (errored = true)); if (v) throw "fail"; }); set(1); }) ).not.toThrow("fail"); expect(errored).toBe(true); });
test("In initial nested effect", () => { let errored = false; expect(() => createRoot(() => { createEffect(() => { createEffect(() => { onError(() => (errored = true)); throw "fail"; }); }); }) ).not.toThrow("fail"); expect(errored).toBe(true); });
test("In nested update effect", () => { let errored = false; expect(() => createRoot(() => { const [s, set] = createSignal(0); createEffect(() => { createEffect(() => { const v = s(); onError(() => (errored = true)); if (v) throw "fail"; }); }); set(1); }) ).not.toThrow("fail"); expect(errored).toBe(true); });
test("In nested update effect different levels", () => { let errored = false; expect(() => createRoot(() => { const [s, set] = createSignal(0); createEffect(() => { onError(() => (errored = true)); createEffect(() => { const v = s(); if (v) throw "fail"; }); }); set(1); }) ).not.toThrow("fail"); expect(errored).toBe(true); });});
describe("createDeferred", () => { test("simple defer", done => { createRoot(() => { const [s, set] = createSignal("init"), r = createDeferred(s, { timeoutMs: 20 }); expect(r()).toBe("init"); set("Hi"); expect(r()).toBe("init"); setTimeout(() => { expect(r()).toBe("Hi"); done(); }, 100); }); });});
describe("createSelector", () => { test("simple selection", done => { createRoot(() => { const [s, set] = createSignal<number>(), isSelected = createSelector(s); let count = 0; const list = Array.from({ length: 100 }, (_, i) => createMemo(() => { count++; return isSelected(i) ? "selected" : "no"; }) ); expect(count).toBe(100); expect(list[3]()).toBe("no"); setTimeout(() => { count = 0; set(3); expect(count).toBe(1); expect(list[3]()).toBe("selected"); count = 0; set(6); expect(count).toBe(2); expect(list[3]()).toBe("no"); expect(list[6]()).toBe("selected"); set(undefined); expect(count).toBe(3); expect(list[6]()).toBe("no"); set(5); expect(count).toBe(4); expect(list[5]()).toBe("selected"); done(); }); }); });
test("double selection", done => { createRoot(() => { const [s, set] = createSignal<number>(-1), isSelected = createSelector<number, number>(s); let count = 0; const list = Array.from({ length: 100 }, (_, i) => [ createMemo(() => { count++; return isSelected(i) ? "selected" : "no"; }), createMemo(() => { count++; return isSelected(i) ? "oui" : "non"; }) ]); expect(count).toBe(200); expect(list[3][0]()).toBe("no"); expect(list[3][1]()).toBe("non"); setTimeout(() => { count = 0; set(3); expect(count).toBe(2); expect(list[3][0]()).toBe("selected"); expect(list[3][1]()).toBe("oui"); count = 0; set(6); expect(count).toBe(4); expect(list[3][0]()).toBe("no"); expect(list[6][0]()).toBe("selected"); expect(list[3][1]()).toBe("non"); expect(list[6][1]()).toBe("oui"); done(); }); }); });
test("zero index", done => { createRoot(() => { const [s, set] = createSignal<number>(-1), isSelected = createSelector<number, number>(s); let count = 0; const list = [ createMemo(() => { count++; return isSelected(0) ? "selected" : "no"; }) ]; expect(count).toBe(1); expect(list[0]()).toBe("no"); setTimeout(() => { count = 0; set(0); expect(count).toBe(1); expect(list[0]()).toBe("selected"); count = 0; set(-1); expect(count).toBe(1); expect(list[0]()).toBe("no"); done(); }); }); });});
describe("create and use context", () => { test("createContext without arguments defaults to undefined", () => { const context = createContext<number>(); const res = useContext(context); expect(res).toBe<typeof res>(undefined); });});
describe("runWithOwner", () => { test("Top level owner execute and disposal", () => { let effectRun = false; let cleanupRun = false; const [owner, dispose] = createRoot(dispose => { return [getOwner()!, dispose]; });
runWithOwner(owner, () => { createEffect(() => (effectRun = true)); onCleanup(() => (cleanupRun = true)); expect(effectRun).toBe(false); expect(cleanupRun).toBe(false); }); expect(effectRun).toBe(true); expect(cleanupRun).toBe(false); dispose(); expect(cleanupRun).toBe(true); });});
describe("createReaction", () => { test("Create and trigger a Reaction", done => { createRoot(() => { let count = 0; const [sign, setSign] = createSignal("thoughts"); const track = createReaction(() => count++); expect(count).toBe(0); track(sign); expect(count).toBe(0); setTimeout(() => { expect(count).toBe(0); setSign("mind"); expect(count).toBe(1); setSign("body"); expect(count).toBe(1); track(sign); setSign("everything"); expect(count).toBe(2); done(); }); }); });});
Version Info