diff --git a/frontend/src/components/InlineEditCell.test.tsx b/frontend/src/components/InlineEditCell.test.tsx
new file mode 100644
index 0000000..3b43af3
--- /dev/null
+++ b/frontend/src/components/InlineEditCell.test.tsx
@@ -0,0 +1,106 @@
+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 (
+
+ )
+}
+
+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)
+ })
+})