Archived
init
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user