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
This commit is contained in:
54
frontend/src/components/BillsTracker.test.tsx
Normal file
54
frontend/src/components/BillsTracker.test.tsx
Normal file
@@ -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(<BillsTracker budget={emptyBudgetFixture} onUpdate={vi.fn()} />)
|
||||||
|
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
|
||||||
|
})
|
||||||
|
})
|
||||||
37
frontend/src/components/BudgetSetup.test.tsx
Normal file
37
frontend/src/components/BudgetSetup.test.tsx
Normal file
@@ -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(<BudgetSetup {...defaultProps} />)
|
||||||
|
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
|
||||||
|
})
|
||||||
|
})
|
||||||
48
frontend/src/pages/CategoriesPage.test.tsx
Normal file
48
frontend/src/pages/CategoriesPage.test.tsx
Normal file
@@ -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(
|
||||||
|
<MemoryRouter>
|
||||||
|
<CategoriesPage />
|
||||||
|
</MemoryRouter>
|
||||||
|
)
|
||||||
|
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
|
||||||
|
})
|
||||||
|
})
|
||||||
54
frontend/src/pages/DashboardPage.test.tsx
Normal file
54
frontend/src/pages/DashboardPage.test.tsx
Normal file
@@ -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(
|
||||||
|
<MemoryRouter>
|
||||||
|
<DashboardPage />
|
||||||
|
</MemoryRouter>
|
||||||
|
)
|
||||||
|
// 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
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user