feat(app): новый визуальный язык, верхняя навигация, портфели, создание счетов и ручные события

Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
This commit is contained in:
Dmitry
2026-09-19 22:14:21 +03:00
parent a559d6de3e
commit 62d36aa3e8
73 changed files with 6406 additions and 1192 deletions
@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fintracker_app/core/widgets/asset_icon.dart';
Future<void> _pump(WidgetTester tester, Widget icon) => tester.pumpWidget(
MaterialApp(
home: Scaffold(body: Center(child: icon)),
),
);
void main() {
test('parseBrandColor reads #RRGGBB and #AARRGGBB, and refuses the rest', () {
expect(parseBrandColor('#21A038'), const Color(0xFF21A038));
expect(parseBrandColor('80FF0000'), const Color(0x80FF0000));
expect(parseBrandColor(null), isNull);
expect(parseBrandColor('#12'), isNull);
expect(parseBrandColor('#GGGGGG'), isNull);
});
testWidgets('without a logo the class icon shows on the brand colour', (
tester,
) async {
await _pump(
tester,
const AssetIcon(assetClass: 'bond', logoColor: '#21A038'),
);
expect(find.byType(Image), findsNothing);
final icon = tester.widget<Icon>(find.byType(Icon));
expect(icon.icon, assetClassIcon('bond'));
expect(icon.color, Colors.white); // dark green → light glyph
final fill = tester.widget<ColoredBox>(find.byType(ColoredBox).last);
expect(fill.color, const Color(0xFF21A038));
});
testWidgets(
'with a logo an image is requested, and a failed load falls back',
(tester) async {
await _pump(
tester,
const AssetIcon(
assetClass: 'share',
logoUrl: 'http://127.0.0.1:1/none.png',
),
);
expect(find.byType(Image), findsOneWidget);
await tester.runAsync(
() => Future<void>.delayed(const Duration(milliseconds: 200)),
);
await tester.pump();
expect(find.byIcon(assetClassIcon('share')), findsOneWidget);
},
);
test('every asset class has its own icon, unknown ones a neutral one', () {
expect(assetClassIcon('share'), isNot(assetClassIcon('bond')));
expect(assetClassIcon('something-new'), Icons.circle_outlined);
});
}