import { fireEvent, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import PostNewPage from '@/pages/posts/PostNewPage' import { buildUser } from '@/test/factories' import { renderWithProviders } from '@/test/render' const api = vi.hoisted (() => ({ apiGet: vi.fn (), apiPost: vi.fn (), isApiError: vi.fn (), })) const toastApi = vi.hoisted (() => ({ toast: vi.fn (), })) vi.mock ('@/lib/api', () => api) vi.mock ('@/components/ui/use-toast', () => toastApi) describe ('PostNewPage', () => { beforeEach (() => { vi.clearAllMocks () api.isApiError.mockReturnValue (false) }) it ('blocks guests', () => { renderWithProviders () expect (screen.getByText ('403')).toBeInTheDocument () }) it ('submits a new post with manual title and thumbnail settings', async () => { api.apiPost.mockResolvedValueOnce ({}) api.apiGet.mockResolvedValue ([]) renderWithProviders () const checkboxes = screen.getAllByRole ('checkbox', { name: '自動' }) fireEvent.click (checkboxes[0]) fireEvent.click (checkboxes[1]) const textboxes = screen.getAllByRole ('textbox') fireEvent.change (textboxes[0], { target: { value: 'https://example.com/post' } }) fireEvent.change (textboxes[1], { target: { value: '投稿タイトル' } }) fireEvent.change (textboxes[2], { target: { value: '1 2' } }) fireEvent.change (textboxes[3], { target: { value: 'tag1 tag2' } }) fireEvent.click (screen.getByRole ('button', { name: '追加' })) await waitFor (() => { expect (api.apiPost).toHaveBeenCalledWith ( '/posts', expect.any (FormData), { headers: { 'Content-Type': 'multipart/form-data' } }, ) }) const formData = api.apiPost.mock.calls[0]?.[1] as FormData expect (formData.get ('url')).toBe ('https://example.com/post') expect (formData.get ('title')).toBe ('投稿タイトル') expect (formData.get ('parent_post_ids')).toBe ('1 2') expect (formData.get ('tags')).toBe ('tag1 tag2') expect (toastApi.toast).toHaveBeenCalledWith ({ title: '投稿成功!' }) }) it ('shows 422 validation errors for post fields', async () => { api.apiGet.mockResolvedValue ([]) api.isApiError.mockReturnValue (true) api.apiPost.mockRejectedValueOnce ({ response: { status: 422, data: { type: 'validation_error', message: '入力内容を確認してください.', errors: { tags: ['ニコニコ・タグは直接指定できません.'] }, base_errors: ['投稿内容を確認してください.'], }, }, }) renderWithProviders () const checkboxes = screen.getAllByRole ('checkbox', { name: '自動' }) fireEvent.click (checkboxes[0]) fireEvent.click (checkboxes[1]) const textboxes = screen.getAllByRole ('textbox') fireEvent.change (textboxes[0], { target: { value: 'https://example.com/post' } }) fireEvent.change (textboxes[1], { target: { value: '投稿タイトル' } }) fireEvent.change (textboxes[3], { target: { value: 'nico:nico_tag' } }) fireEvent.click (screen.getByRole ('button', { name: '追加' })) expect (await screen.findByText ('投稿内容を確認してください.')).toBeInTheDocument () expect (screen.getByText ('ニコニコ・タグは直接指定できません.')).toBeInTheDocument () expect (screen.getAllByRole ('textbox')[3]).toHaveAttribute ('aria-invalid', 'true') }) })