forked from eatvio/eatvio_app
feat(insights): review fixes
This commit is contained in:
@@ -49,7 +49,9 @@ void main() {
|
||||
AppConfig(buildType: AppBuildType.tests)
|
||||
..setBaseUrl('http://localhost'),
|
||||
);
|
||||
sl.registerLazySingleton<InsightsRepository>(() => FakeInsightsRepository());
|
||||
sl.registerLazySingleton<InsightsRepository>(
|
||||
() => FakeInsightsRepository(),
|
||||
);
|
||||
});
|
||||
|
||||
Widget wrap(Widget child) {
|
||||
@@ -401,17 +403,31 @@ void main() {
|
||||
required FakeInsightsRepository insightsRepository,
|
||||
}) async {
|
||||
final diaryCubit = MockDiaryCubit();
|
||||
final state = loadedState(hasChosenPlan: false, ownerSlug: diaryOwnerSlug).copyWith(isConsultantView: isConsultantView);
|
||||
final state = loadedState(
|
||||
hasChosenPlan: false,
|
||||
ownerSlug: diaryOwnerSlug,
|
||||
).copyWith(isConsultantView: isConsultantView);
|
||||
when(diaryCubit.state).thenReturn(state);
|
||||
when(diaryCubit.stream).thenAnswer((_) => diaryStates ?? const Stream<DiaryState>.empty());
|
||||
when(
|
||||
diaryCubit.stream,
|
||||
).thenAnswer((_) => diaryStates ?? const Stream<DiaryState>.empty());
|
||||
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(
|
||||
diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId')),
|
||||
diaryCubit.fetchDiaryV2(
|
||||
needLoading: anyNamed('needLoading'),
|
||||
diaryId: anyNamed('diaryId'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
|
||||
final commentsCubit = MockConsultantCommentsCubit();
|
||||
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
|
||||
when(commentsCubit.stream).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
|
||||
when(commentsCubit.state).thenReturn(
|
||||
ConsultantCommentsState(
|
||||
commentsModel: ConsultantCommentModel(comment: ''),
|
||||
),
|
||||
);
|
||||
when(
|
||||
commentsCubit.stream,
|
||||
).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
|
||||
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
|
||||
sl.registerFactory<DiaryCubit>(() => diaryCubit);
|
||||
@@ -420,43 +436,84 @@ void main() {
|
||||
sl.registerLazySingleton<InsightsRepository>(() => insightsRepository);
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(isConsultantView ? UserDiaryScreen(diaryId: 1, ownerSlug: ownerSlug) : DiaryScreen(date: DateTime(2025, 1, 1))),
|
||||
wrap(
|
||||
isConsultantView
|
||||
? UserDiaryScreen(diaryId: 1, ownerSlug: ownerSlug)
|
||||
: DiaryScreen(date: DateTime(2025, 1, 1)),
|
||||
),
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
testWidgets('own diary shows insights card for own data with add button enabled', (tester) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
testWidgets(
|
||||
'own diary shows insights card for own data with add button enabled',
|
||||
(tester) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
|
||||
await pumpDiaryWithInsights(tester, isConsultantView: false, insightsRepository: insightsRepository);
|
||||
await pumpDiaryWithInsights(
|
||||
tester,
|
||||
isConsultantView: false,
|
||||
insightsRepository: insightsRepository,
|
||||
);
|
||||
|
||||
expect(find.byType(InsightsCard), findsOneWidget);
|
||||
expect(tester.widget<InsightsCard>(find.byType(InsightsCard)).slug, isNull);
|
||||
expect(tester.widget<InsightsCard>(find.byType(InsightsCard)).canAdd, isTrue);
|
||||
expect(insightsRepository.serverRequests.single.slug, isNull);
|
||||
});
|
||||
expect(find.byType(InsightsCard), findsOneWidget);
|
||||
final card = tester.widget<InsightsCard>(find.byType(InsightsCard));
|
||||
expect(card.slug, isNull);
|
||||
expect(card.canAdd, isTrue);
|
||||
expect(card.initialIntraDate, '2025-01-01');
|
||||
expect(insightsRepository.serverRequests.single.slug, isNull);
|
||||
expect(
|
||||
insightsRepository.serverRequests.single.intraDate,
|
||||
'2025-01-01',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('consultant view shows insights card of the client without add button', (tester) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
testWidgets(
|
||||
'consultant view shows insights card of the client without add button',
|
||||
(tester) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
|
||||
await pumpDiaryWithInsights(tester, isConsultantView: true, ownerSlug: 'client', insightsRepository: insightsRepository);
|
||||
await pumpDiaryWithInsights(
|
||||
tester,
|
||||
isConsultantView: true,
|
||||
ownerSlug: 'client',
|
||||
insightsRepository: insightsRepository,
|
||||
);
|
||||
|
||||
final card = tester.widget<InsightsCard>(find.byType(InsightsCard));
|
||||
expect(card.slug, 'client');
|
||||
expect(card.canAdd, isFalse);
|
||||
expect(insightsRepository.serverRequests.single.slug, 'client');
|
||||
});
|
||||
final card = tester.widget<InsightsCard>(find.byType(InsightsCard));
|
||||
expect(card.slug, 'client');
|
||||
expect(card.canAdd, isFalse);
|
||||
expect(insightsRepository.serverRequests.single.slug, 'client');
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('consultant view takes the owner slug from the diary (user.slug)', (tester) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
testWidgets(
|
||||
'consultant view takes the owner slug from the diary (user.slug)',
|
||||
(tester) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
|
||||
await pumpDiaryWithInsights(tester, isConsultantView: true, diaryOwnerSlug: 'client-from-diary', insightsRepository: insightsRepository);
|
||||
await pumpDiaryWithInsights(
|
||||
tester,
|
||||
isConsultantView: true,
|
||||
diaryOwnerSlug: 'client-from-diary',
|
||||
insightsRepository: insightsRepository,
|
||||
);
|
||||
|
||||
expect(tester.widget<InsightsCard>(find.byType(InsightsCard)).slug, 'client-from-diary');
|
||||
expect(insightsRepository.serverRequests.single.slug, 'client-from-diary');
|
||||
});
|
||||
expect(
|
||||
tester.widget<InsightsCard>(find.byType(InsightsCard)).slug,
|
||||
'client-from-diary',
|
||||
);
|
||||
expect(
|
||||
insightsRepository.serverRequests.single.slug,
|
||||
'client-from-diary',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('diary owner slug wins over the route parameter', (tester) async {
|
||||
testWidgets('diary owner slug wins over the route parameter', (
|
||||
tester,
|
||||
) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
|
||||
await pumpDiaryWithInsights(
|
||||
@@ -467,37 +524,58 @@ void main() {
|
||||
insightsRepository: insightsRepository,
|
||||
);
|
||||
|
||||
expect(tester.widget<InsightsCard>(find.byType(InsightsCard)).slug, 'from-diary');
|
||||
expect(
|
||||
tester.widget<InsightsCard>(find.byType(InsightsCard)).slug,
|
||||
'from-diary',
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('own diary refreshes insights when meals or wake-up time change', (tester) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
final states = StreamController<DiaryState>.broadcast();
|
||||
addTearDown(states.close);
|
||||
testWidgets(
|
||||
'own diary refreshes insights when meals or wake-up time change',
|
||||
(tester) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
final states = StreamController<DiaryState>.broadcast();
|
||||
addTearDown(states.close);
|
||||
|
||||
await pumpDiaryWithInsights(tester, isConsultantView: false, diaryStates: states.stream, insightsRepository: insightsRepository);
|
||||
expect(insightsRepository.serverRequests, hasLength(1));
|
||||
await pumpDiaryWithInsights(
|
||||
tester,
|
||||
isConsultantView: false,
|
||||
diaryStates: states.stream,
|
||||
insightsRepository: insightsRepository,
|
||||
);
|
||||
expect(insightsRepository.serverRequests, hasLength(1));
|
||||
|
||||
states.add(loadedState(hasChosenPlan: false, wakeup: '06:30'));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
states.add(loadedState(hasChosenPlan: false, wakeup: '06:30'));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(insightsRepository.serverRequests, hasLength(2));
|
||||
});
|
||||
expect(insightsRepository.serverRequests, hasLength(2));
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('own diary does not refresh insights when nothing relevant changed', (tester) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
final states = StreamController<DiaryState>.broadcast();
|
||||
addTearDown(states.close);
|
||||
testWidgets(
|
||||
'own diary does not refresh insights when nothing relevant changed',
|
||||
(tester) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
final states = StreamController<DiaryState>.broadcast();
|
||||
addTearDown(states.close);
|
||||
|
||||
await pumpDiaryWithInsights(tester, isConsultantView: false, diaryStates: states.stream, insightsRepository: insightsRepository);
|
||||
await pumpDiaryWithInsights(
|
||||
tester,
|
||||
isConsultantView: false,
|
||||
diaryStates: states.stream,
|
||||
insightsRepository: insightsRepository,
|
||||
);
|
||||
|
||||
states.add(loadedState(hasChosenPlan: false));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
states.add(loadedState(hasChosenPlan: false));
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(insightsRepository.serverRequests, hasLength(1));
|
||||
});
|
||||
expect(insightsRepository.serverRequests, hasLength(1));
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('consultant view does not refresh insights on diary changes', (tester) async {
|
||||
testWidgets('consultant view does not refresh insights on diary changes', (
|
||||
tester,
|
||||
) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
final states = StreamController<DiaryState>.broadcast();
|
||||
addTearDown(states.close);
|
||||
@@ -510,25 +588,43 @@ void main() {
|
||||
insightsRepository: insightsRepository,
|
||||
);
|
||||
|
||||
states.add(loadedState(hasChosenPlan: false, wakeup: '06:30').copyWith(isConsultantView: true));
|
||||
states.add(
|
||||
loadedState(
|
||||
hasChosenPlan: false,
|
||||
wakeup: '06:30',
|
||||
).copyWith(isConsultantView: true),
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
expect(insightsRepository.serverRequests, hasLength(1));
|
||||
});
|
||||
|
||||
testWidgets('consultant view without owner slug hides insights card', (tester) async {
|
||||
testWidgets('consultant view without owner slug hides insights card', (
|
||||
tester,
|
||||
) async {
|
||||
final insightsRepository = FakeInsightsRepository();
|
||||
|
||||
await pumpDiaryWithInsights(tester, isConsultantView: true, insightsRepository: insightsRepository);
|
||||
await pumpDiaryWithInsights(
|
||||
tester,
|
||||
isConsultantView: true,
|
||||
insightsRepository: insightsRepository,
|
||||
);
|
||||
|
||||
expect(find.byType(InsightsCard), findsNothing);
|
||||
expect(insightsRepository.serverRequests, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('consultant view hides insights card on 403', (tester) async {
|
||||
final insightsRepository = FakeInsightsRepository(error: const InsightsForbidden());
|
||||
final insightsRepository = FakeInsightsRepository(
|
||||
error: const InsightsForbidden(),
|
||||
);
|
||||
|
||||
await pumpDiaryWithInsights(tester, isConsultantView: true, ownerSlug: 'client', insightsRepository: insightsRepository);
|
||||
await pumpDiaryWithInsights(
|
||||
tester,
|
||||
isConsultantView: true,
|
||||
ownerSlug: 'client',
|
||||
insightsRepository: insightsRepository,
|
||||
);
|
||||
|
||||
expect(find.byKey(const Key('insights_card')), findsNothing);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'package:eatvio_app/features/insights/presentation/charts/insights_chart_common.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
|
||||
void main() {
|
||||
test('insightsSplineType — monotonic, чтобы кривая не уходила ниже точек', () {
|
||||
expect(insightsSplineType, SplineType.monotonic);
|
||||
});
|
||||
|
||||
group('buildPoints', () {
|
||||
test('сохраняет индекс и дату даже при повторяющихся labels', () {
|
||||
final points = buildPoints(
|
||||
['05.07', '05.07', '06.07', '07.07'],
|
||||
[10, 20, 30, 40],
|
||||
);
|
||||
|
||||
expect(points.map((p) => p.index).toList(), [0, 1, 2, 3]);
|
||||
expect(points.map((p) => p.label).toList(), [
|
||||
'05.07',
|
||||
'05.07',
|
||||
'06.07',
|
||||
'07.07',
|
||||
]);
|
||||
expect(points.map((p) => p.value).toList(), [10, 20, 30, 40]);
|
||||
});
|
||||
|
||||
test('обрезает по короткому массиву', () {
|
||||
expect(buildPoints(['a', 'b'], [1]), hasLength(1));
|
||||
});
|
||||
});
|
||||
|
||||
group('buildInsightsIndexAxis', () {
|
||||
test('ось строится по числу точек, а не по уникальным строкам дат', () {
|
||||
final labels = [
|
||||
'05.07',
|
||||
'06.07',
|
||||
'07.07',
|
||||
'08.07',
|
||||
'09.07',
|
||||
'10.07',
|
||||
'11.07',
|
||||
];
|
||||
final axis = buildInsightsIndexAxis(labels);
|
||||
|
||||
expect(axis.minimum, 0);
|
||||
expect(axis.maximum, 6);
|
||||
expect(axis.interval, 1);
|
||||
expect(axis.axisLabelFormatter, isNotNull);
|
||||
});
|
||||
|
||||
test('для длинного периода ставит интервал больше 1', () {
|
||||
final labels = List.generate(30, (i) => '${i + 1}.07');
|
||||
final axis = buildInsightsIndexAxis(labels);
|
||||
|
||||
expect(axis.maximum, 29);
|
||||
expect(axis.interval, greaterThan(1));
|
||||
});
|
||||
});
|
||||
|
||||
group('paddedRange', () {
|
||||
test('даёт локальный диапазон вокруг данных', () {
|
||||
final range = paddedRange([70, 72], minPadding: 1);
|
||||
|
||||
expect(range.min, lessThan(70));
|
||||
expect(range.max, greaterThan(72));
|
||||
expect(range.min, greaterThan(0));
|
||||
});
|
||||
|
||||
test('clampZero не уводит минимум ниже нуля', () {
|
||||
final range = paddedRange([2, 8], minPadding: 5, clampZero: true);
|
||||
|
||||
expect(range.min, 0);
|
||||
expect(range.max, greaterThan(8));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -121,7 +121,7 @@ void main() {
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
test('ошибка возвращает прежний период и оставляет данные', () async {
|
||||
test('ошибка оставляет выбранный период и старые данные', () async {
|
||||
final cubit = buildCubit();
|
||||
await cubit.load();
|
||||
final before = loaded(cubit).payload;
|
||||
@@ -129,7 +129,7 @@ void main() {
|
||||
|
||||
await cubit.setDailyPeriod(DailyPeriod.week);
|
||||
|
||||
expect(loaded(cubit).dailyPeriod, DailyPeriod.twoWeeks);
|
||||
expect(loaded(cubit).dailyPeriod, DailyPeriod.week);
|
||||
expect(loaded(cubit).payload, before);
|
||||
expect(loaded(cubit).isRefreshing, isFalse);
|
||||
await cubit.close();
|
||||
@@ -179,6 +179,65 @@ void main() {
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
test(
|
||||
'быстрые «назад» запрашивают разные дни (оптимистичная дата)',
|
||||
() async {
|
||||
repository.fetchIntraDelay = const Duration(milliseconds: 30);
|
||||
repository.intraByDate['2026-11-07'] = yesterday.copyWith(
|
||||
date: '2026-11-07',
|
||||
dateLabel: '7 ноября',
|
||||
);
|
||||
final cubit = buildCubit();
|
||||
await cubit.load();
|
||||
|
||||
final first = cubit.showPreviousDay();
|
||||
final second = cubit.showPreviousDay();
|
||||
await Future.wait([first, second]);
|
||||
|
||||
expect(repository.intraRequests.map((e) => e.date).toList(), [
|
||||
'2026-11-08',
|
||||
'2026-11-07',
|
||||
]);
|
||||
expect(loaded(cubit).payload.intra!.date, '2026-11-07');
|
||||
await cubit.close();
|
||||
},
|
||||
);
|
||||
|
||||
test('полный ответ не затирает более свежий intra и UI-выбор', () async {
|
||||
repository.fetchInsightsDelay = const Duration(milliseconds: 40);
|
||||
final cubit = buildCubit();
|
||||
await cubit.load();
|
||||
repository.fetchInsightsDelay = const Duration(milliseconds: 80);
|
||||
repository.serverRequests.clear();
|
||||
|
||||
final periodChange = cubit.setBodyPeriod(BodyPeriod.year);
|
||||
await Future<void>.delayed(const Duration(milliseconds: 10));
|
||||
cubit.setPanel(InsightsPanel.measures);
|
||||
cubit.toggleExpanded();
|
||||
await cubit.setIntraDate(DateTime(2026, 11, 8));
|
||||
await periodChange;
|
||||
|
||||
final state = loaded(cubit);
|
||||
expect(state.bodyPeriod, BodyPeriod.year);
|
||||
expect(state.payload.weight, repository.payload.weight);
|
||||
expect(state.payload.intra, yesterday);
|
||||
expect(state.intraDate, '2026-11-08');
|
||||
expect(state.panel, InsightsPanel.measures);
|
||||
expect(state.isExpanded, isTrue);
|
||||
expect(state.isRefreshing, isFalse);
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
test('load с initialIntraDate запрашивает этот день', () async {
|
||||
final cubit = buildCubit();
|
||||
|
||||
await cubit.load(intraDate: '2026-11-08');
|
||||
|
||||
expect(repository.serverRequests.single.intraDate, '2026-11-08');
|
||||
expect(loaded(cubit).intraDate, '2026-11-08');
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
test('showPreviousDay и showNextDay листают по дню', () async {
|
||||
final cubit = buildCubit();
|
||||
await cubit.load();
|
||||
|
||||
@@ -200,7 +200,7 @@ void main() {
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
test('ошибка перезапроса возвращает прежний период и данные', () async {
|
||||
test('ошибка перезапроса оставляет выбранный период и старые данные', () async {
|
||||
final cubit = buildCubit();
|
||||
await cubit.load();
|
||||
when(
|
||||
@@ -213,7 +213,7 @@ void main() {
|
||||
|
||||
await cubit.setBodyPeriod(BodyPeriod.all);
|
||||
|
||||
expect(loaded(cubit).bodyPeriod, BodyPeriod.threeMonths);
|
||||
expect(loaded(cubit).bodyPeriod, BodyPeriod.all);
|
||||
expect(loaded(cubit).payload, _payload);
|
||||
expect(loaded(cubit).isRefreshing, isFalse);
|
||||
await cubit.close();
|
||||
|
||||
@@ -97,7 +97,10 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(repository.serverRequests.single.dailyPeriod, DailyPeriod.month);
|
||||
expect(repository.serverRequests.single.bodyPeriod, BodyPeriod.threeMonths);
|
||||
expect(
|
||||
repository.serverRequests.single.bodyPeriod,
|
||||
BodyPeriod.threeMonths,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('без daily показывает заглушку', (tester) async {
|
||||
@@ -150,32 +153,32 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('на сегодняшнем дне «следующий день» отключён, «предыдущий» нет', (
|
||||
tester,
|
||||
) async {
|
||||
await pumpKbjuCard(
|
||||
tester,
|
||||
FakeInsightsRepository(),
|
||||
'insights_tab_intra',
|
||||
);
|
||||
testWidgets(
|
||||
'на сегодняшнем дне «следующий день» отключён, «предыдущий» нет',
|
||||
(tester) async {
|
||||
await pumpKbjuCard(
|
||||
tester,
|
||||
FakeInsightsRepository(),
|
||||
'insights_tab_intra',
|
||||
);
|
||||
|
||||
IconButton buttonIn(String key) => tester.widget<IconButton>(
|
||||
find.descendant(
|
||||
of: find.byKey(Key(key)),
|
||||
matching: find.byType(IconButton),
|
||||
),
|
||||
);
|
||||
IconButton buttonIn(String key) => tester.widget<IconButton>(
|
||||
find.descendant(
|
||||
of: find.byKey(Key(key)),
|
||||
matching: find.byType(IconButton),
|
||||
),
|
||||
);
|
||||
|
||||
expect(buttonIn('insights_intra_next').onPressed, isNull);
|
||||
expect(buttonIn('insights_intra_prev').onPressed, isNotNull);
|
||||
});
|
||||
expect(buttonIn('insights_intra_next').onPressed, isNull);
|
||||
expect(buttonIn('insights_intra_prev').onPressed, isNotNull);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('«предыдущий день» запрашивает только intra и меняет дату', (
|
||||
tester,
|
||||
) async {
|
||||
final repository = FakeInsightsRepository();
|
||||
repository.intraByDate['2026-11-08'] = loadInsightsPayloadFixture()
|
||||
.intra!
|
||||
repository.intraByDate['2026-11-08'] = loadInsightsPayloadFixture().intra!
|
||||
.copyWith(date: '2026-11-08', dateLabel: '8 ноября', isToday: false);
|
||||
await pumpKbjuCard(tester, repository, 'insights_tab_intra');
|
||||
repository.serverRequests.clear();
|
||||
@@ -312,6 +315,34 @@ void main() {
|
||||
expect(find.byType(IntraLegend), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('день только со сном рисует затемнение, без заглушки', (
|
||||
tester,
|
||||
) async {
|
||||
final intra = loadInsightsPayloadFixture().intra!.copyWith(
|
||||
bars: const [],
|
||||
satiety: const [],
|
||||
mood: const [],
|
||||
);
|
||||
await pumpKbjuCard(
|
||||
tester,
|
||||
FakeInsightsRepository(
|
||||
payload: loadInsightsPayloadFixture().copyWith(intra: intra),
|
||||
),
|
||||
'insights_tab_intra',
|
||||
);
|
||||
|
||||
expect(find.byKey(const Key('insights_intra_chart')), findsOneWidget);
|
||||
expect(find.text('Недостаточно данных'), findsNothing);
|
||||
final xAxis =
|
||||
tester
|
||||
.widget<SfCartesianChart>(
|
||||
find.byKey(const Key('insights_intra_chart')),
|
||||
)
|
||||
.primaryXAxis
|
||||
as NumericAxis;
|
||||
expect(xAxis.plotBands, isNotEmpty);
|
||||
});
|
||||
|
||||
testWidgets('пустой день показывает заглушку', (tester) async {
|
||||
await pumpKbjuCard(
|
||||
tester,
|
||||
|
||||
57
test/fixtures/doubles/insights_doubles.dart
vendored
57
test/fixtures/doubles/insights_doubles.dart
vendored
@@ -6,14 +6,22 @@ import 'package:eatvio_app/features/insights/models/insights_period.dart';
|
||||
import 'package:eatvio_app/features/insights/repositories/insights_repository.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
InsightsPayloadModel loadInsightsPayloadFixture([String name = 'insights_payload.json']) {
|
||||
final json = jsonDecode(File('test/fixtures/insights/$name').readAsStringSync()) as Map<String, dynamic>;
|
||||
InsightsPayloadModel loadInsightsPayloadFixture([
|
||||
String name = 'insights_payload.json',
|
||||
]) {
|
||||
final json =
|
||||
jsonDecode(File('test/fixtures/insights/$name').readAsStringSync())
|
||||
as Map<String, dynamic>;
|
||||
return InsightsPayloadModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// Подмена [InsightsRepository] для виджет-тестов: без сети, с журналом вызовов.
|
||||
class FakeInsightsRepository extends Fake implements InsightsRepository {
|
||||
FakeInsightsRepository({InsightsPayloadModel? payload, this.error, this.heightCm = 176}) : payload = payload ?? loadInsightsPayloadFixture();
|
||||
FakeInsightsRepository({
|
||||
InsightsPayloadModel? payload,
|
||||
this.error,
|
||||
this.heightCm = 176,
|
||||
}) : payload = payload ?? loadInsightsPayloadFixture();
|
||||
|
||||
InsightsPayloadModel payload;
|
||||
|
||||
@@ -28,8 +36,22 @@ class FakeInsightsRepository extends Fake implements InsightsRepository {
|
||||
Object? error;
|
||||
int? heightCm;
|
||||
|
||||
/// Задержка ответа [fetchInsights] (для тестов гонок).
|
||||
Duration? fetchInsightsDelay;
|
||||
|
||||
/// Задержка ответа [fetchIntra] (для тестов гонок).
|
||||
Duration? fetchIntraDelay;
|
||||
|
||||
final List<({String? slug, BodyPeriod period})> insightsRequests = [];
|
||||
final List<({String? slug, BodyPeriod bodyPeriod, DailyPeriod dailyPeriod, String? intraDate})> serverRequests = [];
|
||||
final List<
|
||||
({
|
||||
String? slug,
|
||||
BodyPeriod bodyPeriod,
|
||||
DailyPeriod dailyPeriod,
|
||||
String? intraDate,
|
||||
})
|
||||
>
|
||||
serverRequests = [];
|
||||
final List<({String? slug, String? date})> intraRequests = [];
|
||||
final List<double> addedWeights = [];
|
||||
final List<({int chest, int waist, int hip})> addedCircumferences = [];
|
||||
@@ -38,7 +60,11 @@ class FakeInsightsRepository extends Fake implements InsightsRepository {
|
||||
Future<int?> getHeightCm({String? slug}) async => heightCm;
|
||||
|
||||
@override
|
||||
Future<InsightsPayloadModel> getInsights({String? slug, required BodyPeriod bodyPeriod, int? heightCm}) async {
|
||||
Future<InsightsPayloadModel> getInsights({
|
||||
String? slug,
|
||||
required BodyPeriod bodyPeriod,
|
||||
int? heightCm,
|
||||
}) async {
|
||||
insightsRequests.add((slug: slug, period: bodyPeriod));
|
||||
final failure = error;
|
||||
if (failure != null) {
|
||||
@@ -54,7 +80,16 @@ class FakeInsightsRepository extends Fake implements InsightsRepository {
|
||||
required DailyPeriod dailyPeriod,
|
||||
String? intraDate,
|
||||
}) async {
|
||||
serverRequests.add((slug: slug, bodyPeriod: bodyPeriod, dailyPeriod: dailyPeriod, intraDate: intraDate));
|
||||
serverRequests.add((
|
||||
slug: slug,
|
||||
bodyPeriod: bodyPeriod,
|
||||
dailyPeriod: dailyPeriod,
|
||||
intraDate: intraDate,
|
||||
));
|
||||
final delay = fetchInsightsDelay;
|
||||
if (delay != null) {
|
||||
await Future<void>.delayed(delay);
|
||||
}
|
||||
final failure = error;
|
||||
if (failure != null) {
|
||||
throw failure;
|
||||
@@ -65,6 +100,10 @@ class FakeInsightsRepository extends Fake implements InsightsRepository {
|
||||
@override
|
||||
Future<IntraChartModel> fetchIntra({String? slug, String? intraDate}) async {
|
||||
intraRequests.add((slug: slug, date: intraDate));
|
||||
final delay = fetchIntraDelay;
|
||||
if (delay != null) {
|
||||
await Future<void>.delayed(delay);
|
||||
}
|
||||
final failure = intraError ?? error;
|
||||
if (failure != null) {
|
||||
throw failure;
|
||||
@@ -83,6 +122,10 @@ class FakeInsightsRepository extends Fake implements InsightsRepository {
|
||||
required int waistCircumference,
|
||||
required int hipCircumference,
|
||||
}) async {
|
||||
addedCircumferences.add((chest: chestCircumference, waist: waistCircumference, hip: hipCircumference));
|
||||
addedCircumferences.add((
|
||||
chest: chestCircumference,
|
||||
waist: waistCircumference,
|
||||
hip: hipCircumference,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user