From 6697bb02dd38f9399c21a3c5cfe79aa9ffaddac4 Mon Sep 17 00:00:00 2001 From: Mike Kell Date: Fri, 10 Jul 2026 20:03:22 -0400 Subject: [PATCH] =?UTF-8?q?feat(mobile):=20Stage=206B=20=E2=80=94=20mobile?= =?UTF-8?q?=20orders=20surface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add MobileOrdersPage with search bar, horizontal status filter chips (All/Pending/Processing/Shipped/Delivered/Cancelled), order count display, OrderCard list at 160px height, pull-to-refresh, and \_detailPageActive guard to suppress behind-route SnackBars - Add MobileOrderDetailPage wrapping shared OrderDetailPanel as a StatefulWidget with local controller listener for SnackBar feedback, confirmation dialog before status changes, haptic feedback (mediumImpact for status, lightImpact for note additions), and SafeArea - Wire Orders tab (case 3) in MobileShell to MobileOrdersPage; remove unused feature_orders direct import from shell - Add 6 new Stage 6B widget tests: Orders tab search bar, status filter chips, order count, order card navigation to detail, confirmation dialog for status change, back navigation returning to orders list - Update build_execution_tracker.md and master_development_brief.md Tests: 20/20 kell_mobile, 115/115 feature_orders — all passing Analyze: dart analyze --fatal-infos — no issues found --- docs/development/build_execution_tracker.md | 21 +- docs/development/master_development_brief.md | 10 +- .../lib/pages/mobile_order_detail_page.dart | 171 ++++++++++++ .../lib/pages/mobile_orders_page.dart | 244 ++++++++++++++++++ .../kell_mobile/lib/shell/mobile_shell.dart | 4 +- .../apps/kell_mobile/test/widget_test.dart | 140 ++++++++++ 6 files changed, 580 insertions(+), 10 deletions(-) create mode 100644 kell_creations_apps/apps/kell_mobile/lib/pages/mobile_order_detail_page.dart create mode 100644 kell_creations_apps/apps/kell_mobile/lib/pages/mobile_orders_page.dart diff --git a/docs/development/build_execution_tracker.md b/docs/development/build_execution_tracker.md index 8f12664..8cf7edf 100644 --- a/docs/development/build_execution_tracker.md +++ b/docs/development/build_execution_tracker.md @@ -2,9 +2,9 @@ ## Current status -- main baseline updated through: feat/orders-page-ui (Stage 5D complete) -- next branch: feat/bulk-operator-workflows (Stage 7B) or feat/orders-mobile-surface (Stage 6B) -- current stage: Stage 5D complete — orders page UI with search/filter toolbar, OrderDetailPanel, AddNoteDialog, OrderCard, OrderStatusChip, OrdersActionSnackBar, and 115 tests +- main baseline updated through: feat/orders-mobile-surface (Stage 6B complete) +- next branch: feat/bulk-operator-workflows (Stage 7B) or feat/inventory-mobile-surface (Stage 6C) +- current stage: Stage 6B complete — mobile orders surface with MobileOrdersPage (search, filter chips, order count, pull-to-refresh), MobileOrderDetailPage (haptic feedback, confirmation dialogs, local SnackBar listener), and 6 new kell_mobile tests (20/20 total) ## Slice tracker @@ -329,6 +329,21 @@ - analyze: passed (dart analyze — no issues found) - brief updated: yes +### feat/orders-mobile-surface + +- status: merged to main +- date: 2026-07-10 +- inspection: complete +- implementation: complete +- files changed: + - `kell_mobile/lib/pages/mobile_orders_page.dart` — new `MobileOrdersPage` with search bar, horizontal status filter chips (All / Pending / Processing / Shipped / Delivered / Cancelled), order count display, `OrderCard` list at 160px height, pull-to-refresh, `_detailPageActive` guard to suppress behind-route SnackBars + - `kell_mobile/lib/pages/mobile_order_detail_page.dart` — new `MobileOrderDetailPage` wrapping shared `OrderDetailPanel`; StatefulWidget with local controller listener for SnackBar feedback; confirmation dialog before status changes; haptic feedback (mediumImpact for status, lightImpact for note additions); `SafeArea` wrapper + - `kell_mobile/lib/shell/mobile_shell.dart` — Orders tab (case 3) switched from shared `OrdersPage` to `MobileOrdersPage`; removed unused `feature_orders` direct import + - `kell_mobile/test/widget_test.dart` — added 6 new Stage 6B tests: Orders tab search bar, status filter chips, order count, order card navigation to detail, confirmation dialog for status change, back navigation returning to orders list — 20 total kell_mobile tests +- tests: passed (20/20 kell_mobile, 115/115 feature_orders — all passing) +- analyze: passed (dart analyze --fatal-infos — no issues found) +- brief updated: yes + ### chore/analyze-cleanup-and-tracker-sync - status: merged to main diff --git a/docs/development/master_development_brief.md b/docs/development/master_development_brief.md index fccf3f5..a957421 100644 --- a/docs/development/master_development_brief.md +++ b/docs/development/master_development_brief.md @@ -137,9 +137,9 @@ Rules: - latest reported count for `feature_orders`: `115/115 passed` - latest reported count for `feature_wordpress`: `311/311 passed` - latest reported count for `kell_web`: `24/24 passed` -- latest reported count for `kell_mobile`: `14/14 passed` -- total: `600/600 passed` -- baseline commit: merge of `feat/orders-page-ui` (Stage 5D complete, 2026-07-10) +- latest reported count for `kell_mobile`: `20/20 passed` +- total: `606/606 passed` +- baseline commit: merge of `feat/orders-mobile-surface` (Stage 6B complete, 2026-07-10) #### Baseline test coverage (established 2026-05-22) @@ -155,8 +155,8 @@ No minimum thresholds are enforced — this is visibility-only tracking. Coverag ### Next recommended branch -**`feat/orders-mobile-surface`** — Stage 6B (orders): Mobile orders surface for Android (matching the pattern established by `feat/android-publishing-surface`), or **`feat/bulk-operator-workflows`** — Stage 7B: Additional bulk actions (bulk publish, bulk move to pending review). -Branch from latest `main`. Stage 5D (orders page UI) is complete. The orders feature now has full write-capable UI on web. Next logical step is either surfacing orders on mobile or adding more bulk publishing actions. +**`feat/inventory-mobile-surface`** — Stage 6C (inventory): Mobile inventory surface for Android (matching the pattern established by `feat/android-publishing-surface` and `feat/orders-mobile-surface`), or **`feat/bulk-operator-workflows`** — Stage 7B: Additional bulk actions (bulk publish, bulk move to pending review). +Branch from latest `main`. Stage 6B (orders mobile surface) is complete. The orders feature now has full write-capable UI on both web and mobile. Next logical step is either surfacing inventory on mobile or adding more bulk publishing actions. --- diff --git a/kell_creations_apps/apps/kell_mobile/lib/pages/mobile_order_detail_page.dart b/kell_creations_apps/apps/kell_mobile/lib/pages/mobile_order_detail_page.dart new file mode 100644 index 0000000..996b0d9 --- /dev/null +++ b/kell_creations_apps/apps/kell_mobile/lib/pages/mobile_order_detail_page.dart @@ -0,0 +1,171 @@ +import 'package:feature_orders/feature_orders.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// Full-screen order detail page for mobile. +/// +/// Receives the shared [OrdersController] so that status updates and note +/// additions are immediately reflected in the list when the user pops back. +/// +/// Wraps the shared [OrderDetailPanel] from [feature_orders] inside a +/// [Scaffold] with an [AppBar] showing the order ID. +/// +/// Unlike the web layout where feedback SnackBars are handled by the orders +/// page wrapper, this detail page is pushed via [Navigator] and owns its own +/// [Scaffold]. It therefore attaches its own listener to the controller and +/// shows action-result SnackBars using its local [BuildContext], ensuring +/// they are visible on the active screen. +/// +/// Status-change actions present a confirmation dialog before executing, +/// reducing accidental taps on touch screens. +class MobileOrderDetailPage extends StatefulWidget { + final OrdersController controller; + + /// Optional callback to navigate to the Products page for a given SKU. + final void Function(String sku)? onViewProduct; + + /// Optional callback to navigate to the Inventory page for a given SKU. + final void Function(String sku)? onViewInventory; + + const MobileOrderDetailPage({ + super.key, + required this.controller, + this.onViewProduct, + this.onViewInventory, + }); + + @override + State createState() => _MobileOrderDetailPageState(); +} + +class _MobileOrderDetailPageState extends State { + OrdersController get _controller => widget.controller; + + @override + void initState() { + super.initState(); + _controller.addListener(_onControllerChanged); + } + + @override + void dispose() { + _controller.removeListener(_onControllerChanged); + super.dispose(); + } + + /// Handles action result feedback via SnackBars on this detail page. + /// + /// Because this page is pushed on top of the list page via [Navigator], + /// the list page's listener cannot reliably display SnackBars (they + /// would appear behind this route). This listener ensures feedback is + /// always visible to the operator. + void _onControllerChanged() { + if (!mounted) return; + + final result = _controller.lastActionResult; + if (result != null && !_controller.isUpdating) { + _controller.lastActionResult = null; + showOrderActionSnackBar(context, result); + if (result.success) HapticFeedback.mediumImpact(); + } + } + + /// Shows a confirmation dialog before executing a status change. + /// + /// Returns `true` if the user confirmed, `false` otherwise. + Future _confirmStatusChange({ + required String orderId, + required OrderStatus newStatus, + }) async { + final label = _statusLabel(newStatus); + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Update to $label?'), + content: Text('Change order $orderId status to "$label"?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Cancel'), + ), + FilledButton(onPressed: () => Navigator.of(context).pop(true), child: Text('Update')), + ], + ), + ); + return confirmed ?? false; + } + + /// Handles a status update with confirmation dialog. + Future _handleUpdateStatus(OrderStatus newStatus) async { + final order = _controller.selectedOrder; + if (order == null) return; + + final confirmed = await _confirmStatusChange(orderId: order.id, newStatus: newStatus); + if (confirmed && mounted) { + await _controller.updateOrderStatus(order.id, newStatus); + } + } + + /// Handles adding a note via the shared dialog. + Future _handleAddNote() async { + final order = _controller.selectedOrder; + if (order == null) return; + + final dialogResult = await showAddNoteDialog(context); + if (dialogResult == null) return; + + if (mounted) { + await _controller.addOrderNote(order.id, dialogResult.note); + if (_controller.lastActionResult?.success == true) { + HapticFeedback.lightImpact(); + } + } + } + + static String _statusLabel(OrderStatus status) { + switch (status) { + case OrderStatus.pending: + return 'Pending'; + case OrderStatus.processing: + return 'Processing'; + case OrderStatus.shipped: + return 'Shipped'; + case OrderStatus.delivered: + return 'Delivered'; + case OrderStatus.cancelled: + return 'Cancelled'; + } + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, _) { + final order = _controller.selectedOrder; + + // If the order was removed or deselected, pop back. + if (order == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) Navigator.of(context).pop(); + }); + return const Scaffold(body: Center(child: CircularProgressIndicator())); + } + + return Scaffold( + appBar: AppBar(title: Text('Order ${order.id}')), + body: SafeArea( + child: OrderDetailPanel( + order: order, + isUpdating: _controller.isUpdating, + onUpdateStatus: _handleUpdateStatus, + onAddNote: _handleAddNote, + onViewProduct: widget.onViewProduct, + onViewInventory: widget.onViewInventory, + ), + ), + ); + }, + ); + } +} diff --git a/kell_creations_apps/apps/kell_mobile/lib/pages/mobile_orders_page.dart b/kell_creations_apps/apps/kell_mobile/lib/pages/mobile_orders_page.dart new file mode 100644 index 0000000..2538a8f --- /dev/null +++ b/kell_creations_apps/apps/kell_mobile/lib/pages/mobile_orders_page.dart @@ -0,0 +1,244 @@ +import 'package:design_system/design_system.dart'; +import 'package:feature_orders/feature_orders.dart'; +import 'package:flutter/material.dart'; + +import 'mobile_order_detail_page.dart'; + +/// Mobile-optimized orders workspace page. +/// +/// Provides browsing, filtering, and searching for orders on smaller screens. +/// Tapping an order pushes a full-screen detail page for viewing, status +/// updates, and note additions. +/// +/// Reuses the shared [OrdersController] and all use cases from +/// [feature_orders] — no business logic is forked for mobile. +class MobileOrdersPage extends StatefulWidget { + final OrdersRepository repository; + + /// Optional callback to navigate to the Products page for a given SKU. + final void Function(String sku)? onViewProduct; + + /// Optional callback to navigate to the Inventory page for a given SKU. + final void Function(String sku)? onViewInventory; + + const MobileOrdersPage({ + super.key, + required this.repository, + this.onViewProduct, + this.onViewInventory, + }); + + @override + State createState() => _MobileOrdersPageState(); +} + +class _MobileOrdersPageState extends State { + late final OrdersController _controller; + late final TextEditingController _searchController; + + /// Whether the detail page is currently pushed on top. + /// + /// When `true`, action-result SnackBars are suppressed here because the + /// detail page owns its own listener and shows feedback in its own + /// [Scaffold]. Without this guard, SnackBars would be rendered behind + /// the detail route and be invisible to the operator. + bool _detailPageActive = false; + + @override + void initState() { + super.initState(); + _searchController = TextEditingController(); + + final repo = widget.repository; + _controller = OrdersController( + GetOrders(repo), + updateOrderStatus: UpdateOrderStatus(repo), + addOrderNote: AddOrderNote(repo), + ); + + _controller.addListener(_onControllerChanged); + _controller.load(); + } + + @override + void dispose() { + _controller.removeListener(_onControllerChanged); + _controller.dispose(); + _searchController.dispose(); + super.dispose(); + } + + /// Handles action result feedback via SnackBars. + /// + /// Suppressed while the detail page is active — the detail page has its + /// own listener that shows SnackBars using its local context. + void _onControllerChanged() { + if (_detailPageActive) return; + + final result = _controller.lastActionResult; + if (result != null && !_controller.isUpdating) { + _controller.lastActionResult = null; + showOrderActionSnackBar(context, result); + } + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, _) { + if (_controller.isLoading) { + return const KcLoadingState(message: 'Loading orders…'); + } + + if (_controller.error != null) { + return KcErrorState(message: 'Failed to load orders.', onRetry: _controller.load); + } + + return Column( + children: [ + _buildSearchBar(), + _buildFilterChips(), + _buildOrderCount(), + Expanded(child: _buildOrderList()), + ], + ); + }, + ); + } + + /// Search bar with real-time filtering. + Widget _buildSearchBar() { + return Padding( + padding: const EdgeInsets.fromLTRB(KcSpacing.md, KcSpacing.sm, KcSpacing.md, KcSpacing.xs), + child: TextField( + controller: _searchController, + decoration: InputDecoration( + hintText: 'Search by customer or order ID…', + prefixIcon: const Icon(Icons.search, size: 20), + suffixIcon: _searchController.text.isNotEmpty + ? IconButton( + icon: const Icon(Icons.clear, size: 20), + onPressed: () { + _searchController.clear(); + _controller.setSearchQuery(''); + }, + ) + : null, + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: KcSpacing.sm, + vertical: KcSpacing.sm, + ), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), + ), + onChanged: (query) { + _controller.setSearchQuery(query); + setState(() {}); // refresh clear button visibility + }, + ), + ); + } + + /// Horizontal scrollable filter chips for status filtering. + Widget _buildFilterChips() { + const filters = [ + (null, 'All'), + ('pending', 'Pending'), + ('processing', 'Processing'), + ('shipped', 'Shipped'), + ('delivered', 'Delivered'), + ('cancelled', 'Cancelled'), + ]; + + return SizedBox( + height: 48, + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: KcSpacing.md), + itemCount: filters.length, + separatorBuilder: (_, _) => const SizedBox(width: KcSpacing.xs), + itemBuilder: (context, index) { + final (value, label) = filters[index]; + final isActive = _controller.activeFilter == value; + return FilterChip( + label: Text(label), + selected: isActive, + onSelected: (_) => _controller.setFilter(isActive ? null : value), + ); + }, + ), + ); + } + + /// Order count row. + Widget _buildOrderCount() { + final count = _controller.orders.length; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: KcSpacing.md, vertical: KcSpacing.xs), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + '$count ${count == 1 ? 'order' : 'orders'}', + style: Theme.of(context).textTheme.bodySmall?.copyWith(color: KcColors.neutral), + ), + ), + ); + } + + /// Scrollable order list using compact cards. + Widget _buildOrderList() { + final orders = _controller.orders; + + if (orders.isEmpty) { + return KcEmptyState( + icon: Icons.receipt_long_outlined, + message: _controller.searchQuery.isNotEmpty || _controller.activeFilter != null + ? 'No orders match your criteria.' + : 'No orders available.', + ); + } + + return RefreshIndicator( + onRefresh: _controller.load, + child: ListView.separated( + padding: const EdgeInsets.symmetric(horizontal: KcSpacing.md), + itemCount: orders.length, + separatorBuilder: (_, _) => const SizedBox(height: KcSpacing.xs), + itemBuilder: (context, index) { + final order = orders[index]; + return SizedBox( + height: 160, + child: OrderCard( + order: order, + isSelected: order.id == _controller.selectedOrder?.id, + onTap: () => _navigateToDetail(order), + ), + ); + }, + ), + ); + } + + /// Navigates to the full-screen order detail page. + /// + /// Sets [_detailPageActive] to suppress SnackBars on this page while the + /// detail page is visible. Cleared when the detail page pops back. + void _navigateToDetail(Order order) { + _controller.selectOrder(order); + setState(() => _detailPageActive = true); + Navigator.of(context) + .push( + MaterialPageRoute( + builder: (_) => MobileOrderDetailPage( + controller: _controller, + onViewProduct: widget.onViewProduct, + onViewInventory: widget.onViewInventory, + ), + ), + ) + .then((_) { + if (mounted) setState(() => _detailPageActive = false); + }); + } +} diff --git a/kell_creations_apps/apps/kell_mobile/lib/shell/mobile_shell.dart b/kell_creations_apps/apps/kell_mobile/lib/shell/mobile_shell.dart index 9c1d88c..3ea24f6 100644 --- a/kell_creations_apps/apps/kell_mobile/lib/shell/mobile_shell.dart +++ b/kell_creations_apps/apps/kell_mobile/lib/shell/mobile_shell.dart @@ -1,6 +1,5 @@ import 'package:core/core.dart'; import 'package:feature_inventory/feature_inventory.dart'; -import 'package:feature_orders/feature_orders.dart'; import 'package:feature_policy/feature_policy.dart'; import 'package:flutter/material.dart'; @@ -10,6 +9,7 @@ import '../dashboard/application/get_dashboard_summary.dart'; import '../pages/dashboard_page.dart'; import '../pages/finance_placeholder_page.dart'; import '../pages/integrations_placeholder_page.dart'; +import '../pages/mobile_orders_page.dart'; import '../pages/mobile_publishing_page.dart'; /// The main shell for the mobile app. @@ -106,7 +106,7 @@ class _MobileShellState extends State { case 2: return MobilePublishingPage(repository: services.productPublishingRepository); case 3: - return OrdersPage( + return MobileOrdersPage( repository: services.ordersRepository, onViewProduct: (_) { setState(() => _selectedIndex = 2); diff --git a/kell_creations_apps/apps/kell_mobile/test/widget_test.dart b/kell_creations_apps/apps/kell_mobile/test/widget_test.dart index c6aa1b9..ce89564 100644 --- a/kell_creations_apps/apps/kell_mobile/test/widget_test.dart +++ b/kell_creations_apps/apps/kell_mobile/test/widget_test.dart @@ -262,4 +262,144 @@ void main() { } } }); + + // ── Stage 6B: Mobile orders surface tests ──────────────────────── + + testWidgets('Orders tab shows mobile orders page with search bar', (WidgetTester tester) async { + await tester.pumpWidget(_buildTestApp()); + await tester.pumpAndSettle(); + + // Tap the Orders destination + await tester.tap(find.text('Orders').last); + await tester.pumpAndSettle(); + + // Should show the search bar + expect(find.byType(TextField), findsOneWidget); + expect(find.text('Search by customer or order ID…'), findsOneWidget); + }); + + testWidgets('Orders tab shows status filter chips', (WidgetTester tester) async { + await tester.pumpWidget(_buildTestApp()); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Orders').last); + await tester.pumpAndSettle(); + + // Should show filter chips for order status categories + expect(find.widgetWithText(FilterChip, 'All'), findsOneWidget); + expect(find.widgetWithText(FilterChip, 'Pending'), findsOneWidget); + expect(find.widgetWithText(FilterChip, 'Shipped'), findsOneWidget); + }); + + testWidgets('Orders tab shows order count', (WidgetTester tester) async { + await tester.pumpWidget(_buildTestApp()); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Orders').last); + await tester.pumpAndSettle(); + + // Fake data should produce an order count label + expect(find.textContaining('order'), findsWidgets); + }); + + testWidgets('tapping order card navigates to order detail page', (WidgetTester tester) async { + await tester.pumpWidget(_buildTestApp()); + await tester.pumpAndSettle(); + + // Navigate to Orders tab + await tester.tap(find.text('Orders').last); + await tester.pumpAndSettle(); + + // Tap the first 160-height order card + final firstOrder = find.byWidgetPredicate( + (widget) => widget is SizedBox && widget.height == 160 && widget.child != null, + ); + if (firstOrder.evaluate().isNotEmpty) { + await tester.tap(firstOrder.first); + await tester.pumpAndSettle(); + + // Should now be on the detail page — AppBar shows "Order ..." + expect(find.byType(AppBar), findsOneWidget); + expect(find.textContaining('Order'), findsWidgets); + } + }); + + testWidgets('order detail page shows confirmation dialog for status change', ( + WidgetTester tester, + ) async { + await tester.pumpWidget(_buildTestApp()); + await tester.pumpAndSettle(); + + // Navigate to Orders tab + await tester.tap(find.text('Orders').last); + await tester.pumpAndSettle(); + + // Tap the first order card to navigate to detail + final firstOrder = find.byWidgetPredicate( + (widget) => widget is SizedBox && widget.height == 160 && widget.child != null, + ); + if (firstOrder.evaluate().isNotEmpty) { + await tester.tap(firstOrder.first); + await tester.pumpAndSettle(); + + // Scroll down to reveal the status dropdown + await tester.drag(find.byType(SingleChildScrollView), const Offset(0, -300)); + await tester.pumpAndSettle(); + + // Look for the status dropdown and change it + final dropdown = find.byType(DropdownButtonFormField); + if (dropdown.evaluate().isNotEmpty) { + await tester.tap(dropdown.first); + await tester.pumpAndSettle(); + + // Pick a different status from the dropdown menu + final menuItems = find.byType(DropdownMenuItem); + if (menuItems.evaluate().length > 1) { + await tester.tap(menuItems.at(1)); + await tester.pumpAndSettle(); + + // Confirmation dialog should appear + expect(find.byType(AlertDialog), findsOneWidget); + expect(find.text('Cancel'), findsOneWidget); + expect(find.text('Update'), findsOneWidget); + + // Cancel should dismiss the dialog + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); + + expect(find.byType(AlertDialog), findsNothing); + } + } + } + }); + + testWidgets('order detail page back navigation returns to orders list', ( + WidgetTester tester, + ) async { + await tester.pumpWidget(_buildTestApp()); + await tester.pumpAndSettle(); + + // Navigate to Orders tab + await tester.tap(find.text('Orders').last); + await tester.pumpAndSettle(); + + // Tap the first order card + final firstOrder = find.byWidgetPredicate( + (widget) => widget is SizedBox && widget.height == 160 && widget.child != null, + ); + if (firstOrder.evaluate().isNotEmpty) { + await tester.tap(firstOrder.first); + await tester.pumpAndSettle(); + + // Press back + final backButton = find.byType(BackButton); + if (backButton.evaluate().isNotEmpty) { + await tester.tap(backButton); + await tester.pumpAndSettle(); + + // Should be back on the orders list with search bar + expect(find.text('Search by customer or order ID…'), findsOneWidget); + } + } + }); }