O mergulho do hero termina direto em Projetos. As capturas de tela seguem nos cards (ProjectCover). Remove componentes, testes e 670 linhas de CSS do notebook. Co-Authored-By: Claude Opus 5.5 <[email protected]>
160 lines
5.2 KiB
TypeScript
160 lines
5.2 KiB
TypeScript
import { act, fireEvent, render, screen } from "@testing-library/react";
|
|
import {
|
|
resetHomeArrivalState,
|
|
useHomeFragmentNavigation,
|
|
} from "./useHomeFragmentNavigation";
|
|
|
|
function HomeNavigation() {
|
|
const onLayoutReady = useHomeFragmentNavigation();
|
|
return (
|
|
<>
|
|
<button type="button" onClick={onLayoutReady}>
|
|
Establish layout
|
|
</button>
|
|
<a href="/#about">Sobre</a>
|
|
<a href="/#contact">Contato</a>
|
|
<section id="about" aria-label="Sobre">
|
|
<button type="button">Read biography</button>
|
|
</section>
|
|
<section id="contact" aria-label="Contato">
|
|
<button type="button">Send message</button>
|
|
</section>
|
|
</>
|
|
);
|
|
}
|
|
|
|
const originalScrollIntoView = HTMLElement.prototype.scrollIntoView;
|
|
|
|
beforeEach(() => {
|
|
resetHomeArrivalState();
|
|
window.history.replaceState(null, "", "/");
|
|
HTMLElement.prototype.scrollIntoView = jest.fn();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks();
|
|
window.history.replaceState(null, "", "/");
|
|
if (originalScrollIntoView) {
|
|
HTMLElement.prototype.scrollIntoView = originalScrollIntoView;
|
|
} else {
|
|
Reflect.deleteProperty(HTMLElement.prototype, "scrollIntoView");
|
|
}
|
|
});
|
|
|
|
describe("home fragment navigation", () => {
|
|
it("starts an unfragmented visit at the top after the home establishes layout", () => {
|
|
let restoredPosition = 3200;
|
|
jest
|
|
.spyOn(window, "scrollTo")
|
|
.mockImplementation((options: ScrollToOptions | number) => {
|
|
if (typeof options === "object")
|
|
restoredPosition = options.top ?? restoredPosition;
|
|
});
|
|
render(<HomeNavigation />);
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "Establish layout" }),
|
|
);
|
|
expect(restoredPosition).toBe(0);
|
|
});
|
|
|
|
it("leaves a same-document return to the position the browser restores", () => {
|
|
const scrollTo = jest
|
|
.spyOn(window, "scrollTo")
|
|
.mockImplementation(() => {});
|
|
const firstVisit = render(<HomeNavigation />);
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "Establish layout" }),
|
|
);
|
|
firstVisit.unmount();
|
|
expect(window.history.scrollRestoration).not.toBe("manual");
|
|
|
|
scrollTo.mockClear();
|
|
render(<HomeNavigation />);
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "Establish layout" }),
|
|
);
|
|
expect(scrollTo).not.toHaveBeenCalled();
|
|
expect(window.history.scrollRestoration).not.toBe("manual");
|
|
});
|
|
|
|
it("does not reset the visitor's position after intentional scrolling during startup", () => {
|
|
let position = 0;
|
|
jest
|
|
.spyOn(window, "scrollTo")
|
|
.mockImplementation((options: ScrollToOptions | number) => {
|
|
if (typeof options === "object") position = options.top ?? position;
|
|
});
|
|
render(<HomeNavigation />);
|
|
position = 600;
|
|
fireEvent.wheel(window, { deltaY: 600 });
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "Establish layout" }),
|
|
);
|
|
expect(position).toBe(600);
|
|
});
|
|
|
|
it("waits for the home layout on fragment arrival, then never steals focus on refresh", () => {
|
|
window.history.replaceState(null, "", "/#contact");
|
|
render(<HomeNavigation />);
|
|
const contact = screen.getByRole("region", { name: "Contato" });
|
|
expect(contact).not.toHaveFocus();
|
|
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "Establish layout" }),
|
|
);
|
|
expect(contact).toHaveFocus();
|
|
screen.getByRole("button", { name: "Read biography" }).focus();
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "Establish layout" }),
|
|
);
|
|
expect(
|
|
screen.getByRole("button", { name: "Read biography" }),
|
|
).toHaveFocus();
|
|
});
|
|
|
|
it("abandons a pending arrival when the visitor intentionally scrolls", () => {
|
|
window.history.replaceState(null, "", "/#about");
|
|
render(<HomeNavigation />);
|
|
const message = screen.getByRole("button", { name: "Send message" });
|
|
message.focus();
|
|
fireEvent.wheel(window, { deltaY: 200 });
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "Establish layout" }),
|
|
);
|
|
expect(message).toHaveFocus();
|
|
});
|
|
|
|
it("keeps repeated same-fragment links native while moving focus to the destination", () => {
|
|
window.history.replaceState(null, "", "/#about");
|
|
render(<HomeNavigation />);
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "Establish layout" }),
|
|
);
|
|
const link = screen.getByRole("link", { name: "Sobre" });
|
|
link.focus();
|
|
expect(fireEvent.click(link)).toBe(true);
|
|
expect(screen.getByRole("region", { name: "Sobre" })).toHaveFocus();
|
|
link.focus();
|
|
expect(fireEvent.click(link)).toBe(true);
|
|
expect(screen.getByRole("region", { name: "Sobre" })).toHaveFocus();
|
|
link.focus();
|
|
fireEvent.click(link, { ctrlKey: true });
|
|
expect(link).toHaveFocus();
|
|
});
|
|
|
|
it("does not resurrect a stale fragment when the URL changes before readiness", () => {
|
|
window.history.replaceState(null, "", "/#about");
|
|
render(<HomeNavigation />);
|
|
const message = screen.getByRole("button", { name: "Send message" });
|
|
message.focus();
|
|
act(() => {
|
|
window.history.replaceState(null, "", "/#contact");
|
|
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
|
});
|
|
fireEvent.click(
|
|
screen.getByRole("button", { name: "Establish layout" }),
|
|
);
|
|
expect(message).toHaveFocus();
|
|
});
|
|
});
|