From c95c7f248e610149d5f2ac86480dcffe52ef5203 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Wed, 11 Mar 2026 22:32:07 +0100 Subject: [PATCH] test(03-00): add Wave 0 test stub files for 4 components - BudgetSetup.test.tsx: smoke test + 2 it.skip for IXTN-01 spinner/disable - CategoriesPage.test.tsx: smoke test + 4 it.skip for IXTN-05 and STATE-02 - DashboardPage.test.tsx: smoke test + 2 it.skip for STATE-01 and STATE-03 - BillsTracker.test.tsx: smoke test + 3 it.skip for STATE-03 and IXTN-03 --- frontend/src/components/BillsTracker.test.tsx | 54 +++++++++++++++++++ frontend/src/components/BudgetSetup.test.tsx | 37 +++++++++++++ frontend/src/pages/CategoriesPage.test.tsx | 48 +++++++++++++++++ frontend/src/pages/DashboardPage.test.tsx | 54 +++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 frontend/src/components/BillsTracker.test.tsx create mode 100644 frontend/src/components/BudgetSetup.test.tsx create mode 100644 frontend/src/pages/CategoriesPage.test.tsx create mode 100644 frontend/src/pages/DashboardPage.test.tsx diff --git a/frontend/src/components/BillsTracker.test.tsx b/frontend/src/components/BillsTracker.test.tsx new file mode 100644 index 0000000..3fc2a94 --- /dev/null +++ b/frontend/src/components/BillsTracker.test.tsx @@ -0,0 +1,54 @@ +import { render, screen } from '@testing-library/react' +import { describe, it, expect, vi } from 'vitest' +import { BillsTracker } from './BillsTracker' +import type { BudgetDetail } from '@/lib/api' + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ t: (key: string) => key, i18n: { language: 'en' } }), +})) + +const emptyBudgetFixture: BudgetDetail = { + id: 'b1', + name: 'Test Budget', + start_date: '2025-01-01', + end_date: '2025-01-31', + currency: 'EUR', + carryover_amount: 0, + items: [], + totals: { + income_budget: 0, + income_actual: 0, + bills_budget: 0, + bills_actual: 0, + expenses_budget: 0, + expenses_actual: 0, + debts_budget: 0, + debts_actual: 0, + savings_budget: 0, + savings_actual: 0, + investments_budget: 0, + investments_actual: 0, + available: 0, + }, +} + +describe('BillsTracker', () => { + it('renders without crashing', () => { + render() + expect(screen.getByText('dashboard.billsTracker')).toBeInTheDocument() + }) + + it.skip('shows tinted skeleton when no bill items', () => { + // STATE-03: when budget.items contains no bill-type items, BillsTracker renders + // skeleton rows with a bill-category tint (palette bill light shade) instead of an empty table body + }) + + it.skip('flashes row green on successful inline save', () => { + // IXTN-03: after onUpdate resolves successfully, the updated row briefly gets a green + // flash class before returning to its normal appearance + }) + + it.skip('flashes row red on failed inline save', () => { + // IXTN-03: after onUpdate rejects, the row briefly gets a red flash class to signal failure + }) +}) diff --git a/frontend/src/components/BudgetSetup.test.tsx b/frontend/src/components/BudgetSetup.test.tsx new file mode 100644 index 0000000..93973c5 --- /dev/null +++ b/frontend/src/components/BudgetSetup.test.tsx @@ -0,0 +1,37 @@ +import { render, screen } from '@testing-library/react' +import { describe, it, expect, vi } from 'vitest' +import { BudgetSetup } from './BudgetSetup' + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ t: (key: string) => key, i18n: { language: 'en' } }), +})) + +vi.mock('@/lib/api', () => ({ + budgets: { + create: vi.fn().mockResolvedValue({ id: 'b1', name: 'Test Budget' }), + copyFrom: vi.fn().mockResolvedValue({}), + }, +})) + +describe('BudgetSetup', () => { + const defaultProps = { + existingBudgets: [], + onCreated: vi.fn(), + onCancel: vi.fn(), + } + + it('renders without crashing', () => { + render() + expect(screen.getByText('budget.setup')).toBeInTheDocument() + }) + + it.skip('shows spinner in create button when saving', () => { + // IXTN-01: while saving is true, the create button renders a spinner (Loader2 icon) + // Steps: fill required fields, click create, assert spinner is visible before resolve + }) + + it.skip('disables create button when saving', () => { + // IXTN-01: while saving is true, the create button is disabled to prevent double-submit + // Steps: fill required fields, click create, assert button is disabled before resolve + }) +}) diff --git a/frontend/src/pages/CategoriesPage.test.tsx b/frontend/src/pages/CategoriesPage.test.tsx new file mode 100644 index 0000000..8a4bf55 --- /dev/null +++ b/frontend/src/pages/CategoriesPage.test.tsx @@ -0,0 +1,48 @@ +import { render, screen } from '@testing-library/react' +import { describe, it, expect, vi } from 'vitest' +import { MemoryRouter } from 'react-router-dom' +import { CategoriesPage } from './CategoriesPage' + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ t: (key: string) => key, i18n: { language: 'en' } }), +})) + +vi.mock('@/lib/api', () => ({ + categories: { + list: vi.fn().mockResolvedValue([]), + create: vi.fn().mockResolvedValue({}), + update: vi.fn().mockResolvedValue({}), + delete: vi.fn().mockResolvedValue(undefined), + }, +})) + +describe('CategoriesPage', () => { + it('renders without crashing', () => { + render( + + + + ) + expect(screen.getByText('nav.categories')).toBeInTheDocument() + }) + + it.skip('opens confirmation dialog when delete button clicked', () => { + // IXTN-05: clicking the delete button on a category row should open a confirmation dialog + // before any deletion is performed, not immediately call categories.delete + }) + + it.skip('executes delete on confirm and shows spinner', () => { + // IXTN-05: after confirming the dialog, categories.delete is called and a spinner is shown + // on the confirm button while the async call is in flight + }) + + it.skip('shows error inline when delete fails', () => { + // IXTN-05: when categories.delete rejects, an error message is shown inline (role=alert) + // without closing the confirmation dialog + }) + + it.skip('shows empty state when no categories exist', () => { + // STATE-02: when categories.list returns [], the page renders an empty state element + // (e.g. a message or illustration) rather than just an empty table + }) +}) diff --git a/frontend/src/pages/DashboardPage.test.tsx b/frontend/src/pages/DashboardPage.test.tsx new file mode 100644 index 0000000..4f23816 --- /dev/null +++ b/frontend/src/pages/DashboardPage.test.tsx @@ -0,0 +1,54 @@ +import { render, screen } from '@testing-library/react' +import { describe, it, expect, vi } from 'vitest' +import { MemoryRouter } from 'react-router-dom' +import { DashboardPage } from './DashboardPage' + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ t: (key: string) => key, i18n: { language: 'en' } }), +})) + +vi.mock('@/lib/api', () => ({ + budgets: { + list: vi.fn().mockResolvedValue([]), + get: vi.fn().mockResolvedValue(null), + create: vi.fn().mockResolvedValue({}), + copyFrom: vi.fn().mockResolvedValue({}), + }, + budgetItems: { + update: vi.fn().mockResolvedValue({}), + }, +})) + +// Mock useBudgets so loading starts as false (skips the loading skeleton branch) +vi.mock('@/hooks/useBudgets', () => ({ + useBudgets: () => ({ + list: [], + current: null, + loading: false, + fetchList: vi.fn().mockResolvedValue(undefined), + selectBudget: vi.fn().mockResolvedValue(undefined), + setCurrent: vi.fn(), + }), +})) + +describe('DashboardPage', () => { + it('renders without crashing', () => { + render( + + + + ) + // When list is empty and not loading, the create budget button should be present + expect(screen.getByText('budget.create')).toBeInTheDocument() + }) + + it.skip('shows empty state with CTA when no budgets', () => { + // STATE-01: when budgets.list returns [] and loading is false, the dashboard renders + // a dedicated empty state component with a call-to-action to create the first budget + }) + + it.skip('shows loading skeleton while fetching', () => { + // STATE-03 (dashboard): while the initial budget list fetch is in flight, + // skeleton placeholders are displayed instead of the empty state or budget content + }) +})