Files
siftlode/frontend/src/components/ui/stickyTop.dom.test.tsx
T
peter 6fda49369f fix(ui): a sticky table header turns the scroller's top fade off itself; DataTable renders one layout
- "A page with a sticky ColumnHead must declare fadeTop: false" lived in
  prose: a registry flag on three pages plus an App special case for the
  Channels card layout, and the next table page would have re-acquired the
  fade-on-the-header defect with every lane green. ColumnHead now registers
  itself with the enclosing PageScroller (ui/stickyTop) while mounted; the
  registry flag, the App branch and the prop plumbing are gone. Measured on
  localdev: the top mask is 0px under Channels' and Audit's headers, 20px on
  Home and the Feed.
- ui/DataTable mounted its table AND its card list at every width, one
  hidden by CSS: double DOM and render cost, duplicate ids, two keyboard
  copies of every row. It now mounts the one the `md` media query calls for
  (lib/useMediaQuery); the keyboard test asserts one layout at each width.
- The Plex module's content width went px → rem with the rest of the page.
2026-09-19 07:51:33 +02:00

63 lines
2.0 KiB
TypeScript

// @vitest-environment jsdom
//
// The page scroller's top fade sits ON a sticky table header, so a page with one used to opt out by
// hand (`fadeTop: false` in the module registry, plus an App special case for the Channels card
// layout) — and the next table page would have re-acquired the defect with every lane green. The
// header now says so itself: while a `ColumnHead` is mounted the scroller asks for no top fade.
import { afterEach, describe, expect, it, vi } from "vitest";
import { cleanup, render } from "@testing-library/react";
const fadeTopArgs: boolean[] = [];
vi.mock("../../lib/useScrollFade", () => ({
useScrollFade: (_px: number, fadeTop: boolean) => {
fadeTopArgs.push(fadeTop);
return { ref: () => {}, style: {} };
},
}));
vi.mock("react-i18next", async (importOriginal) => {
const actual = await importOriginal<typeof import("react-i18next")>();
return { ...actual, useTranslation: () => ({ t: (key: string) => key }) };
});
import PageScroller from "./PageScroller";
import { ColumnHead } from "./tableHeader";
afterEach(() => {
cleanup();
fadeTopArgs.length = 0;
});
function page(withTable: boolean) {
return (
<PageScroller scrollKey="k">
{withTable ? (
<table>
<ColumnHead
columns={[]}
sort={null}
onToggleSort={() => {}}
filters={{}}
onFilter={() => {}}
/>
</table>
) : (
<div>cards</div>
)}
</PageScroller>
);
}
describe("the page scroller's top fade and a sticky table header", () => {
it("is off while the header is mounted, and back on the moment it is gone", () => {
const view = render(page(true));
expect(fadeTopArgs.at(-1)).toBe(false);
view.rerender(page(false)); // e.g. the Channels card layout, or a narrow DataTable
expect(fadeTopArgs.at(-1)).toBe(true);
});
it("is on for a page with no sticky header at all", () => {
render(page(false));
expect(fadeTopArgs.at(-1)).toBe(true);
});
});