This commit is contained in:
2026-09-05 04:33:55 +05:30
commit 02a4264bc3
121 changed files with 7913 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
import { beforeEach, describe, expect, it, vi } from "bun:test";
import { LRUCache, MemoryCache } from "../src/cache";
describe("LRUCache", () => {
let cache: LRUCache<string, Error>;
const maxSize = 2;
beforeEach(() => {
cache = new LRUCache(maxSize);
});
it("inserts and retrieves items", () => {
const key = "foo";
const lastModified = Date.now();
const entry = { data: "bar", isValidating: false, isLoading: false, lastModified };
cache.set(key, entry);
expect(cache.get(key)).toEqual(entry);
});
it("evicts oldest item when max size is reached", () => {
cache.set("1", { data: "1", isValidating: false, isLoading: false, lastModified: Date.now() });
cache.set("2", { data: "2", isValidating: false, isLoading: false, lastModified: Date.now() });
cache.set("3", { data: "3", isValidating: false, isLoading: false, lastModified: Date.now() });
expect(cache.has("1")).toBe(false);
expect(cache.has("2")).toBe(true);
expect(cache.has("3")).toBe(true);
});
it("updates LRU order on get", () => {
cache.set("1", { data: "1", isValidating: false, isLoading: false, lastModified: Date.now() });
cache.set("2", { data: "2", isValidating: false, isLoading: false, lastModified: Date.now() });
// Access "1" to make it most recent
cache.get("1");
cache.set("3", { data: "3", isValidating: false, isLoading: false, lastModified: Date.now() });
expect(cache.has("2")).toBe(false); // "2" was oldest
expect(cache.has("1")).toBe(true);
expect(cache.has("3")).toBe(true);
});
it("handles TTL expiration", () => {
vi.useFakeTimers();
const now = Date.now();
cache.set("exp", { data: "val", isValidating: false, isLoading: false, lastModified: now, ttl: 1000 });
expect(cache.get("exp")).toBeDefined();
vi.advanceTimersByTime(1001);
expect(cache.get("exp")).toBeUndefined();
vi.useRealTimers();
});
});
describe("MemoryCache", () => {
it("cleans up on destroy", () => {
vi.useFakeTimers();
const cache = new MemoryCache(100, 1000);
const clearIntervalSpy = vi.spyOn(globalThis, 'clearInterval');
cache.destroy();
expect(clearIntervalSpy).toHaveBeenCalled();
vi.useRealTimers();
});
});
+116
View File
@@ -0,0 +1,116 @@
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);
});
});