import { render, screen, fireEvent } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, it, expect, vi, beforeEach } from 'vitest' import { InlineEditCell } from './InlineEditCell' // Wrap in a table/row to satisfy semantic HTML for TableCell function Wrapper({ children }: { children: React.ReactNode }) { return ( {children}
) } describe('InlineEditCell', () => { const defaultProps = { value: 42.5, currency: 'EUR', onSave: vi.fn().mockResolvedValue(undefined), } beforeEach(() => { vi.clearAllMocks() }) it('renders formatted currency value in display mode', () => { render( ) // Should show formatted value (42.5 in EUR → some formatted string containing 42) expect(screen.getByText(/42/)).toBeInTheDocument() }) it('enters edit mode on click', async () => { const user = userEvent.setup() render( ) const span = screen.getByText(/42/) await user.click(span) // After click, an input should be visible expect(screen.getByRole('spinbutton')).toBeInTheDocument() }) it('calls onSave with parsed number on blur', async () => { const user = userEvent.setup() const onSave = vi.fn().mockResolvedValue(undefined) render( ) const span = screen.getByText(/42/) await user.click(span) const input = screen.getByRole('spinbutton') await user.clear(input) await user.type(input, '100') fireEvent.blur(input) expect(onSave).toHaveBeenCalledWith(100) }) it('does not call onSave when value unchanged', async () => { const user = userEvent.setup() const onSave = vi.fn().mockResolvedValue(undefined) render( ) const span = screen.getByText(/42/) await user.click(span) // Don't change the value, just blur const input = screen.getByRole('spinbutton') fireEvent.blur(input) expect(onSave).not.toHaveBeenCalled() }) it('calls onSave on Enter key', async () => { const user = userEvent.setup() const onSave = vi.fn().mockResolvedValue(undefined) render( ) const span = screen.getByText(/42/) await user.click(span) const input = screen.getByRole('spinbutton') await user.clear(input) await user.type(input, '99') await user.keyboard('{Enter}') expect(onSave).toHaveBeenCalledWith(99) }) // New tests for pencil icon and callbacks it('renders a pencil icon in display mode', () => { render( ) // Pencil icon from lucide-react renders as an SVG — query by its test-id or aria role // The Pencil icon should be present in the DOM (opacity-0, shown on hover via CSS) const pencilIcon = document.querySelector('svg[data-testid="pencil-icon"]') || document.querySelector('.lucide-pencil') expect(pencilIcon).not.toBeNull() }) it('calls onSaveSuccess callback after successful save', async () => { const user = userEvent.setup() const onSave = vi.fn().mockResolvedValue(undefined) const onSaveSuccess = vi.fn() render( ) const span = screen.getByText(/42/) await user.click(span) const input = screen.getByRole('spinbutton') await user.clear(input) await user.type(input, '100') fireEvent.blur(input) // Wait for async operations await vi.waitFor(() => { expect(onSaveSuccess).toHaveBeenCalledTimes(1) }) }) it('calls onSaveError and reverts value when onSave rejects', async () => { const user = userEvent.setup() const onSave = vi.fn().mockRejectedValue(new Error('Save failed')) const onSaveError = vi.fn() render( ) const span = screen.getByText(/42/) await user.click(span) const input = screen.getByRole('spinbutton') await user.clear(input) await user.type(input, '999') fireEvent.blur(input) // Wait for async operations await vi.waitFor(() => { expect(onSaveError).toHaveBeenCalledTimes(1) }) // Value should revert back to original — display mode shows original value await vi.waitFor(() => { expect(screen.getByText(/42/)).toBeInTheDocument() }) }) it('does not call onSaveSuccess when value is unchanged', async () => { const user = userEvent.setup() const onSave = vi.fn().mockResolvedValue(undefined) const onSaveSuccess = vi.fn() render( ) const span = screen.getByText(/42/) await user.click(span) // Don't change the value, just blur const input = screen.getByRole('spinbutton') fireEvent.blur(input) expect(onSave).not.toHaveBeenCalled() expect(onSaveSuccess).not.toHaveBeenCalled() }) })