diet-guard/app/test/screens/log_meal_screen_future_test.dart
Krzysztof kuhy Rudnicki f83938bfe3 app: minute-of-day slots (phase 2 of 3: every app consumer)
- FoodEntry.slot -> slotMinute: read via entrySlotMinute, written via
  slotFields (whole-hour stays byte-identical, else slot_min), kept by
  copyWithDeleted and the edit screen, so the phone never republishes a
  stripped copy of a PC entry. Out-of-range peer values echo, never throw.
- Satisfaction is nearest-slot snapping (satisfiedSlots) everywhere:
  loggedSlotsToday, due check, kuchnia occupancy, future-day logged set
  (that day's schedule), slot selector. Schedule edits no longer un-log.
- Future pre-log builds DateTime from slot ~/ 60, slot % 60.
- Schedule history + sync go through scheduleToWire/scheduleFromWire.
- Notifications: ids are slot minutes; syncToSlots cancels whatever the
  platform reports active (getActiveNotifications, reminder channel) that
  is not due, covering legacy hour ids; full 0..1439 sweep only if the
  lookup throws.
- Settings: 15-minute dropdowns + "Custom..." (24h showTimePicker), off-grid
  values always selectable, counts capped by grid capacity; editor moved
  to widgets/meal_schedule_editor.dart + meal_time_dropdown.dart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015npsYVB2PdtWkG7MPAi4tn
2026-10-10 19:55:26 +02:00

144 lines
5.2 KiB
Dart

import 'dart:io';
import 'package:diet_guard_app/models/local_time.dart';
import 'package:diet_guard_app/models/slot.dart';
import 'package:diet_guard_app/screens/log_meal_screen.dart';
import 'package:diet_guard_app/services/app_settings_service.dart';
import 'package:diet_guard_app/services/budget_history_service.dart';
import 'package:diet_guard_app/services/document_store_io.dart';
import 'package:diet_guard_app/services/foodbank_service.dart';
import 'package:diet_guard_app/services/log_storage_service.dart';
import 'package:diet_guard_app/services/meal_schedule_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
/// Covers logging a meal against a future date/slot through [LogMealScreen]'s
/// "When" row -- its own file, split out the same way
/// `log_meal_screen_nav_test.dart` and `log_meal_screen_sync_test.dart` are.
void main() {
late Directory tempDir;
setUp(() async {
tempDir = await Directory.systemTemp.createTemp('diet_guard_future_');
LogStorageService.resetForTesting(store: FileDocumentStore(tempDir));
FoodBankService.resetForTesting(store: FileDocumentStore(tempDir));
AppSettingsService.resetForTesting(store: FileDocumentStore(tempDir));
BudgetHistoryService.resetForTesting(store: FileDocumentStore(tempDir));
await MealScheduleService.initForTesting(FileDocumentStore(tempDir));
});
tearDown(() async {
LogStorageService.resetForTesting();
FoodBankService.resetForTesting();
AppSettingsService.resetForTesting();
BudgetHistoryService.resetForTesting();
MealScheduleService.resetForTesting();
await tempDir.delete(recursive: true);
});
final logMealButton = find.byTooltip('Log meal');
// See log_meal_screen_test.dart's `settle` for why: fire-and-forget I/O
// that pumpAndSettle() alone does not wait for.
Future<void> settle(WidgetTester tester) async {
await Future<void>.delayed(const Duration(milliseconds: 200));
await tester.pumpAndSettle();
}
DateTime tomorrow() {
final today = DateTime.now();
return DateTime(today.year, today.month, today.day + 1);
}
testWidgets(
'logging for a future date/slot persists under that date, not today, '
'and resets back to Today',
(tester) async {
await tester.runAsync(() async {
await tester.pumpWidget(const MaterialApp(home: LogMealScreen()));
await settle(tester);
await tester.tap(find.text('Log for later'));
await settle(tester);
// The date picker's initialDate is already tomorrow, so confirming
// immediately picks it without touching a specific calendar cell.
await tester.tap(find.text('OK'));
await settle(tester);
final tomorrowKey = localDateKey(tomorrow());
expect(find.text(tomorrowKey), findsOneWidget);
final firstSlot = daySlots(MealScheduleService.current).first;
await tester.tap(find.text(slotLabel(firstSlot)));
await settle(tester);
await tester.enterText(find.byType(TextField).at(0), 'future meal');
await settle(tester);
await tester.enterText(find.byType(TextField).at(2), '150');
await settle(tester);
await tester.ensureVisible(logMealButton);
await tester.tap(logMealButton);
await settle(tester);
expect(await LogStorageService.instance.todayEntries(), isEmpty);
final log = await LogStorageService.instance.readLog();
final future = log[tomorrowKey]!.single;
expect(future.desc, 'future meal');
expect(future.slotMinute, firstSlot);
expect(future.time.startsWith(tomorrowKey), isTrue);
// Back to logging for today.
expect(find.text('Today'), findsOneWidget);
expect(find.text('Log for later'), findsOneWidget);
});
},
);
testWidgets('refuses to log a future date with no slot picked', (
tester,
) async {
await tester.runAsync(() async {
await tester.pumpWidget(const MaterialApp(home: LogMealScreen()));
await settle(tester);
await tester.tap(find.text('Log for later'));
await settle(tester);
await tester.tap(find.text('OK'));
await settle(tester);
await tester.enterText(find.byType(TextField).at(0), 'no slot meal');
await settle(tester);
await tester.ensureVisible(logMealButton);
await tester.tap(logMealButton);
await settle(tester);
expect(find.text('Pick a slot for that date first.'), findsOneWidget);
final log = await LogStorageService.instance.readLog();
expect(log.values.expand((e) => e), isEmpty);
});
});
testWidgets('the "x" button clears the future date back to today', (
tester,
) async {
await tester.runAsync(() async {
await tester.pumpWidget(const MaterialApp(home: LogMealScreen()));
await settle(tester);
await tester.tap(find.text('Log for later'));
await settle(tester);
await tester.tap(find.text('OK'));
await settle(tester);
expect(find.text('Today'), findsNothing);
await tester.tap(find.byTooltip('Back to today'));
await settle(tester);
expect(find.text('Today'), findsOneWidget);
expect(find.text('Log for later'), findsOneWidget);
});
});
}