このコミットが含まれているのは:
@@ -59,6 +59,7 @@ export const buildUser = (overrides: Partial<User> = {}): User => ({
|
||||
})
|
||||
|
||||
export const buildWikiPage = (overrides: Partial<WikiPage> = {}): WikiPage => ({
|
||||
resourceType: 'tag_description',
|
||||
id: 1,
|
||||
title: 'テストWiki',
|
||||
deprecatedAt: null,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createElement } from 'react'
|
||||
import { ModuleKind, ScriptTarget, transpileModule } from 'typescript'
|
||||
import { vi } from 'vitest'
|
||||
import { parse } from 'yaml'
|
||||
|
||||
import source from '@/lib/help.ts?raw'
|
||||
|
||||
@@ -14,51 +13,113 @@ export const editionRows = [
|
||||
['guide', 'ja', '2026-10-02', 'ホーム', 'タグを検索する'],
|
||||
['guide', 'ja', '2026-10-02.2', 'ホーム第二版', '第二版本文'],
|
||||
['guide', 'ja', '2026-10-02.3', 'ホーム第三版', '第三版本文'],
|
||||
['guide', 'ja', '2026-10-02.10', 'ホーム第十版', '第十版本文'],
|
||||
['guide', 'ja', '2026-10-02.10', 'ホーム第十版', 'タグを検索する 第十版本文'],
|
||||
['guide', 'ja', '2026-10-02.11', '未公開ホーム', '未公開限定本文'],
|
||||
['guide', 'en', '2026-10-02', 'Home', 'English body'],
|
||||
['設定 手順', 'ja', '2026-10-03', '設定', 'タグを設定する']] as const
|
||||
|
||||
const compiled = transpileModule (source.replaceAll ('import.meta', 'helpMeta'), {
|
||||
const compiled = transpileModule (source, {
|
||||
compilerOptions: { module: ModuleKind.CommonJS, target: ScriptTarget.ES2022 } }).outputText
|
||||
|
||||
|
||||
// Replace Vite's file-discovery boundary, executing the actual Help module.
|
||||
// Supply generated virtual modules, executing the actual runtime Help module.
|
||||
export const createHelpFixture = () => {
|
||||
const articles: Record<string, string> = {
|
||||
'../../help/guide/article.yml':
|
||||
'identifier: guide\nlanguages:\n ja:\n current_edition: 2026-10-02\n'
|
||||
+ ' en:\n current_edition: 2026-10-02\n',
|
||||
'../../help/設定 手順/article.yml':
|
||||
'identifier: 設定 手順\nlanguages:\n ja:\n current_edition: 2026-10-03\n' }
|
||||
const articles: Record<string, Help.ArticleMeta> = {
|
||||
guide: { identifier: 'guide', canonicalLanguage: 'ja', languages: {
|
||||
ja: { currentEdition: '2026-10-02.10', publishedEditions: [
|
||||
'2026-10-01', '2026-10-02', '2026-10-02.2', '2026-10-02.3', '2026-10-02.10'] },
|
||||
en: { currentEdition: '2026-10-02', publishedEditions: ['2026-10-02'] } } },
|
||||
'設定 手順': { identifier: '設定 手順', canonicalLanguage: 'ja', languages: {
|
||||
ja: { currentEdition: '2026-10-03', publishedEditions: ['2026-10-03'] } } } }
|
||||
const published = editionRows.filter (([id, language, edition]) =>
|
||||
articles[id].languages[language].publishedEditions.includes (edition))
|
||||
const raw = Object.fromEntries (editionRows.map (([id, language, edition, title, body]) => [
|
||||
`../../help/${id}/editions/${language}/${edition}.mdx`,
|
||||
vi.fn (async () => `# ${title}\n\n${body}`)]))
|
||||
const moduleLoads = Object.fromEntries (editionRows.map (([id, language, edition, title, body]) => [
|
||||
const moduleLoads = Object.fromEntries (published.map (([id, language, edition, title, body]) => [
|
||||
`../../help/${id}/editions/${language}/${edition}.mdx`,
|
||||
vi.fn (async (): Promise<{ default: () => ReactElement }> => ({
|
||||
default: () => createElement ('div', null,
|
||||
createElement ('h1', null, title), createElement ('p', null, body)) }))]))
|
||||
// Dynamic imports reuse a module namespace even when called more than once.
|
||||
const caches: Record<string, { promise?: Promise<{ default: () => ReactElement }> }> = { }
|
||||
const modules = Object.fromEntries (Object.entries (moduleLoads).map (([path, load]) => {
|
||||
const cache: { promise?: ReturnType<typeof load> } = { }
|
||||
return [path, vi.fn (() => cache.promise ??= load ())]
|
||||
caches[path] = { }
|
||||
return [path, vi.fn (() => caches[path].promise ??= load ())]
|
||||
}))
|
||||
const glob = (pattern: string, options?: { query?: string }) => {
|
||||
if (pattern.endsWith ('article.yml'))
|
||||
return articles
|
||||
if (pattern.endsWith ('*.mdx'))
|
||||
return options?.query === '?raw' ? raw : modules
|
||||
throw new Error (`Unexpected Help glob: ${pattern}`)
|
||||
}
|
||||
const texts = Object.fromEntries (published.map (([id, language, edition, title, body]) => [
|
||||
`${ id }/${ language }/${ edition }`,
|
||||
vi.fn (async () => ({ default: { title, body: `\n${ body }` } }))]))
|
||||
const components = Object.fromEntries (published.map (([id, language, edition]) => [
|
||||
`${ id }/${ language }/${ edition }`,
|
||||
modules[`../../help/${ id }/editions/${ language }/${ edition }.mdx`]]))
|
||||
const sources = Object.fromEntries (published.map (([id, language, edition]) => [
|
||||
`${ id }/${ language }/${ edition }`,
|
||||
async () => ({ default: await raw[
|
||||
`../../help/${ id }/editions/${ language }/${ edition }.mdx`] () })]))
|
||||
const search: Record<string, Help.HelpSearchEntry[]> = { }
|
||||
const changes: Record<string, Help.HelpChange[]> = { }
|
||||
const titles: Record<string, Help.HelpTitleIndex> = { }
|
||||
published.forEach (([id, language, edition, title, body]) => {
|
||||
const editions = articles[id].languages[language].publishedEditions
|
||||
const index = editions.indexOf (edition)
|
||||
const searchRows = search[language] ??= []
|
||||
if (articles[id].languages[language].currentEdition === edition)
|
||||
searchRows.push ({ identifier: id, edition, title, body: `\n${ body }` })
|
||||
const rows = changes[language] ??= []
|
||||
rows.push ({ identifier: id, edition, title,
|
||||
pred: editions[index - 1] ?? null,
|
||||
change: index === 0 ? 'new' : 'update', date: edition.split ('.')[0] })
|
||||
const lookup = (titles[language] ??= { })
|
||||
const identifiers = lookup[title.trim ().normalize ('NFKC').toLowerCase ()] ??= []
|
||||
if (!(identifiers.includes (id)))
|
||||
identifiers.push (id)
|
||||
})
|
||||
Object.values (changes).forEach (rows => rows.sort ((a, b) => {
|
||||
const [dateA, revisionA = '1'] = a.edition.split ('.')
|
||||
const [dateB, revisionB = '1'] = b.edition.split ('.')
|
||||
return dateB.localeCompare (dateA) || Number (revisionB) - Number (revisionA)
|
||||
|| a.identifier.localeCompare (b.identifier)
|
||||
}))
|
||||
const assets = {
|
||||
articles: { default: articles }, search: { default: search },
|
||||
changes: { default: changes }, titles: { default: titles } }
|
||||
const snapshot = JSON.stringify (assets)
|
||||
const loaders = { components, sources, texts }
|
||||
const loads = {
|
||||
articles: vi.fn (() => assets.articles), search: vi.fn (() => assets.search),
|
||||
changes: vi.fn (() => assets.changes), titles: vi.fn (() => assets.titles),
|
||||
loaders: vi.fn (() => loaders) }
|
||||
const api = { } as typeof Help
|
||||
const requireFixture = (name: string) => {
|
||||
if (name === 'yaml')
|
||||
return { parse }
|
||||
const key = name.replace ('virtual:help/', '') as keyof typeof loads
|
||||
if (Object.hasOwn (loads, key))
|
||||
return loads[key] ()
|
||||
throw new Error (`Unexpected Help dependency: ${name}`)
|
||||
}
|
||||
new Function ('require', 'exports', 'helpMeta', compiled) (
|
||||
requireFixture, api, { glob })
|
||||
return { api, articles, raw, modules, moduleLoads }
|
||||
new Function ('require', 'exports', compiled) (requireFixture, api)
|
||||
const reset = () => {
|
||||
Object.assign (assets, JSON.parse (snapshot))
|
||||
editionRows.forEach (([id, language, edition, title, body]) => {
|
||||
const path = `../../help/${ id }/editions/${ language }/${ edition }.mdx`
|
||||
raw[path].mockReset ().mockResolvedValue (`# ${ title }\n\n${ body }`)
|
||||
})
|
||||
published.forEach (([id, language, edition, title, body]) => {
|
||||
const path = `../../help/${ id }/editions/${ language }/${ edition }.mdx`
|
||||
caches[path].promise = undefined
|
||||
moduleLoads[path].mockClear ()
|
||||
modules[path].mockReset ().mockImplementation (() =>
|
||||
caches[path].promise ??= moduleLoads[path] ())
|
||||
texts[`${ id }/${ language }/${ edition }`].mockReset ().mockResolvedValue ({
|
||||
default: { title, body: `\n${ body }` } })
|
||||
})
|
||||
loads.articles.mockReset ().mockImplementation (() => assets.articles)
|
||||
loads.search.mockReset ().mockImplementation (() => assets.search)
|
||||
loads.changes.mockReset ().mockImplementation (() => assets.changes)
|
||||
loads.titles.mockReset ().mockImplementation (() => assets.titles)
|
||||
loads.loaders.mockReset ().mockImplementation (() => loaders)
|
||||
}
|
||||
return { api, assets, loads, raw, modules, moduleLoads, texts, reset }
|
||||
}
|
||||
|
||||
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする