このコミットが含まれているのは:
2026-10-12 03:45:11 +09:00
コミット 4164c3c3f7
29個のファイルの変更、1014行の追加、382行の削除
+56
ファイルの表示
@@ -0,0 +1,56 @@
import { describe, expect, it } from 'vitest'
import { createHelpFixture } from '@/test/helpFixtures'
describe ('Help file catalogue', () => {
it ('uses current_edition rather than the newest file, without YAML titles', async () => {
const { api } = createHelpFixture ()
expect (api.getHelpArticle ('guide')?.languages.ja.currentEdition).toBe ('2026-10-02')
expect (await api.getHelpSearchEntries ('ja')).toEqual ([
{ identifier: 'guide', edition: '2026-10-02',
title: 'ホーム', body: '\nタグを検索する' },
{ identifier: '設定 手順', edition: '2026-10-03',
title: '設定', body: '\nタグを設定する' }])
})
it ('orders same-day editions numerically and separates languages', () => {
const { api } = createHelpFixture ()
expect (api.getHelpEditions ('guide', 'ja')).toEqual ([
'2026-10-01', '2026-10-02', '2026-10-02.2', '2026-10-02.3', '2026-10-02.10'])
expect (api.getHelpEditions ('guide', 'en')).toEqual (['2026-10-02'])
})
it ('derives each edition title from its first H1', async () => {
const { api, raw } = createHelpFixture ()
expect (await api.getHelpEditionText ('guide', 'ja', '2026-10-01'))
.toEqual ({ title: '旧ホーム', body: '\n過去限定本文' })
raw['../../help/guide/editions/ja/2026-10-02.mdx']
.mockResolvedValue ('\uFEFF# 改題\r\n\r\n本文\r\n# 別見出し')
expect (await api.getHelpEditionText ('guide', 'ja', '2026-10-02'))
.toEqual ({ title: '改題', body: '\r\n本文\r\n# 別見出し' })
})
it ('returns missing resources without substituting another edition', async () => {
const { api } = createHelpFixture ()
expect (api.getHelpArticle ('absent')).toBeNull ()
expect (api.getHelpEditions ('absent', 'ja')).toEqual ([])
expect (await api.getHelpEdition ('guide', 'ja', 'missing')).toBeNull ()
expect (await api.getHelpEditionText ('guide', 'fr', '2026-10-02')).toBeNull ()
})
it ('rejects a missing current file instead of silently hiding an article', async () => {
const { api, raw } = createHelpFixture ()
delete raw['../../help/guide/editions/ja/2026-10-02.mdx']
await expect (api.getHelpSearchEntries ('ja')).rejects.toThrow ()
})
it ('reports raw loading failures and missing leading H1', async () => {
const { api, raw } = createHelpFixture ()
const loader = raw['../../help/guide/editions/ja/2026-10-02.mdx']
loader.mockRejectedValueOnce (new Error ('fixture failure'))
await expect (api.getHelpSearchEntries ('ja')).rejects.toThrow ('fixture failure')
loader.mockResolvedValueOnce ('本文\n# 遅い見出し')
await expect (api.getHelpEditionText ('guide', 'ja', '2026-10-02')).rejects.toThrow ()
})
})
+94
ファイルの表示
@@ -0,0 +1,94 @@
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 { 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 <output data-testid="location">{location.pathname}</output>
}
describe ('Help prefetch integration', () => {
beforeEach (() => {
settings.linkPreload = 'intent'
vi.clearAllMocks ()
})
it.each (['/help', '/help/guide', '/help/changes',
'/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 client = new QueryClient ()
const loader = helpFixture.modules['../../help/guide/editions/ja/2026-10-02.mdx']
await Promise.all ([
prefetchers.prefetchForURL (client, '/help/guide'),
prefetchers.prefetchForURL (client, '/help/guide')])
expect (loader).toHaveBeenCalledTimes (1)
renderWithProviders (
<Routes><Route path="/help/:identifier" element={<HelpDetailPage/>}/></Routes>,
{ route: '/help/guide' })
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.raw['../../help/guide/editions/ja/2026-10-01.mdx'])
.toHaveBeenCalled ()
})
it.each (['intent', 'click', 'off']) ('respects %s intent settings and still prefetches clicks',
async mode => {
settings.linkPreload = mode
const run = vi.spyOn (prefetchers, 'prefetchForURL').mockResolvedValue ()
renderWithProviders (<><Location/><PrefetchLink to="/help/guide">Help</PrefetchLink></>)
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 (<><Location/><PrefetchLink to="/help/guide">Help</PrefetchLink></>)
fireEvent.click (screen.getByRole ('link', { name: 'Help' }))
await waitFor (() => expect (screen.getByTestId ('location'))
.toHaveTextContent ('/help/guide'))
})
})
+24 -17
ファイルの表示
@@ -2,9 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { fetchWikiPage, fetchWikiPageByTitle, fetchWikiPages } from '@/lib/wiki'
const api = vi.hoisted (() => ({
apiGet: vi.fn (),
}))
const api = vi.hoisted (() => ({ apiGet: vi.fn () }))
vi.mock ('@/lib/api', () => api)
@@ -21,28 +19,37 @@ describe ('wiki API functions', () => {
await fetchWikiPage ('1', { version: '3' })
expect (api.apiGet).toHaveBeenNthCalledWith (
1,
'/wiki',
{ params: { title: '虹' } },
)
1,
'/wiki',
{ params: { title: '虹' } })
expect (api.apiGet).toHaveBeenNthCalledWith (
2,
'/wiki/1',
{ params: { version: '3' } },
)
2,
'/wiki/1',
{ params: { version: '3' } })
})
it ('encodes title path segments and returns null on misses', async () => {
api.apiGet.mockResolvedValueOnce ({ id: 2 })
api.apiGet.mockRejectedValueOnce (new Error ('missing'))
api.apiGet.mockRejectedValueOnce ({ isAxiosError: true, response: { status: 404 } })
await fetchWikiPageByTitle ('a/b c', { version: undefined })
await expect (fetchWikiPageByTitle ('missing', {})).resolves.toBeNull ()
await expect (fetchWikiPageByTitle ('missing', { })).resolves.toBeNull ()
expect (api.apiGet).toHaveBeenNthCalledWith (
1,
'/wiki/title/a%2Fb%20c',
{ params: { version: undefined } },
)
1,
'/wiki/title/a%2Fb%20c',
{ params: { version: undefined } })
})
it.each ([403, 500]) ('preserves HTTP %s errors', async status => {
const error = { isAxiosError: true, response: { status } }
api.apiGet.mockRejectedValueOnce (error)
await expect (fetchWikiPageByTitle ('ホーム', { })).rejects.toBe (error)
})
it ('preserves network failures for callers', async () => {
const error = new Error ('network failure')
api.apiGet.mockRejectedValueOnce (error)
await expect (fetchWikiPageByTitle ('ホーム', { })).rejects.toBe (error)
})
})