The 43 react-refresh/only-export-components warnings the gate let through meant an edit to a provider (or AnchoredPopover, Toaster, plexDetailUi, …) could not be hot-swapped: Fast Refresh reloads a module that also exports hooks or helpers. - Each provider's contexts, the types its hooks return and the hooks themselves live in a sibling context module (navigationContext.ts, channelsContext.ts, …); the provider file keeps the component. loadAccountFilters moved next to the feed filters' defaults (feedFiltersInit.ts), following navigationInit/playlistsInit. - Helpers that sat in component files have their own modules: welcomeMode, downloadTitle, plexPlayable, channelSync, ui/anchoredPopoverHooks, ui/pageToolbarSlot, ui/pagerContent, ui/toastLevels, ui/overlayOrder, ui/tableAlign, lib/plexDetail; the cells of the channel columns and the registry's page wrappers are components files of their own (channelCells, modulePages). - usePersistedTab, an alias of useAccountPersistedState, is gone; its callers use the original. npm run lint is now eslint --max-warnings 0, so a warning fails the gate like an error does.
62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
// @vitest-environment jsdom
|
|
//
|
|
// Gates the shared PopoverItem ARIA contract: aria-checked for menuitemradio/menuitemcheckbox and
|
|
// aria-selected for option are rendered as explicit attributes AFTER the floating-ui item props (so they
|
|
// win over getItemProps' single-select guess). Six live menus (LanguageSwitcher, ViewSwitcher,
|
|
// AddToPlaylist, ChannelTags, tableHeader, plexDetailUi) depend on this; without a test, a regression in
|
|
// the shared path would silently drop the checked indicator for screen-reader users. Floating-ui recipe:
|
|
// stub the two observers autoUpdate wires, wrap in a FloatingTree, query the hidden surface.
|
|
import { describe, it, expect, beforeAll, afterEach } from "vitest";
|
|
import { useState } from "react";
|
|
import { render, screen, cleanup } from "@testing-library/react";
|
|
import { FloatingTree } from "@floating-ui/react";
|
|
import { PopoverSurface, PopoverItem } from "./AnchoredPopover";
|
|
import { useAnchoredPopover } from "./anchoredPopoverHooks";
|
|
|
|
beforeAll(() => {
|
|
class Stub {
|
|
observe() {}
|
|
unobserve() {}
|
|
disconnect() {}
|
|
takeRecords() {
|
|
return [];
|
|
}
|
|
}
|
|
globalThis.ResizeObserver ??= Stub as unknown as typeof ResizeObserver;
|
|
globalThis.IntersectionObserver ??= Stub as unknown as typeof IntersectionObserver;
|
|
});
|
|
|
|
afterEach(cleanup);
|
|
|
|
function Menu() {
|
|
const [open, setOpen] = useState(true);
|
|
const pop = useAnchoredPopover({ open, onOpenChange: setOpen, mode: "menu" });
|
|
return (
|
|
<PopoverSurface popover={pop} ariaLabel="Choices">
|
|
<PopoverItem role="menuitemradio" checked onSelect={() => {}}>
|
|
Newest
|
|
</PopoverItem>
|
|
<PopoverItem role="menuitemradio" checked={false} onSelect={() => {}}>
|
|
Oldest
|
|
</PopoverItem>
|
|
<PopoverItem role="menuitemcheckbox" checked onSelect={() => {}}>
|
|
Show shorts
|
|
</PopoverItem>
|
|
</PopoverSurface>
|
|
);
|
|
}
|
|
|
|
describe("PopoverItem ARIA contract", () => {
|
|
it("exposes aria-checked on menuitemradio and menuitemcheckbox items", () => {
|
|
render(
|
|
<FloatingTree>
|
|
<Menu />
|
|
</FloatingTree>,
|
|
);
|
|
const radios = screen.getAllByRole("menuitemradio", { hidden: true });
|
|
expect(radios.map((r) => r.getAttribute("aria-checked"))).toEqual(["true", "false"]);
|
|
const checkbox = screen.getByRole("menuitemcheckbox", { hidden: true });
|
|
expect(checkbox.getAttribute("aria-checked")).toBe("true");
|
|
});
|
|
});
|