import { QueryClient } from '@tanstack/react-query' import { fireEvent, screen, waitFor } from '@testing-library/react' import { Route, Routes, useLocation } from 'react-router-dom' import { beforeEach, describe, expect, it, vi } from 'vitest' import PrefetchLink from '@/components/PrefetchLink' import * as prefetchers from '@/lib/prefetchers' import HelpDetailPage from '@/pages/help/HelpDetailPage' import HelpDiffPage from '@/pages/help/HelpDiffPage' import { helpFixture } from '@/test/helpFixtures' import { renderWithProviders } from '@/test/render' const settings = vi.hoisted (() => ({ linkPreload: 'intent' })) vi.mock ('@/lib/useClientBehaviourSettings', () => ({ useClientBehaviourSettings: () => settings })) vi.mock ('@/lib/help', async () => (await import ('@/test/helpFixtures')).helpFixture.api) const Location = () => { const location = useLocation () return {location.pathname} } describe ('Help prefetch integration', () => { beforeEach (() => { helpFixture.reset () settings.linkPreload = 'intent' vi.clearAllMocks () }) it.each (['/help', '/help?title=ホーム&text=検索&page=2', '/help/guide', '/help/changes', '/help/guide/diff?from=2026-10-01&to=2026-10-02.10', '/help/%E8%A8%AD%E5%AE%9A%20%E6%89%8B%E9%A0%86?edition=2026-10-03']) ( 'registers a unique prefetcher for %s', path => { const url = new URL (path, window.location.origin) expect (prefetchers.routePrefetchers.filter (entry => entry.test (url))).toHaveLength (1) }) it ('loads and reuses the same MDX module for repeated prefetch and display', async () => { const { queryClient } = renderWithProviders ( <> Help }/> ) const loader = helpFixture.moduleLoads['../../help/guide/editions/ja/2026-10-02.10.mdx'] await Promise.all ([ prefetchers.prefetchForURL (queryClient, '/help/guide'), prefetchers.prefetchForURL (queryClient, '/help/guide')]) expect (loader).toHaveBeenCalledTimes (1) fireEvent.click (screen.getByRole ('link', { name: 'Help' })) await screen.findByRole ('heading', { name: 'ホーム第十版' }) expect (loader).toHaveBeenCalledTimes (1) }) it ('prefetches the requested identifier and edition independently', async () => { const client = new QueryClient () await prefetchers.prefetchForURL (client, '/help/guide?edition=2026-10-02.3') await prefetchers.prefetchForURL (client, `/help/${encodeURIComponent ('設定 手順')}`) expect (helpFixture.modules['../../help/guide/editions/ja/2026-10-02.3.mdx']) .toHaveBeenCalledTimes (1) expect (helpFixture.modules['../../help/設定 手順/editions/ja/2026-10-03.mdx']) .toHaveBeenCalledTimes (1) expect (helpFixture.modules['../../help/guide/editions/en/2026-10-02.mdx']) .not.toHaveBeenCalled () }) it ('loads history editions without treating changes as an article', async () => { const article = vi.spyOn (helpFixture.api, 'getHelpArticle') await prefetchers.prefetchForURL (new QueryClient (), '/help/changes') expect (article).not.toHaveBeenCalledWith ('changes') expect (helpFixture.loads.changes).toHaveBeenCalledTimes (1) Object.values (helpFixture.raw).forEach (load => expect (load).not.toHaveBeenCalled ()) Object.values (helpFixture.modules).forEach (load => expect (load).not.toHaveBeenCalled ()) }) it ('reuses two prefetched raw editions when displaying their diff', async () => { const path = '/help/guide/diff?from=2026-10-01&to=2026-10-02.10' const { queryClient } = renderWithProviders ( <> diff }/> ) await Promise.all ([prefetchers.prefetchForURL (queryClient, path), prefetchers.prefetchForURL (queryClient, path)]) fireEvent.click (screen.getByRole ('link', { name: 'diff' })) expect (await screen.findByRole ('link', { name: '旧ホーム (2026-10-01)' })) .toHaveAttribute ('href', '/ja/help/guide?edition=2026-10-01') expect (screen.getByText ('+ # ホーム第十版')).toBeInTheDocument () Object.entries (helpFixture.raw).forEach (([name, load]) => { const expected = /\/ja\/(2026-10-01|2026-10-02\.10)\.mdx$/.test (name) ? 1 : 0 expect (load).toHaveBeenCalledTimes (expected) }) Object.values (helpFixture.modules).forEach (load => expect (load).not.toHaveBeenCalled ()) }) it.each (['intent', 'off']) ('respects %s intent settings and still prefetches clicks', async mode => { settings.linkPreload = mode const run = vi.spyOn (prefetchers, 'prefetchForURL').mockResolvedValue () renderWithProviders (<>Help) const link = screen.getByRole ('link', { name: 'Help' }) fireEvent.mouseEnter (link) fireEvent.touchStart (link) expect (run).toHaveBeenCalledTimes (mode === 'intent' ? 2 : 0) fireEvent.click (link) await waitFor (() => expect (screen.getByTestId ('location')) .toHaveTextContent ('/help/guide')) expect (run).toHaveBeenCalledTimes (mode === 'intent' ? 3 : 1) }) it ('continues navigation after prefetch failure', async () => { vi.spyOn (console, 'error').mockImplementation (() => {}) vi.spyOn (prefetchers, 'prefetchForURL').mockRejectedValue (new Error ('offline')) renderWithProviders (<>Help) fireEvent.click (screen.getByRole ('link', { name: 'Help' })) await waitFor (() => expect (screen.getByTestId ('location')) .toHaveTextContent ('/help/guide')) }) })