画面上でのテストは完了. 移行にす早さが求められるので,準備ができたらマージする. Reviewed-on: #430 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #430 でマージされました。
このコミットが含まれているのは:
@@ -1,14 +1,19 @@
|
||||
import { screen } from '@testing-library/react'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||
import { Route, Routes, useLocation } from 'react-router-dom'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { apiPost } from '@/lib/api'
|
||||
import { getHelpTitleIndex } from '@/lib/help'
|
||||
import { tagsKeys, wikiKeys } from '@/lib/queryKeys'
|
||||
import WikiDetailPage from '@/pages/wiki/WikiDetailPage'
|
||||
import { buildTag, buildWikiPage } from '@/test/factories'
|
||||
import WikiNewPage from '@/pages/wiki/WikiNewPage'
|
||||
import { buildTag, buildUser, buildWikiPage } from '@/test/factories'
|
||||
import { renderWithProviders } from '@/test/render'
|
||||
|
||||
const wikiApi = vi.hoisted (() => ({
|
||||
fetchWikiPage: vi.fn (),
|
||||
fetchWikiPageByTitle: vi.fn (),
|
||||
fetchWikiPages: vi.fn (),
|
||||
}))
|
||||
|
||||
const tagsApi = vi.hoisted (() => ({
|
||||
@@ -22,8 +27,140 @@ const postsApi = vi.hoisted (() => ({
|
||||
vi.mock ('@/lib/wiki', () => wikiApi)
|
||||
vi.mock ('@/lib/tags', () => tagsApi)
|
||||
vi.mock ('@/lib/posts', () => postsApi)
|
||||
vi.mock ('@/lib/help', () => ({ getHelpTitleIndex: vi.fn () }))
|
||||
vi.mock ('@/lib/api', async importOriginal => ({
|
||||
...await importOriginal<typeof import ('@/lib/api')> (), apiPost: vi.fn () }))
|
||||
vi.mock ('@/components/ui/use-toast', () => ({ toast: vi.fn () }))
|
||||
vi.mock ('react-markdown-editor-lite', () => ({
|
||||
default: ({ value, onChange }: {
|
||||
value: string
|
||||
onChange: (event: { text: string }) => void }) => (
|
||||
<textarea aria-label="本文エディタ" value={value}
|
||||
onChange={event => onChange ({ text: event.target.value })}/>) }))
|
||||
|
||||
|
||||
const Location = () => {
|
||||
const location = useLocation ()
|
||||
return <output data-testid="location">{location.pathname}{location.search}</output>
|
||||
}
|
||||
|
||||
|
||||
describe ('WikiDetailPage', () => {
|
||||
beforeEach (() => {
|
||||
wikiApi.fetchWikiPageByTitle.mockReset ().mockResolvedValue (null)
|
||||
wikiApi.fetchWikiPages.mockReset ().mockResolvedValue ([])
|
||||
tagsApi.fetchTagByName.mockReset ().mockResolvedValue (null)
|
||||
postsApi.fetchPosts.mockReset ().mockResolvedValue ({ posts: [], count: 0 })
|
||||
vi.mocked (getHelpTitleIndex).mockReset ().mockResolvedValue ({ })
|
||||
vi.mocked (apiPost).mockReset ()
|
||||
})
|
||||
|
||||
it ('offers creation for an existing tag after the Help lookup completes', async () => {
|
||||
let finish!: (index: Record<string, string[]>) => void
|
||||
const pending = new Promise<Record<string, string[]>> (resolve => {
|
||||
finish = resolve
|
||||
})
|
||||
vi.mocked (getHelpTitleIndex).mockReturnValueOnce (pending)
|
||||
tagsApi.fetchTagByName.mockResolvedValue (buildTag ({ name: '既存タグ', hasWiki: false }))
|
||||
renderWithProviders (
|
||||
<Routes><Route path="/wiki/:title" element={<WikiDetailPage/>}/></Routes>,
|
||||
{ route: `/wiki/${ encodeURIComponent ('既存タグ') }` })
|
||||
await waitFor (() => expect (getHelpTitleIndex).toHaveBeenCalled ())
|
||||
expect (screen.queryByText ('404')).not.toBeInTheDocument ()
|
||||
expect (screen.queryByRole ('link', { name: '新規作成してください' }))
|
||||
.not.toBeInTheDocument ()
|
||||
await act (async () => finish ({ }))
|
||||
expect (await screen.findByRole ('link', { name: '新規作成してください' }))
|
||||
.toHaveAttribute ('href', `/wiki/new?title=${ encodeURIComponent ('既存タグ') }`)
|
||||
})
|
||||
|
||||
it ('creates a tag description through the existing form and displays its content', async () => {
|
||||
const page = buildWikiPage ({ title: '既存タグ', body: '説明文',
|
||||
resourceType: 'tag_description' })
|
||||
tagsApi.fetchTagByName.mockResolvedValue (buildTag ({ name: page.title, hasWiki: false }))
|
||||
vi.mocked (apiPost).mockResolvedValueOnce (page)
|
||||
const path = `/wiki/${ encodeURIComponent (page.title) }`
|
||||
const { container, queryClient } = renderWithProviders (
|
||||
<>
|
||||
<Location/>
|
||||
<Routes>
|
||||
<Route path="/wiki/new" element={<WikiNewPage user={buildUser ({ role: 'member' })}/>}/>
|
||||
<Route path="/wiki/:title" element={<WikiDetailPage/>}/>
|
||||
</Routes>
|
||||
</>, { route: path })
|
||||
fireEvent.click (await screen.findByRole ('link', { name: '新規作成してください' }))
|
||||
await screen.findByRole ('heading', { name: '新規 Wiki ページ' })
|
||||
expect (queryClient.getQueryData (wikiKeys.show (page.title, { }))).toBeNull ()
|
||||
queryClient.setQueryData (wikiKeys.index ({ }), [])
|
||||
wikiApi.fetchWikiPageByTitle.mockResolvedValue (page)
|
||||
wikiApi.fetchWikiPages.mockResolvedValue ([page])
|
||||
tagsApi.fetchTagByName.mockResolvedValue (buildTag ({ name: page.title, hasWiki: true }))
|
||||
expect (screen.getAllByRole ('textbox')[0]).toHaveValue (page.title)
|
||||
fireEvent.change (screen.getByRole ('textbox', { name: '本文エディタ' }), {
|
||||
target: { value: page.body } })
|
||||
fireEvent.click (screen.getByRole ('button', { name: '追加' }))
|
||||
await screen.findByRole ('heading', { name: page.title })
|
||||
expect (screen.getByTestId ('location')).toHaveTextContent (path)
|
||||
expect (screen.queryByRole ('textbox', { name: '本文エディタ' })).not.toBeInTheDocument ()
|
||||
expect (within (container.querySelector ('article')!).getByText (page.body))
|
||||
.toBeInTheDocument ()
|
||||
const form = vi.mocked (apiPost).mock.calls[0][1] as FormData
|
||||
expect (form.get ('title')).toBe (page.title)
|
||||
expect (form.get ('body')).toBe (page.body)
|
||||
expect (apiPost).toHaveBeenCalledWith ('/wiki', form,
|
||||
{ headers: { 'Content-Type': 'multipart/form-data' } })
|
||||
expect (screen.queryByText ('404')).not.toBeInTheDocument ()
|
||||
expect (queryClient.getQueryData (wikiKeys.show (page.title, { }))).toEqual (page)
|
||||
await waitFor (() => expect (queryClient.getQueryData (wikiKeys.index ({ })))
|
||||
.toEqual ([page]))
|
||||
await waitFor (() => expect (queryClient.getQueryData (tagsKeys.show (page.title)))
|
||||
.toMatchObject ({ hasWiki: true }))
|
||||
expect (getHelpTitleIndex).toHaveBeenCalledTimes (1)
|
||||
})
|
||||
|
||||
it ('keeps failed saves on the form without changing cached Wiki or tag state', async () => {
|
||||
const title = '既存タグ'
|
||||
const tag = buildTag ({ name: title, hasWiki: false })
|
||||
const path = `/wiki/new?title=${ encodeURIComponent (title) }`
|
||||
vi.mocked (apiPost).mockRejectedValueOnce (new Error ('save failed'))
|
||||
const { queryClient } = renderWithProviders (
|
||||
<><Location/><WikiNewPage user={buildUser ({ role: 'member' })}/></>, { route: path })
|
||||
queryClient.setQueryData (wikiKeys.show (title, { }), null)
|
||||
queryClient.setQueryData (wikiKeys.index ({ }), [])
|
||||
queryClient.setQueryData (tagsKeys.show (title), tag)
|
||||
fireEvent.change (screen.getByRole ('textbox', { name: '本文エディタ' }), {
|
||||
target: { value: '保存しない本文' } })
|
||||
await act (async () => fireEvent.click (screen.getByRole ('button', { name: '追加' })))
|
||||
expect (apiPost).toHaveBeenCalledTimes (1)
|
||||
expect (screen.getByTestId ('location')).toHaveTextContent (path)
|
||||
expect (screen.getByRole ('heading', { name: '新規 Wiki ページ' })).toBeInTheDocument ()
|
||||
expect (queryClient.getQueryData (wikiKeys.show (title, { }))).toBeNull ()
|
||||
expect (queryClient.getQueryData (tagsKeys.show (title))).toEqual (tag)
|
||||
expect (queryClient.getQueryState (wikiKeys.index ({ }))?.isInvalidated).toBe (false)
|
||||
})
|
||||
|
||||
it ('shows NotFound when neither a tag nor a Help title exists', async () => {
|
||||
renderWithProviders (
|
||||
<Routes><Route path="/wiki/:title" element={<WikiDetailPage/>}/></Routes>,
|
||||
{ route: '/wiki/missing' })
|
||||
expect (await screen.findByText ('404')).toBeInTheDocument ()
|
||||
expect (screen.queryByRole ('link', { name: '新規作成してください' }))
|
||||
.not.toBeInTheDocument ()
|
||||
})
|
||||
|
||||
it.each (['403', '500', 'offline']) ('keeps Wiki error %s distinct from missing content',
|
||||
async message => {
|
||||
wikiApi.fetchWikiPageByTitle.mockRejectedValue (new Error (message))
|
||||
tagsApi.fetchTagByName.mockResolvedValue (buildTag ({ name: '既存タグ' }))
|
||||
renderWithProviders (
|
||||
<Routes><Route path="/wiki/:title" element={<WikiDetailPage/>}/></Routes>,
|
||||
{ route: `/wiki/${ encodeURIComponent ('既存タグ') }` })
|
||||
expect (await screen.findByRole ('alert')).toHaveTextContent (message)
|
||||
expect (getHelpTitleIndex).not.toHaveBeenCalled ()
|
||||
expect (screen.queryByRole ('link', { name: '新規作成してください' }))
|
||||
.not.toBeInTheDocument ()
|
||||
})
|
||||
|
||||
it ('renders deprecated state outside the wiki title link', async () => {
|
||||
wikiApi.fetchWikiPageByTitle.mockResolvedValueOnce (buildWikiPage ({
|
||||
title: '旧タグ',
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする