/** @jest-environment jsdom */
import { handleAnchorClick } from './index';
describe('index.handleAnchorClick', () => {
let document: Document;
let scrollIntoViewMock: jest.Mock;
// Helper function to replace .click() and avoid code duplication.
// We dispatch a cancelable event so e.preventDefault() can actually stop the navigation.
const simulateClick = (element: HTMLElement) => {
element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
};
const preventNavigation = (e: Event) => e.preventDefault();
beforeAll(() => {
// JSDOM intentionally does not support full-page navigation.
// When our logic correctly ignores an external link, the click event bubbles up
// and triggers the native browser navigation, causing a "Not implemented: navigation" error.
// This global listener stops the native navigation attempt for the entire test suite.
window.addEventListener('click', preventNavigation);
});
afterAll(() => {
window.removeEventListener('click', preventNavigation);
});
beforeEach(() => {
document = window.document;
document.body.innerHTML = `
Section A
Section B
Go to A
Go to B
External
`;
scrollIntoViewMock = jest.fn();
Element.prototype.scrollIntoView = scrollIntoViewMock;
document.addEventListener('click', handleAnchorClick, true);
});
afterEach(() => {
document.removeEventListener('click', handleAnchorClick, true);
jest.clearAllMocks();
});
test('scrollIntoView is called even when the same link is clicked twice', () => {
const linkA = document.getElementById('link-a')!;
simulateClick(linkA);
expect(scrollIntoViewMock).toHaveBeenCalledTimes(1);
simulateClick(linkA);
expect(scrollIntoViewMock).toHaveBeenCalledTimes(2);
});
test('scrollIntoView is called for each click when different links are clicked alternately', () => {
simulateClick(document.getElementById('link-a')!);
simulateClick(document.getElementById('link-b')!);
simulateClick(document.getElementById('link-a')!);
expect(scrollIntoViewMock).toHaveBeenCalledTimes(3);
});
test('does not intercept external links (http://...#hash)', () => {
const linkExt = document.getElementById('link-ext')!;
simulateClick(linkExt);
expect(scrollIntoViewMock).not.toHaveBeenCalled();
});
test('works with URL-encoded Japanese anchors', () => {
document.body.innerHTML += `
日本語セクション
日本語リンク
`;
simulateClick(document.getElementById('link-ja')!);
expect(scrollIntoViewMock).toHaveBeenCalledTimes(1);
});
test('does not throw when clicking a link to a missing anchor', () => {
document.body.innerHTML += 'dead link';
expect(() => {
simulateClick(document.getElementById('link-dead')!);
}).not.toThrow();
});
});