93 lines
3.4 KiB
TypeScript
93 lines
3.4 KiB
TypeScript
// @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<HTMLDivElement>(null);
|
|
useListNav(ref, { enabled: true });
|
|
return (
|
|
<div ref={ref}>
|
|
{items.map((t, i) => (
|
|
<div key={`${t}-${i}`} {...navItemAttrs(i, t)}>
|
|
<button data-nav-open="">{t}</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const Page = ({ lists }: { lists: string[][] }) => (
|
|
<main>
|
|
{lists.map((items, i) => (
|
|
<List key={i} items={items} />
|
|
))}
|
|
</main>
|
|
);
|
|
|
|
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(<Page lists={before} />);
|
|
button(from).focus();
|
|
const pos = navPosition()!;
|
|
unmount(); // the page switch
|
|
render(<Page lists={after} />); // 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();
|
|
});
|
|
});
|