import { describe, expect, test } from "bun:test"; import { batch, computed, effect, resource, signal, watch } from "../src/index.ts"; describe("reactive primitives", () => { test("signals skip no-op updates", () => { const value = signal(1); const seen: number[] = []; value.subscribe((next) => seen.push(next)); value.set(1); value.set(2); expect(seen).toEqual([2]); }); test("batch coalesces notifications", () => { const value = signal(0); const seen: number[] = []; value.subscribe((next) => seen.push(next)); batch(() => { value.set(1); value.set(2); value.set(3); }); expect(seen).toEqual([3]); }); test("computed values and effects track dependencies", () => { const count = signal(2); const doubled = computed(() => count.get() * 2); const seen: number[] = []; const dispose = effect(() => { seen.push(doubled.get()); }); count.set(3); dispose(); count.set(4); expect(seen).toEqual([4, 6]); }); test("effects ignore accidental non-function return values", () => { const value = signal(0); const seen: number[] = []; const dispose = effect(() => seen.push(value.get()) as unknown as void); value.set(1); dispose(); expect(seen).toEqual([0, 1]); }); }); test("watch reports undefined as the previous value for an immediate run", () => { const value = signal(1); const seen: Array<[number, number | undefined]> = []; const stop = watch( () => value.get(), (next, previous) => void seen.push([next, previous]), { immediate: true }, ); value.set(2); stop(); expect(seen).toEqual([ [1, undefined], [2, 1], ]); }); test("aborting a pending resource does not dereference a cleared controller", async () => { let resolve!: (value: string) => void; const pending = new Promise((done) => { resolve = done; }); const value = resource(() => pending); const run = value.run(); value.abort("cancelled"); resolve("late"); await expect(run).resolves.toBeUndefined(); expect(value.status.get()).toBe("idle"); expect(value.data.get()).toBeUndefined(); });