Тема и общие виджеты (AssetIcon, ServiceMark, HelpTip, глоссарий), верхняя панель TopNav и вкладки Аналитики вместо NavSidebar, иконки PWA. Экраны: портфели, создание брокерского счёта, ручное событие, правка инструмента, Обзор карточками по скоупам и таблица активов, пересчёт метрик с опросом /metrics/status. design-system.md обновлён.
122 lines
3.7 KiB
Dart
122 lines
3.7 KiB
Dart
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',
|
|
);
|
|
});
|
|
}
|