Files
finance/frontend/src/test/review-queue-save.test.tsx
T
k1nq f5ae63e158
Gitea Actions CI / detect (push) Successful in 33s
Gitea Actions CI / Backend (lint / type / test / build) (push) Failing after 28s
Gitea Actions CI / Compose & nginx validation (push) Failing after 1m8s
Gitea Actions CI / Frontend (lint / type / test / build) (push) Successful in 50s
Update manifest.json with new file metadata and add new test files
- Updated timestamps and AST hashes for existing files in the backend and frontend.
- Added new test files: `backend/tests/test_review_queue_batch.py`, `frontend/src/test/review-queue-save.test.tsx`, `frontend/src/test/app-layout.test.tsx`, and `frontend/src/test/ledger-view.test.tsx`.
- Updated documentation file `docs/tasks/0001-fixes.md` with new metadata.
2026-09-23 15:06:45 +05:00

124 lines
4.4 KiB
TypeScript

import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
import { waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { api } from '@/api/endpoints'
import type { BudgetCategory, Operation } from '@/api/types'
import { ReviewQueuePage } from '@/pages/ReviewQueuePage'
import { renderWithProviders } from './helpers'
vi.mock('@/api/endpoints', () => ({
api: {
accounts: { list: vi.fn() },
operations: {
list: vi.fn(),
create: vi.fn(),
void: vi.fn(),
categorize: vi.fn(),
categorizeBatch: vi.fn(),
savingsTransfer: vi.fn(),
savingsTransferRevert: vi.fn(),
},
budget: { categories: vi.fn() },
imports: { list: vi.fn() },
},
}))
const importsListMock = vi.mocked(api.imports.list)
const operationsListMock = vi.mocked(api.operations.list)
const categoriesMock = vi.mocked(api.budget.categories)
const categorizeBatchMock = vi.mocked(api.operations.categorizeBatch)
const categorizeMock = vi.mocked(api.operations.categorize)
const categories: BudgetCategory[] = [
{ id: 'cat-groceries', name: 'Продукты', nameKey: 'groceries', kind: 'monthly', system: false, archived: false, creditAccountId: null, plannedKopecks: 0, fundedKopecks: 0, spentKopecks: 0, availableKopecks: 0 },
{ id: 'cat-others', name: 'Остальное', nameKey: 'others', kind: 'monthly', system: true, archived: false, creditAccountId: null, plannedKopecks: 0, fundedKopecks: 0, spentKopecks: 0, availableKopecks: 0 },
]
function operation(id: string, description: string): Operation {
return {
id,
kind: 'expense',
status: 'confirmed',
amountKopecks: 150_00,
occurredOn: '2026-02-10',
postedOn: null,
accountId: 'acc-1',
counterpartyAccountId: null,
categoryId: null,
categoryName: null,
categoryKey: null,
payeeName: null,
incomeSourceName: null,
description,
parentOperationId: null,
convertedFromExpense: false,
version: 1,
createdAt: '2026-02-11T10:00:00Z',
}
}
describe('ReviewQueuePage single save with Other fallback', () => {
beforeEach(() => {
importsListMock.mockResolvedValue([])
operationsListMock.mockResolvedValue({
items: [operation('op-1', 'Coffee'), operation('op-2', 'Books')],
nextCursor: null,
})
categoriesMock.mockResolvedValue(categories)
categorizeBatchMock.mockResolvedValue({ updated: ['op-1', 'op-2'], skipped: [] })
})
afterEach(() => {
vi.clearAllMocks()
})
it('submits one batch request: picked categories win, the rest fall back to null (Other)', async () => {
const screen = renderWithProviders(<ReviewQueuePage />, '/review?month=2026-02')
const user = userEvent.setup()
await screen.findByText('Coffee')
const selects = screen.getAllByRole('combobox', { name: 'Assign category' })
expect(selects).toHaveLength(2)
await user.selectOptions(selects[0]!, 'cat-groceries')
await user.click(screen.getByRole('button', { name: 'Save' }))
await waitFor(() => expect(categorizeBatchMock).toHaveBeenCalledTimes(1))
expect(categorizeBatchMock).toHaveBeenCalledWith({
items: [
{ operationId: 'op-1', categoryId: 'cat-groceries' },
{ operationId: 'op-2', categoryId: null },
],
})
expect(categorizeMock).not.toHaveBeenCalled()
await screen.findByText('Categories saved')
})
it('offers exactly one save button and clears drafts after saving', async () => {
const screen = renderWithProviders(<ReviewQueuePage />, '/review?month=2026-02')
const user = userEvent.setup()
await screen.findByText('Books')
expect(screen.getAllByRole('button', { name: 'Save' })).toHaveLength(1)
const selects = screen.getAllByRole('combobox', { name: 'Assign category' })
await user.selectOptions(selects[1]!, 'cat-others')
await user.click(screen.getByRole('button', { name: 'Save' }))
await waitFor(() => {
const current = screen.getAllByRole('combobox', { name: 'Assign category' })
expect(current.every((el) => (el as HTMLSelectElement).value === '')).toBe(true)
})
})
it('explains the Others fallback in a hint', async () => {
const screen = renderWithProviders(<ReviewQueuePage />, '/review?month=2026-02')
await screen.findByText('Books')
expect(
screen.getByText('Transactions left without a category will be moved to “Other”.'),
).toBeInTheDocument()
})
})