// @vitest-environment jsdom import '../../../test/dom-setup'; import { describe, it, expect, beforeEach, vi } from 'vitest'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithProviders } from '../../../test/render-helpers'; import { OverviewTab } from './OverviewTab'; import { __resetDraftPruneForTest } from '../../../hooks/useDraft'; import type { LocalTask } from '../../../api'; const DRAFT_KEY = 'maestro:draft:v1:feedback-comment:7'; const baseTask = { id: 7, title: 'タスク', body: '本文', pieceName: 'chat', priority: 'medium', latestJob: { status: 'succeeded' }, feedbackRating: null, feedbackTags: [], feedbackComment: null, } as unknown as LocalTask; beforeEach(() => { localStorage.clear(); __resetDraftPruneForTest(); vi.stubGlobal('fetch', vi.fn(async () => new Response('{}', { status: 404 }))); }); describe('FeedbackPanel comment draft', () => { it('👍を押してコメントを書くと下書きが保存され、再マウントで復元される', async () => { const user = userEvent.setup(); const first = renderWithProviders(); await user.click(screen.getByRole('button', { name: /👍/ })); await user.type(screen.getByRole('textbox'), '精度が良かった'); first.unmount(); expect(JSON.parse(localStorage.getItem(DRAFT_KEY)!).text).toBe('精度が良かった'); renderWithProviders(); await user.click(screen.getByRole('button', { name: /👍/ })); expect(screen.getByRole('textbox')).toHaveValue('精度が良かった'); }); it('保存済みコメントがあるタスクではそちらを優先する', async () => { localStorage.setItem(DRAFT_KEY, JSON.stringify({ text: '古い下書き', updatedAt: Date.now() })); const saved = { ...baseTask, feedbackRating: 'good', feedbackComment: '確定済みコメント', } as unknown as LocalTask; const user = userEvent.setup(); renderWithProviders(); // 保存済み表示 → change で編集に入る await user.click(screen.getByRole('button', { name: /change|変更/i })); expect(screen.getByRole('textbox')).toHaveValue('確定済みコメント'); }); it('フィードバック送信が成功すると下書きが削除される', async () => { // 送信前にデバウンス書き込みのタイミングに依存しないよう、下書きは // localStorage へ直接シードする(既存テストと同じ堅牢なパターン)。 localStorage.setItem(DRAFT_KEY, JSON.stringify({ text: '送信予定のコメント', updatedAt: Date.now() })); vi.stubGlobal('fetch', vi.fn(async (url: string, init?: RequestInit) => { if (typeof url === 'string' && url.includes('/feedback') && init?.method === 'PUT') { return new Response(JSON.stringify({ task: { ...baseTask, feedbackRating: 'good' } }), { status: 200 }); } return new Response('{}', { status: 404 }); })); const user = userEvent.setup(); renderWithProviders(); await user.click(screen.getByRole('button', { name: /👍/ })); await user.click(screen.getByRole('button', { name: /submit|送信/i })); await waitFor(() => expect(localStorage.getItem(DRAFT_KEY)).toBeNull()); }); it('キャンセルすると下書きが削除される', async () => { const saved = { ...baseTask, feedbackRating: 'good', feedbackComment: '確定済みコメント', } as unknown as LocalTask; const user = userEvent.setup(); renderWithProviders(); await user.click(screen.getByRole('button', { name: /change|変更/i })); await user.type(screen.getByRole('textbox'), '書きかけの変更'); // saveDraft はデバウンス書き込みのため、反映を待ってからキャンセルする await waitFor(() => expect(JSON.parse(localStorage.getItem(DRAFT_KEY)!).text).toContain('書きかけの変更')); await user.click(screen.getByRole('button', { name: /cancel|キャンセル/i })); expect(localStorage.getItem(DRAFT_KEY)).toBeNull(); }); });