fix: canonicalize browser navigation after commit

This commit is contained in:
Codex
2026-07-30 22:35:37 +08:00
parent 95abd09c39
commit 9bc0f27f12
4 changed files with 103 additions and 16 deletions
@@ -4,12 +4,20 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
import { useBrowserPathname } from './use-browser-pathname.js';
function Harness({ link = <a href="/automation">Automation</a> }: { link?: React.ReactNode }) {
const pathname = useBrowserPathname();
function Harness({
link = <a href="/automation">Automation</a>,
onMainFocus,
resolvePathname,
}: {
link?: React.ReactNode;
onMainFocus?: (pathname: string) => void;
resolvePathname?: (pathname: string) => string;
}) {
const pathname = useBrowserPathname(resolvePathname);
return (
<>
{link}
<main id="main-content" tabIndex={-1}>
<main id="main-content" tabIndex={-1} onFocus={() => onMainFocus?.(pathname)}>
{pathname}
</main>
</>
@@ -27,7 +35,8 @@ describe('useBrowserPathname', () => {
window.history.replaceState(null, '', '/fleet');
const pushState = vi.spyOn(window.history, 'pushState');
const scrollTo = vi.spyOn(window, 'scrollTo').mockImplementation(() => undefined);
render(<Harness />);
const focusedPathnames: string[] = [];
render(<Harness onMainFocus={(pathname) => focusedPathnames.push(pathname)} />);
const link = screen.getByRole('link', { name: 'Automation' });
const accepted = fireEvent.click(link);
@@ -36,9 +45,39 @@ describe('useBrowserPathname', () => {
expect(pushState).toHaveBeenCalledWith(null, '', '/automation');
expect(screen.getByRole('main').textContent).toBe('/automation');
expect(document.activeElement).toBe(screen.getByRole('main'));
expect(focusedPathnames).toEqual(['/automation']);
expect(scrollTo).toHaveBeenCalledWith({ top: 0, left: 0, behavior: 'auto' });
});
it('replaces a legacy pathname with its canonical destination', () => {
window.history.replaceState(null, '', '/settings/instances');
const replaceState = vi.spyOn(window.history, 'replaceState');
render(
<Harness
resolvePathname={(pathname) =>
pathname === '/settings/instances' ? '/settings/system' : pathname
}
/>,
);
expect(replaceState).toHaveBeenCalledWith(null, '', '/settings/system');
expect(window.location.pathname).toBe('/settings/system');
expect(screen.getByRole('main').textContent).toBe('/settings/system');
});
it('makes an exact-current link a no-op without adding history', () => {
window.history.replaceState(null, '', '/fleet');
const pushState = vi.spyOn(window.history, 'pushState');
render(<Harness link={<a href="/fleet">Current</a>} />);
const accepted = fireEvent.click(screen.getByRole('link', { name: 'Current' }));
expect(accepted).toBe(false);
expect(pushState).not.toHaveBeenCalled();
expect(window.location.pathname).toBe('/fleet');
});
it('tracks browser history traversal and restores the page start', () => {
window.history.replaceState(null, '', '/automation');
const scrollTo = vi.spyOn(window, 'scrollTo').mockImplementation(() => undefined);