diff --git a/src/components/card-form.test.tsx b/src/components/card-form.test.tsx new file mode 100644 index 0000000..e846901 --- /dev/null +++ b/src/components/card-form.test.tsx @@ -0,0 +1,38 @@ +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { CardForm } from './card-form' + +vi.mock('@/lib/api-client', () => ({ + ApiClientError: class extends Error { status = 0 }, + cardsApi: { create: vi.fn(), update: vi.fn() }, +})) + +const routerMock = { push: vi.fn(), refresh: vi.fn() } +vi.mock('next/navigation', () => ({ + useRouter: () => routerMock, +})) + +import { cardsApi } from '@/lib/api-client' + +beforeEach(() => { + vi.mocked(cardsApi.create).mockReset() + vi.mocked(cardsApi.update).mockReset() + routerMock.push.mockReset() + routerMock.refresh.mockReset() +}) + +it('crée une carte puis navigue vers son détail (F-05)', async () => { + vi.mocked(cardsApi.create).mockResolvedValue({ id: 'c1' } as never) + render() + await userEvent.type(screen.getByLabelText('Question'), 'Cest quoi un commit ?') + await userEvent.type(screen.getByLabelText('Réponse'), 'Un snapshot') + await userEvent.type(screen.getByLabelText('Thème'), 'git') + await userEvent.click(screen.getByRole('button', { name: 'Créer la carte' })) + expect(vi.mocked(cardsApi.create)).toHaveBeenCalledWith({ + question: 'Cest quoi un commit ?', + response: 'Un snapshot', + theme: 'git', + }) + await screen.findByText('Enregistrement…') +}) diff --git a/src/components/card-form.tsx b/src/components/card-form.tsx new file mode 100644 index 0000000..903d0e4 --- /dev/null +++ b/src/components/card-form.tsx @@ -0,0 +1,95 @@ +'use client' + +import { useRouter } from 'next/navigation' +import { useState } from 'react' +import type { CardInput } from '@/domain/card' +import { ApiClientError, cardsApi } from '@/lib/api-client' + +interface CardFormProps { + mode: 'create' | 'edit' + cardId?: string + initial?: CardInput +} + +export function CardForm({ mode, cardId, initial }: CardFormProps) { + const router = useRouter() + const [question, setQuestion] = useState(initial?.question ?? '') + const [response, setResponse] = useState(initial?.response ?? '') + const [theme, setTheme] = useState(initial?.theme ?? '') + const [error, setError] = useState(null) + const [saving, setSaving] = useState(false) + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + setSaving(true) + setError(null) + const input: CardInput = { question, response, theme } + try { + if (mode === 'create') { + const card = await cardsApi.create(input) + router.push(`/cards/${card.id}`) + router.refresh() + } else if (cardId) { + await cardsApi.update(cardId, input) + router.push(`/cards/${cardId}`) + router.refresh() + } + } catch (err) { + setError(err instanceof ApiClientError ? err.message : 'Impossible de sauvegarder la carte') + setSaving(false) + } + } + + return ( +
+ {error && ( +

{error}

+ )} +
+ + setQuestion(e.target.value)} + required + className="mt-1 w-full rounded-lg border border-slate-300 px-3 py-2" + /> +
+
+ +