#419 フロントのテスト追加
このコミットが含まれているのは:
@@ -24,6 +24,7 @@ class NicoTagsController < ApplicationController
|
|||||||
ExternalTag
|
ExternalTag
|
||||||
.joins("LEFT JOIN (#{ post_tag_max_sql }) post_tag_max " \
|
.joins("LEFT JOIN (#{ post_tag_max_sql }) post_tag_max " \
|
||||||
'ON post_tag_max.external_tag_id = external_tags.id')
|
'ON post_tag_max.external_tag_id = external_tags.id')
|
||||||
|
.includes(linked_tags: { tag_name: :wiki_page })
|
||||||
if name
|
if name
|
||||||
q = q.where(('external_tags.name LIKE ? ' +
|
q = q.where(('external_tags.name LIKE ? ' +
|
||||||
"OR CONCAT(external_tags.platform, ':', external_tags.name) LIKE ?"),
|
"OR CONCAT(external_tags.platform, ':', external_tags.name) LIKE ?"),
|
||||||
|
|||||||
@@ -13,11 +13,14 @@ vi.mock ('@dnd-kit/core', () => dndKit)
|
|||||||
|
|
||||||
const tag = buildTag ({ id: 7, name: 'ドラッグ元', postCount: 3 })
|
const tag = buildTag ({ id: 7, name: 'ドラッグ元', postCount: 3 })
|
||||||
|
|
||||||
const renderRow = (activeDndId?: string) => {
|
const renderRow = (
|
||||||
|
activeDndId?: string,
|
||||||
|
renderedTag = tag,
|
||||||
|
) => {
|
||||||
renderWithProviders (
|
renderWithProviders (
|
||||||
<DraggableDroppableTagRow
|
<DraggableDroppableTagRow
|
||||||
activeDndId={activeDndId}
|
activeDndId={activeDndId}
|
||||||
tag={tag}
|
tag={renderedTag}
|
||||||
nestLevel={2}
|
nestLevel={2}
|
||||||
pathKey="cat-general-7"
|
pathKey="cat-general-7"
|
||||||
suppressClickRef={{ current: false }}/>,
|
suppressClickRef={{ current: false }}/>,
|
||||||
@@ -72,4 +75,26 @@ describe ('DraggableDroppableTagRow', () => {
|
|||||||
renderRow ('tag-node:other')
|
renderRow ('tag-node:other')
|
||||||
expect (tagBody ()).toHaveStyle ({ visibility: 'visible' })
|
expect (tagBody ()).toHaveStyle ({ visibility: 'visible' })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it ('disables drag and drop for external tags', () => {
|
||||||
|
const external = buildTag ({
|
||||||
|
id: 7,
|
||||||
|
name: 'nico:external',
|
||||||
|
category: 'nico',
|
||||||
|
})
|
||||||
|
|
||||||
|
renderRow (undefined, external)
|
||||||
|
|
||||||
|
expect (dndKit.useDraggable).toHaveBeenCalledWith (
|
||||||
|
expect.objectContaining ({
|
||||||
|
disabled: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect (dndKit.useDroppable).toHaveBeenCalledWith (
|
||||||
|
expect.objectContaining ({
|
||||||
|
disabled: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { screen } from '@testing-library/react'
|
||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import TagDetailSidebar from '@/components/TagDetailSidebar'
|
||||||
|
import { setClientTagRelationDisplayMode } from '@/lib/settings'
|
||||||
|
import { buildPost, buildTag } from '@/test/factories'
|
||||||
|
import { renderWithProviders } from '@/test/render'
|
||||||
|
|
||||||
|
import type { ReactNode } from 'react'
|
||||||
|
|
||||||
|
vi.mock ('@/components/TagSearch', () => ({
|
||||||
|
default: () => null,
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock ('@/components/DraggableDroppableTagRow', () => ({
|
||||||
|
default: ({ tag }: { tag: { name: string } }) => (
|
||||||
|
<span>{tag.name}</span>
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock ('@dnd-kit/core', () => ({
|
||||||
|
DndContext: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||||
|
DragOverlay: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||||
|
MeasuringStrategy: { Always: 'always' },
|
||||||
|
MouseSensor: vi.fn (),
|
||||||
|
TouchSensor: vi.fn (),
|
||||||
|
pointerWithin: vi.fn (),
|
||||||
|
useDroppable: vi.fn (() => ({
|
||||||
|
setNodeRef: vi.fn (),
|
||||||
|
isOver: false,
|
||||||
|
})),
|
||||||
|
useSensor: vi.fn (() => ({ })),
|
||||||
|
useSensors: vi.fn (() => []),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe ('TagDetailSidebar', () => {
|
||||||
|
beforeEach (() => {
|
||||||
|
localStorage.clear ()
|
||||||
|
vi.clearAllMocks ()
|
||||||
|
})
|
||||||
|
|
||||||
|
it ('keeps internal and external tags with the same numeric id in flat mode', () => {
|
||||||
|
setClientTagRelationDisplayMode ('flat')
|
||||||
|
|
||||||
|
const internal = buildTag ({
|
||||||
|
id: 7,
|
||||||
|
name: 'internal_collision',
|
||||||
|
category: 'general',
|
||||||
|
})
|
||||||
|
const external = buildTag ({
|
||||||
|
id: 7,
|
||||||
|
name: 'nico:external_collision',
|
||||||
|
category: 'nico',
|
||||||
|
})
|
||||||
|
|
||||||
|
renderWithProviders (
|
||||||
|
<TagDetailSidebar
|
||||||
|
post={buildPost ({
|
||||||
|
tags: [internal, external],
|
||||||
|
})}/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect (screen.getByText ('internal_collision')).toBeInTheDocument ()
|
||||||
|
expect (screen.getByText ('nico:external_collision')).toBeInTheDocument ()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -57,4 +57,31 @@ describe ('TagLink', () => {
|
|||||||
expect (screen.getByText ('正式名')).toBeInTheDocument ()
|
expect (screen.getByText ('正式名')).toBeInTheDocument ()
|
||||||
expect (screen.queryByRole ('link')).not.toBeInTheDocument ()
|
expect (screen.queryByRole ('link')).not.toBeInTheDocument ()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it ('does not show a missing-information marker for external tags', () => {
|
||||||
|
renderWithProviders (
|
||||||
|
<TagLink
|
||||||
|
tag={buildTag ({
|
||||||
|
id: 7,
|
||||||
|
name: 'nico:external',
|
||||||
|
category: 'nico',
|
||||||
|
hasWiki: false,
|
||||||
|
materialId: null,
|
||||||
|
hasDeerjikists: false,
|
||||||
|
})}
|
||||||
|
withCount={false}/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect (
|
||||||
|
screen.getByRole ('link', { name: 'nico:external' }),
|
||||||
|
).toBeInTheDocument ()
|
||||||
|
|
||||||
|
expect (
|
||||||
|
screen.queryByRole ('link', { name: '!' }),
|
||||||
|
).not.toBeInTheDocument ()
|
||||||
|
|
||||||
|
expect (
|
||||||
|
screen.queryByTitle ('nico:external Wiki が存在しません.'),
|
||||||
|
).not.toBeInTheDocument ()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -116,4 +116,25 @@ describe ('posts API functions', () => {
|
|||||||
{ params: { page: 2, limit: 50 } },
|
{ params: { page: 2, limit: 50 } },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it ('maps an explicit external tag history filter to external_tag', async () => {
|
||||||
|
api.apiGet.mockResolvedValueOnce ({ versions: [], count: 0 })
|
||||||
|
|
||||||
|
await fetchPostChanges ({
|
||||||
|
externalTag: '7',
|
||||||
|
page: 2,
|
||||||
|
limit: 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect (api.apiGet).toHaveBeenCalledWith (
|
||||||
|
'/posts/versions',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
external_tag: '7',
|
||||||
|
page: 2,
|
||||||
|
limit: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -137,4 +137,19 @@ describe ('prefetchForURL', () => {
|
|||||||
expect (tagsApi.fetchTags).not.toHaveBeenCalled ()
|
expect (tagsApi.fetchTags).not.toHaveBeenCalled ()
|
||||||
expect (wikiApi.fetchWikiPages).not.toHaveBeenCalled ()
|
expect (wikiApi.fetchWikiPages).not.toHaveBeenCalled ()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it ('prefetches external tag post history without treating it as an internal tag', async () => {
|
||||||
|
await prefetchForURL (
|
||||||
|
qc (),
|
||||||
|
'http://localhost/posts/changes?external_tag=12&page=2&limit=50',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect (postsApi.fetchPostChanges).toHaveBeenCalledWith ({
|
||||||
|
externalTag: '12',
|
||||||
|
page: 2,
|
||||||
|
limit: 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect (tagsApi.fetchTag).not.toHaveBeenCalled ()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { waitFor } from '@testing-library/react'
|
||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import PostHistoryPage from '@/pages/posts/PostHistoryPage'
|
||||||
|
import { renderWithProviders } from '@/test/render'
|
||||||
|
|
||||||
|
const postsApi = vi.hoisted (() => ({
|
||||||
|
fetchPostChanges: vi.fn (),
|
||||||
|
updatePost: vi.fn (),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const tagsApi = vi.hoisted (() => ({
|
||||||
|
fetchTag: vi.fn (),
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock ('@/lib/posts', () => postsApi)
|
||||||
|
vi.mock ('@/lib/tags', () => tagsApi)
|
||||||
|
|
||||||
|
describe ('PostHistoryPage', () => {
|
||||||
|
beforeEach (() => {
|
||||||
|
vi.clearAllMocks ()
|
||||||
|
postsApi.fetchPostChanges.mockResolvedValue ({
|
||||||
|
versions: [],
|
||||||
|
count: 0,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it ('filters by external_tag without resolving the id as an internal tag', async () => {
|
||||||
|
renderWithProviders (
|
||||||
|
<PostHistoryPage/>,
|
||||||
|
{ route: '/posts/changes?external_tag=7' },
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor (() => {
|
||||||
|
expect (postsApi.fetchPostChanges).toHaveBeenCalledWith ({
|
||||||
|
externalTag: '7',
|
||||||
|
page: 1,
|
||||||
|
limit: 20,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
expect (tagsApi.fetchTag).not.toHaveBeenCalled ()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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 { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import TagListPage from '@/pages/tags/TagListPage'
|
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')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
新しいイシューから参照
ユーザーをブロックする