Archived
117 lines
4.0 KiB
TypeScript
117 lines
4.0 KiB
TypeScript
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);
|
|
});
|
|
});
|