文件
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

90 行
3.1 KiB
TypeScript

/** @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 = `
<h2 id="section-a">Section A</h2>
<h2 id="section-b">Section B</h2>
<a id="link-a" href="#section-a">Go to A</a>
<a id="link-b" href="#section-b">Go to B</a>
<a id="link-ext" href="https://example.com/page#section">External</a>
`;
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 += `
<h2 id="セクション">日本語セクション</h2>
<a id="link-ja" href="#%E3%82%BB%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3">日本語リンク</a>
`;
simulateClick(document.getElementById('link-ja')!);
expect(scrollIntoViewMock).toHaveBeenCalledTimes(1);
});
test('does not throw when clicking a link to a missing anchor', () => {
document.body.innerHTML += '<a id="link-dead" href="#missing-section">dead link</a>';
expect(() => {
simulateClick(document.getElementById('link-dead')!);
}).not.toThrow();
});
});