fix: Tour-Datum — Leer-Texte, veraltete Antworten, Kalenderfenster
- Leer-Ansichten (Auswählen, Sortieren, Beladen, Ausliefern) nennen den gewählten Tag statt immer „heute": „Für den 02.09.2026 ist keine Tour zugewiesen." (TourDayText). - TourBloc: Loads/Refreshes laufen nebenläufig; bei schnellem Datumswechsel konnte die langsamere Antwort des vorigen Tages die Tour des gewählten Tages überschreiben. Jetzt schreibt nur die jüngste Anfrage ihr Ergebnis. - Kalender reicht 90 statt 14 Tage zurück (z. B. nachimportierte Touren vom Monatsanfang). - Test: neuer Fahrer startet auf „heute", auch wenn der vorige einen anderen Tag gewählt hatte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@ -72,6 +72,12 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
|||||||
/// damit ein Pull-to-refresh denselben Tag neu lädt (nicht heute).
|
/// damit ein Pull-to-refresh denselben Tag neu lädt (nicht heute).
|
||||||
DateTime? _selectedDate;
|
DateTime? _selectedDate;
|
||||||
|
|
||||||
|
/// Laufnummer der Tour-Anfragen. Loads/Refreshes laufen nebenläufig; wählt
|
||||||
|
/// der Fahrer schnell nacheinander zwei Tage, darf die langsamere Antwort
|
||||||
|
/// des ersten Tages die des zuletzt gewählten nicht überschreiben. Nur die
|
||||||
|
/// jüngste Anfrage schreibt ihr Ergebnis.
|
||||||
|
int _requestSeq = 0;
|
||||||
|
|
||||||
/// Räumt verwaiste Cache-Bilder weg, sobald frische Tour-Daten vorliegen.
|
/// Räumt verwaiste Cache-Bilder weg, sobald frische Tour-Daten vorliegen.
|
||||||
/// Fire-and-forget — Cache-Pflege darf den Load nie verzögern oder
|
/// Fire-and-forget — Cache-Pflege darf den Load nie verzögern oder
|
||||||
/// scheitern lassen.
|
/// scheitern lassen.
|
||||||
@ -85,9 +91,11 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
|||||||
// Nur bei explizit gesetztem Datum die Wahl aktualisieren; ein reiner
|
// Nur bei explizit gesetztem Datum die Wahl aktualisieren; ein reiner
|
||||||
// Reload (LoadTour()) behält den aktuell angezeigten Tag.
|
// Reload (LoadTour()) behält den aktuell angezeigten Tag.
|
||||||
if (event.hasDate) _selectedDate = event.date;
|
if (event.hasDate) _selectedDate = event.date;
|
||||||
|
final seq = ++_requestSeq;
|
||||||
emit(const TourLoading());
|
emit(const TourLoading());
|
||||||
try {
|
try {
|
||||||
final details = await tourRepository.getMyTourDetails(date: _selectedDate);
|
final details = await tourRepository.getMyTourDetails(date: _selectedDate);
|
||||||
|
if (seq != _requestSeq) return; // überholt von neuerer Anfrage
|
||||||
if (details == null) {
|
if (details == null) {
|
||||||
emit(const TourEmpty());
|
emit(const TourEmpty());
|
||||||
return;
|
return;
|
||||||
@ -95,6 +103,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
|||||||
emit(TourLoaded(details: details));
|
emit(TourLoaded(details: details));
|
||||||
_pruneAttachmentCache(details);
|
_pruneAttachmentCache(details);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
if (seq != _requestSeq) return;
|
||||||
debugPrint('TourBloc.LoadTour fehlgeschlagen: $e\n$st');
|
debugPrint('TourBloc.LoadTour fehlgeschlagen: $e\n$st');
|
||||||
final message = _messageOf(e, 'Tour konnte nicht geladen werden');
|
final message = _messageOf(e, 'Tour konnte nicht geladen werden');
|
||||||
emit(TourLoadFailed(message: message));
|
emit(TourLoadFailed(message: message));
|
||||||
@ -113,9 +122,11 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final seq = ++_requestSeq;
|
||||||
emit(current.copyWith(isRefreshing: true, refreshError: null));
|
emit(current.copyWith(isRefreshing: true, refreshError: null));
|
||||||
try {
|
try {
|
||||||
final details = await tourRepository.getMyTourDetails(date: _selectedDate);
|
final details = await tourRepository.getMyTourDetails(date: _selectedDate);
|
||||||
|
if (seq != _requestSeq) return; // überholt von neuerer Anfrage
|
||||||
if (details == null) {
|
if (details == null) {
|
||||||
emit(const TourEmpty());
|
emit(const TourEmpty());
|
||||||
return;
|
return;
|
||||||
@ -123,6 +134,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
|||||||
emit(TourLoaded(details: details));
|
emit(TourLoaded(details: details));
|
||||||
_pruneAttachmentCache(details);
|
_pruneAttachmentCache(details);
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
if (seq != _requestSeq) return;
|
||||||
debugPrint('TourBloc.RefreshTour fehlgeschlagen: $e\n$st');
|
debugPrint('TourBloc.RefreshTour fehlgeschlagen: $e\n$st');
|
||||||
final message = _messageOf(e, 'Tour konnte nicht neu geladen werden');
|
final message = _messageOf(e, 'Tour konnte nicht neu geladen werden');
|
||||||
// alten Stand sichtbar lassen, Fehler oben mitführen
|
// alten Stand sichtbar lassen, Fehler oben mitführen
|
||||||
|
|||||||
40
lib/feature/delivery/model/tour_day_text.dart
Normal file
40
lib/feature/delivery/model/tour_day_text.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/// Formulierung des gewählten Tour-Tags für Fließtexte, z. B. in den
|
||||||
|
/// Leer-Ansichten („Für heute ist keine Tour zugewiesen." bzw. „Für den
|
||||||
|
/// 22.09.2026 …").
|
||||||
|
///
|
||||||
|
/// `null` als gewähltes Datum heißt „heute" (Server-Default, siehe
|
||||||
|
/// `TourDateCubit`). Heute/morgen/gestern werden relativ zur Geräte-Uhr
|
||||||
|
/// benannt, alle anderen Tage mit Datum.
|
||||||
|
class TourDayText {
|
||||||
|
const TourDayText._(this._relative, this.date);
|
||||||
|
|
||||||
|
factory TourDayText.of(DateTime? selected, {DateTime? now}) {
|
||||||
|
final clock = now ?? DateTime.now();
|
||||||
|
final today = DateTime(clock.year, clock.month, clock.day);
|
||||||
|
final day = selected == null
|
||||||
|
? today
|
||||||
|
: DateTime(selected.year, selected.month, selected.day);
|
||||||
|
final relative = switch (day.difference(today).inDays) {
|
||||||
|
0 => 'heute',
|
||||||
|
1 => 'morgen',
|
||||||
|
-1 => 'gestern',
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
return TourDayText._(relative, _format(day));
|
||||||
|
}
|
||||||
|
|
||||||
|
final String? _relative;
|
||||||
|
|
||||||
|
/// Datum als „TT.MM.JJJJ".
|
||||||
|
final String date;
|
||||||
|
|
||||||
|
/// Umstandsangabe: „heute" / „am 22.09.2026".
|
||||||
|
String get when => _relative ?? 'am $date';
|
||||||
|
|
||||||
|
/// Nach „für": „heute" / „den 22.09.2026".
|
||||||
|
String get afterFor => _relative ?? 'den $date';
|
||||||
|
|
||||||
|
static String _format(DateTime d) =>
|
||||||
|
'${d.day.toString().padLeft(2, '0')}.'
|
||||||
|
'${d.month.toString().padLeft(2, '0')}.${d.year}';
|
||||||
|
}
|
||||||
@ -11,6 +11,8 @@ import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery
|
|||||||
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_overview.dart';
|
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_overview.dart';
|
||||||
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
||||||
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/bloc/tour_date_cubit.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/model/tour_day_text.dart';
|
||||||
|
|
||||||
/// Inhalt der Phase "Ausliefern". Sortieren und Beladen werden über eigene
|
/// Inhalt der Phase "Ausliefern". Sortieren und Beladen werden über eigene
|
||||||
/// Pages und das Phasen-Routing in `Home` gerendert — diese Page übernimmt
|
/// Pages und das Phasen-Routing in `Home` gerendert — diese Page übernimmt
|
||||||
@ -77,25 +79,26 @@ class _EmptyTourBody extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Wenn der ERP-Sync für heute keine Tour gemeldet hat, ist das ein
|
// Wenn der ERP-Sync für den Tag keine Tour gemeldet hat, ist das ein
|
||||||
// normaler Zustand — kein Fehler. UX-Hinweis und Pull-to-refresh.
|
// normaler Zustand — kein Fehler. UX-Hinweis und Pull-to-refresh.
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
context.read<TourBloc>().add(const RefreshTour());
|
context.read<TourBloc>().add(const RefreshTour());
|
||||||
},
|
},
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: const [
|
children: [
|
||||||
SizedBox(height: 120),
|
const SizedBox(height: 120),
|
||||||
Icon(Icons.event_busy, size: 64, color: Colors.grey),
|
const Icon(Icons.event_busy, size: 64, color: Colors.grey),
|
||||||
SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 32),
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Für heute ist keine Tour zugewiesen.\n'
|
'Für ${TourDayText.of(context.watch<TourDateCubit>().state).afterFor} '
|
||||||
|
'ist keine Tour zugewiesen.\n'
|
||||||
'Zum Aktualisieren nach unten ziehen.',
|
'Zum Aktualisieren nach unten ziehen.',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(fontSize: 16),
|
style: const TextStyle(fontSize: 16),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery
|
|||||||
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
||||||
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
||||||
import 'package:hl_lieferservice/widget/orderer_hint.dart';
|
import 'package:hl_lieferservice/widget/orderer_hint.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/bloc/tour_date_cubit.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/model/tour_day_text.dart';
|
||||||
|
|
||||||
/// Page für die erste Phase bei Mehr-Auto-Teams: Auswählen der eigenen
|
/// Page für die erste Phase bei Mehr-Auto-Teams: Auswählen der eigenen
|
||||||
/// Lieferungen aus dem gemeinsamen Tour-Pool.
|
/// Lieferungen aus dem gemeinsamen Tour-Pool.
|
||||||
@ -388,12 +390,13 @@ class _DeliverySelectionPageState extends State<DeliverySelectionPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
drawer: const HomeAppDrawer(),
|
drawer: const HomeAppDrawer(),
|
||||||
appBar: AppBar(title: const Text('Lieferungen auswählen')),
|
appBar: AppBar(title: const Text('Lieferungen auswählen')),
|
||||||
body: const Center(
|
body: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Für heute ist keine Tour zugewiesen.',
|
'Für ${TourDayText.of(context.watch<TourDateCubit>().state).afterFor} '
|
||||||
style: TextStyle(fontSize: 16),
|
'ist keine Tour zugewiesen.',
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -15,6 +15,8 @@ import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery
|
|||||||
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
||||||
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
||||||
import 'package:hl_lieferservice/widget/orderer_hint.dart';
|
import 'package:hl_lieferservice/widget/orderer_hint.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/bloc/tour_date_cubit.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/model/tour_day_text.dart';
|
||||||
|
|
||||||
/// Page für die zweite Phase des Lieferprozesses (Sortieren). Der Fahrer
|
/// Page für die zweite Phase des Lieferprozesses (Sortieren). Der Fahrer
|
||||||
/// legt per Drag&Drop die Reihenfolge fest, ändert lokal so oft er möchte
|
/// legt per Drag&Drop die Reihenfolge fest, ändert lokal so oft er möchte
|
||||||
@ -142,25 +144,27 @@ class _DeliverySortPageState extends State<DeliverySortPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _emptyState() {
|
Widget _emptyState() {
|
||||||
|
final day = TourDayText.of(context.watch<TourDateCubit>().state);
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.inbox_outlined, size: 64, color: Colors.grey),
|
const Icon(Icons.inbox_outlined, size: 64, color: Colors.grey),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
'Keine Lieferungen heute',
|
'Keine Lieferungen ${day.when}',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
const Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 32),
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Für das ausgewählte Fahrzeug sind heute keine Lieferungen geplant.',
|
'Für das ausgewählte Fahrzeug sind ${day.when} keine '
|
||||||
|
'Lieferungen geplant.',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
// Erneut die heutige Tour vom Backend laden. `LoadTour` zeigt
|
// Erneut die Tour des gewählten Tages vom Backend laden. `LoadTour` zeigt
|
||||||
// währenddessen den Lade-Branch (Spinner) und landet danach wieder
|
// währenddessen den Lade-Branch (Spinner) und landet danach wieder
|
||||||
// hier (TourEmpty) oder in der sortierbaren Lieferungsliste.
|
// hier (TourEmpty) oder in der sortierbaren Lieferungsliste.
|
||||||
FilledButton.tonalIcon(
|
FilledButton.tonalIcon(
|
||||||
|
|||||||
@ -18,6 +18,8 @@ import 'package:hl_lieferservice/feature/loading/presentation/loading_customer_p
|
|||||||
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
|
||||||
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
|
||||||
import 'package:hl_lieferservice/widget/orderer_hint.dart';
|
import 'package:hl_lieferservice/widget/orderer_hint.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/bloc/tour_date_cubit.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/model/tour_day_text.dart';
|
||||||
|
|
||||||
/// Übersichts-Ansicht für die Beladen-Phase: alle Kunden mit ihren
|
/// Übersichts-Ansicht für die Beladen-Phase: alle Kunden mit ihren
|
||||||
/// Artikeln und Soll/Ist-Mengen.
|
/// Artikeln und Soll/Ist-Mengen.
|
||||||
@ -884,6 +886,7 @@ class _EmptyOverview extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
final day = TourDayText.of(context.watch<TourDateCubit>().state);
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -896,7 +899,7 @@ class _EmptyOverview extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
'Für heute ist aktuell keine Tour verfügbar.',
|
'Für ${day.afterFor} ist aktuell keine Tour verfügbar.',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.bodySmall
|
.bodySmall
|
||||||
|
|||||||
@ -432,7 +432,9 @@ class _SelectedDatePill extends StatelessWidget {
|
|||||||
// Standard-Fenster um heute; falls das aktuell angezeigte Datum (z. B.
|
// Standard-Fenster um heute; falls das aktuell angezeigte Datum (z. B.
|
||||||
// ein Dev-Override in der Vergangenheit) außerhalb liegt, Fenster
|
// ein Dev-Override in der Vergangenheit) außerhalb liegt, Fenster
|
||||||
// erweitern, damit initialDate immer im gültigen Bereich bleibt.
|
// erweitern, damit initialDate immer im gültigen Bereich bleibt.
|
||||||
final firstDefault = today.subtract(const Duration(days: 14));
|
// 90 Tage zurück: vergangene Touren (z. B. zum Nachsehen oder nach
|
||||||
|
// einem Nach-Import) müssen erreichbar sein.
|
||||||
|
final firstDefault = today.subtract(const Duration(days: 90));
|
||||||
final lastDefault = today.add(const Duration(days: 60));
|
final lastDefault = today.add(const Duration(days: 60));
|
||||||
final firstDate = current.isBefore(firstDefault) ? current : firstDefault;
|
final firstDate = current.isBefore(firstDefault) ? current : firstDefault;
|
||||||
final lastDate = current.isAfter(lastDefault) ? current : lastDefault;
|
final lastDate = current.isAfter(lastDefault) ? current : lastDefault;
|
||||||
|
|||||||
@ -106,12 +106,16 @@ void main() {
|
|||||||
await send(tester, _login(423));
|
await send(tester, _login(423));
|
||||||
final fuer423 = carSelections.last;
|
final fuer423 = carSelections.last;
|
||||||
expect(payments.loads, 1);
|
expect(payments.loads, 1);
|
||||||
|
// 423 schaut sich einen anderen Tag an …
|
||||||
|
tourDates.last.pick(DateTime(2026, 9, 2));
|
||||||
|
|
||||||
// Abmelden und anderer Fahrer: nichts von 423 darf übrig bleiben.
|
// Abmelden und anderer Fahrer: nichts von 423 darf übrig bleiben.
|
||||||
await send(tester, const AuthLoggedOut());
|
await send(tester, const AuthLoggedOut());
|
||||||
await send(tester, _login(414));
|
await send(tester, _login(414));
|
||||||
expect(carSelections.last, isNot(same(fuer423)));
|
expect(carSelections.last, isNot(same(fuer423)));
|
||||||
expect(tourDates.last, isNot(same(tourDates.first)));
|
expect(tourDates.last, isNot(same(tourDates.first)));
|
||||||
|
// … der nächste Fahrer startet trotzdem auf „heute".
|
||||||
|
expect(tourDates.last.state, isNull);
|
||||||
expect(payments.loads, 2);
|
expect(payments.loads, 2);
|
||||||
|
|
||||||
// Auch derselbe Fahrer nach erneutem Login lädt neu.
|
// Auch derselbe Fahrer nach erneutem Login lädt neu.
|
||||||
|
|||||||
98
test/tour_date_test.dart
Normal file
98
test/tour_date_test.dart
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:hl_lieferservice/data/cache/attachment_cache.dart';
|
||||||
|
import 'package:hl_lieferservice/domain/entity/tour.dart';
|
||||||
|
import 'package:hl_lieferservice/domain/entity/tour_details.dart';
|
||||||
|
import 'package:hl_lieferservice/domain/repository/tour_repository.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/bloc/tour_state.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/model/tour_day_text.dart';
|
||||||
|
import 'package:hl_lieferservice/widget/operations/bloc/operation_bloc.dart';
|
||||||
|
|
||||||
|
/// Antwortet pro angefragtem Datum erst, wenn der Test es freigibt.
|
||||||
|
class _SlowTours implements TourRepository {
|
||||||
|
final pending = <DateTime?, Completer<TourDetails?>>{};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TourDetails?> getMyTourDetails({DateTime? date}) =>
|
||||||
|
(pending[date] = Completer<TourDetails?>()).future;
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NoCache implements AttachmentCache {
|
||||||
|
@override
|
||||||
|
Future<void> retainOnly(Set<String> ids) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
TourDetails _tourAm(DateTime date) => TourDetails(
|
||||||
|
tour: Tour(id: 't-$date', accountId: 414, date: date, syncedAt: date),
|
||||||
|
deliveries: const [],
|
||||||
|
customers: const {},
|
||||||
|
contacts: const {},
|
||||||
|
articles: const {},
|
||||||
|
warehouses: const {},
|
||||||
|
notesByDeliveryId: const {},
|
||||||
|
creditsByDeliveryId: const {},
|
||||||
|
paymentsByDeliveryId: const {},
|
||||||
|
services: const [],
|
||||||
|
serviceValuesByDeliveryId: const {},
|
||||||
|
contactSourcesByDeliveryId: const {},
|
||||||
|
contactChannelsBySourceId: const {},
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('TourDayText', () {
|
||||||
|
final now = DateTime(2026, 9, 25, 16, 30);
|
||||||
|
|
||||||
|
test('ohne Auswahl und am selben Tag: heute', () {
|
||||||
|
expect(TourDayText.of(null, now: now).afterFor, 'heute');
|
||||||
|
expect(TourDayText.of(DateTime(2026, 9, 25), now: now).when, 'heute');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('morgen und gestern', () {
|
||||||
|
expect(TourDayText.of(DateTime(2026, 9, 26), now: now).when, 'morgen');
|
||||||
|
expect(TourDayText.of(DateTime(2026, 9, 24), now: now).when, 'gestern');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('anderer Tag mit Datum und passender Präposition', () {
|
||||||
|
final t = TourDayText.of(DateTime(2026, 9, 2), now: now);
|
||||||
|
expect(t.afterFor, 'den 02.09.2026');
|
||||||
|
expect(t.when, 'am 02.09.2026');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('langsame Antwort eines älteren Tages überschreibt nicht die neue Wahl',
|
||||||
|
() async {
|
||||||
|
final repo = _SlowTours();
|
||||||
|
final bloc = TourBloc(
|
||||||
|
tourRepository: repo,
|
||||||
|
opBloc: OperationBloc(),
|
||||||
|
attachmentCache: _NoCache(),
|
||||||
|
);
|
||||||
|
final erster = DateTime(2026, 9, 2);
|
||||||
|
final zweiter = DateTime(2026, 9, 25);
|
||||||
|
|
||||||
|
bloc.add(LoadTour.forDate(erster));
|
||||||
|
await Future<void>.delayed(Duration.zero);
|
||||||
|
bloc.add(LoadTour.forDate(zweiter));
|
||||||
|
await Future<void>.delayed(Duration.zero);
|
||||||
|
|
||||||
|
// Neuere Anfrage antwortet zuerst, die ältere danach.
|
||||||
|
repo.pending[zweiter]!.complete(_tourAm(zweiter));
|
||||||
|
await Future<void>.delayed(Duration.zero);
|
||||||
|
repo.pending[erster]!.complete(_tourAm(erster));
|
||||||
|
await Future<void>.delayed(Duration.zero);
|
||||||
|
|
||||||
|
final state = bloc.state as TourLoaded;
|
||||||
|
expect(state.details.tour.date, zweiter);
|
||||||
|
await bloc.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user