Files
fin-tracker/app/test/event_row_test.dart
T
Dmitry c588b575a7 feat(app): экран «События»
Лента брокерского леджера: фильтры по счёту, типу, датам и поиск, бесконечная
прокрутка с явной кнопкой «Ещё», карточка события в bottom sheet с переходом на
инструмент. Паттерн контроллера и пагинации взят у транзакций один в один — это тот
же список с фильтрами, и второй способ делать одно и то же был бы просто вторым
способом его чинить.

Пункт навигации стоит между «Портфелем» и «Потоками», а не рядом с «Операциями»:
это инвестиционная лента, а в операциях лежит ZenMoney.

eventKindLabels переиспользован из portfolio/labels.dart, где он уже жил ради карточки
инструмента, а не скопирован: два словаря подписей для одного енума разъезжаются на
первом же новом типе события.

Бэкенд не менялся — /events с фильтрами и пагинацией закрывает экран целиком,
клиент не перегенерировался.
2026-09-18 15:08:12 +03:00

84 lines
2.7 KiB
Dart

import 'package:fintracker_api/fintracker_api.dart';
import 'package:fintracker_app/core/widgets/money_text.dart';
import 'package:fintracker_app/features/events/event_row.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
EventOut event({
EventKind kind = EventKind.buy,
String currency = 'RUB',
String amount = '-10000',
String? amountRub = '-10000',
String? ticker = 'SBER',
String? quantity = '100',
EventStatus status = EventStatus.confirmed,
bool externalFlow = false,
}) {
return EventOut(
accountId: 46,
accruedInterest: null,
amount: amount,
amountRub: amountRub,
currency: currency,
description: 'Покупка ценных бумаг',
externalFlow: externalFlow,
fee: '30',
id: 1,
instrumentId: 311,
kind: kind,
price: '100',
priceCurrency: currency,
quantity: quantity,
status: status,
tax: null,
ticker: ticker,
tradeDate: DateTime(2026, 9, 10),
ts: DateTime(2026, 9, 10, 12, 30),
);
}
Widget wrap(EventOut e) => MaterialApp(
home: Scaffold(
body: EventRow(event: e, accountName: 'T-Invest ИИС', onTap: () {}),
),
);
void main() {
testWidgets('shows kind, ticker, account and the native amount', (tester) async {
await tester.pumpWidget(wrap(event()));
expect(find.text('Покупка · SBER'), findsOneWidget);
expect(find.textContaining('T-Invest ИИС'), findsOneWidget);
expect(find.textContaining('100 шт.'), findsOneWidget);
expect(find.text(MoneyText.format('-10000', 'RUB')), findsOneWidget);
});
testWidgets('adds the RUB equivalent only for a foreign currency', (tester) async {
await tester.pumpWidget(wrap(event(currency: 'USD', amount: '-100', amountRub: '-9500')));
expect(find.text(MoneyText.format('-100', 'USD')), findsOneWidget);
expect(find.text(MoneyText.format('-9500', 'RUB')), findsOneWidget);
});
testWidgets('leaves the RUB line out when no rate was available', (tester) async {
await tester.pumpWidget(wrap(event(currency: 'USD', amount: '-100', amountRub: null)));
expect(find.text(MoneyText.format('-100', 'USD')), findsOneWidget);
expect(find.textContaining('₽'), findsNothing);
});
testWidgets('names a non-confirmed status in the subtitle', (tester) async {
await tester.pumpWidget(wrap(event(status: EventStatus.pending)));
expect(find.textContaining('Ожидает'), findsOneWidget);
});
testWidgets('drops the separator for an event with no instrument', (tester) async {
await tester.pumpWidget(
wrap(event(kind: EventKind.deposit, ticker: null, quantity: null, externalFlow: true)),
);
expect(find.text('Пополнение'), findsOneWidget);
});
}