style(app): остальные экраны под новый визуальный язык и форматирование
Доходы, ребалансировка, налоги, импорт, цели, здоровье данных, правила, транзакции, категории, cashflow, pending: новые виджеты и токены темы, dart format. Тесты подстроены под новые модели.
This commit is contained in:
@@ -33,6 +33,10 @@ void main() {
|
||||
|
||||
test('non-date parameters are passed through untouched', () {
|
||||
final out = run({'page': 2, 'q': 'кофе', 'include_deleted': false});
|
||||
expect(out.queryParameters, {'page': 2, 'q': 'кофе', 'include_deleted': false});
|
||||
expect(out.queryParameters, {
|
||||
'page': 2,
|
||||
'q': 'кофе',
|
||||
'include_deleted': false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ EventOut event({
|
||||
kind: kind,
|
||||
price: '100',
|
||||
priceCurrency: currency,
|
||||
source_: 'tinvest',
|
||||
quantity: quantity,
|
||||
status: status,
|
||||
tax: null,
|
||||
@@ -38,13 +39,15 @@ EventOut event({
|
||||
}
|
||||
|
||||
Widget wrap(EventOut e) => MaterialApp(
|
||||
home: Scaffold(
|
||||
body: EventRow(event: e, accountName: 'T-Invest ИИС', onTap: () {}),
|
||||
),
|
||||
);
|
||||
home: Scaffold(
|
||||
body: EventRow(event: e, accountName: 'T-Invest ИИС', onTap: () {}),
|
||||
),
|
||||
);
|
||||
|
||||
void main() {
|
||||
testWidgets('shows kind, ticker, account and the native amount', (tester) async {
|
||||
testWidgets('shows kind, ticker, account and the native amount', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(wrap(event()));
|
||||
|
||||
expect(find.text('Покупка · SBER'), findsOneWidget);
|
||||
@@ -53,15 +56,23 @@ void main() {
|
||||
expect(find.text(MoneyText.format('-10000', 'RUB')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('adds the RUB equivalent only for a foreign currency', (tester) async {
|
||||
await tester.pumpWidget(wrap(event(currency: 'USD', amount: '-100', amountRub: '-9500')));
|
||||
testWidgets('adds the RUB equivalent only for a foreign currency', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
wrap(event(currency: 'USD', amount: '-100', amountRub: '-9500')),
|
||||
);
|
||||
|
||||
expect(find.text(MoneyText.format('-100', 'USD')), findsOneWidget);
|
||||
expect(find.text(MoneyText.format('-9500', 'RUB')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('leaves the RUB line out when no rate was available', (tester) async {
|
||||
await tester.pumpWidget(wrap(event(currency: 'USD', amount: '-100', amountRub: null)));
|
||||
testWidgets('leaves the RUB line out when no rate was available', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
wrap(event(currency: 'USD', amount: '-100', amountRub: null)),
|
||||
);
|
||||
|
||||
expect(find.text(MoneyText.format('-100', 'USD')), findsOneWidget);
|
||||
expect(find.textContaining('₽'), findsNothing);
|
||||
@@ -73,9 +84,18 @@ void main() {
|
||||
expect(find.textContaining('Ожидает'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('drops the separator for an event with no instrument', (tester) async {
|
||||
testWidgets('drops the separator for an event with no instrument', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
wrap(event(kind: EventKind.deposit, ticker: null, quantity: null, externalFlow: true)),
|
||||
wrap(
|
||||
event(
|
||||
kind: EventKind.deposit,
|
||||
ticker: null,
|
||||
quantity: null,
|
||||
externalFlow: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Пополнение'), findsOneWidget);
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
import 'package:fintracker_api/fintracker_api.dart';
|
||||
import 'package:fintracker_app/core/cache/cached.dart';
|
||||
import 'package:fintracker_app/features/health/health_page.dart';
|
||||
import 'package:fintracker_app/features/home/providers.dart' show dataQualityProvider;
|
||||
import 'package:fintracker_app/features/home/providers.dart'
|
||||
show dataQualityProvider;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
SourceStatus _source(String name) => SourceStatus(
|
||||
source_: name,
|
||||
lastRunStatus: RunStatus.ok,
|
||||
lastRunAt: DateTime(2026, 9, 18, 10),
|
||||
lastSuccessAt: DateTime(2026, 9, 18, 10),
|
||||
cursor: null,
|
||||
queued: false,
|
||||
);
|
||||
source_: name,
|
||||
lastRunStatus: RunStatus.ok,
|
||||
lastRunAt: DateTime(2026, 9, 18, 10),
|
||||
lastSuccessAt: DateTime(2026, 9, 18, 10),
|
||||
cursor: null,
|
||||
queued: false,
|
||||
);
|
||||
|
||||
DataQualityRow _issue() => DataQualityRow(
|
||||
id: 1,
|
||||
ref: null,
|
||||
severity: 'warn',
|
||||
checkName: 'missing_fx',
|
||||
count: 3,
|
||||
detail: 'Курс не найден для 3 операций.',
|
||||
computedAt: DateTime(2026, 9, 18, 10),
|
||||
);
|
||||
id: 1,
|
||||
ref: null,
|
||||
severity: 'warn',
|
||||
checkName: 'missing_fx',
|
||||
count: 3,
|
||||
detail: 'Курс не найден для 3 операций.',
|
||||
computedAt: DateTime(2026, 9, 18, 10),
|
||||
);
|
||||
|
||||
void main() {
|
||||
testWidgets('shows sources on the first tab and findings on the second', (tester) async {
|
||||
testWidgets('shows sources on the first tab and findings on the second', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
@@ -50,7 +53,9 @@ void main() {
|
||||
expect(find.text('Курс не найден для 3 операций.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('opens directly on the findings tab when initialTab is 1', (tester) async {
|
||||
testWidgets('opens directly on the findings tab when initialTab is 1', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
@@ -72,7 +77,9 @@ void main() {
|
||||
overrides: [
|
||||
syncStatusProvider.overrideWith((ref) async => const []),
|
||||
syncRunsProvider.overrideWith((ref) async => const []),
|
||||
dataQualityProvider.overrideWith((ref) => const Cached(<DataQualityRow>[])),
|
||||
dataQualityProvider.overrideWith(
|
||||
(ref) => const Cached(<DataQualityRow>[]),
|
||||
),
|
||||
],
|
||||
child: const MaterialApp(home: HealthPage(initialTab: 1)),
|
||||
),
|
||||
|
||||
@@ -145,36 +145,45 @@ void main() {
|
||||
final json = Map<String, dynamic>.from(_previewJson)..['account_id'] = null;
|
||||
expect(ImportPreview.fromJson(json).canCommit, isFalse);
|
||||
|
||||
final failed = Map<String, dynamic>.from(_previewJson)..['parse_status'] = 'failed';
|
||||
final failed = Map<String, dynamic>.from(_previewJson)
|
||||
..['parse_status'] = 'failed';
|
||||
expect(ImportPreview.fromJson(failed).canCommit, isFalse);
|
||||
expect(ImportPreview.fromJson(failed).canDelete, isTrue);
|
||||
|
||||
final committed = Map<String, dynamic>.from(_previewJson)..['parse_status'] = 'committed';
|
||||
final committed = Map<String, dynamic>.from(_previewJson)
|
||||
..['parse_status'] = 'committed';
|
||||
expect(ImportPreview.fromJson(committed).canDelete, isFalse);
|
||||
});
|
||||
|
||||
test('parses PendingResolveResult and builds the create body as plain strings', () {
|
||||
final r = PendingResolveResult.fromJson(const {
|
||||
'id': 4,
|
||||
'status': 'resolved',
|
||||
'instrument_id': 88,
|
||||
'events_bound': 3,
|
||||
'alias_created': true,
|
||||
'metrics_refreshed': true,
|
||||
});
|
||||
expect(r.eventsBound, 3);
|
||||
expect(r.instrumentId, 88);
|
||||
test(
|
||||
'parses PendingResolveResult and builds the create body as plain strings',
|
||||
() {
|
||||
final r = PendingResolveResult.fromJson(const {
|
||||
'id': 4,
|
||||
'status': 'resolved',
|
||||
'instrument_id': 88,
|
||||
'events_bound': 3,
|
||||
'alias_created': true,
|
||||
'metrics_refreshed': true,
|
||||
});
|
||||
expect(r.eventsBound, 3);
|
||||
expect(r.instrumentId, 88);
|
||||
|
||||
final body = const NewInstrument(
|
||||
assetClass: 'index',
|
||||
name: 'Индекс МосБиржи',
|
||||
currency: 'RUB',
|
||||
isin: '',
|
||||
ticker: 'IMOEX',
|
||||
lot: 1,
|
||||
).toJson();
|
||||
expect(body['asset_class'], 'index');
|
||||
expect(body.containsKey('isin'), isFalse, reason: 'empty optionals are omitted');
|
||||
expect(body['ticker'], 'IMOEX');
|
||||
});
|
||||
final body = const NewInstrument(
|
||||
assetClass: 'index',
|
||||
name: 'Индекс МосБиржи',
|
||||
currency: 'RUB',
|
||||
isin: '',
|
||||
ticker: 'IMOEX',
|
||||
lot: 1,
|
||||
).toJson();
|
||||
expect(body['asset_class'], 'index');
|
||||
expect(
|
||||
body.containsKey('isin'),
|
||||
isFalse,
|
||||
reason: 'empty optionals are omitted',
|
||||
);
|
||||
expect(body['ticker'], 'IMOEX');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
+147
-104
@@ -33,7 +33,11 @@ void main() {
|
||||
'tax_withheld': null,
|
||||
},
|
||||
],
|
||||
'by_basis': {'schedule': '8100.00', 'announced': '4130.50', 'history': '2000.00'},
|
||||
'by_basis': {
|
||||
'schedule': '8100.00',
|
||||
'announced': '4130.50',
|
||||
'history': '2000.00',
|
||||
},
|
||||
};
|
||||
|
||||
test('parses the calendar, keeping every amount as a string', () {
|
||||
@@ -44,13 +48,20 @@ void main() {
|
||||
expect(c.entries.single.basis, 'announced');
|
||||
expect(c.entries.single.ticker, 'SBER');
|
||||
expect(c.entries.single.taxWithheld, isNull);
|
||||
expect(c.entries.single.perUnit, '34.84', reason: 'never parsed through double');
|
||||
expect(
|
||||
c.entries.single.perUnit,
|
||||
'34.84',
|
||||
reason: 'never parsed through double',
|
||||
);
|
||||
});
|
||||
|
||||
test('a null amount_rub stays null — no FX rate is not zero', () {
|
||||
final json = Map<String, dynamic>.from(calendarJson);
|
||||
json['entries'] = [
|
||||
{...(calendarJson['entries'] as List).first as Map<String, dynamic>, 'amount_rub': null},
|
||||
{
|
||||
...(calendarJson['entries'] as List).first as Map<String, dynamic>,
|
||||
'amount_rub': null,
|
||||
},
|
||||
];
|
||||
expect(IncomeCalendar.fromJson(json).entries.single.amountRub, isNull);
|
||||
});
|
||||
@@ -82,18 +93,28 @@ void main() {
|
||||
{
|
||||
'month': '2026-10-01',
|
||||
'amount_rub': '1830.20',
|
||||
'by_basis': {'schedule': '1133.40', 'announced': '696.80', 'history': '0'},
|
||||
'by_basis': {
|
||||
'schedule': '1133.40',
|
||||
'announced': '696.80',
|
||||
'history': '0',
|
||||
},
|
||||
},
|
||||
],
|
||||
'total_rub': '21960.00',
|
||||
'annual_yield_on_value': '0.081',
|
||||
'warnings': ['у 3 инструментов нет истории выплат — в прогноз не вошли'],
|
||||
'warnings': [
|
||||
'у 3 инструментов нет истории выплат — в прогноз не вошли',
|
||||
],
|
||||
});
|
||||
expect(f.months.single.byBasis['schedule'], '1133.40');
|
||||
expect(f.totalRub, '21960.00');
|
||||
expect(f.annualYieldOnValue, '0.081');
|
||||
expect(f.warnings, hasLength(1));
|
||||
expect(f.bases, ['schedule', 'announced', 'history'], reason: 'contract order');
|
||||
expect(f.bases, [
|
||||
'schedule',
|
||||
'announced',
|
||||
'history',
|
||||
], reason: 'contract order');
|
||||
|
||||
final noYield = IncomeForecast.fromJson(const {
|
||||
'months': <dynamic>[],
|
||||
@@ -110,7 +131,12 @@ void main() {
|
||||
'dimension': 'asset_class',
|
||||
'weights_sum': '1.00',
|
||||
'targets': [
|
||||
{'bucket': 'share', 'target_weight': '0.60', 'band': '0.05', 'note': null},
|
||||
{
|
||||
'bucket': 'share',
|
||||
'target_weight': '0.60',
|
||||
'band': '0.05',
|
||||
'note': null,
|
||||
},
|
||||
{'bucket': 'bond', 'target_weight': '0.30', 'band': '0.05'},
|
||||
{'bucket': 'cash', 'target_weight': '0.10', 'band': '0.02'},
|
||||
],
|
||||
@@ -121,22 +147,29 @@ void main() {
|
||||
expect(set.targets.first.band, '0.05');
|
||||
|
||||
// 0.1 + 0.2 + 0.7 is exactly 1 in Decimal and would not be in double
|
||||
final tenths = TargetSet(dimension: 'asset_class', targets: const [
|
||||
TargetWeight(bucket: 'a', targetWeight: '0.1'),
|
||||
TargetWeight(bucket: 'b', targetWeight: '0.2'),
|
||||
TargetWeight(bucket: 'c', targetWeight: '0.7'),
|
||||
]);
|
||||
final tenths = TargetSet(
|
||||
dimension: 'asset_class',
|
||||
targets: const [
|
||||
TargetWeight(bucket: 'a', targetWeight: '0.1'),
|
||||
TargetWeight(bucket: 'b', targetWeight: '0.2'),
|
||||
TargetWeight(bucket: 'c', targetWeight: '0.7'),
|
||||
],
|
||||
);
|
||||
expect(tenths.sumIsValid, isTrue);
|
||||
|
||||
final short = TargetSet(dimension: 'asset_class', targets: const [
|
||||
TargetWeight(bucket: 'a', targetWeight: '0.9'),
|
||||
]);
|
||||
final short = TargetSet(
|
||||
dimension: 'asset_class',
|
||||
targets: const [TargetWeight(bucket: 'a', targetWeight: '0.9')],
|
||||
);
|
||||
expect(short.sumIsValid, isFalse);
|
||||
});
|
||||
|
||||
test('the PUT body omits empty optionals and keeps shares as strings', () {
|
||||
final body = const TargetWeight(bucket: 'bond', targetWeight: '0.30', band: '0.05')
|
||||
.toJson();
|
||||
final body = const TargetWeight(
|
||||
bucket: 'bond',
|
||||
targetWeight: '0.30',
|
||||
band: '0.05',
|
||||
).toJson();
|
||||
expect(body['target_weight'], '0.30');
|
||||
expect(body.containsKey('note'), isFalse);
|
||||
});
|
||||
@@ -151,51 +184,54 @@ void main() {
|
||||
expect(formatShareAsPercent(null), '—');
|
||||
});
|
||||
|
||||
test('parses the rebalance plan, including within_band and blocked_by_cash', () {
|
||||
final plan = RebalancePlan.fromJson(const {
|
||||
'portfolio_id': 1,
|
||||
'dimension': 'asset_class',
|
||||
'as_of': '2026-09-18',
|
||||
'total_value_rub': '1284300.00',
|
||||
'cash_available_rub': '48120.87',
|
||||
'buckets': [
|
||||
{
|
||||
'bucket': 'share',
|
||||
'current_value_rub': '812000.00',
|
||||
'current_weight': '0.632',
|
||||
'target_weight': '0.60',
|
||||
'drift': '0.032',
|
||||
'within_band': true,
|
||||
'delta_value_rub': '-41420.00',
|
||||
'trades': [
|
||||
{
|
||||
'instrument_id': 88,
|
||||
'ticker': 'SBER',
|
||||
'name': 'Сбербанк России',
|
||||
'action': 'sell',
|
||||
'suggested_qty': '150',
|
||||
'lot': 10,
|
||||
'price': '275.89',
|
||||
'price_currency': 'RUB',
|
||||
'amount_rub': '41383.50',
|
||||
'blocked_by_cash': false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
'warnings': ['у 2 инструментов нет цены — в рекомендации не вошли'],
|
||||
});
|
||||
test(
|
||||
'parses the rebalance plan, including within_band and blocked_by_cash',
|
||||
() {
|
||||
final plan = RebalancePlan.fromJson(const {
|
||||
'portfolio_id': 1,
|
||||
'dimension': 'asset_class',
|
||||
'as_of': '2026-09-18',
|
||||
'total_value_rub': '1284300.00',
|
||||
'cash_available_rub': '48120.87',
|
||||
'buckets': [
|
||||
{
|
||||
'bucket': 'share',
|
||||
'current_value_rub': '812000.00',
|
||||
'current_weight': '0.632',
|
||||
'target_weight': '0.60',
|
||||
'drift': '0.032',
|
||||
'within_band': true,
|
||||
'delta_value_rub': '-41420.00',
|
||||
'trades': [
|
||||
{
|
||||
'instrument_id': 88,
|
||||
'ticker': 'SBER',
|
||||
'name': 'Сбербанк России',
|
||||
'action': 'sell',
|
||||
'suggested_qty': '150',
|
||||
'lot': 10,
|
||||
'price': '275.89',
|
||||
'price_currency': 'RUB',
|
||||
'amount_rub': '41383.50',
|
||||
'blocked_by_cash': false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
'warnings': ['у 2 инструментов нет цены — в рекомендации не вошли'],
|
||||
});
|
||||
|
||||
expect(plan.portfolioId, 1);
|
||||
expect(plan.buckets.single.withinBand, isTrue);
|
||||
expect(plan.everythingWithinBand, isTrue);
|
||||
final trade = plan.buckets.single.trades.single;
|
||||
expect(trade.action, 'sell');
|
||||
expect(trade.suggestedQty, '150');
|
||||
expect(trade.lot, 10);
|
||||
expect(trade.blockedByCash, isFalse);
|
||||
expect(plan.warnings, hasLength(1));
|
||||
});
|
||||
expect(plan.portfolioId, 1);
|
||||
expect(plan.buckets.single.withinBand, isTrue);
|
||||
expect(plan.everythingWithinBand, isTrue);
|
||||
final trade = plan.buckets.single.trades.single;
|
||||
expect(trade.action, 'sell');
|
||||
expect(trade.suggestedQty, '150');
|
||||
expect(trade.lot, 10);
|
||||
expect(trade.blockedByCash, isFalse);
|
||||
expect(plan.warnings, hasLength(1));
|
||||
},
|
||||
);
|
||||
|
||||
test('suggested_qty stays null when there is no price', () {
|
||||
final trade = RebalanceTrade.fromJson(const {
|
||||
@@ -214,49 +250,55 @@ void main() {
|
||||
});
|
||||
|
||||
group('benchmarks', () {
|
||||
test('parses a period row with excess, kind and days_skipped on both sides', () {
|
||||
final rows = [
|
||||
for (final r in const [
|
||||
{
|
||||
'period': '1y',
|
||||
'date_from': '2025-09-18',
|
||||
'date_to': '2026-09-18',
|
||||
'portfolio_twr': '0.184',
|
||||
'portfolio_twr_annualized': '0.184',
|
||||
'portfolio_days_skipped': 3,
|
||||
'benchmarks': [
|
||||
{
|
||||
'benchmark_id': 1,
|
||||
'code': 'MCFTR',
|
||||
'kind': 'total_return',
|
||||
'twr': '0.121',
|
||||
'twr_annualized': '0.121',
|
||||
'days_skipped': 0,
|
||||
'excess': '0.063',
|
||||
},
|
||||
{
|
||||
'benchmark_id': 2,
|
||||
'code': 'IMOEX',
|
||||
'kind': 'price',
|
||||
'twr': '0.084',
|
||||
'days_skipped': 2,
|
||||
'excess': '0.100',
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
BenchmarkRow.fromJson(r),
|
||||
];
|
||||
test(
|
||||
'parses a period row with excess, kind and days_skipped on both sides',
|
||||
() {
|
||||
final rows = [
|
||||
for (final r in const [
|
||||
{
|
||||
'period': '1y',
|
||||
'date_from': '2025-09-18',
|
||||
'date_to': '2026-09-18',
|
||||
'portfolio_twr': '0.184',
|
||||
'portfolio_twr_annualized': '0.184',
|
||||
'portfolio_days_skipped': 3,
|
||||
'benchmarks': [
|
||||
{
|
||||
'benchmark_id': 1,
|
||||
'code': 'MCFTR',
|
||||
'kind': 'total_return',
|
||||
'twr': '0.121',
|
||||
'twr_annualized': '0.121',
|
||||
'days_skipped': 0,
|
||||
'excess': '0.063',
|
||||
},
|
||||
{
|
||||
'benchmark_id': 2,
|
||||
'code': 'IMOEX',
|
||||
'kind': 'price',
|
||||
'twr': '0.084',
|
||||
'days_skipped': 2,
|
||||
'excess': '0.100',
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
BenchmarkRow.fromJson(r),
|
||||
];
|
||||
|
||||
final row = rows.single;
|
||||
expect(row.period, '1y');
|
||||
expect(row.portfolioDaysSkipped, 3);
|
||||
expect(row.hasSkippedDays, isTrue);
|
||||
expect(row.benchmarks.first.isPriceIndex, isFalse);
|
||||
expect(row.benchmarks.last.isPriceIndex, isTrue,
|
||||
reason: 'a price index must be markable');
|
||||
expect(row.benchmarks.first.excess, '0.063');
|
||||
});
|
||||
final row = rows.single;
|
||||
expect(row.period, '1y');
|
||||
expect(row.portfolioDaysSkipped, 3);
|
||||
expect(row.hasSkippedDays, isTrue);
|
||||
expect(row.benchmarks.first.isPriceIndex, isFalse);
|
||||
expect(
|
||||
row.benchmarks.last.isPriceIndex,
|
||||
isTrue,
|
||||
reason: 'a price index must be markable',
|
||||
);
|
||||
expect(row.benchmarks.first.excess, '0.063');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('goals', () {
|
||||
@@ -346,7 +388,8 @@ void main() {
|
||||
'dividends_gross_rub': '12400.00',
|
||||
'estimated_tax_rub': '1963.00',
|
||||
},
|
||||
'disclaimer': 'Оценка. Налоговый агент — брокер; сверяйтесь с его справкой.',
|
||||
'disclaimer':
|
||||
'Оценка. Налоговый агент — брокер; сверяйтесь с его справкой.',
|
||||
});
|
||||
|
||||
expect(s.year, 2026);
|
||||
|
||||
@@ -7,7 +7,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('the add-rule dialog requires a non-empty pattern', (tester) async {
|
||||
testWidgets('the add-rule dialog requires a non-empty pattern', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
|
||||
@@ -5,33 +5,35 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
TransactionOut _usdExpense() => TransactionOut(
|
||||
categoryId: 1,
|
||||
comment: null,
|
||||
date: DateTime(2026, 9, 10),
|
||||
deleted: false,
|
||||
flowType: FlowType.expense,
|
||||
hold: false,
|
||||
id: 1,
|
||||
income: '0',
|
||||
incomeAccountId: null,
|
||||
incomeCurrency: null,
|
||||
incomeRub: null,
|
||||
isOneOff: false,
|
||||
mcc: null,
|
||||
outcome: '100',
|
||||
outcomeAccountId: 7,
|
||||
outcomeCurrency: 'USD',
|
||||
outcomeRub: '9500.00',
|
||||
payee: 'Amazon.com',
|
||||
payeeCanonical: 'Amazon',
|
||||
sourceId: 'src-1',
|
||||
tags: const [],
|
||||
tripId: null,
|
||||
ts: DateTime(2026, 9, 10),
|
||||
);
|
||||
categoryId: 1,
|
||||
comment: null,
|
||||
date: DateTime(2026, 9, 10),
|
||||
deleted: false,
|
||||
flowType: FlowType.expense,
|
||||
hold: false,
|
||||
id: 1,
|
||||
income: '0',
|
||||
incomeAccountId: null,
|
||||
incomeCurrency: null,
|
||||
incomeRub: null,
|
||||
isOneOff: false,
|
||||
mcc: null,
|
||||
outcome: '100',
|
||||
outcomeAccountId: 7,
|
||||
outcomeCurrency: 'USD',
|
||||
outcomeRub: '9500.00',
|
||||
payee: 'Amazon.com',
|
||||
payeeCanonical: 'Amazon',
|
||||
sourceId: 'src-1',
|
||||
tags: const [],
|
||||
tripId: null,
|
||||
ts: DateTime(2026, 9, 10),
|
||||
);
|
||||
|
||||
void main() {
|
||||
testWidgets('shows a USD amount with its RUB equivalent as secondary text', (tester) async {
|
||||
testWidgets('shows a USD amount with its RUB equivalent as secondary text', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
|
||||
@@ -3,7 +3,10 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('AuthState.signedIn reflects status', () {
|
||||
expect(const AuthState(AuthStatus.signedIn, accessToken: 't').signedIn, isTrue);
|
||||
expect(
|
||||
const AuthState(AuthStatus.signedIn, accessToken: 't').signedIn,
|
||||
isTrue,
|
||||
);
|
||||
expect(const AuthState(AuthStatus.signedOut).signedIn, isFalse);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user