Files
eatvio_app/test/features/insights/presentation/widgets/insights_card_test.dart
2026-11-09 15:35:10 +03:00

383 lines
14 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/insights/models/insights_period.dart';
import 'package:eatvio_app/features/insights/presentation/charts/bmi_chart.dart';
import 'package:eatvio_app/features/insights/presentation/charts/measure_chart.dart';
import 'package:eatvio_app/features/insights/presentation/charts/weight_chart.dart';
import 'package:eatvio_app/features/insights/presentation/widgets/insights_card.dart';
import 'package:eatvio_app/features/insights/repositories/insights_repository.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../../../fixtures/doubles/insights_doubles.dart';
void main() {
Widget wrap(Widget child) {
return MaterialApp(
theme: ThemeHelper().themeData(),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('ru')],
home: Scaffold(body: SingleChildScrollView(child: child)),
);
}
Future<void> pumpCard(
WidgetTester tester,
FakeInsightsRepository repository, {
String? slug,
bool canAdd = false,
bool kbjuEnabled = false,
bool expand = true,
}) async {
tester.view.physicalSize = const Size(900, 2400);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await tester.pumpWidget(
wrap(
InsightsCard(
slug: slug,
canAdd: canAdd,
repository: repository,
kbjuEnabled: kbjuEnabled,
),
),
);
await tester.pumpAndSettle();
if (expand &&
find.byKey(const Key('insights_toggle')).evaluate().isNotEmpty) {
await tester.tap(find.byKey(const Key('insights_toggle')));
await tester.pumpAndSettle();
}
}
group('свёрнутое состояние', () {
testWidgets('по умолчанию свёрнут: виден заголовок, графиков нет', (
tester,
) async {
await pumpCard(tester, FakeInsightsRepository(), expand: false);
expect(find.text('Аналитика'), findsOneWidget);
expect(find.text('нажмите, чтобы посмотреть прогресс'), findsOneWidget);
expect(find.byType(WeightChart), findsNothing);
expect(find.byKey(const Key('insights_tab_weight')), findsNothing);
});
testWidgets('тап по заголовку раскрывает и сворачивает блок', (
tester,
) async {
await pumpCard(tester, FakeInsightsRepository(), expand: false);
await tester.tap(find.byKey(const Key('insights_toggle')));
await tester.pumpAndSettle();
expect(find.byType(WeightChart), findsOneWidget);
expect(find.text('вес, замеры и питание'), findsOneWidget);
await tester.tap(find.byKey(const Key('insights_toggle')));
await tester.pumpAndSettle();
expect(find.byType(WeightChart), findsNothing);
});
});
group('вкладки', () {
testWidgets(
'показаны Вес, ИМТ, Замеры; вкладки КБЖУ скрыты при выключенном флаге',
(tester) async {
await pumpCard(tester, FakeInsightsRepository());
expect(find.byKey(const Key('insights_tab_weight')), findsOneWidget);
expect(find.byKey(const Key('insights_tab_bmi')), findsOneWidget);
expect(find.byKey(const Key('insights_tab_measures')), findsOneWidget);
expect(find.byKey(const Key('insights_tab_daily')), findsNothing);
expect(find.byKey(const Key('insights_tab_intra')), findsNothing);
},
);
testWidgets('вкладки переключают панели', (tester) async {
await pumpCard(tester, FakeInsightsRepository());
expect(find.byType(WeightChart), findsOneWidget);
expect(find.byKey(const Key('insights_headline')), findsOneWidget);
await tester.tap(find.byKey(const Key('insights_tab_measures')));
await tester.pumpAndSettle();
expect(find.byType(MeasureChart), findsOneWidget);
expect(find.byType(WeightChart), findsNothing);
await tester.tap(find.byKey(const Key('insights_tab_bmi')));
await tester.pumpAndSettle();
expect(find.byType(BmiChart), findsOneWidget);
expect(find.byType(MeasureChart), findsNothing);
await tester.tap(find.byKey(const Key('insights_tab_weight')));
await tester.pumpAndSettle();
expect(find.byType(WeightChart), findsOneWidget);
});
testWidgets('вкладки КБЖУ видны при включённом флаге', (tester) async {
await pumpCard(tester, FakeInsightsRepository(), kbjuEnabled: true);
expect(find.byKey(const Key('insights_tab_daily')), findsOneWidget);
expect(find.byKey(const Key('insights_tab_intra')), findsOneWidget);
});
});
group('вес', () {
testWidgets('показывает крупное число и текст дельты из payload', (
tester,
) async {
await pumpCard(tester, FakeInsightsRepository());
expect(find.text('73.2'), findsOneWidget);
expect(find.text('кг'), findsWidgets);
expect(find.text('Снизили на 1.3 кг за период'), findsOneWidget);
});
});
group('пилюли периодов', () {
testWidgets(
'по умолчанию запрошены 3 месяца; смена периода перезапрашивает данные',
(tester) async {
final repository = FakeInsightsRepository();
await pumpCard(tester, repository);
expect(
repository.insightsRequests.single.period,
BodyPeriod.threeMonths,
);
await tester.tap(find.byKey(const Key('insights_period_12')));
await tester.pumpAndSettle();
expect(repository.insightsRequests.map((e) => e.period), [
BodyPeriod.threeMonths,
BodyPeriod.year,
]);
await tester.tap(find.byKey(const Key('insights_period_all')));
await tester.pumpAndSettle();
expect(repository.insightsRequests.last.period, BodyPeriod.all);
},
);
testWidgets('повторный тап по активной пилюле не делает запрос', (
tester,
) async {
final repository = FakeInsightsRepository();
await pumpCard(tester, repository);
await tester.tap(find.byKey(const Key('insights_period_3')));
await tester.pumpAndSettle();
expect(repository.insightsRequests, hasLength(1));
});
testWidgets('slug пробрасывается в запросы', (tester) async {
final repository = FakeInsightsRepository();
await pumpCard(tester, repository, slug: 'client');
await tester.tap(find.byKey(const Key('insights_period_1')));
await tester.pumpAndSettle();
expect(repository.insightsRequests.map((e) => e.slug), [
'client',
'client',
]);
});
});
group('кнопка «+»', () {
testWidgets('нет при canAdd = false', (tester) async {
await pumpCard(tester, FakeInsightsRepository());
expect(find.byKey(const Key('insights_fab')), findsNothing);
});
testWidgets('есть на вкладках «Вес» и «Замеры», нет на «ИМТ»', (
tester,
) async {
await pumpCard(tester, FakeInsightsRepository(), canAdd: true);
expect(find.byKey(const Key('insights_fab')), findsOneWidget);
await tester.tap(find.byKey(const Key('insights_tab_bmi')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('insights_fab')), findsNothing);
await tester.tap(find.byKey(const Key('insights_tab_measures')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('insights_fab')), findsOneWidget);
});
testWidgets('нет в свёрнутом состоянии', (tester) async {
await pumpCard(
tester,
FakeInsightsRepository(),
canAdd: true,
expand: false,
);
expect(find.byKey(const Key('insights_fab')), findsNothing);
});
testWidgets(
'сохранение веса отправляет значение и перезапрашивает данные',
(tester) async {
final repository = FakeInsightsRepository();
await pumpCard(tester, repository, canAdd: true);
await tester.tap(find.byKey(const Key('insights_fab')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('add_weight_kilo_picker')), findsOneWidget);
await tester.tap(find.byKey(const Key('add_sheet_save')));
await tester.pumpAndSettle();
// Начальное значение шторки — последний вес из payload (73.2).
expect(repository.addedWeights, [73.2]);
expect(repository.insightsRequests, hasLength(2));
},
);
testWidgets(
'сохранение замеров отправляет значения и перезапрашивает данные',
(tester) async {
final repository = FakeInsightsRepository();
await pumpCard(tester, repository, canAdd: true);
await tester.tap(find.byKey(const Key('insights_tab_measures')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('insights_fab')));
await tester.pumpAndSettle();
expect(
find.byKey(const Key('add_circumference_waist_picker')),
findsOneWidget,
);
await tester.tap(find.byKey(const Key('add_sheet_save')));
await tester.pumpAndSettle();
// Последние значения из payload: грудь 96, талия 80.5 → 81 (округление), бёдра 100.
expect(repository.addedCircumferences, [
(chest: 96, waist: 81, hip: 100),
]);
expect(repository.insightsRequests, hasLength(2));
},
);
});
group('серии замеров', () {
testWidgets('чипы переключают видимость серии', (tester) async {
await pumpCard(tester, FakeInsightsRepository());
await tester.tap(find.byKey(const Key('insights_tab_measures')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('insights_series_0')), findsOneWidget);
expect(find.byKey(const Key('insights_series_1')), findsOneWidget);
expect(find.byKey(const Key('insights_series_2')), findsOneWidget);
expect(
tester
.widget<Opacity>(
find
.ancestor(
of: find.byKey(const Key('insights_series_1')),
matching: find.byType(Opacity),
)
.first,
)
.opacity,
1,
);
await tester.tap(find.byKey(const Key('insights_series_1')));
await tester.pumpAndSettle();
expect(
tester
.widget<Opacity>(
find
.ancestor(
of: find.byKey(const Key('insights_series_1')),
matching: find.byType(Opacity),
)
.first,
)
.opacity,
0.45,
);
});
});
group('ИМТ', () {
testWidgets('без значений показывает только deltaText', (tester) async {
await pumpCard(tester, FakeInsightsRepository());
await tester.tap(find.byKey(const Key('insights_tab_bmi')));
await tester.pumpAndSettle();
expect(find.text('Укажите рост, чтобы посчитать ИМТ.'), findsOneWidget);
expect(find.byKey(const Key('insights_bmi_chart')), findsNothing);
expect(find.byKey(const Key('insights_headline')), findsNothing);
});
testWidgets('со значениями рисует график и подсказку', (tester) async {
final payload = loadInsightsPayloadFixture().copyWith.bmi(
labels: ['01.11', '09.11'],
values: [26.1, 24.2],
last: 24.2,
delta: -1.9,
headline: '24.2',
category: 'норма',
deltaText: 'ИМТ ниже на 1.9 за 3 месяца · норма',
hint: 'Считаем при росте 176 см.',
);
await pumpCard(tester, FakeInsightsRepository(payload: payload));
await tester.tap(find.byKey(const Key('insights_tab_bmi')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('insights_bmi_chart')), findsOneWidget);
expect(find.byKey(const Key('insights_bmi_hint')), findsOneWidget);
expect(find.text('ИМТ ниже на 1.9 за 3 месяца · норма'), findsOneWidget);
});
});
group('скрытие и ошибки', () {
testWidgets('403 → блок скрыт целиком', (tester) async {
await pumpCard(
tester,
FakeInsightsRepository(error: const InsightsForbidden()),
slug: 'client',
expand: false,
);
expect(find.byKey(const Key('insights_card')), findsNothing);
expect(find.text('Аналитика'), findsNothing);
});
testWidgets(
'ошибка загрузки показывает кнопку «Повторить», повтор грузит данные',
(tester) async {
final repository = FakeInsightsRepository(error: Exception('boom'));
await pumpCard(tester, repository, expand: false);
expect(find.text('Не удалось загрузить аналитику'), findsOneWidget);
repository.error = null;
await tester.tap(find.byKey(const Key('insights_retry')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('insights_toggle')), findsOneWidget);
expect(find.text('Не удалось загрузить аналитику'), findsNothing);
},
);
});
}