feat(app): экран «События»
Лента брокерского леджера: фильтры по счёту, типу, датам и поиск, бесконечная прокрутка с явной кнопкой «Ещё», карточка события в bottom sheet с переходом на инструмент. Паттерн контроллера и пагинации взят у транзакций один в один — это тот же список с фильтрами, и второй способ делать одно и то же был бы просто вторым способом его чинить. Пункт навигации стоит между «Портфелем» и «Потоками», а не рядом с «Операциями»: это инвестиционная лента, а в операциях лежит ZenMoney. eventKindLabels переиспользован из portfolio/labels.dart, где он уже жил ради карточки инструмента, а не скопирован: два словаря подписей для одного енума разъезжаются на первом же новом типе события. Бэкенд не менялся — /events с фильтрами и пагинацией закрывает экран целиком, клиент не перегенерировался.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user