Files
eatvio_app/test/features/insights/presentation/cubit/insights_cubit_test.dart
2026-11-11 16:05:11 +03:00

440 lines
13 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/features/insights/models/insights_payload_model.dart';
import 'package:eatvio_app/features/insights/models/insights_period.dart';
import 'package:eatvio_app/features/insights/presentation/cubit/insights_cubit.dart';
import 'package:eatvio_app/features/insights/presentation/cubit/insights_panel.dart';
import 'package:eatvio_app/features/insights/presentation/cubit/insights_state.dart';
import 'package:eatvio_app/features/insights/repositories/insights_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class _MockInsightsRepository extends Mock implements InsightsRepository {}
const _payload = InsightsPayloadModel(
weight: WeightChartModel(values: [70, 71], headline: '71.0'),
bmi: BmiChartModel(),
measure: MeasureChartModel(),
);
const _payload2 = InsightsPayloadModel(
weight: WeightChartModel(values: [72], headline: '72.0'),
bmi: BmiChartModel(),
measure: MeasureChartModel(),
);
void main() {
late _MockInsightsRepository repository;
setUpAll(() {
registerFallbackValue(BodyPeriod.month);
registerFallbackValue(DailyPeriod.week);
});
setUp(() {
repository = _MockInsightsRepository();
when(
() => repository.getHeightCm(slug: any(named: 'slug')),
).thenAnswer((_) async => 176);
when(
() => repository.getInsights(
slug: any(named: 'slug'),
bodyPeriod: any(named: 'bodyPeriod'),
heightCm: any(named: 'heightCm'),
),
).thenAnswer((_) async => _payload);
when(
() => repository.fetchInsights(
slug: any(named: 'slug'),
bodyPeriod: any(named: 'bodyPeriod'),
dailyPeriod: any(named: 'dailyPeriod'),
intraDate: any(named: 'intraDate'),
),
).thenAnswer((_) async => _payload);
});
InsightsCubit buildCubit({
String? slug,
bool canAdd = false,
bool kbjuEnabled = false,
}) {
return InsightsCubit(
repository: repository,
slug: slug,
canAdd: canAdd,
kbjuEnabled: kbjuEnabled,
);
}
InsightsStateLoaded loaded(InsightsCubit cubit) =>
cubit.state as InsightsStateLoaded;
test('начальное состояние — initial', () {
expect(buildCubit().state, isA<InsightsStateInitial>());
});
group('load', () {
test('дефолты: период 3М, вкладка «Вес», блок свёрнут', () async {
final cubit = buildCubit();
final states = <InsightsState>[];
cubit.stream.listen(states.add);
await cubit.load();
await Future<void>.delayed(Duration.zero);
expect(states.first, isA<InsightsStateLoading>());
final state = loaded(cubit);
expect(state.bodyPeriod, BodyPeriod.threeMonths);
expect(state.panel, InsightsPanel.weight);
expect(state.isExpanded, isFalse);
expect(state.isRefreshing, isFalse);
expect(state.showSleep, isTrue);
expect(state.payload, _payload);
verify(
() => repository.getInsights(
slug: null,
bodyPeriod: BodyPeriod.threeMonths,
heightCm: 176,
),
).called(1);
await cubit.close();
});
test('пробрасывает slug владельца', () async {
final cubit = buildCubit(slug: 'client');
await cubit.load();
verify(() => repository.getHeightCm(slug: 'client')).called(1);
verify(
() => repository.getInsights(
slug: 'client',
bodyPeriod: BodyPeriod.threeMonths,
heightCm: 176,
),
).called(1);
await cubit.close();
});
test('403 → hidden', () async {
when(
() => repository.getHeightCm(slug: any(named: 'slug')),
).thenThrow(const InsightsForbidden());
final cubit = buildCubit(slug: 'client');
await cubit.load();
expect(cubit.state, isA<InsightsStateHidden>());
await cubit.close();
});
test('403 на запросе точек → hidden', () async {
when(
() => repository.getInsights(
slug: any(named: 'slug'),
bodyPeriod: any(named: 'bodyPeriod'),
heightCm: any(named: 'heightCm'),
),
).thenThrow(const InsightsForbidden());
final cubit = buildCubit(slug: 'client');
await cubit.load();
expect(cubit.state, isA<InsightsStateHidden>());
await cubit.close();
});
test('другая ошибка → error', () async {
when(
() => repository.getInsights(
slug: any(named: 'slug'),
bodyPeriod: any(named: 'bodyPeriod'),
heightCm: any(named: 'heightCm'),
),
).thenThrow(Exception('boom'));
final cubit = buildCubit();
await cubit.load();
expect(cubit.state, isA<InsightsStateError>());
await cubit.close();
});
});
group('setBodyPeriod', () {
test(
'смена периода → перезапрос; рост не запрашивается повторно',
() async {
final cubit = buildCubit();
await cubit.load();
when(
() => repository.getInsights(
slug: null,
bodyPeriod: BodyPeriod.year,
heightCm: 176,
),
).thenAnswer((_) async => _payload2);
await cubit.setBodyPeriod(BodyPeriod.year);
expect(loaded(cubit).bodyPeriod, BodyPeriod.year);
expect(loaded(cubit).payload, _payload2);
expect(loaded(cubit).isRefreshing, isFalse);
verify(() => repository.getHeightCm(slug: null)).called(1);
await cubit.close();
},
);
test('тот же период — без запроса', () async {
final cubit = buildCubit();
await cubit.load();
clearInteractions(repository);
await cubit.setBodyPeriod(BodyPeriod.threeMonths);
verifyNever(
() => repository.getInsights(
slug: any(named: 'slug'),
bodyPeriod: any(named: 'bodyPeriod'),
heightCm: any(named: 'heightCm'),
),
);
await cubit.close();
});
test('ошибка перезапроса оставляет выбранный период и старые данные', () async {
final cubit = buildCubit();
await cubit.load();
when(
() => repository.getInsights(
slug: null,
bodyPeriod: BodyPeriod.all,
heightCm: 176,
),
).thenThrow(Exception('boom'));
await cubit.setBodyPeriod(BodyPeriod.all);
expect(loaded(cubit).bodyPeriod, BodyPeriod.all);
expect(loaded(cubit).payload, _payload);
expect(loaded(cubit).isRefreshing, isFalse);
await cubit.close();
});
test('403 при перезапросе → hidden', () async {
final cubit = buildCubit(slug: 'c');
await cubit.load();
when(
() => repository.getInsights(
slug: 'c',
bodyPeriod: BodyPeriod.month,
heightCm: 176,
),
).thenThrow(const InsightsForbidden());
await cubit.setBodyPeriod(BodyPeriod.month);
expect(cubit.state, isA<InsightsStateHidden>());
await cubit.close();
});
});
group('панели и серии', () {
test('toggleExpanded раскрывает и сворачивает', () async {
final cubit = buildCubit();
await cubit.load();
cubit.toggleExpanded();
expect(loaded(cubit).isExpanded, isTrue);
cubit.toggleExpanded();
expect(loaded(cubit).isExpanded, isFalse);
await cubit.close();
});
test('setPanel переключает вкладку', () async {
final cubit = buildCubit();
await cubit.load();
cubit.setPanel(InsightsPanel.measures);
expect(loaded(cubit).panel, InsightsPanel.measures);
await cubit.close();
});
test('вкладки КБЖУ недоступны при выключенном флаге', () async {
final cubit = buildCubit();
await cubit.load();
cubit.setPanel(InsightsPanel.daily);
expect(loaded(cubit).panel, InsightsPanel.weight);
expect(cubit.panels, [
InsightsPanel.weight,
InsightsPanel.bmi,
InsightsPanel.measures,
]);
await cubit.close();
});
test('вкладки КБЖУ доступны при включённом флаге', () async {
final cubit = buildCubit(kbjuEnabled: true);
await cubit.load();
cubit.setPanel(InsightsPanel.intra);
expect(loaded(cubit).panel, InsightsPanel.intra);
expect(cubit.panels, InsightsPanel.values);
await cubit.close();
});
test('toggleSeries переключает только выбранную серию', () async {
final cubit = buildCubit();
await cubit.load();
cubit.toggleSeries(InsightsPanel.measures, 1);
expect(loaded(cubit).seriesVisibility.measure, [true, false, true]);
cubit.toggleSeries(InsightsPanel.measures, 1);
expect(loaded(cubit).seriesVisibility.measure, [true, true, true]);
cubit.toggleSeries(InsightsPanel.daily, 0);
expect(loaded(cubit).seriesVisibility.daily, [false, true, true, true]);
expect(loaded(cubit).seriesVisibility.measure, [true, true, true]);
await cubit.close();
});
test(
'toggleSeries игнорирует неверный индекс и вкладку без серий',
() async {
final cubit = buildCubit();
await cubit.load();
final before = loaded(cubit).seriesVisibility;
cubit.toggleSeries(InsightsPanel.measures, 5);
cubit.toggleSeries(InsightsPanel.weight, 0);
expect(loaded(cubit).seriesVisibility, before);
await cubit.close();
},
);
test('toggleSleep', () async {
final cubit = buildCubit();
await cubit.load();
cubit.toggleSleep();
expect(loaded(cubit).showSleep, isFalse);
await cubit.close();
});
});
group('refresh и добавление записей', () {
test('refresh сохраняет вкладку, период, серии и раскрытие', () async {
final cubit = buildCubit();
await cubit.load();
cubit.toggleExpanded();
cubit.setPanel(InsightsPanel.measures);
cubit.toggleSeries(InsightsPanel.measures, 0);
when(
() => repository.getInsights(
slug: null,
bodyPeriod: BodyPeriod.threeMonths,
heightCm: 176,
),
).thenAnswer((_) async => _payload2);
await cubit.refresh();
final state = loaded(cubit);
expect(state.payload, _payload2);
expect(state.panel, InsightsPanel.measures);
expect(state.isExpanded, isTrue);
expect(state.seriesVisibility.measure, [false, true, true]);
expect(state.isRefreshing, isFalse);
await cubit.close();
});
test('addWeight сохраняет и обновляет данные', () async {
when(
() => repository.addWeight(weight: any(named: 'weight')),
).thenAnswer((_) async {});
final cubit = buildCubit(canAdd: true);
await cubit.load();
when(
() => repository.getInsights(
slug: null,
bodyPeriod: BodyPeriod.threeMonths,
heightCm: 176,
),
).thenAnswer((_) async => _payload2);
final isSaved = await cubit.addWeight(weight: 72);
expect(isSaved, isTrue);
verify(() => repository.addWeight(weight: 72)).called(1);
expect(loaded(cubit).payload, _payload2);
await cubit.close();
});
test('addCircumference сохраняет и обновляет данные', () async {
when(
() => repository.addCircumference(
chestCircumference: any(named: 'chestCircumference'),
waistCircumference: any(named: 'waistCircumference'),
hipCircumference: any(named: 'hipCircumference'),
),
).thenAnswer((_) async {});
final cubit = buildCubit(canAdd: true);
await cubit.load();
when(
() => repository.getInsights(
slug: null,
bodyPeriod: BodyPeriod.threeMonths,
heightCm: 176,
),
).thenAnswer((_) async => _payload2);
final isSaved = await cubit.addCircumference(
chestCircumference: 95,
waistCircumference: 80,
hipCircumference: 100,
);
expect(isSaved, isTrue);
verify(
() => repository.addCircumference(
chestCircumference: 95,
waistCircumference: 80,
hipCircumference: 100,
),
).called(1);
expect(loaded(cubit).payload, _payload2);
await cubit.close();
});
test(
'ошибка сохранения не перезагружает данные и возвращает false',
() async {
when(
() => repository.addWeight(weight: any(named: 'weight')),
).thenThrow(Exception('boom'));
final cubit = buildCubit(canAdd: true);
await cubit.load();
clearInteractions(repository);
expect(await cubit.addWeight(weight: 72), isFalse);
verifyNever(
() => repository.getInsights(
slug: any(named: 'slug'),
bodyPeriod: any(named: 'bodyPeriod'),
heightCm: any(named: 'heightCm'),
),
);
expect(loaded(cubit).payload, _payload);
await cubit.close();
},
);
});
}