moex-vibe/apps/frontend/src/widgets/broker-dashboard/ui/BrokerDashboard.test.tsx

215 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import type { ReactNode } from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { BrokerPortfolio } from '@/shared/api'
import { BrokerDashboard } from './BrokerDashboard'
const hookMocks = vi.hoisted(() => ({
useBrokerEvents: vi.fn(),
useBrokerOperations: vi.fn(),
}))
vi.mock('@tanstack/react-router', () => ({
Link: ({ children, to }: { children: React.ReactNode; to: string }) => (
<a href={to}>{children}</a>
),
}))
vi.mock('@/entities/broker-analytics', () => ({
useBrokerAnalytics: () => ({
data: {
totalDeposits: 1000,
totalWithdrawn: 100,
netInvested: 900,
totalDividends: 25,
totalCoupons: 15,
totalReceived: 40,
totalReturnPercent: 4.44,
currency: 'RUB',
},
isLoading: false,
isError: false,
}),
}))
vi.mock('@/entities/broker-event', () => ({
useBrokerEvents: hookMocks.useBrokerEvents,
}))
vi.mock('@/entities/broker-operation', () => ({
useBrokerOperations: hookMocks.useBrokerOperations,
}))
const portfolio: BrokerPortfolio = {
account: {
id: 'acc-1',
name: 'Основной счёт',
type: 'brokerage',
status: 'open',
openedAt: null,
accessLevel: null,
},
positionCounts: { shares: 2, bonds: 1, etf: 0, other: 0 },
totals: {
shares: null,
bonds: null,
etf: null,
currencies: null,
futures: null,
options: null,
structuredProducts: null,
dfa: null,
portfolio: { currency: 'RUB', units: '1000', nano: 0, value: 1000 },
},
yields: { expectedPercent: null, daily: null, dailyPercent: null },
cash: [],
blockedCash: [],
asOf: '2026-06-26T00:00:00.000Z',
}
function renderWithProviders(ui: ReactNode) {
return render(<LocalizationProvider dateAdapter={AdapterDayjs}>{ui}</LocalizationProvider>)
}
describe('BrokerDashboard', () => {
beforeEach(() => {
hookMocks.useBrokerEvents.mockReturnValue({
data: { items: [], summary: {}, asOf: '2026-06-26T00:00:00.000Z' },
isLoading: false,
isError: false,
})
hookMocks.useBrokerOperations.mockReturnValue({
data: {
accountId: 'acc-1',
items: [],
nextCursor: null,
hasNext: false,
asOf: '2026-06-26T00:00:00.000Z',
},
isLoading: false,
isError: false,
})
})
it('renders the dashboard sections', () => {
renderWithProviders(<BrokerDashboard accountId="acc-1" portfolio={portfolio} />)
expect(screen.getByText('Основной счёт')).toBeInTheDocument()
expect(screen.getByText('События')).toBeInTheDocument()
expect(screen.getByText('Доходы')).toBeInTheDocument()
expect(screen.getByText('Аналитика доходности')).toBeInTheDocument()
expect(screen.getByText('Аллокация')).toBeInTheDocument()
})
it('applies event type chips immediately to useBrokerEvents', async () => {
const user = userEvent.setup()
renderWithProviders(<BrokerDashboard accountId="acc-1" portfolio={portfolio} />)
const couponChips = screen.getAllByRole('button', { name: 'Купоны' })
await user.click(couponChips[0])
await waitFor(() => {
expect(hookMocks.useBrokerEvents).toHaveBeenLastCalledWith(
'acc-1',
expect.objectContaining({ types: 'dividend,maturity,offer' }),
{ enabled: true },
)
})
})
it('applies income type chips immediately to useBrokerOperations', async () => {
const user = userEvent.setup()
renderWithProviders(<BrokerDashboard accountId="acc-1" portfolio={portfolio} />)
const couponChips = screen.getAllByRole('button', { name: 'Купоны' })
await user.click(couponChips[1])
await waitFor(() => {
expect(hookMocks.useBrokerOperations).toHaveBeenLastCalledWith(
'acc-1',
expect.objectContaining({
operationTypes: 'OPERATION_TYPE_DIVIDEND,OPERATION_TYPE_DIV_EXT',
}),
{ enabled: true },
)
})
})
it('shows date filter toggle button with apply action and accessible name', async () => {
const user = userEvent.setup()
renderWithProviders(<BrokerDashboard accountId="acc-1" portfolio={portfolio} />)
const applyButtons = screen.getAllByRole('button', { name: /Применить период/ })
expect(applyButtons).toHaveLength(2)
const toggleButtons = screen.getAllByRole('button', { name: /^Период/ })
expect(toggleButtons).toHaveLength(2)
await user.click(toggleButtons[0])
expect(screen.getByText('7д')).toBeInTheDocument()
expect(screen.getByText('30д')).toBeInTheDocument()
expect(screen.getByText('90д')).toBeInTheDocument()
expect(screen.getByText('1г')).toBeInTheDocument()
expect(screen.getByText('Всё')).toBeInTheDocument()
expect(screen.getByText('Сбросить')).toBeInTheDocument()
})
it('does not render a text chevron glyph inside the period toggle button', () => {
renderWithProviders(<BrokerDashboard accountId="acc-1" portfolio={portfolio} />)
const toggleButtons = screen.getAllByRole('button', { name: /^Период/ })
expect(toggleButtons).toHaveLength(2)
for (const button of toggleButtons) {
const text = button.textContent ?? ''
expect(text).not.toMatch(/[▼▲vV]/)
expect(button.querySelector('svg')).not.toBeNull()
}
})
it('renders hero "Всего доходов" with the ₽ symbol and no "RUB" code', () => {
renderWithProviders(<BrokerDashboard accountId="acc-1" portfolio={portfolio} />)
expect(screen.getByText('Всего доходов')).toBeInTheDocument()
const hero = screen.getByLabelText('Ключевые показатели брокерского счёта')
const heroText = hero.textContent ?? ''
expect(heroText).toContain('₽')
expect(heroText).not.toContain('RUB')
})
it('shows skeleton table while events are loading', () => {
hookMocks.useBrokerEvents.mockReturnValue({
data: undefined,
isLoading: true,
isError: false,
})
renderWithProviders(<BrokerDashboard accountId="acc-1" portfolio={portfolio} />)
const skeletons = screen.getAllByTestId('dashboard-table-skeleton')
expect(skeletons.length).toBeGreaterThanOrEqual(1)
})
it('shows skeleton table while income operations are loading', () => {
hookMocks.useBrokerOperations.mockReturnValue({
data: undefined,
isLoading: true,
isError: false,
})
renderWithProviders(<BrokerDashboard accountId="acc-1" portfolio={portfolio} />)
const skeletons = screen.getAllByTestId('dashboard-table-skeleton')
expect(skeletons.length).toBeGreaterThanOrEqual(1)
})
it('does not show skeleton when data is loaded', () => {
renderWithProviders(<BrokerDashboard accountId="acc-1" portfolio={portfolio} />)
expect(screen.queryByTestId('dashboard-table-skeleton')).not.toBeInTheDocument()
})
})