feat(app): новый визуальный язык, верхняя навигация, портфели, создание счетов и ручные события
Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import 'package:fintracker_app/core/glossary.dart';
|
||||
import 'package:fintracker_app/core/widgets/help_tip.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
Widget _app(Widget child) => MaterialApp(
|
||||
home: Scaffold(body: Center(child: child)),
|
||||
);
|
||||
|
||||
void main() {
|
||||
test('the glossary finds a term regardless of case, spaces and «ё»', () {
|
||||
expect(glossaryHint('XIRR'), isNotNull);
|
||||
expect(glossaryHint(' xirr '), glossaryHint('XIRR'));
|
||||
expect(glossaryHint('Запас хода (runway)'), isNotNull);
|
||||
expect(glossaryHint('Внешний поток'), isNotNull);
|
||||
expect(
|
||||
glossaryHint('Счёт'),
|
||||
isNull,
|
||||
reason: 'an ordinary word is not a term',
|
||||
);
|
||||
});
|
||||
|
||||
test('every explanation is a real sentence, not a stub', () {
|
||||
for (final label in [
|
||||
'XIRR',
|
||||
'TWR',
|
||||
'НКД',
|
||||
'ЛДВ',
|
||||
'Пассивный доход',
|
||||
'Запас хода (runway)',
|
||||
]) {
|
||||
final text = glossaryHint(label)!;
|
||||
expect(text.length, greaterThan(40), reason: label);
|
||||
expect(text.trim().endsWith('.'), isTrue, reason: label);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('a known term carries a «?», an unknown label stays plain text', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
_app(
|
||||
const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [TermLabel('TWR'), TermLabel('Название')],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('TWR'), findsOneWidget);
|
||||
expect(find.text('Название'), findsOneWidget);
|
||||
expect(find.byIcon(Icons.help_outline), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('an explicit hint wins over the glossary', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
_app(const TermLabel('Прибыль', hint: 'Своё пояснение для налогов.')),
|
||||
);
|
||||
|
||||
final tip = tester.widget<Tooltip>(find.byType(Tooltip));
|
||||
expect(tip.message, 'Своё пояснение для налогов.');
|
||||
});
|
||||
|
||||
testWidgets('hovering the «?» with a mouse shows the explanation', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(_app(const TermLabel('XIRR')));
|
||||
|
||||
final mouse = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await mouse.addPointer(location: Offset.zero);
|
||||
addTearDown(mouse.removePointer);
|
||||
await mouse.moveTo(tester.getCenter(find.byIcon(Icons.help_outline)));
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
find.textContaining('годовая доходность с учётом дат'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('a tap shows it too, for a phone with no hover', (tester) async {
|
||||
await tester.pumpWidget(_app(const TermLabel('НКД')));
|
||||
|
||||
await tester.tap(find.byIcon(Icons.help_outline));
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
|
||||
expect(find.textContaining('накопленный купонный доход'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('a numeric header keeps its label at the end of the cell', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
_app(
|
||||
const SizedBox(
|
||||
width: 200,
|
||||
child: TermLabel(
|
||||
'XIRR',
|
||||
alignment: MainAxisAlignment.end,
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final label = tester.getRect(find.text('XIRR'));
|
||||
final icon = tester.getRect(find.byIcon(Icons.help_outline));
|
||||
expect(
|
||||
icon.left,
|
||||
greaterThanOrEqualTo(label.right),
|
||||
reason: 'the «?» follows the text',
|
||||
);
|
||||
expect(
|
||||
icon.right,
|
||||
greaterThan(150),
|
||||
reason: 'and the pair hugs the right edge',
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user