**本番 DB のバックアップをかならずすること** Reviewed-on: #420 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #420 でマージされました。
このコミットが含まれているのは:
@@ -13,11 +13,14 @@ vi.mock ('@dnd-kit/core', () => dndKit)
|
||||
|
||||
const tag = buildTag ({ id: 7, name: 'ドラッグ元', postCount: 3 })
|
||||
|
||||
const renderRow = (activeDndId?: string) => {
|
||||
const renderRow = (
|
||||
activeDndId?: string,
|
||||
renderedTag = tag,
|
||||
) => {
|
||||
renderWithProviders (
|
||||
<DraggableDroppableTagRow
|
||||
activeDndId={activeDndId}
|
||||
tag={tag}
|
||||
tag={renderedTag}
|
||||
nestLevel={2}
|
||||
pathKey="cat-general-7"
|
||||
suppressClickRef={{ current: false }}/>,
|
||||
@@ -72,4 +75,26 @@ describe ('DraggableDroppableTagRow', () => {
|
||||
renderRow ('tag-node:other')
|
||||
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,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -38,6 +38,7 @@ const DraggableDroppableTagRow: FC<Props> = ({
|
||||
{ normal: { duration: .2, ease: 'easeOut' as const } },
|
||||
)
|
||||
const dndId = `tag-node:${ pathKey }`
|
||||
const dndDisabled = tag.category === 'nico'
|
||||
|
||||
const downPosRef = useRef<{ x: number; y: number } | null> (null)
|
||||
const armedRef = useRef (false)
|
||||
@@ -62,7 +63,8 @@ const DraggableDroppableTagRow: FC<Props> = ({
|
||||
const { attributes,
|
||||
listeners,
|
||||
setNodeRef: setDragRef,
|
||||
transform } = useDraggable ({ id: dndId,
|
||||
transform } = useDraggable ({ id: dndId,
|
||||
disabled: dndDisabled,
|
||||
data: { kind: 'tag',
|
||||
dndId,
|
||||
tagId: tag.id,
|
||||
@@ -70,8 +72,9 @@ const DraggableDroppableTagRow: FC<Props> = ({
|
||||
nestLevel } })
|
||||
|
||||
const { setNodeRef: setDropRef, isOver: over } = useDroppable ({
|
||||
id: dndId,
|
||||
data: { kind: 'tag', tagId: tag.id } })
|
||||
id: dndId,
|
||||
disabled: dndDisabled,
|
||||
data: { kind: 'tag', tagId: tag.id } })
|
||||
|
||||
const activeDragging = activeDndId === dndId
|
||||
const style: CSSProperties = { transform: CSS.Translate.toString (transform),
|
||||
|
||||
@@ -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 ()
|
||||
})
|
||||
})
|
||||
@@ -150,16 +150,17 @@ const buildFlatTagByCategory = (
|
||||
byCategory: TagByCategory,
|
||||
): TagByCategory => {
|
||||
const tagsTmp = { } as TagByCategory
|
||||
const seen = new Set<number> ()
|
||||
const seen = new Set<string> ()
|
||||
|
||||
for (const category of CATEGORIES)
|
||||
tagsTmp[category] = []
|
||||
|
||||
const visit = (tag: TagWithSections) => {
|
||||
if (seen.has (tag.id))
|
||||
const key = `${ tag.category }:${ tag.id }`
|
||||
if (seen.has (key))
|
||||
return
|
||||
|
||||
seen.add (tag.id)
|
||||
seen.add (key)
|
||||
tagsTmp[tag.category].push ({ ...tag, children: [] })
|
||||
|
||||
for (const child of tag.children ?? [])
|
||||
|
||||
@@ -57,4 +57,31 @@ describe ('TagLink', () => {
|
||||
expect (screen.getByText ('正式名')).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 ()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -86,7 +86,7 @@ const TagLink: FC<Props> = ({ tag,
|
||||
className={cn (
|
||||
'inline-flex min-w-0 max-w-full flex-nowrap items-stretch align-baseline',
|
||||
'gap-x-1 md:items-baseline')}>
|
||||
{(linkFlg && withWiki && isFullTag (tag)) && (
|
||||
{(linkFlg && withWiki && isFullTag (tag) && tag.category !== 'nico') && (
|
||||
<span className={markerWrapClass}>
|
||||
{(tag.materialId != null || tag.hasWiki || tag.hasDeerjikists)
|
||||
? (
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import type { ComponentProps, FC } from 'react'
|
||||
|
||||
type Props = { children: React.ReactNode; className?: string }
|
||||
type Props = ComponentProps<'h1'>
|
||||
|
||||
|
||||
const PageTitle: FC<Props> = ({ children, className, ...rest }) => (
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする