feat(app): новый визуальный язык, верхняя навигация, портфели, создание счетов и ручные события
Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user