Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
113 lines
3.1 KiB
Dart
113 lines
3.1 KiB
Dart
import 'package:fintracker_api/fintracker_api.dart';
|
|
import 'package:fintracker_app/core/widgets/money_text.dart';
|
|
import 'package:fintracker_app/features/home/scope_cards.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
ScopeCardOut _card({
|
|
String scope = 'portfolio:1',
|
|
String name = 'ИИСы',
|
|
String kind = 'portfolio',
|
|
String total = '224608.65',
|
|
String? pnl = '6643.47',
|
|
String? pnlPct = '0.015',
|
|
String? day = '-265.68',
|
|
String? dayPct = '-0.001',
|
|
String? xirr = '0.0298',
|
|
String income = '17387.49',
|
|
String? incomePct = '0.043',
|
|
}) => ScopeCardOut(
|
|
scope: scope,
|
|
name: name,
|
|
kind: kind,
|
|
asOf: DateTime(2026, 9, 19),
|
|
totalRub: total,
|
|
investedRub: '200000',
|
|
pnlRub: pnl,
|
|
pnlPct: pnlPct,
|
|
dayChangeRub: day,
|
|
dayChangePct: dayPct,
|
|
xirr: xirr,
|
|
incomeYearRub: income,
|
|
incomeYearPct: incomePct,
|
|
);
|
|
|
|
Widget _app(List<ScopeCardOut> cards, {double width = 1200}) => ProviderScope(
|
|
child: MaterialApp(
|
|
home: MediaQuery(
|
|
data: MediaQueryData(size: Size(width, 900)),
|
|
child: Scaffold(
|
|
body: SingleChildScrollView(child: ScopeCards(cards: cards)),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
void main() {
|
|
testWidgets('a card shows the name, the value and the four figures', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(_app([_card()]));
|
|
|
|
expect(find.text('ИИСЫ'), findsOneWidget);
|
|
expect(find.text(MoneyText.format('224608.65', 'RUB')), findsOneWidget);
|
|
for (final label in [
|
|
'Прибыль',
|
|
'За день',
|
|
'Доходность',
|
|
'Пассивный доход',
|
|
]) {
|
|
expect(find.text(label), findsOneWidget, reason: label);
|
|
}
|
|
// the gain carries an explicit plus, the loss its own minus
|
|
expect(find.text('+${MoneyText.format('6643.47', 'RUB')}'), findsOneWidget);
|
|
expect(find.text(MoneyText.format('-265.68', 'RUB')), findsOneWidget);
|
|
expect(find.text('2,98 %'), findsOneWidget);
|
|
expect(
|
|
find.textContaining(MoneyText.format('17387.49', 'RUB')),
|
|
findsOneWidget,
|
|
);
|
|
});
|
|
|
|
testWidgets('missing figures read as a dash, not as zero', (tester) async {
|
|
await tester.pumpWidget(
|
|
_app([
|
|
_card(
|
|
pnl: null,
|
|
pnlPct: null,
|
|
day: null,
|
|
dayPct: null,
|
|
xirr: null,
|
|
income: '0',
|
|
incomePct: null,
|
|
),
|
|
]),
|
|
);
|
|
|
|
expect(find.text('—'), findsNWidgets(4));
|
|
});
|
|
|
|
testWidgets('cards flow into columns by the available width', (tester) async {
|
|
final cards = [
|
|
for (var i = 0; i < 6; i++) _card(scope: 'account:$i', name: 'Счёт $i'),
|
|
];
|
|
addTearDown(tester.view.reset);
|
|
|
|
Future<int> columns(double width) async {
|
|
tester.view.physicalSize = Size(width, 900);
|
|
tester.view.devicePixelRatio = 1;
|
|
await tester.pumpWidget(_app(cards, width: width));
|
|
final xs = {
|
|
for (final c in cards)
|
|
tester.getTopLeft(find.text('СЧЁТ ${cards.indexOf(c)}')).dx.round(),
|
|
};
|
|
return xs.length;
|
|
}
|
|
|
|
expect(await columns(1200), 3);
|
|
expect(await columns(800), 2);
|
|
expect(await columns(400), 1);
|
|
});
|
|
}
|