AI: address review on PR #465

このコミットが含まれているのは:
Codex Night Worker
2026-11-13 12:43:55 +09:00
コミット a259087840
+51 -7
ファイルの表示
@@ -1,11 +1,13 @@
import { fireEvent, screen, waitFor } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { textContentOf } from '@/test/text'
import PostEditForm from '@/components/PostEditForm'
import { buildPost, buildTag } from '@/test/factories'
import { renderWithProviders as render } from '@/test/render'
import type { DialogueAPI } from '@/lib/dialogues/useDialogue'
const postsApi = vi.hoisted (() => ({
updatePost: vi.fn (),
}))
@@ -18,18 +20,27 @@ const toastApi = vi.hoisted (() => ({
toast: vi.fn (),
}))
const dialogue = vi.hoisted (() => ({
confirm: vi.fn<DialogueAPI['confirm']> (),
choice: vi.fn () }))
vi.mock ('@/lib/posts', () => postsApi)
vi.mock ('@/lib/api', () => api)
vi.mock ('@/components/ui/use-toast', () => toastApi)
vi.mock ('@/lib/dialogues/useDialogue', async importOriginal => ({
...await importOriginal<typeof import ('@/lib/dialogues/useDialogue')> (),
default: () => ({
choice: vi.fn (),
}),
}))
...await importOriginal<typeof import ('@/lib/dialogues/useDialogue')> (),
default: () => dialogue }))
describe ('PostEditForm', () => {
beforeEach (() => {
vi.resetAllMocks ()
})
it ('submits edited post fields with the current base version', async () => {
const confirm = vi.fn<(value: boolean) => void> ()
dialogue.confirm.mockReturnValueOnce (new Promise<boolean> (resolve => {
confirm.mockImplementation (resolve)
}))
const onSave = vi.fn ()
const post = buildPost ({
id: 8,
@@ -55,6 +66,16 @@ describe ('PostEditForm', () => {
fireEvent.change (parentIds, { target: { value: '3 4' } })
fireEvent.submit (screen.getByRole ('button', { name: '更新' }).closest ('form')!)
expect (postsApi.updatePost).not.toHaveBeenCalled ()
expect (dialogue.confirm).toHaveBeenCalledTimes (1)
const difference = textContentOf (
dialogue.confirm.mock.calls[0][0].description)
expect (difference).toContain ('oldnew')
expect (difference).toContain ('23 4')
expect (difference).not.toContain ('general-tag')
await act (async () => { confirm (true) })
await waitFor (() => {
expect (postsApi.updatePost).toHaveBeenCalledWith (
expect.objectContaining ({
@@ -71,6 +92,28 @@ describe ('PostEditForm', () => {
expect (textContentOf (toastApi.toast.mock.calls[0][0].description)).toBe ('更新しました.')
})
it ('keeps edited fields without submitting when confirmation is cancelled', async () => {
const onSave = vi.fn ()
dialogue.confirm.mockResolvedValueOnce (false)
render (<PostEditForm post={buildPost ()} onSave={onSave}/>)
const [title, parentIds] = screen.getAllByRole ('textbox')
fireEvent.change (title, { target: { value: 'new' } })
fireEvent.change (parentIds, { target: { value: '3 4' } })
const button = screen.getByRole ('button', { name: '更新' })
fireEvent.submit (button.closest ('form')!)
await waitFor (() => {
expect (button).toBeEnabled ()
})
expect (dialogue.confirm).toHaveBeenCalledTimes (1)
expect (postsApi.updatePost).not.toHaveBeenCalled ()
expect (onSave).not.toHaveBeenCalled ()
expect (toastApi.toast).not.toHaveBeenCalled ()
expect (title).toHaveValue ('new')
expect (parentIds).toHaveValue ('3 4')
})
it ('preserves duration while the video tag is temporarily removed', () => {
const post = buildPost ({
videoMs: 180_500,
@@ -100,6 +143,7 @@ describe ('PostEditForm', () => {
'shows deduplicated original-created endpoint errors on the shared datetime field',
async () => {
const post = buildPost ()
dialogue.confirm.mockResolvedValueOnce (true)
api.isApiError.mockReturnValue (true)
postsApi.updatePost.mockRejectedValueOnce ({
response: {