diff --git a/app/lib/screens/log_meal_fill_mixin.dart b/app/lib/screens/log_meal_fill_mixin.dart index 8dbae44..aa3f760 100644 --- a/app/lib/screens/log_meal_fill_mixin.dart +++ b/app/lib/screens/log_meal_fill_mixin.dart @@ -6,6 +6,7 @@ /// `kuchnia_fill_flow.dart`; this only connects them to the screen. library; +import 'package:diet_guard_app/screens/log_meal_future_mixin.dart'; import 'package:diet_guard_app/screens/log_meal_kuchnia_mixin.dart'; import 'package:diet_guard_app/screens/log_meal_sync_mixin.dart'; import 'package:diet_guard_app/services/kuchnia_fill_flow.dart'; @@ -15,7 +16,7 @@ import 'package:flutter/material.dart'; /// Drives the screen's [FillAllFlow] and tidies the screen after a fill. mixin LogMealFillMixin - on LogMealSyncMixin, LogMealKuchniaMixin { + on LogMealSyncMixin, LogMealKuchniaMixin, LogMealFutureMixin { /// The flow behind the button. Per-screen, so leaving the screen drops an /// armed proposal along with it. FillAllFlow fillFlow = FillAllFlow(); @@ -23,9 +24,19 @@ mixin LogMealFillMixin /// Shows [message] in the form's status line. Supplied by the screen. void showFillStatus(String message); + /// A proposal belongs to the date it was fetched for; picking another (or + /// going back to today) must not leave "Confirm (N)" offering it. + @override + void onLogDateChanged() { + super.onLogDateChanged(); + fillFlow.cancel(); + } + /// Runs one tap: the first proposes, a second on the same day confirms. + /// Fills the "Log for later" date when one is picked, else today. Future onFillAll() async { - final pending = fillFlow.tap(); + final day = futureDate; + final pending = fillFlow.tap(day: day); setState(() {}); // show the busy label while the fetch/writes run final FillOutcome? outcome; try { @@ -44,7 +55,9 @@ mixin LogMealFillMixin if (!mounted) return; setState(() {}); if (outcome == null) return; - if (outcome.written.isNotEmpty) await _afterFill(outcome.written); + if (outcome.written.isNotEmpty) { + await _afterFill(outcome.written, later: day != null); + } if (!mounted) return; showFillStatus(outcome.message); } @@ -58,7 +71,20 @@ mixin LogMealFillMixin /// Publishes the batch once and drops the filled dishes from the delivery /// queue, so "Today's delivery (N more to go)" and the prefilled form do /// not keep offering meals that were just logged. - Future _afterFill(List written) async { + /// + /// A [later] fill logged another day's delivery: today's queue and form are + /// left alone, and the screen goes back to today, as after a manual + /// "Log for later" -- the picked day's slot row would otherwise be stale. + Future _afterFill( + List written, { + required bool later, + }) async { + if (later) { + resetFutureLog(); + await publishAfterLog(); + await refreshSlots(); + return; + } if (KuchniaQueueService.isInitialized) { for (final item in written) { KuchniaQueueService.instance.markLogged(item.dish); diff --git a/app/lib/screens/log_meal_future_mixin.dart b/app/lib/screens/log_meal_future_mixin.dart index a12fc2e..50fc13a 100644 --- a/app/lib/screens/log_meal_future_mixin.dart +++ b/app/lib/screens/log_meal_future_mixin.dart @@ -33,6 +33,10 @@ mixin LogMealFutureMixin on State { /// The slot chosen for [futureDate], or null until one is picked. int? get futureSlot => _futureSlot; + /// Called after the picked date changes (including back to today), for + /// state keyed to the old date to drop itself. No-op unless overridden. + void onLogDateChanged() {} + /// Clears back to "log for today", dropping the picked date and slot. void resetFutureLog() { setState(() { @@ -41,6 +45,7 @@ mixin LogMealFutureMixin on State { _futureLoggedSlots = {}; _futureSchedule = null; }); + onLogDateChanged(); } Future _pickFutureDate() async { @@ -53,6 +58,12 @@ mixin LogMealFutureMixin on State { initialDate: _futureDate ?? tomorrow, ); if (picked == null || !mounted) return; + await selectLogDate(picked); + } + + /// Switches the form to log against [picked] (strictly after today): what + /// the date picker does once a date is chosen. + Future selectLogDate(DateTime picked) async { final log = await LogStorageService.instance.readLog(); if (!mounted) return; final day = localDateKey(picked); @@ -70,6 +81,7 @@ mixin LogMealFutureMixin on State { _futureLoggedSlots = logged; _futureSlot = null; }); + onLogDateChanged(); } /// The "When" row: a "Log for later" button, or the picked date with a diff --git a/app/lib/screens/log_meal_screen.dart b/app/lib/screens/log_meal_screen.dart index aab3de2..0f57a0e 100644 --- a/app/lib/screens/log_meal_screen.dart +++ b/app/lib/screens/log_meal_screen.dart @@ -47,8 +47,8 @@ class _LogMealScreenState extends State LogMealSyncMixin, LogMealNavMixin, LogMealKuchniaMixin, - LogMealFillMixin, - LogMealFutureMixin { + LogMealFutureMixin, + LogMealFillMixin { @override http.Client? get syncHttpClient => widget.httpClient; diff --git a/app/lib/services/kuchnia_fill.dart b/app/lib/services/kuchnia_fill.dart index 97e9bd3..f64b0f8 100644 --- a/app/lib/services/kuchnia_fill.dart +++ b/app/lib/services/kuchnia_fill.dart @@ -9,11 +9,14 @@ /// reached from the second tap of `FillAllFlow`. library; +import 'package:diet_guard_app/models/food_entry.dart'; import 'package:diet_guard_app/models/kuchnia_dish.dart'; +import 'package:diet_guard_app/models/local_time.dart'; import 'package:diet_guard_app/models/nutrition.dart'; import 'package:diet_guard_app/models/slot.dart'; import 'package:diet_guard_app/services/kuchnia_spread.dart'; import 'package:diet_guard_app/services/log_storage_service.dart'; +import 'package:diet_guard_app/services/meal_schedule_service.dart'; /// Marks entries this feature created, the same tag the PC's Fill all writes /// (`_kuchnia_log.SOURCE`), so both devices' batch fills read alike. @@ -61,23 +64,49 @@ Nutrition dishNutrition(KuchniaDish dish) => nutritionForPortion( String _dedupKey(String desc, int? slotMinute) => '${desc.trim().toLowerCase()}\u0000$slotMinute'; -/// Logs each chosen dish, skipping any already logged today in that slot. +/// Returns [day]'s live entries; today's when [day] is null. +Future> dayEntries( + LogStorageService store, + DateTime? day, +) async { + if (day == null) return await store.todayEntries(); + final log = await store.readLog(); + return [ + for (final entry in log[localDateKey(day)] ?? const []) + if (!entry.deleted) entry, + ]; +} + +/// [day]'s satisfied slot minutes under the schedule in force *that* day -- +/// `LogStorageService.loggedSlotsToday` for any date, as the "Log for later" +/// slot row computes it. +Future> loggedSlotsOn(LogStorageService store, DateTime day) async { + final entries = await dayEntries(store, day); + return satisfiedSlots([ + for (final entry in entries) ?entry.slotMinute, + ], MealScheduleService.history.forDay(localDateKey(day))); +} + +/// Logs each chosen dish, skipping any already logged that day in that slot. /// /// Mirrors `log_dishes`, including its intra-batch skip: two identical dishes /// spread into the same slot land once, because the write is reflected in the /// seen set before the next dish is checked. /// /// Writes through [LogStorageService.logMeal], the same call a manual log -/// makes; the caller then publishes once for the whole batch. +/// makes; the caller then publishes once for the whole batch. With [day] +/// (a "Log for later" date, strictly after today) each dish is stamped at its +/// slot's time on that day; without it, now -- as a manual log is. /// /// Returns the dishes actually written, in order. Future> logDishes( List chosen, { LogStorageService? storage, + DateTime? day, }) async { final store = storage ?? LogStorageService.instance; final seen = { - for (final entry in await store.todayEntries()) + for (final entry in await dayEntries(store, day)) _dedupKey(entry.desc, entry.slotMinute), }; final written = []; @@ -88,6 +117,15 @@ Future> logDishes( item.dish.name, dishNutrition(item.dish), slotMinute: item.slot, + when: day == null + ? null + : DateTime( + day.year, + day.month, + day.day, + item.slot ~/ 60, + item.slot % 60, + ), ); written.add(item); } diff --git a/app/lib/services/kuchnia_fill_flow.dart b/app/lib/services/kuchnia_fill_flow.dart index 40e3a5e..fe2f679 100644 --- a/app/lib/services/kuchnia_fill_flow.dart +++ b/app/lib/services/kuchnia_fill_flow.dart @@ -1,15 +1,17 @@ /// The two-tap state machine behind "Fill all with catering". /// /// Mirrors `diet_guard/_gatelock_fillall.py`'s `FillAllFlow`. Two taps, never -/// one: the first fetches today's delivery and proposes exactly what would be -/// written; only the second writes. No modal dialog -- the proposal is a -/// status line and the button relabels to "✓ Confirm (N)". +/// one: the first fetches the day's delivery (today's, or the date picked +/// through "Log for later") and proposes exactly what would be written; only +/// the second writes. No modal dialog -- the proposal is a status line and +/// the button relabels to "✓ Confirm (N)". /// /// Widget-free so the flow's guarantees (first tap writes nothing, confirm /// re-checks occupancy, a plan from before midnight refetches) are testable /// with an injected clock and fetch. library; +import 'package:diet_guard_app/models/local_time.dart'; import 'package:diet_guard_app/models/slot.dart'; import 'package:diet_guard_app/services/kuchnia_fill.dart'; import 'package:diet_guard_app/services/kuchnia_import.dart'; @@ -17,6 +19,7 @@ import 'package:diet_guard_app/services/kuchnia_queue.dart'; import 'package:diet_guard_app/services/kuchnia_spread.dart'; import 'package:diet_guard_app/services/log_storage_service.dart'; import 'package:diet_guard_app/services/meal_schedule_service.dart'; +import 'package:flutter/foundation.dart' show visibleForTesting; /// Fetches one day's delivery. typedef DeliveryFetch = Future Function(DateTime day); @@ -31,11 +34,19 @@ const _none = []; const nothingToFillMessage = 'Every slot already has a meal — nothing to fill.'; /// The explicit tap's fetch: unguarded, like "Today's delivery", and it -/// records the fetch so the automatic path is spared a second walk. -Future _fetchAndRecord(DateTime day) async { - final result = await refreshDelivery(day); +/// records today's fetch so the automatic path is spared a second walk. +/// +/// Only today's: the marker holds a single day, so recording a "Log for +/// later" date would make the automatic fetch skip that day when it comes. +@visibleForTesting +Future fetchAndRecord( + DateTime day, { + DeliveryFetch fetch = refreshDelivery, +}) async { + final result = await fetch(day); if (result.ok && result.dishes.isNotEmpty && + localDateKey(day) == localDateKey(DateTime.now()) && KuchniaQueueService.isInitialized) { await KuchniaQueueService.instance.recordFetched(day); } @@ -48,21 +59,24 @@ class FillAllFlow { FillAllFlow({ DeliveryFetch? fetch, DateTime Function()? now, - List Function()? slots, - Future> Function()? occupiedSlots, + List Function(DateTime day)? slots, + Future> Function(DateTime day)? occupiedSlots, LogStorageService? storage, - }) : _fetch = fetch ?? _fetchAndRecord, + }) : _fetch = fetch ?? fetchAndRecord, _now = now ?? DateTime.now, - _slots = slots ?? (() => daySlots(MealScheduleService.current)), + _slots = + slots ?? + ((day) => + daySlots(MealScheduleService.history.forDay(localDateKey(day)))), _occupied = occupiedSlots ?? - (() => (storage ?? LogStorageService.instance).loggedSlotsToday()), + ((day) => loggedSlotsOn(storage ?? LogStorageService.instance, day)), _storage = storage; final DeliveryFetch _fetch; final DateTime Function() _now; - final List Function() _slots; - final Future> Function() _occupied; + final List Function(DateTime day) _slots; + final Future> Function(DateTime day) _occupied; final LogStorageService? _storage; List _plan = const []; @@ -82,37 +96,43 @@ class FillAllFlow { /// First tap fetches and proposes; a second tap on the same day confirms. /// + /// [day] is the "Log for later" date when one is picked; null means today. + /// /// Returns null when the tap was ignored because one is already in flight, /// or when [cancel] ran while its fetch was. - Future tap() async { + Future tap({DateTime? day}) async { if (busy) return null; - final today = _today(); - if (_plan.isNotEmpty && _planDay == today) return await _confirm(); - // No plan, or one proposed before midnight: fetch (again). The day is - // pinned here, not when the result lands, so a fetch still in flight at - // midnight can never confirm yesterday's menu into today. + final target = day == null + ? _today() + : DateTime(day.year, day.month, day.day); + if (_plan.isNotEmpty && _planDay == target) return await _confirm(); + // No plan, one for another picked day, or one proposed before midnight: + // fetch (again). The day is pinned here, not when the result lands, so a + // fetch still in flight at midnight can never confirm yesterday's menu + // into today. cancel(); - _planDay = today; + _planDay = target; busy = true; try { - return await _propose(today); + return await _propose(target); } finally { busy = false; } } - Future _propose(DateTime today) async { - final result = await _fetch(today); + Future _propose(DateTime day) async { + final result = await _fetch(day); // Cancelled while the fetch was in flight: arming now would show // "Confirm (N)" for a plan the user already dismissed. - if (_planDay != today) return null; + if (_planDay != day) return null; if (!result.ok) return (message: result.reason!, written: _none); if (result.dishes.isEmpty) { - return (message: 'No catering delivery today.', written: _none); + final when = day == _today() ? 'today' : 'on ${localDateKey(day)}'; + return (message: 'No catering delivery $when.', written: _none); } final plan = fillPlan( - assignSlots(result.dishes, _slots()), - await _occupied(), + assignSlots(result.dishes, _slots(day)), + await _occupied(day), ); if (plan.isEmpty) return (message: nothingToFillMessage, written: _none); _plan = plan; @@ -124,15 +144,20 @@ class FillAllFlow { try { // Re-checked at write time: a meal logged in between the two taps -- // here or synced from the PC -- has taken its slot and keeps it. - final plan = fillPlan(_plan, await _occupied()); + final day = _planDay!; + final plan = fillPlan(_plan, await _occupied(day)); cancel(); if (plan.isEmpty) { return (message: nothingToFillMessage, written: _none); } - final written = await logDishes(plan, storage: _storage); + // A later day's dishes are stamped at their slot times on that day; + // today's at now, like any manual log. + final later = day == _today() ? null : day; + final written = await logDishes(plan, storage: _storage, day: later); final noun = written.length == 1 ? 'dish' : 'dishes'; + final suffix = later == null ? '' : ' for ${localDateKey(later)}'; return ( - message: 'Logged ${written.length} catering $noun.', + message: 'Logged ${written.length} catering $noun$suffix.', written: written, ); } finally { diff --git a/app/pubspec.yaml b/app/pubspec.yaml index f1e7896..e5d9a68 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -2,7 +2,7 @@ name: diet_guard_app description: "Companion phone app for diet_guard: log meals on the go." publish_to: 'none' -version: 1.0.120+1 +version: 1.0.121+1 environment: sdk: ^3.12.2 diff --git a/app/test/kuchnia_fill_cancel_test.dart b/app/test/kuchnia_fill_cancel_test.dart index b0b8d25..62ce53c 100644 --- a/app/test/kuchnia_fill_cancel_test.dart +++ b/app/test/kuchnia_fill_cancel_test.dart @@ -25,8 +25,8 @@ void main() { return KuchniaRefresh(dishes: dishes(4)); }, now: () => DateTime(2026, 8, 23, 10), - slots: () => defaultSlots, - occupiedSlots: () async => {}, + slots: (_) => defaultSlots, + occupiedSlots: (_) async => {}, storage: freshStorage(), ); diff --git a/app/test/kuchnia_fill_flow_future_test.dart b/app/test/kuchnia_fill_flow_future_test.dart new file mode 100644 index 0000000..c2fcd8e --- /dev/null +++ b/app/test/kuchnia_fill_flow_future_test.dart @@ -0,0 +1,150 @@ +/// "Fill all with catering" with a later date picked through "Log for later". +/// +/// The flow used to hard-code today: picking 24.10 on 23.10 fetched 23.10's +/// delivery and reported "No catering delivery today." Each test pins the +/// *picked* day end to end -- the fetch, the occupancy, and where the entries +/// land -- by reading the log, not a return value. +/// +/// The clock is the real one: `logMeal` refuses a `when` in the past against +/// `DateTime.now()`, so the picked day has to be genuinely in the future. +library; + +import 'package:diet_guard_app/models/local_time.dart'; +import 'package:diet_guard_app/models/nutrition.dart'; +import 'package:diet_guard_app/services/kuchnia_fill_flow.dart'; +import 'package:diet_guard_app/services/kuchnia_import.dart'; +import 'package:diet_guard_app/services/kuchnia_queue.dart'; +import 'package:diet_guard_app/services/log_storage_service.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'kuchnia_fill_support.dart'; + +/// `desc@slot time` for every live entry stored under [day]. +Future> loggedOn(LogStorageService storage, DateTime day) async { + final log = await storage.readLog(); + return [ + for (final e in log[localDateKey(day)] ?? const []) + if (!e.deleted) '${e.desc}@${e.slotMinute} ${e.time.substring(11, 16)}', + ]; +} + +void main() { + late LogStorageService storage; + late List fetched; + late KuchniaRefresh delivery; + late FillAllFlow flow; + final now = DateTime.now(); + final tomorrow = DateTime(now.year, now.month, now.day + 1); + final dayAfter = DateTime(now.year, now.month, now.day + 2); + + setUp(() { + storage = freshStorage(); + fetched = []; + delivery = KuchniaRefresh(dishes: dishes(4)); + flow = FillAllFlow( + fetch: (day) async { + fetched.add(day); + return delivery; + }, + storage: storage, + ); + }); + + tearDown(LogStorageService.resetForTesting); + + test('fetches the picked day and logs onto it at slot times', () async { + final first = await flow.tap(day: tomorrow); + + expect(fetched, [tomorrow]); + expect(first!.message, startsWith('Will log 4')); + expect(await loggedOn(storage, tomorrow), isEmpty); + + final second = await flow.tap(day: tomorrow); + + expect( + second!.message, + 'Logged 4 catering dishes for ' + '${localDateKey(tomorrow)}.', + ); + expect(await loggedOn(storage, tomorrow), [ + 'D1@480 08:00', + 'D2@720 12:00', + 'D3@960 16:00', + 'D4@1200 20:00', + ]); + expect(await loggedToday(storage), isEmpty); + }); + + test("occupancy is the picked day's, not today's", () async { + // Today's 08:00 is taken, which must not block tomorrow's 08:00 ... + await storage.logMeal('eaten today', _kcal, slotMinute: 480); + // ... while tomorrow's pre-logged 12:00 keeps its meal. + await storage.logMeal( + 'pre-logged', + _kcal, + slotMinute: 720, + when: tomorrow.add(const Duration(hours: 12)), + ); + + await flow.tap(day: tomorrow); + await flow.tap(day: tomorrow); + + expect(await loggedOn(storage, tomorrow), [ + 'pre-logged@720 12:00', + 'D1@480 08:00', + 'D3@960 16:00', + 'D4@1200 20:00', + ]); + }); + + test('a plan proposed for one day never confirms onto another', () async { + await flow.tap(day: tomorrow); + final outcome = await flow.tap(day: dayAfter); + + expect(fetched, [tomorrow, dayAfter]); + expect(outcome!.written, isEmpty); + expect(await loggedOn(storage, tomorrow), isEmpty); + expect(await loggedOn(storage, dayAfter), isEmpty); + }); + + test('an empty delivery names the picked day', () async { + delivery = const KuchniaRefresh(dishes: []); + + final outcome = await flow.tap(day: tomorrow); + + expect( + outcome!.message, + 'No catering delivery on ${localDateKey(tomorrow)}.', + ); + }); + + test('no day still means today', () async { + await flow.tap(); + + expect(fetched, [DateTime(now.year, now.month, now.day)]); + }); + + test("a later day's fetch never marks that day as already fetched", () async { + // The marker holds one day, read by the automatic "Today's delivery" + // fetch: recording tomorrow would skip that fetch when tomorrow comes. + await KuchniaQueueService.initForTesting(MemoryStore()); + addTearDown(KuchniaQueueService.resetForTesting); + final today = DateTime(now.year, now.month, now.day); + Future fake(DateTime _) async => delivery; + + await fetchAndRecord(tomorrow, fetch: fake); + expect(KuchniaQueueService.instance.alreadyFetched(tomorrow), isFalse); + + await fetchAndRecord(today, fetch: fake); + expect(KuchniaQueueService.instance.alreadyFetched(today), isTrue); + }); +} + +const _kcal = Nutrition( + grams: 100, + kcal: 100, + proteinG: 1, + carbsG: 1, + fatG: 1, + source: 'manual', +); diff --git a/app/test/kuchnia_fill_flow_test.dart b/app/test/kuchnia_fill_flow_test.dart index ae0be4f..7cb2112 100644 --- a/app/test/kuchnia_fill_flow_test.dart +++ b/app/test/kuchnia_fill_flow_test.dart @@ -49,8 +49,8 @@ void main() { flow = FillAllFlow( fetch: fetcher.call, now: () => clock, - slots: () => defaultSlots, - occupiedSlots: () async { + slots: (_) => defaultSlots, + occupiedSlots: (_) async { occupancyReads++; if (occupancyGate != null) await occupancyGate!.future; return {...occupied}; diff --git a/app/test/screens/log_meal_fill_mixin_test.dart b/app/test/screens/log_meal_fill_mixin_test.dart index 0da9a3c..d1b00fd 100644 --- a/app/test/screens/log_meal_fill_mixin_test.dart +++ b/app/test/screens/log_meal_fill_mixin_test.dart @@ -12,7 +12,9 @@ library; import 'package:diet_guard_app/models/kuchnia_dish.dart'; +import 'package:diet_guard_app/models/local_time.dart'; import 'package:diet_guard_app/screens/log_meal_fill_mixin.dart'; +import 'package:diet_guard_app/screens/log_meal_future_mixin.dart'; import 'package:diet_guard_app/screens/log_meal_kuchnia_mixin.dart'; import 'package:diet_guard_app/screens/log_meal_sync_mixin.dart'; import 'package:diet_guard_app/services/kuchnia_fill.dart'; @@ -42,6 +44,7 @@ class _HostState extends State<_Host> WidgetsBindingObserver, LogMealSyncMixin<_Host>, LogMealKuchniaMixin<_Host>, + LogMealFutureMixin<_Host>, LogMealFillMixin<_Host> { final TextEditingController desc = TextEditingController(); final MacroControllers macros = MacroControllers(); @@ -65,7 +68,7 @@ class _HostState extends State<_Host> super.initState(); fillFlow = FillAllFlow( fetch: (_) async => KuchniaRefresh(dishes: widget.delivery), - slots: () => defaultSlots, + slots: (_) => defaultSlots, storage: widget.storage, ); } @@ -175,8 +178,8 @@ void main() { await pumpHost(t); host.fillFlow = FillAllFlow( fetch: (_) async => KuchniaRefresh(dishes: delivery), - slots: () => defaultSlots, - occupiedSlots: () async => throw Exception('storage unavailable'), + slots: (_) => defaultSlots, + occupiedSlots: (_) async => throw Exception('storage unavailable'), storage: storage, ); @@ -186,4 +189,43 @@ void main() { expect(host.publishes, 1, reason: 'a partial batch must still sync'); expect(host.fillFlow.busy, isFalse); }); + + testWidgets('a later-day fill leaves today alone and returns to it', ( + t, + ) async { + final now = DateTime.now(); + final tomorrow = DateTime(now.year, now.month, now.day + 1); + await pumpHost(t); + await t.runAsync(() async { + await host.selectLogDate(tomorrow); + await host.onFillAll(); + await host.onFillAll(); + }); + + expect( + host.status, + 'Logged 4 catering dishes for ${localDateKey(tomorrow)}.', + ); + final log = await t.runAsync(storage.readLog); + expect(log![localDateKey(tomorrow)], hasLength(4)); + expect(await t.runAsync(storage.todayEntries), isEmpty); + expect(host.publishes, 1); + expect(KuchniaQueueService.remaining, 4, reason: "today's queue untouched"); + expect(host.desc.text, 'D1'); + expect(host.futureDate, isNull); + }); + + testWidgets('changing the date disarms a proposal', (t) async { + final now = DateTime.now(); + await pumpHost(t); + await t.runAsync(() async { + await host.selectLogDate(DateTime(now.year, now.month, now.day + 1)); + await host.onFillAll(); + }); + expect(host.fillFlow.armedCount, 4); + + host.resetFutureLog(); + + expect(host.fillFlow.armedCount, 0); + }); }