import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:marianum_mobile/api/marianumconnect/queries/get_ticker_nav/get_ticker_nav_response.dart'; import 'package:marianum_mobile/api/marianumconnect/queries/get_ticker_page/get_ticker_page_response.dart'; import 'package:marianum_mobile/view/pages/ticker/ticker_view.dart'; List _sections() => [ TickerNavSection( title: 'Infos', pages: [ TickerNavPage(title: 'Über uns', slug: 'about', kind: TickerPageKind.content), TickerNavPage( title: 'Webseite', slug: 'web', kind: TickerPageKind.redirect, externalUrl: 'https://marianum-fulda.de', ), ], ), ]; Widget _host({ List? sections, String? initialSlug, void Function(String? slug)? onSelectionChanged, String? homePublishedAt, VoidCallback? onRefreshHome, }) => MaterialApp( home: TickerScaffold( sections: sections ?? _sections(), initialSlug: initialSlug, onSelectionChanged: onSelectionChanged, homePublishedAt: homePublishedAt, onRefreshHome: onRefreshHome, homeBuilder: (context, onLinkTap) => const Text('HOME'), pageBuilder: (context, slug, onLinkTap) => Text('PAGE:$slug'), ), ); Finder _appBarText(String text) => find.descendant(of: find.byType(AppBar), matching: find.text(text)); // The "Aktuelles" home action is always present; it is greyed out (disabled) // while home is the current surface, active while a page is open. bool _homeEnabled(WidgetTester tester) => tester .widget( find.widgetWithIcon(IconButton, Icons.home_outlined), ) .onPressed != null; // The home surface stays mounted (IndexedStack) while a page is open, so // "which content is shown" is the stack index, not widget presence. int _shownIndex(WidgetTester tester) => tester.widget(find.byType(IndexedStack)).index!; const String _navTooltip = 'Seiten'; void main() { group('narrow layout (< 900)', () { testWidgets('opens the nav in a bottom sheet from the floating button', ( tester, ) async { await tester.pumpWidget(_host()); await tester.pumpAndSettle(); // No drawer, and the nav is hidden until the sheet is opened. expect(find.byType(Drawer), findsNothing); expect(find.text('Über uns'), findsNothing); expect(find.byTooltip(_navTooltip), findsOneWidget); await tester.tap(find.byTooltip(_navTooltip)); await tester.pumpAndSettle(); // Sheet shows the section-grouped nav. expect(find.text('Aktuelles'), findsOneWidget); expect(find.text('Über uns'), findsOneWidget); // Selecting a page closes the sheet and switches the content in place. await tester.tap(find.text('Über uns')); await tester.pumpAndSettle(); expect(find.text('Aktuelles'), findsNothing); expect(_shownIndex(tester), 1); expect(_appBarText('Über uns'), findsOneWidget); }); }); group('wide layout (>= 900)', () { Future pumpWide(WidgetTester tester) async { tester.view.physicalSize = const Size(1200, 2000); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(_host()); await tester.pumpAndSettle(); } testWidgets('shows a permanent sidebar and no drawer', (tester) async { await pumpWide(tester); expect(find.byType(Drawer), findsNothing); // No bottom-sheet trigger on tablets — the sidebar is permanent. expect(find.byTooltip(_navTooltip), findsNothing); // Sidebar nav item is visible without any interaction. expect(find.text('Über uns'), findsOneWidget); }); testWidgets('selecting a page swaps content, title and home action', ( tester, ) async { await pumpWide(tester); expect(_shownIndex(tester), 0); expect(find.text('PAGE:about'), findsNothing); expect(_appBarText('Ticker'), findsOneWidget); // Home action is shown but greyed out on the home surface. expect(find.byIcon(Icons.home_outlined), findsOneWidget); expect(_homeEnabled(tester), isFalse); await tester.tap(find.text('Über uns')); await tester.pumpAndSettle(); expect(_shownIndex(tester), 1); expect(find.text('PAGE:about'), findsOneWidget); expect(_appBarText('Über uns'), findsOneWidget); expect(_homeEnabled(tester), isTrue); await tester.tap(find.byIcon(Icons.home_outlined)); await tester.pumpAndSettle(); expect(_shownIndex(tester), 0); expect(find.text('PAGE:about'), findsNothing); expect(_appBarText('Ticker'), findsOneWidget); expect(find.byIcon(Icons.home_outlined), findsOneWidget); expect(_homeEnabled(tester), isFalse); }); }); group('remembered selection', () { Future pumpWide(WidgetTester tester, Widget host) async { tester.view.physicalSize = const Size(1200, 2000); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(host); await tester.pumpAndSettle(); } testWidgets('reopens the persisted page on mount', (tester) async { await tester.pumpWidget(_host(initialSlug: 'about')); await tester.pumpAndSettle(); expect(_shownIndex(tester), 1); expect(find.text('PAGE:about'), findsOneWidget); expect(_appBarText('Über uns'), findsOneWidget); expect(_homeEnabled(tester), isTrue); }); testWidgets('falls back to home and drops a slug that is gone', ( tester, ) async { String? persisted = 'ghost'; await tester.pumpWidget( _host(initialSlug: 'ghost', onSelectionChanged: (slug) => persisted = slug), ); await tester.pumpAndSettle(); expect(_shownIndex(tester), 0); expect(_appBarText('Ticker'), findsOneWidget); expect(persisted, isNull); }); testWidgets('does not restore a redirect slug', (tester) async { await tester.pumpWidget(_host(initialSlug: 'web')); await tester.pumpAndSettle(); expect(_shownIndex(tester), 0); }); testWidgets('reports selection changes so the host can persist them', ( tester, ) async { final changes = []; await pumpWide(tester, _host(onSelectionChanged: changes.add)); await tester.tap(find.text('Über uns')); await tester.pumpAndSettle(); await tester.tap(find.byIcon(Icons.home_outlined)); await tester.pumpAndSettle(); expect(changes, ['about', null]); }); testWidgets('drops the open page when a fresh nav no longer lists it', ( tester, ) async { String? persisted; await pumpWide( tester, _host(onSelectionChanged: (slug) => persisted = slug), ); await tester.tap(find.text('Über uns')); await tester.pumpAndSettle(); expect(_shownIndex(tester), 1); await tester.pumpWidget( _host( sections: [ TickerNavSection( title: 'Infos', pages: [ TickerNavPage( title: 'Anderes', slug: 'other', kind: TickerPageKind.content, ), ], ), ], onSelectionChanged: (slug) => persisted = slug, ), ); await tester.pumpAndSettle(); expect(_shownIndex(tester), 0); expect(_appBarText('Ticker'), findsOneWidget); expect(persisted, isNull); }); }); group('home date button', () { testWidgets('shows the publish date on home and refreshes on tap', ( tester, ) async { var refreshed = 0; await tester.pumpWidget( _host( homePublishedAt: '2026-02-17T14:30:00', onRefreshHome: () => refreshed++, ), ); await tester.pumpAndSettle(); expect(find.text('17.02.2026, 14:30'), findsOneWidget); await tester.tap(find.text('17.02.2026, 14:30')); await tester.pumpAndSettle(); expect(refreshed, 1); }); testWidgets('hides the date button when there is no date', (tester) async { await tester.pumpWidget(_host()); await tester.pumpAndSettle(); expect(find.byIcon(Icons.schedule), findsNothing); }); testWidgets('hides the date button once a page is open', (tester) async { tester.view.physicalSize = const Size(1200, 2000); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(_host(homePublishedAt: '2026-02-17T14:30:00')); await tester.pumpAndSettle(); expect(find.byIcon(Icons.schedule), findsOneWidget); await tester.tap(find.text('Über uns')); await tester.pumpAndSettle(); expect(find.byIcon(Icons.schedule), findsNothing); }); }); group('back gesture', () { testWidgets('pops from a sub-page back to home via the tab navigator', ( tester, ) async { tester.view.physicalSize = const Size(1200, 2000); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(_host()); await tester.pumpAndSettle(); await tester.tap(find.text('Über uns')); await tester.pumpAndSettle(); expect(_shownIndex(tester), 1); final navigator = tester.state(find.byType(Navigator)); // The LocalHistoryEntry is what makes the enclosing tab shell delegate // the system back to this navigator instead of switching tabs. expect(navigator.canPop(), isTrue); await navigator.maybePop(); await tester.pumpAndSettle(); expect(_shownIndex(tester), 0); expect(_appBarText('Ticker'), findsOneWidget); expect(navigator.canPop(), isFalse); }); testWidgets('home selection releases the back interception', ( tester, ) async { tester.view.physicalSize = const Size(1200, 2000); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(_host()); await tester.pumpAndSettle(); await tester.tap(find.text('Über uns')); await tester.pumpAndSettle(); await tester.tap(find.byIcon(Icons.home_outlined)); await tester.pumpAndSettle(); final navigator = tester.state(find.byType(Navigator)); expect(navigator.canPop(), isFalse); }); }); }