diet-guard/app/test/fake_notifications.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

70 lines
2.6 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_test/flutter_test.dart';
/// The channel id `LocalNotificationsBackend` posts reminders on -- and the
/// one older, hour-keyed builds used too.
const reminderChannelId = 'diet_guard_due_slot';
/// Mocks the raw `dexterous.com/flutter/local_notifications` MethodChannel
/// the way the package's own test suite does
/// (`android_flutter_local_notifications_test.dart`), so
/// [NotificationService] can be exercised end-to-end (init/show/cancel)
/// without a real Android plugin.
///
/// Stateful, like the real notification shade: `show` adds an id, `cancel`
/// removes it, and `getActiveNotifications` reports what is left. Without
/// that, the active-id lookup `syncToSlots` relies on would always see an
/// empty shade and every orphan test would pass vacuously. Seed [active]
/// (id -> channel id) to simulate reminders a previous run or an older build
/// left behind.
///
/// Returns the call log so a test can assert which slots were shown vs.
/// cancelled.
List<MethodCall> installFakeAndroidNotifications({
Map<int, String>? active,
}) {
AndroidFlutterLocalNotificationsPlugin.registerWith();
debugDefaultTargetPlatformOverride = TargetPlatform.android;
const channel = MethodChannel('dexterous.com/flutter/local_notifications');
final log = <MethodCall>[];
final shade = active ?? <int, String>{};
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
log.add(call);
final args = call.arguments;
switch (call.method) {
case 'initialize':
return true;
case 'requestNotificationsPermission':
return true;
case 'show':
final map = args as Map;
final details = map['platformSpecifics'] as Map?;
shade[map['id'] as int] =
details?['channelId'] as String? ?? reminderChannelId;
return null;
case 'cancel':
shade.remove((args as Map)['id']);
return null;
case 'getActiveNotifications':
return [
for (final entry in shade.entries)
{'id': entry.key, 'channelId': entry.value},
];
default:
return null;
}
});
addTearDown(() {
debugDefaultTargetPlatformOverride = null;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, null);
});
return log;
}