diff --git a/lib/screens/scanner_screen.dart b/lib/screens/scanner_screen.dart index ed6dd7de..fbb491f2 100644 --- a/lib/screens/scanner_screen.dart +++ b/lib/screens/scanner_screen.dart @@ -103,8 +103,12 @@ class _ScannerScreenState extends State { ).push(MaterialPageRoute(builder: (_) => const AppOnlySettingsScreen())); // A connection that completed while settings were open routed nowhere, // because this screen was not the current route. Route in now, as the - // connection listener would have. + // connection listener would have, and under the same condition: only if + // this screen is current again, not covered by some other route. The + // settings route is already out of the "present" set by the time its + // future completes, so it does not count against that check. if (mounted && + (ModalRoute.of(context)?.isCurrent ?? true) && _connector.state == MeshCoreConnectionState.connected && !_routingIntoApp) { _routeIntoApp(); diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 27e4a2ef..1d77d777 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -154,8 +154,9 @@ class _SettingsScreenState extends State { ), ); // Owner-only Experimental section (#509), revealed by the 7-tap gesture on - // the About version row. Selects narrowly so the category list only rebuilds - // when the flag flips, not on every connector update (same reason as above). + // the Device Info Build row. Selects narrowly so the category list only + // rebuilds when the flag flips, not on every connector update (same reason + // as above). final experimentalUnlocked = context.select( (s) => s.settings.experimentalUnlocked, ); diff --git a/test/screens/app_only_settings_test.dart b/test/screens/app_only_settings_test.dart index c77d9c32..0cecd5ee 100644 --- a/test/screens/app_only_settings_test.dart +++ b/test/screens/app_only_settings_test.dart @@ -79,19 +79,26 @@ Future _openFromScanner(WidgetTester tester) async { return l10n; } -/// Run the real-IO export in the real zone and wait for it to settle, either -/// at the share sheet or at a message on screen. -Future _tapShareAndSettle(WidgetTester tester, String label) async { - final before = _shared.length; - await tester.runAsync(() async { - await tester.tap(find.text(label)); - for (var i = 0; i < 100 && _shared.length == before; i++) { - await Future.delayed(const Duration(milliseconds: 20)); - } - }); - await tester.pump(); +/// Tap Share in the real zone, where the export's file IO can run, then let +/// it progress frame by frame until [done] holds. The ceiling is generous so +/// a slow runner waits rather than flakes; a run that never gets there fails +/// on the caller's next expectation. +Future _tapShareUntil( + WidgetTester tester, + String label, + bool Function() done, +) async { + await tester.runAsync(() => tester.tap(find.text(label))); + for (var i = 0; i < 300 && !done(); i++) { + await tester.runAsync( + () => Future.delayed(const Duration(milliseconds: 50)), + ); + await tester.pump(); + } } +bool _shown(String text) => find.text(text).evaluate().isNotEmpty; + /// Drain ScannerScreen's dispose-time disconnect before teardown. Future _unmount(WidgetTester tester) async { await tester.pumpWidget(const SizedBox.shrink()); @@ -214,7 +221,11 @@ void main() { await tester.pumpAndSettle(); final sharesBefore = _shared.length; - await _tapShareAndSettle(tester, l10n.debugLog_shareLog); + await _tapShareUntil( + tester, + l10n.debugLog_shareLog, + () => _shared.length > sharesBefore, + ); expect(_shared.length, sharesBefore + 1); final logDir = FileLogService.instance.logDir!.path; @@ -239,7 +250,12 @@ void main() { await tester.tap(find.text(l10n.settings_debug)); await tester.pumpAndSettle(); - await _tapShareAndSettle(tester, l10n.debugLog_shareLog); + await _tapShareUntil( + tester, + l10n.debugLog_shareLog, + () => _shown(l10n.debugLog_exportFailed), + ); + // Finish the entrance animation, which is what starts the timeout. await tester.pump(const Duration(seconds: 1)); expect(find.text(l10n.debugLog_exportFailed), findsOneWidget); @@ -266,7 +282,11 @@ void main() { await tester.pumpAndSettle(); final sharesBefore = _shared.length; - await _tapShareAndSettle(tester, l10n.debugLog_shareLog); + await _tapShareUntil( + tester, + l10n.debugLog_shareLog, + () => _shown(l10n.debugLog_logUnavailable), + ); await tester.pump(const Duration(seconds: 1)); expect(_shared.length, sharesBefore, reason: 'nothing to hand over'); expect(find.text(l10n.debugLog_logUnavailable), findsOneWidget);