From 0976c51cc16d4e86229e1c43accab0278e104392 Mon Sep 17 00:00:00 2001 From: gilles Date: Sun, 13 Sep 2026 15:30:01 +0200 Subject: [PATCH] feat(notes): add note editor --- src/components/NoteEditor.test.tsx | 73 ++++++++++++++++++++++++ src/components/NoteEditor.tsx | 91 ++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 src/components/NoteEditor.test.tsx create mode 100644 src/components/NoteEditor.tsx diff --git a/src/components/NoteEditor.test.tsx b/src/components/NoteEditor.test.tsx new file mode 100644 index 0000000..58b44e9 --- /dev/null +++ b/src/components/NoteEditor.test.tsx @@ -0,0 +1,73 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' +import NoteEditor from './NoteEditor' +import type { Note } from '../types' + +const note: Note = { id: 42, title: 'Courses', content: 'Lait\nOeufs' } + +const renderEditor = (overrides: Partial = {}) => { + const onUpdate = vi.fn() + render() + return { onUpdate } +} + +describe('NoteEditor', () => { + it('displays the note title and content in read mode', () => { + renderEditor() + + expect(screen.getByText('Courses')).toBeInTheDocument() + expect(screen.getByText('Lait Oeufs')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Éditer' })).toBeInTheDocument() + }) + + it('shows placeholders for a note without title or content', () => { + renderEditor({ title: '', content: '' }) + + expect(screen.getByText('Sans titre')).toBeInTheDocument() + expect(screen.getByText('Note vide')).toBeInTheDocument() + }) + + it('switches to edit mode with prefilled fields', () => { + renderEditor() + + fireEvent.click(screen.getByRole('button', { name: 'Éditer' })) + + expect(screen.getByLabelText('Titre')).toHaveValue('Courses') + expect(screen.getByLabelText('Contenu')).toHaveValue('Lait\nOeufs') + expect(screen.getByRole('button', { name: 'Enregistrer' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Annuler' })).toBeInTheDocument() + }) + + it('saves edits and calls onUpdate with the updated values', () => { + const { onUpdate } = renderEditor() + + fireEvent.click(screen.getByRole('button', { name: 'Éditer' })) + fireEvent.change(screen.getByLabelText('Titre'), { target: { value: 'Liste de courses' } }) + fireEvent.change(screen.getByLabelText('Contenu'), { target: { value: 'Pain' } }) + fireEvent.click(screen.getByRole('button', { name: 'Enregistrer' })) + + expect(onUpdate).toHaveBeenCalledWith(42, { title: 'Liste de courses', content: 'Pain' }) + expect(screen.getByRole('button', { name: 'Éditer' })).toBeInTheDocument() + }) + + it('saves when the form is submitted', () => { + const { onUpdate } = renderEditor() + + fireEvent.click(screen.getByRole('button', { name: 'Éditer' })) + const form = screen.getByLabelText('Titre').closest('form')! + fireEvent.submit(form) + + expect(onUpdate).toHaveBeenCalledWith(42, { title: 'Courses', content: 'Lait\nOeufs' }) + }) + + it('cancels editing without calling onUpdate', () => { + const { onUpdate } = renderEditor() + + fireEvent.click(screen.getByRole('button', { name: 'Éditer' })) + fireEvent.change(screen.getByLabelText('Titre'), { target: { value: 'Non sauvegardé' } }) + fireEvent.click(screen.getByRole('button', { name: 'Annuler' })) + + expect(onUpdate).not.toHaveBeenCalled() + expect(screen.getByText('Courses')).toBeInTheDocument() + }) +}) diff --git a/src/components/NoteEditor.tsx b/src/components/NoteEditor.tsx new file mode 100644 index 0000000..9769b50 --- /dev/null +++ b/src/components/NoteEditor.tsx @@ -0,0 +1,91 @@ +import { useState } from 'react' +import type { Note } from '../types' + +interface NoteEditorProps { + note: Note + onUpdate: (id: number, patch: Partial>) => void +} + +function NoteEditor({ note, onUpdate }: NoteEditorProps) { + const [editing, setEditing] = useState(false) + const [title, setTitle] = useState(note.title) + const [content, setContent] = useState(note.content) + + const startEditing = () => { + setTitle(note.title) + setContent(note.content) + setEditing(true) + } + + const cancel = () => { + setEditing(false) + } + + const save = () => { + onUpdate(note.id, { title, content }) + setEditing(false) + } + + if (editing) { + return ( +
{ + event.preventDefault() + save() + }} + > + +