fix(sort): address R13 S1 review findings
- PlexSidebar activeCount now compares the HEALED sort, so an out-of-scope key (movie "duration" after switching to shows) no longer inflates the active-filter badge while the control + grid show the default. - PlexProvider strips the legacy `sortDir` from the filters blob, so it stops leaking into the facets query key and being re-persisted on every change. - PlexProvider sort migration guards the fall-through: valid JSON that isn't a SortState now falls back instead of using the raw JSON string as the key. - New shared healPlexSort(sort, scope) is the single heal both PlexBrowse and PlexSidebar use (removes the noop-label plexSortOptions call + the duplication). - Tests: useSortState.dom.test.ts (hydrate/persist/heal round-trip via renderHook) and SortControl.dom.test.tsx (options, pick, direction toggle, directionless, trailing). vitest 94 -> 105. Gate green: tsc/eslint(0 err)/prettier/knip clean, vitest 105.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// The persistence/hydration wiring of useSortState (the pure hydrateSort/healSort logic it leans on is
|
||||
// covered in columnSort.test.ts). storage is mocked with an in-memory map so the hook can be exercised
|
||||
// without a logged-in account (getAccountRaw/setAccountRaw are account-keyed in the real module).
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { renderHook, act } from "@testing-library/react";
|
||||
|
||||
const { store } = vi.hoisted(() => ({ store: new Map<string, string>() }));
|
||||
vi.mock("./storage", () => ({
|
||||
getAccountRaw: (k: string) => store.get(k) ?? null,
|
||||
setAccountRaw: (k: string, v: string) => {
|
||||
store.set(k, v);
|
||||
},
|
||||
}));
|
||||
|
||||
import { useSortState } from "./useSortState";
|
||||
|
||||
describe("useSortState", () => {
|
||||
beforeEach(() => store.clear());
|
||||
|
||||
it("hydrates to the fallback when storage is empty", () => {
|
||||
const { result } = renderHook(() => useSortState("k"));
|
||||
expect(result.current[0]).toBeNull();
|
||||
const withFallback = renderHook(() => useSortState("k2", [], { key: "date", dir: "desc" }));
|
||||
expect(withFallback.result.current[0]).toEqual({ key: "date", dir: "desc" });
|
||||
});
|
||||
|
||||
it("hydrates a stored SortState on mount", () => {
|
||||
store.set("k", JSON.stringify({ key: "views", dir: "desc" }));
|
||||
const { result } = renderHook(() => useSortState("k"));
|
||||
expect(result.current[0]).toEqual({ key: "views", dir: "desc" });
|
||||
});
|
||||
|
||||
it("persists on set, and a fresh mount reads it back (write → reload → hydrate)", () => {
|
||||
const first = renderHook(() => useSortState("k"));
|
||||
act(() => first.result.current[1]({ key: "name", dir: "asc" }));
|
||||
expect(first.result.current[0]).toEqual({ key: "name", dir: "asc" });
|
||||
expect(store.get("k")).toBe(JSON.stringify({ key: "name", dir: "asc" }));
|
||||
const second = renderHook(() => useSortState("k"));
|
||||
expect(second.result.current[0]).toEqual({ key: "name", dir: "asc" });
|
||||
});
|
||||
|
||||
it("heals a stored key that the supplied options no longer offer", () => {
|
||||
store.set("k", JSON.stringify({ key: "gone", dir: "asc" }));
|
||||
const opts = [{ key: "name", label: "Name" }];
|
||||
const { result } = renderHook(() => useSortState("k", opts, { key: "name", dir: "asc" }));
|
||||
expect(result.current[0]).toEqual({ key: "name", dir: "asc" });
|
||||
});
|
||||
|
||||
it("keeps a stored key un-healed when no options are supplied (caller heals for display)", () => {
|
||||
store.set("k", JSON.stringify({ key: "anything", dir: "desc" }));
|
||||
const { result } = renderHook(() => useSortState("k"));
|
||||
expect(result.current[0]).toEqual({ key: "anything", dir: "desc" });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user