diet-guard/app/test/screens/history_screen_test.dart
Krzysztof kuhy Rudnicki feef5984f8 Add photo attach, full-size viewer, and a minimal history screen
Milestone 2 of the diet-app-as-wise-balloon plan, plus feedback from
manually testing it on-device:

- PhotoAttachService wraps image_picker and copies the picked photo into
  <app documents>/images/<uuid>.<ext>, so the file survives after the
  picker's own (possibly cache-cleared) temp copy is gone. Phone-local
  only, per the sync plan: imagePath is never synced.
- PhotoAttachField is a shared attach/preview/remove widget, used
  identically by both the single-item and composite-meal logging
  screens, so logging a multi-item meal can now carry a photo too.
- PhotoViewerScreen gives a full-screen, pinch-to-zoom view of an
  attached photo -- the 64x64 inline thumbnail was too small to
  actually check the photo.
- HistoryScreen lists every logged entry across all days, newest first,
  with a thumbnail when one is attached. There was previously no way to
  confirm what got logged (or whether a photo actually attached) short
  of inspecting food_log.json directly.

Verified on a physical device (BL9000): built, installed, and the user
confirmed the photo-attach flow logs a real entry with a real photo,
visible afterward in the new history list. 88 Flutter tests passing,
`flutter analyze` clean against very_good_analysis.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FU3f5KQ1GHXsbbSecfVEyF
2026-06-22 18:57:58 +02:00

132 lines
3.9 KiB
Dart

import 'dart:io';
import 'package:diet_guard_app/models/food_entry.dart';
import 'package:diet_guard_app/screens/history_screen.dart';
import 'package:diet_guard_app/screens/photo_viewer_screen.dart';
import 'package:diet_guard_app/services/log_storage_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
late Directory tempDir;
setUp(() async {
tempDir = await Directory.systemTemp.createTemp('diet_guard_history_');
LogStorageService.resetForTesting(testDir: tempDir);
});
tearDown(() async {
LogStorageService.resetForTesting();
await tempDir.delete(recursive: true);
});
// HistoryScreen loads via a fire-and-forget Future in initState that
// Flutter's frame scheduler does not track -- see log_meal_screen_test.dart
// for the same issue. Every test therefore runs inside runAsync() with a
// short real delay before settling.
Future<void> settle(WidgetTester tester) async {
await Future<void>.delayed(const Duration(milliseconds: 200));
await tester.pumpAndSettle();
}
testWidgets('shows a message when nothing has been logged', (tester) async {
await tester.runAsync(() async {
await tester.pumpWidget(const MaterialApp(home: HistoryScreen()));
await settle(tester);
expect(find.text('Nothing logged yet.'), findsOneWidget);
});
});
testWidgets('lists logged entries newest first, excluding tombstones',
(tester) async {
await tester.runAsync(() async {
await LogStorageService.instance.writeLog({
'2026-06-01': [
const FoodEntry(
id: 'old',
time: '2026-06-01T08:00:00+02:00',
desc: 'old breakfast',
grams: 100,
kcal: 100,
proteinG: 5,
carbsG: 10,
fatG: 2,
source: 'manual',
),
],
'2026-06-22': [
const FoodEntry(
id: 'new',
time: '2026-06-22T20:00:00+02:00',
desc: 'new dinner',
grams: 100,
kcal: 200,
proteinG: 10,
carbsG: 20,
fatG: 4,
source: 'manual',
),
const FoodEntry(
id: 'gone',
time: '2026-06-22T12:00:00+02:00',
desc: 'undone lunch',
grams: 100,
kcal: 300,
proteinG: 1,
carbsG: 1,
fatG: 1,
source: 'manual',
deleted: true,
),
],
});
await tester.pumpWidget(const MaterialApp(home: HistoryScreen()));
await settle(tester);
expect(find.text('new dinner'), findsOneWidget);
expect(find.text('old breakfast'), findsOneWidget);
expect(find.text('undone lunch'), findsNothing);
final tiles = tester
.widgetList<ListTile>(find.byType(ListTile))
.toList();
expect((tiles[0].title! as Text).data, 'new dinner');
expect((tiles[1].title! as Text).data, 'old breakfast');
});
});
testWidgets('tapping a thumbnail opens the full-screen photo viewer',
(tester) async {
await tester.runAsync(() async {
final imageFile = File('${tempDir.path}/photo.png')
..writeAsBytesSync([1, 2, 3]);
await LogStorageService.instance.writeLog({
'2026-06-22': [
FoodEntry(
id: 'with-photo',
time: '2026-06-22T20:00:00+02:00',
desc: 'dinner with a photo',
grams: 100,
kcal: 200,
proteinG: 10,
carbsG: 20,
fatG: 4,
source: 'manual',
imagePath: imageFile.path,
),
],
});
await tester.pumpWidget(const MaterialApp(home: HistoryScreen()));
await settle(tester);
await tester.tap(find.byType(Image));
await settle(tester);
expect(find.byType(PhotoViewerScreen), findsOneWidget);
});
});
}