// @vitest-environment jsdom // // Back to a page whose item changed lists while it was away. The notification center holds a row // that asked for the reader in "Needs you" for the visit; on the next visit the same row, read now, // sits in the stream below. The restore matched the unnamed list by its place, missed the name there // and fell back to the saved index — the focus landed on the neighbour, and Enter opened the wrong // notification. Its own file: listNav keeps module-level state. import { afterEach, describe, expect, it, vi } from "vitest"; import { act, cleanup, render } from "@testing-library/react"; import { useRef } from "react"; import { navItemAttrs, navPosition, restoreNavPosition, useListNav } from "./listNav"; afterEach(cleanup); function List({ items }: { items: string[] }) { const ref = useRef(null); useListNav(ref, { enabled: true }); return (
{items.map((t, i) => (
))}
); } const Page = ({ lists }: { lists: string[][] }) => (
{lists.map((items, i) => ( ))}
); const button = (name: string) => [...document.querySelectorAll("button")].find((b) => b.textContent === name)!; const focused = () => document.activeElement?.closest("[data-nav-index]")?.textContent; const wait = (ms: number) => act(() => new Promise((r) => setTimeout(r, ms))); async function leaveAndReturn(before: string[][], from: string, after: string[][]) { const { unmount } = render(); button(from).focus(); const pos = navPosition()!; unmount(); // the page switch render(); // Back const cancel = restoreNavPosition(pos); await wait(120); cancel(); } describe("Back to an item that moved to another list", () => { it("finds it by its name in the list it moved to, not its neighbour at the old place", async () => { await leaveAndReturn([["subscribed", "download"], ["older"]], "subscribed", [ ["download"], ["subscribed", "older"], ]); expect(focused()).toBe("subscribed"); }); it("finds it when the list it left is gone altogether", async () => { await leaveAndReturn([["subscribed"], ["older"]], "subscribed", [["subscribed", "older"]]); expect(focused()).toBe("subscribed"); }); it("does not guess between two items of that name elsewhere: the saved place", async () => { await leaveAndReturn([["twin", "download"], ["older"]], "twin", [ ["download"], ["twin", "older", "twin"], ]); expect(focused()).toBe("download"); }); it("brings an item found in another list into view; one in its own list stays put", async () => { // The scroll restore put the page where the item WAS: found elsewhere, it would hold the focus // off screen. const reveal = vi.fn(); Element.prototype.scrollIntoView = reveal; await leaveAndReturn([["subscribed", "download"], ["older"]], "subscribed", [ ["download"], ["subscribed", "older"], ]); expect(reveal).toHaveBeenCalledTimes(1); cleanup(); reveal.mockClear(); await leaveAndReturn([["subscribed", "download"]], "download", [["download", "subscribed"]]); expect(focused()).toBe("download"); expect(reveal).not.toHaveBeenCalled(); }); });