WFDASH-WEBAPP/src/lib/use-split-ratio.test.ts
Dave d9c60aff6d feat: add split views, stock picking slips, and invoice viewer
Comprehensive changes including:
- Refactor picking slip line handling for multiple document types across print endpoints
- Add new `/api/v1/stock-purchases/picking-slip` API for stock booking picking slips
- Implement reusable Splitter component and useSplitRatio hook for resizable panels
- Add floating/fullscreen invoice viewer to collection confirm page
- Add real-time stock booking update event for invoicing tab
- Add full test coverage for all new components and APIs
- Include temporary chat attachment images for testing purposes
2026-08-09 23:16:55 +02:00

171 lines
5.9 KiB
TypeScript

import { act, renderHook } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { useSplitRatio } from "./use-split-ratio";
const STORAGE_KEY = "test:split-ratio";
afterEach(() => {
window.localStorage.clear();
vi.restoreAllMocks();
});
describe("useSplitRatio", () => {
describe("initialisation", () => {
it("returns the initial value when nothing is stored", () => {
const { result } = renderHook(() =>
useSplitRatio({ storageKey: STORAGE_KEY, initial: 0.5 }),
);
expect(result.current.ratio).toBe(0.5);
});
it("returns the stored value on a re-render after mount", () => {
window.localStorage.setItem(STORAGE_KEY, "0.33");
const { result } = renderHook(() =>
useSplitRatio({ storageKey: STORAGE_KEY, initial: 0.5 }),
);
expect(result.current.ratio).toBeCloseTo(0.33, 5);
});
it("clamps a stored value that fell outside the new bounds", () => {
window.localStorage.setItem(STORAGE_KEY, "0.05"); // below min
const { result } = renderHook(() =>
useSplitRatio({
storageKey: STORAGE_KEY,
initial: 0.5,
min: 0.2,
max: 0.8,
}),
);
expect(result.current.ratio).toBe(0.2);
});
it("ignores an unparseable stored value and falls back to initial", () => {
window.localStorage.setItem(STORAGE_KEY, "not-a-number");
const { result } = renderHook(() =>
useSplitRatio({ storageKey: STORAGE_KEY, initial: 0.5 }),
);
expect(result.current.ratio).toBe(0.5);
});
it("ignores an empty stored value and falls back to initial", () => {
window.localStorage.setItem(STORAGE_KEY, "");
const { result } = renderHook(() =>
useSplitRatio({ storageKey: STORAGE_KEY, initial: 0.5 }),
);
expect(result.current.ratio).toBe(0.5);
});
});
describe("setRatio", () => {
it("updates the ratio and persists to localStorage", () => {
const { result } = renderHook(() =>
useSplitRatio({ storageKey: STORAGE_KEY, initial: 0.5 }),
);
act(() => result.current.setRatio(0.6));
expect(result.current.ratio).toBe(0.6);
expect(window.localStorage.getItem(STORAGE_KEY)).toBe("0.6");
});
it("clamps values below the min before persisting", () => {
const { result } = renderHook(() =>
useSplitRatio({
storageKey: STORAGE_KEY,
initial: 0.5,
min: 0.2,
max: 0.8,
}),
);
act(() => result.current.setRatio(0.01));
expect(result.current.ratio).toBe(0.2);
expect(window.localStorage.getItem(STORAGE_KEY)).toBe("0.2");
});
it("clamps values above the max before persisting", () => {
const { result } = renderHook(() =>
useSplitRatio({
storageKey: STORAGE_KEY,
initial: 0.5,
min: 0.2,
max: 0.8,
}),
);
act(() => result.current.setRatio(0.99));
expect(result.current.ratio).toBe(0.8);
expect(window.localStorage.getItem(STORAGE_KEY)).toBe("0.8");
});
it("uses default bounds (0.15, 0.85) when no min/max are passed", () => {
const { result } = renderHook(() =>
useSplitRatio({ storageKey: STORAGE_KEY, initial: 0.5 }),
);
act(() => result.current.setRatio(0.05));
expect(result.current.ratio).toBe(0.15);
act(() => result.current.setRatio(0.95));
expect(result.current.ratio).toBe(0.85);
});
it("survives a localStorage write failure (quota / Safari private mode)", () => {
// Stub the prototype method so the failure path runs even when
// the instance-level setItem isn't spy-able in jsdom. The
// contract is: the in-memory state still updates, and the
// failure doesn't break the UI.
const protoSpy = vi
.spyOn(Storage.prototype, "setItem")
.mockImplementation(() => {
throw new Error("QuotaExceededError");
});
const { result } = renderHook(() =>
useSplitRatio({ storageKey: STORAGE_KEY, initial: 0.5 }),
);
expect(() => {
act(() => result.current.setRatio(0.7));
}).not.toThrow();
expect(result.current.ratio).toBe(0.7);
// The setter did attempt the write — confirm the spy saw it.
expect(protoSpy).toHaveBeenCalled();
protoSpy.mockRestore();
});
});
describe("storage key isolation", () => {
it("two hooks with different keys don't clobber each other", () => {
window.localStorage.setItem("key-a", "0.25");
window.localStorage.setItem("key-b", "0.75");
const { result: a } = renderHook(() =>
useSplitRatio({ storageKey: "key-a", initial: 0.5 }),
);
const { result: b } = renderHook(() =>
useSplitRatio({ storageKey: "key-b", initial: 0.5 }),
);
expect(a.current.ratio).toBeCloseTo(0.25, 5);
expect(b.current.ratio).toBeCloseTo(0.75, 5);
act(() => a.current.setRatio(0.3));
expect(b.current.ratio).toBeCloseTo(0.75, 5); // untouched
});
});
describe("lazy initialisation", () => {
it("a fresh mount picks up the latest stored value via the useState initialiser", () => {
const { result } = renderHook(() =>
useSplitRatio({ storageKey: STORAGE_KEY, initial: 0.5 }),
);
// Simulate another tab writing a new value while this hook was idle.
window.localStorage.setItem(STORAGE_KEY, "0.61");
const { result: remounted } = renderHook(() =>
useSplitRatio({ storageKey: STORAGE_KEY, initial: 0.5 }),
);
// The original hook is unaware (no cross-instance sync required
// — single-tab contract). A fresh mount, however, reads the
// current localStorage value via the useState initialiser, so
// it lands on the latest value with no flicker.
expect(result.current.ratio).toBe(0.5); // unchanged — first hook
expect(remounted.current.ratio).toBeCloseTo(0.61, 5);
});
});
});