/** @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(); }); });