**本番 DB のバックアップをかならずすること** Reviewed-on: #420 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #420 でマージされました。
このコミットが含まれているのは:
@@ -0,0 +1,63 @@
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import PostHistoryPage from '@/pages/posts/PostHistoryPage'
|
||||
import { buildTag } from '@/test/factories'
|
||||
import { renderWithProviders } from '@/test/render'
|
||||
|
||||
const postsApi = vi.hoisted (() => ({
|
||||
fetchPostChanges: vi.fn (),
|
||||
updatePost: vi.fn (),
|
||||
}))
|
||||
|
||||
const tagsApi = vi.hoisted (() => ({
|
||||
fetchTag: vi.fn (),
|
||||
fetchExternalTag: vi.fn (),
|
||||
}))
|
||||
|
||||
vi.mock ('@/lib/posts', () => postsApi)
|
||||
vi.mock ('@/lib/tags', () => tagsApi)
|
||||
|
||||
describe ('PostHistoryPage', () => {
|
||||
beforeEach (() => {
|
||||
vi.clearAllMocks ()
|
||||
|
||||
postsApi.fetchPostChanges.mockResolvedValue ({
|
||||
versions: [],
|
||||
count: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it ('shows the external tag name when filtering by external_tag', async () => {
|
||||
const external = buildTag ({
|
||||
id: 7,
|
||||
name: 'nico:external_history',
|
||||
category: 'nico',
|
||||
})
|
||||
|
||||
tagsApi.fetchExternalTag.mockResolvedValue (external)
|
||||
|
||||
renderWithProviders (
|
||||
<PostHistoryPage/>,
|
||||
{ route: '/posts/changes?external_tag=7' },
|
||||
)
|
||||
|
||||
await waitFor (() => {
|
||||
expect (postsApi.fetchPostChanges).toHaveBeenCalledWith ({
|
||||
externalTag: '7',
|
||||
page: 1,
|
||||
limit: 20,
|
||||
})
|
||||
})
|
||||
|
||||
expect (tagsApi.fetchExternalTag).toHaveBeenCalledWith ('7')
|
||||
expect (tagsApi.fetchTag).not.toHaveBeenCalled ()
|
||||
|
||||
expect (
|
||||
await screen.findByRole ('heading', {
|
||||
level: 1,
|
||||
name: '耕作履歴(nico:external_history)',
|
||||
}),
|
||||
).toBeInTheDocument ()
|
||||
})
|
||||
})
|
||||
@@ -16,7 +16,7 @@ import { clientAnimationTransition,
|
||||
clientScrollBehaviour } from '@/lib/clientAnimation'
|
||||
import { fetchPostChanges, updatePost } from '@/lib/posts'
|
||||
import { postsKeys, tagsKeys } from '@/lib/queryKeys'
|
||||
import { fetchTag } from '@/lib/tags'
|
||||
import { fetchExternalTag, fetchTag } from '@/lib/tags'
|
||||
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
|
||||
import { cn, dateString, originalCreatedAtString } from '@/lib/utils'
|
||||
|
||||
@@ -51,6 +51,7 @@ const PostHistoryPage: FC = () => {
|
||||
const query = new URLSearchParams (location.search)
|
||||
const id = query.get ('id')
|
||||
const tagId = query.get ('tag')
|
||||
const externalTagId = query.get ('external_tag')
|
||||
const page = Number (query.get ('page') ?? 1)
|
||||
const limit = Number (query.get ('limit') ?? 20)
|
||||
|
||||
@@ -63,15 +64,27 @@ const PostHistoryPage: FC = () => {
|
||||
queryKey: tagsKeys.show (tagQueryId),
|
||||
queryFn: () => fetchTag (tagQueryId) })
|
||||
|
||||
const externalTagQueryId = externalTagId ?? ''
|
||||
const { data: externalTag } = useQuery ({
|
||||
enabled: Boolean (externalTagId),
|
||||
queryKey: tagsKeys.externalShow (externalTagQueryId),
|
||||
queryFn: () => fetchExternalTag (externalTagQueryId) })
|
||||
|
||||
const { data, isLoading: loading } = useQuery ({
|
||||
queryKey: postsKeys.changes ({ ...(id && { post: id }),
|
||||
...(tagId && { tag: tagId }),
|
||||
...(externalTagId && { externalTag: externalTagId }),
|
||||
page, limit }),
|
||||
queryFn: () => fetchPostChanges ({ ...(id && { post: id }),
|
||||
...(tagId && { tag: tagId }),
|
||||
...(externalTagId && {
|
||||
externalTag: externalTagId }),
|
||||
page, limit }) })
|
||||
const changes = data?.versions ?? []
|
||||
const totalPages = data ? Math.ceil (data.count / limit) : 0
|
||||
const displayedTag = externalTag ?? tag
|
||||
const pageTitleLabel = `耕作履歴${ id ? `: 投稿 #${ id }` : '' }${
|
||||
displayedTag ? `(${ displayedTag.name })` : '' }`
|
||||
|
||||
const qc = useQueryClient ()
|
||||
|
||||
@@ -134,10 +147,14 @@ const PostHistoryPage: FC = () => {
|
||||
<title>{`耕作履歴 | ${ SITE_TITLE }`}</title>
|
||||
</Helmet>
|
||||
|
||||
<PageTitle>
|
||||
<PageTitle aria-label={pageTitleLabel}>
|
||||
耕作履歴
|
||||
{id && <>: 投稿 {<PrefetchLink to={`/posts/${ id }`}>#{id}</PrefetchLink>}</>}
|
||||
{tag && <>(<TagLink tag={tag} withWiki={false} withCount={false}/>)</>}
|
||||
{displayedTag && (
|
||||
<>(<TagLink
|
||||
tag={displayedTag}
|
||||
withWiki={false}
|
||||
withCount={false}/>)</>)}
|
||||
</PageTitle>
|
||||
|
||||
{loading ? 'Loading...' : (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import TagListPage from '@/pages/tags/TagListPage'
|
||||
@@ -127,4 +127,49 @@ describe ('TagListPage', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it ('keeps colliding internal and external tags on their own routes', async () => {
|
||||
const internal = buildTag ({
|
||||
id: 7,
|
||||
name: 'internal_collision',
|
||||
category: 'general',
|
||||
})
|
||||
const external = buildTag ({
|
||||
id: 7,
|
||||
name: 'nico:external_collision',
|
||||
category: 'nico',
|
||||
})
|
||||
|
||||
tagsApi.fetchTags.mockResolvedValueOnce ({
|
||||
tags: [internal, external],
|
||||
count: 2,
|
||||
})
|
||||
|
||||
renderWithProviders (<TagListPage/>, { route: '/tags' })
|
||||
|
||||
const internalLink =
|
||||
await screen.findByRole ('link', { name: 'internal_collision' })
|
||||
const externalLink =
|
||||
screen.getByRole ('link', { name: 'nico:external_collision' })
|
||||
|
||||
expect (internalLink).toHaveAttribute ('href', '/tags/7')
|
||||
expect (externalLink).toHaveAttribute (
|
||||
'href',
|
||||
'/tags/nico?name=nico%3Aexternal_collision',
|
||||
)
|
||||
|
||||
const internalRow = internalLink.closest ('tr')
|
||||
const externalRow = externalLink.closest ('tr')
|
||||
|
||||
expect (internalRow).not.toBeNull ()
|
||||
expect (externalRow).not.toBeNull ()
|
||||
|
||||
expect (
|
||||
within (internalRow!).getByRole ('link', { name: '耕作履歴' }),
|
||||
).toHaveAttribute ('href', '/posts/changes?tag=7')
|
||||
|
||||
expect (
|
||||
within (externalRow!).getByRole ('link', { name: '耕作履歴' }),
|
||||
).toHaveAttribute ('href', '/posts/changes?external_tag=7')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -406,11 +406,15 @@ const TagListPage: FC = () => {
|
||||
|
||||
<tbody>
|
||||
{results.map (row => (
|
||||
<tr key={row.id} className="even:bg-gray-100 dark:even:bg-gray-700">
|
||||
<tr
|
||||
key={`${ row.category }:${ row.id }`}
|
||||
className="even:bg-gray-100 dark:even:bg-gray-700">
|
||||
<td className="p-2">
|
||||
<TagLink
|
||||
tag={row}
|
||||
to={`/tags/${ encodeURIComponent (row.id) }`}
|
||||
to={row.category === 'nico'
|
||||
? `/tags/nico?name=${ encodeURIComponent (row.name) }`
|
||||
: `/tags/${ encodeURIComponent (row.id) }`}
|
||||
withCount={false}/>
|
||||
</td>
|
||||
<td className="p-2 text-right">{row.postCount}</td>
|
||||
@@ -429,7 +433,10 @@ const TagListPage: FC = () => {
|
||||
<td className="p-2">{dateString (row.createdAt)}</td>
|
||||
<td className="p-2">{dateString (row.updatedAt)}</td>
|
||||
<td className="p-2">
|
||||
<PrefetchLink to={`/posts/changes?tag=${ row.id }`}>
|
||||
<PrefetchLink
|
||||
to={row.category === 'nico'
|
||||
? `/posts/changes?external_tag=${ row.id }`
|
||||
: `/posts/changes?tag=${ row.id }`}>
|
||||
耕作履歴
|
||||
</PrefetchLink>
|
||||
</td>
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする