Ctrl K
ComunidadGuías…

frontend/src/hooks/useToolDefaults.test.tsx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { describe, it, expect, beforeEach } from "vitest";
import { act, renderHook, waitFor } from "@testing-library/react";
import * as db from "@/lib/localStore/db";
import * as defaults from "@/lib/localStore/defaults";
import { loadPersisted, savePersisted } from "@/lib/persistence";
import { useToolDefaults } from "./useToolDefaults";

const DEFAULTS = { level: "recommended", quality: 75 };

/** The hook debounces writes at 400ms; wait past that. */
const settle = () => new Promise((r) => setTimeout(r, 600));

beforeEach(async () => {
  await db.destroy();
  localStorage.clear();
});

describe("useToolDefaults", () => {
  it("starts at defaults with restored=false", () => {
    const { result } = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
    expect(result.current[0]).toEqual(DEFAULTS);
    expect(result.current[2].restored).toBe(false);
  });

  it("registers the slug once a non-default value is written", async () => {
    const { result } = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
    act(() => result.current[1]({ level: "extreme", quality: 40 }));
    await waitFor(async () => {
      expect(await defaults.customizedSlugs()).toEqual(["compress-pdf"]);
    });
  });

  it("does not register when the value equals defaults", async () => {
    const { result } = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
    act(() => result.current[1]({ ...DEFAULTS }));
    await settle();
    expect(await defaults.customizedSlugs()).toEqual([]);
  });

  it("unregisters when the value returns to defaults", async () => {
    const { result } = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
    act(() => result.current[1]({ level: "extreme", quality: 40 }));
    await waitFor(async () => {
      expect(await defaults.customizedSlugs()).toEqual(["compress-pdf"]);
    });
    act(() => result.current[1]({ ...DEFAULTS }));
    await waitFor(async () => {
      expect(await defaults.customizedSlugs()).toEqual([]);
    });
  });

  it("rehydrates synchronously on remount — no flicker through defaults", async () => {
    const first = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
    act(() => first.result.current[1]({ level: "extreme", quality: 40 }));
    await settle();
    first.unmount();

    const second = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
    // Value is correct on the FIRST render, not after an effect.
    expect(second.result.current[0]).toEqual({ level: "extreme", quality: 40 });
    expect(second.result.current[2].restored).toBe(true);
  });

  it("merges newly-added defaults into an old snapshot", async () => {
    const first = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
    act(() => first.result.current[1]({ level: "extreme", quality: 40 }));
    await settle();
    first.unmount();

    const WITH_NEW_FIELD = { ...DEFAULTS, grayscale: false };
    const second = renderHook(() => useToolDefaults("compress-pdf", WITH_NEW_FIELD));
    expect(second.result.current[0]).toEqual({
      level: "extreme",
      quality: 40,
      grayscale: false,
    });
  });

  it("reset clears the value and the registration", async () => {
    const { result } = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
    act(() => result.current[1]({ level: "extreme", quality: 40 }));
    await settle();
    expect(loadPersisted("compress-pdf")).not.toBeNull();

    await act(async () => {
      result.current[2].reset();
    });
    expect(result.current[0]).toEqual(DEFAULTS);
    await waitFor(async () => {
      expect(await defaults.customizedSlugs()).toEqual([]);
    });
  });

  it("keeps two tools independent", async () => {
    const a = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
    const b = renderHook(() => useToolDefaults("bates-numbering", DEFAULTS));
    act(() => a.result.current[1]({ level: "extreme", quality: 40 }));
    await settle();
    expect(b.result.current[0]).toEqual(DEFAULTS);
    await waitFor(async () => {
      expect(await defaults.customizedSlugs()).toEqual(["compress-pdf"]);
    });
  });

  describe("legacy key migration", () => {
    // The 6 tools that already used useFormPersist keyed on short names
    // ("bates", "compress") rather than registry slugs. Renaming the key
    // without migrating would silently discard every existing user's saved
    // settings.
    it("adopts a value stored under the old key", () => {
      savePersisted("bates", { level: "extreme", quality: 40 });
      const { result } = renderHook(() =>
        useToolDefaults("bates-numbering", DEFAULTS, { legacyKey: "bates" }),
      );
      expect(result.current[0]).toEqual({ level: "extreme", quality: 40 });
      expect(result.current[2].restored).toBe(true);
    });

    it("removes the old key once migrated", async () => {
      savePersisted("bates", { level: "extreme", quality: 40 });
      renderHook(() => useToolDefaults("bates-numbering", DEFAULTS, { legacyKey: "bates" }));
      await waitFor(() => expect(loadPersisted("bates")).toBeNull());
      expect(loadPersisted("bates-numbering")).toEqual({ level: "extreme", quality: 40 });
    });

    it("prefers an existing new-key value over the legacy one", () => {
      savePersisted("bates", { level: "light", quality: 10 });
      savePersisted("bates-numbering", { level: "extreme", quality: 40 });
      const { result } = renderHook(() =>
        useToolDefaults("bates-numbering", DEFAULTS, { legacyKey: "bates" }),
      );
      expect(result.current[0]).toEqual({ level: "extreme", quality: 40 });
    });

    it("is a no-op when there is no legacy value", () => {
      const { result } = renderHook(() =>
        useToolDefaults("bates-numbering", DEFAULTS, { legacyKey: "bates" }),
      );
      expect(result.current[0]).toEqual(DEFAULTS);
      expect(result.current[2].restored).toBe(false);
    });
  });

  describe("setField", () => {
    // Generated per-field setters must behave like useState setters, including
    // the updater-function form — several tools call setX(prev => ...).
    it("sets a field by value", async () => {
      const { result } = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
      act(() => result.current[2].setField("level", "extreme"));
      expect(result.current[0]).toEqual({ ...DEFAULTS, level: "extreme" });
    });

    it("sets a field with an updater function", async () => {
      const { result } = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
      act(() => result.current[2].setField("quality", (q) => (q as number) + 5));
      expect(result.current[0].quality).toBe(80);
    });

    it("leaves other fields untouched", async () => {
      const { result } = renderHook(() => useToolDefaults("compress-pdf", DEFAULTS));
      act(() => result.current[2].setField("quality", 10));
      expect(result.current[0].level).toBe("recommended");
    });

    it("supports a boolean toggle via updater", async () => {
      const BOOL = { caseSensitive: false };
      const { result } = renderHook(() => useToolDefaults("highlight-pdf", BOOL));
      act(() => result.current[2].setField("caseSensitive", (v) => !v));
      expect(result.current[0].caseSensitive).toBe(true);
    });

    it("supports an array updater", async () => {
      const ARR = { sizes: [16, 32] as number[] };
      const { result } = renderHook(() => useToolDefaults("generate-favicon", ARR));
      act(() => result.current[2].setField("sizes", (prev) => [...(prev as number[]), 48]));
      expect(result.current[0].sizes).toEqual([16, 32, 48]);
    });
  });
});