fix(ui): the page's end scrolls out from under Back to top

The button floats over the bottom-right corner, and at the end of a long table it covered the closing
pager's '100' and 'All'. While it is up, PageScroller ends the page a button's height later (only
then, so a full-height layout gets no scrollbar). Its shown state is a shared store read through a
use… wrapper: a store's .use() called straight from a component is memoized by the React Compiler
and never re-renders (contract: store.contract.test.ts).
This commit is contained in:
2026-09-27 16:28:18 +02:00
parent 51f881681b
commit 86884e368e
5 changed files with 73 additions and 6 deletions
@@ -7,7 +7,7 @@
// through window.history.state (what ensureNavId/peekNavId read) — firing the nav bus (notifyNav)
// stands in for a Back/Forward that doesn't re-render the scroller.
import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from "vitest";
import { render, fireEvent, cleanup } from "@testing-library/react";
import { render, fireEvent, cleanup, act } from "@testing-library/react";
vi.mock("react-i18next", async (importOriginal) => {
const actual = await importOriginal<typeof import("react-i18next")>();
@@ -15,6 +15,7 @@ vi.mock("react-i18next", async (importOriginal) => {
});
import PageScroller from "./PageScroller";
import BackToTop from "./BackToTop";
import { notifyNav } from "../../lib/history";
beforeAll(() => {
@@ -97,3 +98,27 @@ describe("PageScroller scroll memory", () => {
expect(main.scrollTop).toBe(300);
});
});
// E28 UAT: Back to top floats over the bottom-right corner, and at the end of a long table it covered
// the closing pager's "100" and "All". While it is up, the page ends a button's height later.
describe("PageScroller · room for Back to top", () => {
it("ends the page later while the button is up, and not otherwise", () => {
const { container } = render(
<>
<PageScroller scrollKey="room">
<p>rows</p>
</PageScroller>
<BackToTop />
</>,
);
const main = container.querySelector("main")!;
const spacer = () => main.querySelector(':scope > [aria-hidden="true"]');
expect(spacer()).toBeNull();
main.scrollTop = 900;
act(() => void main.dispatchEvent(new Event("scroll")));
expect(spacer()?.className).toBe("h-18");
main.scrollTop = 0;
act(() => void main.dispatchEvent(new Event("scroll")));
expect(spacer()).toBeNull();
});
});