forked from eatvio/eatvio_app
383 lines
14 KiB
Dart
383 lines
14 KiB
Dart
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);
|
||
},
|
||
);
|
||
});
|
||
}
|