fix(keys): epic-end review round 2 — focus comes back to the card, the right rail and the reason
Whole-epic deep-review of E11 (18 raised, 12 kept; B and G refuted, four latent test-hardening notes dropped), plus the admin refusal copy gap round 1 surfaced. - A context menu opened from a card the keyboard is on gives the focus back to the card. floating-ui returns focus through the opener's first tabbable descendant even when handed a ref, so for a non-tabbable opener the surface restores it itself (measured in Chrome: j, Shift+F10, Escape left it on the thumbnail link). - Back finds a named list (a rail) by its name: a rail that appeared above it after the cache expired sent the focus into the wrong rail. An unnamed list is matched only when the page has the same number of lists. - The slot kept for a removed item is forgotten once focus moves on to page content (search, a chip), so j after new results starts at the first visible item; a menu or dialog opened from the item keeps it. - The list keys reach the cheat sheet only while a list has an item. - A j-focused card root is a named group while it holds the focus. - The channel page's Block and Hide toggles keep the focus while their request runs. - A split export that fails part-way says how many clips were added and deselects them, so a retry adds only the rest. - Config save, the one-click share link and "log out other sessions" report failures with their reason. - VirtualGrid uses the one scroll-parent walk listNav measures against. - Admin refusals (activate, resend, invite, set-password link) say why in HU and EN instead of "try again"; the email-off special cases collapse into that one reason. The contract reads the codes from localizeDetail. HU copy points to Konfiguráció → E-mail / SMTP; HU "unreachable" matches the EN change of round 1.
This commit is contained in:
@@ -11,11 +11,11 @@ import { act, cleanup, render, waitFor } from "@testing-library/react";
|
||||
import { rememberNavPosition } from "./history";
|
||||
import { navItemAttrs, useListNav } from "./listNav";
|
||||
|
||||
function List({ id, count }: { id: string; count: number }) {
|
||||
function List({ id, count, label }: { id: string; count: number; label?: string }) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useListNav(ref, { enabled: true });
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div ref={ref} aria-label={label}>
|
||||
{Array.from({ length: count }, (_, i) => (
|
||||
<div key={i} data-testid={`${id}${i}`} {...navItemAttrs(i)}>
|
||||
{id}
|
||||
@@ -60,6 +60,9 @@ function landOn(state: unknown) {
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
// A restore that gave up waiting for its list polls on to its deadline; a push cancels it, and one
|
||||
// left running fires after the file's jsdom is torn down ("document is not defined").
|
||||
rememberNavPosition();
|
||||
cleanup();
|
||||
(document.activeElement as HTMLElement | null)?.blur?.();
|
||||
window.history.replaceState(null, "");
|
||||
@@ -142,3 +145,43 @@ describe("rememberNavPosition · Back focuses the item a view was left from", ()
|
||||
expect(document.activeElement).toBe(getByRole("textbox"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("rememberNavPosition · the list it comes back to (deep-review E11 round 2)", () => {
|
||||
// Discover: a title opened from "Recently added" and watched — the page's cache expires and
|
||||
// "Continue watching" fills with that very title, a rail ABOVE. Found by its place, focus landed in
|
||||
// that rail, on a title never picked.
|
||||
it("finds a named list by its name when a list appeared above it", async () => {
|
||||
const Rails = () => (
|
||||
<>
|
||||
<List id="a" count={2} label="Recently added" />
|
||||
<List id="b" count={3} label="Genres" />
|
||||
</>
|
||||
);
|
||||
const { unmount } = render(<Rails />);
|
||||
const stamped = leaveFrom("a1", 2);
|
||||
unmount();
|
||||
landOn(stamped);
|
||||
render(
|
||||
<>
|
||||
<List id="c" count={3} label="Continue watching" />
|
||||
<Rails />
|
||||
</>,
|
||||
);
|
||||
await waitFor(() => expect(focused()).toBe("a1"));
|
||||
});
|
||||
|
||||
it("gives up on an unnamed list when the page came back with another number of lists", async () => {
|
||||
const { unmount } = render(<Page />);
|
||||
const stamped = leaveFrom("a1", 2);
|
||||
unmount();
|
||||
landOn(stamped);
|
||||
render(
|
||||
<>
|
||||
<List id="c" count={3} />
|
||||
<Page />
|
||||
</>,
|
||||
);
|
||||
await sleep(150);
|
||||
expect(focused()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user