import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test"; import { createSWR } from "../src/swr"; import type { Key, FetcherOptions } from "../src/types"; describe("SWRCore", () => { let swr: any; let fetcher: any; const key = "test-key"; beforeEach(() => { fetcher = vi.fn((k: Key, options: FetcherOptions) => Promise.resolve(`data-${k}`)); swr = createSWR({ fetcher, dedupingInterval: 100 }); vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); it("fetches data on subscription if not in cache", async () => { const callback = vi.fn(); swr.subscribe(key, callback); // Initial fetch is async vi.runAllTimers(); expect(fetcher).toHaveBeenCalledWith(key, expect.objectContaining({ signal: expect.any(AbortSignal) })); expect(callback).toHaveBeenCalledWith(`data-${key}`, undefined, false, false); }); it("deduplicates concurrent fetches", async () => { swr.revalidate(key); swr.revalidate(key); swr.revalidate(key); expect(fetcher).toHaveBeenCalledTimes(1); }); it("updates all subscribers on mutation", async () => { const cb1 = vi.fn(); const cb2 = vi.fn(); swr.subscribe(key, cb1); swr.subscribe(key, cb2); await swr.mutate(key, "manual-data"); expect(cb1).toHaveBeenCalledWith("manual-data", undefined, false, false); expect(cb2).toHaveBeenCalledWith("manual-data", undefined, false, false); }); it("handles fetcher errors", async () => { const error = new Error("fetch failed"); fetcher.mockRejectedValue(error); const callback = vi.fn(); swr.subscribe(key, callback); vi.runAllTimers(); expect(callback).toHaveBeenCalledWith(undefined, error, false, false); }); it("respects revalidateOnMount: false", async () => { swr.updateConfig({ revalidateOnMount: false }); // Put something in cache await swr.mutate(key, "cached"); fetcher.mockClear(); const callback = vi.fn(); swr.subscribe(key, callback); vi.runAllTimers(); // Should not have fetched again despite subscription expect(fetcher).not.toHaveBeenCalled(); }); it("refreshInterval periodically revalidates with per-key fetcher", async () => { const intervalFetcher = vi.fn((k: Key, options: FetcherOptions) => Promise.resolve(`data-${k}`)); swr.updateConfig({ refreshInterval: 1000 }); const callback = vi.fn(); swr.subscribe(key, callback, intervalFetcher); // Initial fetch (only run pending timers, not the interval) vi.runOnlyPendingTimers(); expect(intervalFetcher).toHaveBeenCalledTimes(1); expect(callback).toHaveBeenCalledWith(`data-${key}`, undefined, false, false); // After interval passes, should revalidate again vi.advanceTimersByTime(1000); expect(intervalFetcher).toHaveBeenCalledTimes(2); // After another interval, should revalidate again vi.advanceTimersByTime(1000); expect(intervalFetcher).toHaveBeenCalledTimes(3); }); it("revalidate uses stored fetcher when called without fetcher option", async () => { // Create SWR without global fetcher const noGlobalFetcherSwr = createSWR({ dedupingInterval: 100 }); const perKeyFetcher = vi.fn((k: Key, options: FetcherOptions) => Promise.resolve(`data-${k}`)); const callback = vi.fn(); noGlobalFetcherSwr.subscribe(key, callback, perKeyFetcher); // Initial fetch vi.runOnlyPendingTimers(); expect(perKeyFetcher).toHaveBeenCalledTimes(1); // Revalidate without passing fetcher - should use stored fetcher await noGlobalFetcherSwr.revalidate(key); expect(perKeyFetcher).toHaveBeenCalledTimes(2); }); });