feat(diary): editing of previous and current finished days

This commit is contained in:
misha
2026-10-01 10:44:52 +03:00
parent a1bae68307
commit 603a158cf2
11 changed files with 1099 additions and 242 deletions

View File

@@ -0,0 +1,59 @@
import 'package:eatvio_app/features/diary/presentation/diary_edit_access.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
final now = DateTime(2026, 9, 30, 12);
bool canEdit({
bool isConsultantView = false,
bool isDiaryCommented = false,
bool isDiaryFinished = false,
int closed = 0,
String diaryDate = '2026-09-28',
}) {
return canEditDiaryDay(
isConsultantView: isConsultantView,
isDiaryCommented: isDiaryCommented,
isDiaryFinished: isDiaryFinished,
closed: closed,
diaryDate: diaryDate,
now: now,
);
}
test('finished today and yesterday stay editable', () {
expect(canEdit(isDiaryFinished: true, diaryDate: '2026-09-30'), isTrue);
expect(canEdit(isDiaryFinished: true, diaryDate: '2026-09-29'), isTrue);
});
test('finished days older than yesterday stay read-only', () {
expect(canEdit(isDiaryFinished: true, diaryDate: '2026-09-28'), isFalse);
});
test('commented days stay read-only inside the window', () {
expect(canEdit(isDiaryCommented: true, diaryDate: '2026-09-30'), isFalse);
expect(
canEdit(
isDiaryCommented: true,
isDiaryFinished: true,
diaryDate: '2026-09-29',
),
isFalse,
);
});
test('an open day outside the window stays editable', () {
expect(canEdit(diaryDate: '2026-09-01'), isTrue);
});
test('a closed flag blocks editing even inside the window', () {
expect(
canEdit(isDiaryFinished: true, closed: 1, diaryDate: '2026-09-30'),
isFalse,
);
});
test('consultant view stays read-only', () {
expect(canEdit(isConsultantView: true, diaryDate: '2026-09-30'), isFalse);
});
}

View File

@@ -25,16 +25,25 @@ import 'package:mockito/mockito.dart';
import 'diary_screen_smoke_test.mocks.dart';
@GenerateNiceMocks([MockSpec<DiaryCubit>(), MockSpec<ConsultantCommentsCubit>()])
@GenerateNiceMocks([
MockSpec<DiaryCubit>(),
MockSpec<ConsultantCommentsCubit>(),
])
void main() {
group('DiaryScreen smoke', () {
setUpAll(() async {
dotenv.loadFromString(envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost');
dotenv.loadFromString(
envString: 'DEV_BASE_URL=http://localhost\nBASE_URL=http://localhost',
);
});
setUp(() async {
await sl.reset();
sl.registerLazySingleton<AppConfig>(() => AppConfig(buildType: AppBuildType.tests)..setBaseUrl('http://localhost'));
sl.registerLazySingleton<AppConfig>(
() =>
AppConfig(buildType: AppBuildType.tests)
..setBaseUrl('http://localhost'),
);
});
Widget wrap(Widget child) {
@@ -51,23 +60,53 @@ void main() {
);
}
DiaryStateLoaded loadedState({required bool hasChosenPlan}) {
DiaryStateLoaded loadedState({
required bool hasChosenPlan,
String date = '2025-01-01',
bool isDiaryFinished = false,
bool isDiaryCommented = false,
int closed = 0,
}) {
final diary = DiaryDayEntity(
id: 1,
date: '2025-01-01',
date: date,
wakeup: '07:00',
retire: '23:00',
closed: 0,
closed: closed,
comment: '',
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: hasChosenPlan, diaryState: null),
meals: const [MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [], photos: [])],
typeOfDiary: TypeOfDiaryModel(
hasChosenDiet: false,
hasChosenPlan: hasChosenPlan,
diaryState: null,
),
meals: const [
MealModel(
id: 1,
time: '08:00',
typeNumber: 1,
satiety: 0,
mood: 0,
portions: [],
photos: [],
),
],
chosenPlanId: hasChosenPlan ? 10 : null,
dayOfChosenDiet: null,
diet: null,
);
const nutrition = NutritionDataModel(
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
dailyNutritionRecommendation: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
dailyNutritionIntake: NutritionModel(
energy: 0,
proteins: 0,
fats: 0,
carbohydrates: 0,
),
dailyNutritionRecommendation: NutritionModel(
energy: 0,
proteins: 0,
fats: 0,
carbohydrates: 0,
),
);
return DiaryState.loaded(
diary: diary,
@@ -75,8 +114,8 @@ void main() {
selectedDay: DateTime(2025, 1, 1),
selectedMeal: 1,
isConsultantView: false,
isDiaryFinished: false,
isDiaryCommented: false,
isDiaryFinished: isDiaryFinished,
isDiaryCommented: isDiaryCommented,
isWakeUpAndRetireEnabled: true,
isMoodEnabled: true,
)
@@ -86,13 +125,26 @@ void main() {
testWidgets('renders loading state', (tester) async {
final diaryCubit = MockDiaryCubit();
when(diaryCubit.state).thenReturn(const DiaryState.loading());
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(
diaryCubit.stream,
).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
when(
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);
@@ -107,13 +159,27 @@ void main() {
testWidgets('renders loaded state (chosen plan)', (tester) async {
final diaryCubit = MockDiaryCubit();
when(diaryCubit.state).thenReturn(loadedState(hasChosenPlan: true));
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(
diaryCubit.stream,
).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
when(
diaryCubit.fetchDiaryV2(
needLoading: anyNamed('needLoading'),
diaryId: anyNamed('diaryId'),
),
).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
when(commentsCubit.stream).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
when(commentsCubit.state).thenReturn(
ConsultantCommentsState(
commentsModel: ConsultantCommentModel(comment: ''),
dayCommentNeedsToBeFilled: false,
),
);
when(
commentsCubit.stream,
).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
sl.registerFactory<DiaryCubit>(() => diaryCubit);
@@ -124,19 +190,37 @@ void main() {
expect(find.byType(DiaryScreen), findsOneWidget);
expect(find.text('Дополнительная информация для консультанта'), findsOneWidget);
expect(
find.text('Дополнительная информация для консультанта'),
findsOneWidget,
);
});
testWidgets('renders error state and reload button', (tester) async {
final diaryCubit = MockDiaryCubit();
when(diaryCubit.state).thenReturn(const DiaryState.error(errorMessage: 'err'));
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(
diaryCubit.state,
).thenReturn(const DiaryState.error(errorMessage: 'err'));
when(
diaryCubit.stream,
).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
when(
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);
@@ -153,19 +237,52 @@ void main() {
final diaryCubit = MockDiaryCubit();
const futureDiary = DiaryFutureDietDayEntity(
date: '2025-01-02',
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: true, hasChosenPlan: false, diaryState: null),
meals: [MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [], photos: [])],
typeOfDiary: TypeOfDiaryModel(
hasChosenDiet: true,
hasChosenPlan: false,
diaryState: null,
),
meals: [
MealModel(
id: 1,
time: '08:00',
typeNumber: 1,
satiety: 0,
mood: 0,
portions: [],
photos: [],
),
],
diet: null,
dayOfChosenDiet: 1,
);
when(diaryCubit.state).thenReturn(DiaryState.loadedTomorrowWithDiet(diary: futureDiary, selectedDay: DateTime(2025, 1, 2), selectedMeal: 1));
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.state).thenReturn(
DiaryState.loadedTomorrowWithDiet(
diary: futureDiary,
selectedDay: DateTime(2025, 1, 2),
selectedMeal: 1,
),
);
when(
diaryCubit.stream,
).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
when(
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);
@@ -187,17 +304,35 @@ void main() {
planName: 'Plan',
planDuration: 30,
currentPlanDay: 1,
consultantComments: ConsultantComments(dayComment: 'Nice', mealsComments: const [], portionsComments: const []),
consultantComments: ConsultantComments(
dayComment: 'Nice',
mealsComments: const [],
portionsComments: const [],
),
),
);
when(diaryCubit.state).thenReturn(state);
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(
diaryCubit.stream,
).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
when(
diaryCubit.fetchDiaryV2(
needLoading: anyNamed('needLoading'),
diaryId: anyNamed('diaryId'),
),
).thenAnswer((_) async => true);
final commentsCubit = MockConsultantCommentsCubit();
when(commentsCubit.state).thenReturn(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: ''), dayCommentNeedsToBeFilled: false));
when(commentsCubit.stream).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
when(commentsCubit.state).thenReturn(
ConsultantCommentsState(
commentsModel: ConsultantCommentModel(comment: ''),
dayCommentNeedsToBeFilled: false,
),
);
when(
commentsCubit.stream,
).thenAnswer((_) => const Stream<ConsultantCommentsState>.empty());
when(commentsCubit.close()).thenAnswer((_) async => Future<void>.value());
when(commentsCubit.submitComments(any)).thenAnswer((_) async => true);
@@ -215,13 +350,26 @@ void main() {
final diaryCubit = MockDiaryCubit();
final state = loadedState(hasChosenPlan: false);
when(diaryCubit.state).thenReturn(state);
when(diaryCubit.stream).thenAnswer((_) => const Stream<DiaryState>.empty());
when(
diaryCubit.stream,
).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(diaryCubit.fetchDiaryV2(needLoading: anyNamed('needLoading'), diaryId: anyNamed('diaryId'))).thenAnswer((_) async => true);
when(
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);
@@ -234,5 +382,96 @@ void main() {
expect(find.byType(Text), findsWidgets);
});
testWidgets('shows add button for a finished day inside the edit window', (
tester,
) async {
final yesterday = DateTime.now().subtract(const Duration(days: 1));
final diaryCubit = MockDiaryCubit();
when(diaryCubit.state).thenReturn(
loadedState(
hasChosenPlan: false,
date: _calendarDate(yesterday),
isDiaryFinished: true,
),
);
when(
diaryCubit.stream,
).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(
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.close()).thenAnswer((_) async => Future<void>.value());
sl.registerFactory<DiaryCubit>(() => diaryCubit);
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
await tester.pumpWidget(wrap(DiaryScreen(date: yesterday)));
await tester.pump(const Duration(milliseconds: 200));
expect(find.text('Добавить'), findsOneWidget);
expect(find.text('Закончить день'), findsNothing);
});
testWidgets('hides add button for a commented day', (tester) async {
final yesterday = DateTime.now().subtract(const Duration(days: 1));
final diaryCubit = MockDiaryCubit();
when(diaryCubit.state).thenReturn(
loadedState(
hasChosenPlan: false,
date: _calendarDate(yesterday),
isDiaryCommented: true,
),
);
when(
diaryCubit.stream,
).thenAnswer((_) => const Stream<DiaryState>.empty());
when(diaryCubit.close()).thenAnswer((_) async => Future<void>.value());
when(
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.close()).thenAnswer((_) async => Future<void>.value());
sl.registerFactory<DiaryCubit>(() => diaryCubit);
sl.registerFactory<ConsultantCommentsCubit>(() => commentsCubit);
await tester.pumpWidget(wrap(DiaryScreen(date: yesterday)));
await tester.pump(const Duration(milliseconds: 200));
expect(find.text('Добавить'), findsNothing);
});
});
}
String _calendarDate(DateTime date) {
final month = date.month.toString().padLeft(2, '0');
final day = date.day.toString().padLeft(2, '0');
return '${date.year}-$month-$day';
}

View File

@@ -1,4 +1,3 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
import 'package:eatvio_app/features/diary/entities/diary_day_entity.dart';
@@ -27,7 +26,8 @@ class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class FakeConsultantCommentsCubit extends Cubit<ConsultantCommentsState> implements ConsultantCommentsCubit {
class FakeConsultantCommentsCubit extends Cubit<ConsultantCommentsState>
implements ConsultantCommentsCubit {
FakeConsultantCommentsCubit(super.initialState);
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@@ -60,9 +60,16 @@ void main() {
</svg>
''';
final bundle = _FakeAssetBundle({'assets/icons/revert_product.svg': fakeSvg, 'assets/icons/edit.svg': fakeSvg});
final bundle = _FakeAssetBundle({
'assets/icons/revert_product.svg': fakeSvg,
'assets/icons/edit.svg': fakeSvg,
});
Widget wrap({required DiaryCubit diaryCubit, required ConsultantCommentsCubit consultantCommentsCubit, required Widget child}) {
Widget wrap({
required DiaryCubit diaryCubit,
required ConsultantCommentsCubit consultantCommentsCubit,
required Widget child,
}) {
return DefaultAssetBundle(
bundle: bundle,
child: MaterialApp(
@@ -77,7 +84,9 @@ void main() {
home: MultiBlocProvider(
providers: [
BlocProvider<DiaryCubit>.value(value: diaryCubit),
BlocProvider<ConsultantCommentsCubit>.value(value: consultantCommentsCubit),
BlocProvider<ConsultantCommentsCubit>.value(
value: consultantCommentsCubit,
),
],
child: Scaffold(body: child),
),
@@ -85,17 +94,36 @@ void main() {
);
}
DiaryStateLoaded loadedState({required PortionModel portion, required bool consultantView}) {
DiaryStateLoaded loadedState({
required PortionModel portion,
required bool consultantView,
String date = '2025-01-01',
bool isDiaryFinished = false,
bool isDiaryCommented = false,
int closed = 0,
}) {
final diary = DiaryDayEntity(
id: 1,
date: '2025-01-01',
date: date,
wakeup: '07:00',
retire: '23:00',
closed: 0,
closed: closed,
comment: '',
typeOfDiary: const TypeOfDiaryModel(hasChosenDiet: false, hasChosenPlan: true, diaryState: DiaryStateEnum.started),
typeOfDiary: const TypeOfDiaryModel(
hasChosenDiet: false,
hasChosenPlan: true,
diaryState: DiaryStateEnum.started,
),
meals: [
MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [portion], photos: const []),
MealModel(
id: 1,
time: '08:00',
typeNumber: 1,
satiety: 0,
mood: 0,
portions: [portion],
photos: const [],
),
],
chosenPlanId: 1,
dayOfChosenDiet: null,
@@ -103,8 +131,18 @@ void main() {
);
const nutrition = NutritionDataModel(
dailyNutritionIntake: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
dailyNutritionRecommendation: NutritionModel(energy: 0, proteins: 0, fats: 0, carbohydrates: 0),
dailyNutritionIntake: NutritionModel(
energy: 0,
proteins: 0,
fats: 0,
carbohydrates: 0,
),
dailyNutritionRecommendation: NutritionModel(
energy: 0,
proteins: 0,
fats: 0,
carbohydrates: 0,
),
);
return DiaryState.loaded(
@@ -113,13 +151,17 @@ void main() {
selectedDay: DateTime(2025, 1, 1),
selectedMeal: 1,
isConsultantView: consultantView,
isDiaryFinished: false,
isDiaryCommented: false,
isDiaryFinished: isDiaryFinished,
isDiaryCommented: isDiaryCommented,
consultantReview: ConsultantReviewModel(
planName: 'Plan',
planDuration: 10,
currentPlanDay: 1,
consultantComments: ConsultantComments(dayComment: null, mealsComments: const [], portionsComments: const []),
consultantComments: ConsultantComments(
dayComment: null,
mealsComments: const [],
portionsComments: const [],
),
),
)
as DiaryStateLoaded;
@@ -140,9 +182,21 @@ void main() {
DiaryState.loadedTomorrowWithDiet(
diary: const DiaryFutureDietDayEntity(
date: '2025-01-02',
typeOfDiary: TypeOfDiaryModel(hasChosenDiet: true, hasChosenPlan: false, diaryState: null),
typeOfDiary: TypeOfDiaryModel(
hasChosenDiet: true,
hasChosenPlan: false,
diaryState: null,
),
meals: [
MealModel(id: 1, time: '08:00', typeNumber: 1, satiety: 0, mood: 0, portions: [portion], photos: []),
MealModel(
id: 1,
time: '08:00',
typeNumber: 1,
satiety: 0,
mood: 0,
portions: [portion],
photos: [],
),
],
diet: null,
dayOfChosenDiet: 1,
@@ -151,7 +205,11 @@ void main() {
selectedMeal: 1,
),
);
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
final commentsCubit = FakeConsultantCommentsCubit(
ConsultantCommentsState(
commentsModel: ConsultantCommentModel(comment: ''),
),
);
await tester.pumpWidget(
wrap(
@@ -177,8 +235,14 @@ void main() {
fromDiet: false,
portionState: PortionState.eatenDeleted,
);
final diaryCubit = FakeDiaryCubit(loadedState(portion: portion, consultantView: false));
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
final diaryCubit = FakeDiaryCubit(
loadedState(portion: portion, consultantView: false),
);
final commentsCubit = FakeConsultantCommentsCubit(
ConsultantCommentsState(
commentsModel: ConsultantCommentModel(comment: ''),
),
);
await tester.pumpWidget(
wrap(
@@ -194,32 +258,89 @@ void main() {
expect(find.byType(ProductWidget), findsOneWidget);
});
testWidgets('consultant view toggles comment widget and shows existing comment container', (tester) async {
testWidgets(
'consultant view toggles comment widget and shows existing comment container',
(tester) async {
const portion = PortionModel(
id: 1,
productNutritionalValueId: null,
productCaloriesInfo: null,
productName: 'Portion',
quantity: 100,
portion: 'gr',
fromDiet: false,
portionState: PortionState.filledDiet,
);
final state = loadedState(portion: portion, consultantView: true)
.copyWith(
consultantReview: ConsultantReviewModel(
planName: 'Plan',
planDuration: 10,
currentPlanDay: 1,
consultantComments: ConsultantComments(
dayComment: null,
mealsComments: const [],
portionsComments: [
PortionComment(portionId: 1, comment: 'Looks good'),
],
),
),
);
final diaryCubit = FakeDiaryCubit(state);
final commentsCubit = FakeConsultantCommentsCubit(
ConsultantCommentsState(
commentsModel: ConsultantCommentModel(comment: ''),
),
);
await tester.pumpWidget(
wrap(
diaryCubit: diaryCubit,
consultantCommentsCubit: commentsCubit,
child: const ProductWidget(portion: portion),
),
);
await tester.pump();
expect(find.byType(CommentContainer), findsOneWidget);
expect(find.text('Looks good'), findsOneWidget);
// Tap edit icon to enable PortionCommentWidget.
await tester.tap(find.byType(GestureDetector).first);
await tester.pump();
expect(find.byType(PortionCommentWidget), findsOneWidget);
},
);
testWidgets('can edit a portion on a finished day inside the edit window', (
tester,
) async {
const portion = PortionModel(
id: 1,
productNutritionalValueId: null,
productCaloriesInfo: null,
productName: 'Portion',
productName: 'Apple',
quantity: 100,
portion: 'gr',
fromDiet: false,
portionState: PortionState.filledDiet,
portionState: PortionState.eaten,
);
final state = loadedState(portion: portion, consultantView: true).copyWith(
consultantReview: ConsultantReviewModel(
planName: 'Plan',
planDuration: 10,
currentPlanDay: 1,
consultantComments: ConsultantComments(
dayComment: null,
mealsComments: const [],
portionsComments: [PortionComment(portionId: 1, comment: 'Looks good')],
),
final yesterday = DateTime.now().subtract(const Duration(days: 1));
final diaryCubit = FakeDiaryCubit(
loadedState(
portion: portion,
consultantView: false,
date: _calendarDate(yesterday),
isDiaryFinished: true,
),
);
final commentsCubit = FakeConsultantCommentsCubit(
ConsultantCommentsState(
commentsModel: ConsultantCommentModel(comment: ''),
),
);
final diaryCubit = FakeDiaryCubit(state);
final commentsCubit = FakeConsultantCommentsCubit(ConsultantCommentsState(commentsModel: ConsultantCommentModel(comment: '')));
await tester.pumpWidget(
wrap(
@@ -230,15 +351,51 @@ void main() {
);
await tester.pump();
expect(tester.widget<ListTile>(find.byType(ListTile)).onTap, isNotNull);
});
expect(find.byType(CommentContainer), findsOneWidget);
expect(find.text('Looks good'), findsOneWidget);
testWidgets('cannot edit a portion on a commented day', (tester) async {
const portion = PortionModel(
id: 1,
productNutritionalValueId: null,
productCaloriesInfo: null,
productName: 'Apple',
quantity: 100,
portion: 'gr',
fromDiet: false,
portionState: PortionState.eaten,
);
final yesterday = DateTime.now().subtract(const Duration(days: 1));
final diaryCubit = FakeDiaryCubit(
loadedState(
portion: portion,
consultantView: false,
date: _calendarDate(yesterday),
isDiaryCommented: true,
),
);
final commentsCubit = FakeConsultantCommentsCubit(
ConsultantCommentsState(
commentsModel: ConsultantCommentModel(comment: ''),
),
);
// Tap edit icon to enable PortionCommentWidget.
await tester.tap(find.byType(GestureDetector).first);
await tester.pumpWidget(
wrap(
diaryCubit: diaryCubit,
consultantCommentsCubit: commentsCubit,
child: const ProductWidget(portion: portion),
),
);
await tester.pump();
expect(find.byType(PortionCommentWidget), findsOneWidget);
expect(tester.widget<ListTile>(find.byType(ListTile)).onTap, isNull);
});
});
}
String _calendarDate(DateTime date) {
final month = date.month.toString().padLeft(2, '0');
final day = date.day.toString().padLeft(2, '0');
return '${date.year}-$month-$day';
}