Files
plezy/test/utils/route_visibility_test.dart
edde746 20896a3d48 fix(player): keep the wake-time profile picker responsive on Android TV
Sleeping the device mid-video with "Ask for profile on app open" enabled
left the app wedged on an unresponsive Choose Profile screen (Nvidia
Shield). The picker is pushed on the root navigator, but the player's
focus self-heal only consulted its own nested profile-session route, so
it yanked D-pad focus back behind the picker whenever it lost it.

Route currency now walks every enclosing navigator (isRouteChainCurrent)
before the player reclaims focus, primes loading-phase navigation focus,
or claims the surface on window focus. The resume-time prompt is also
skipped entirely while a video player is active: waking mid-stream
resumes the stream instead of stacking the picker over the session.

close #2034
2026-08-19 23:08:32 +02:00

59 lines
2.3 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/utils/route_visibility.dart';
void main() {
testWidgets('isRouteChainCurrent sees routes pushed on the own and ancestor navigators (#2034)', (tester) async {
final nestedNavigatorKey = GlobalKey<NavigatorState>();
const contentKey = Key('nested-content');
const nestedAboveKey = Key('nested-above');
const rootAboveKey = Key('root-above');
await tester.pumpWidget(
MaterialApp(
home: Navigator(
key: nestedNavigatorKey,
onGenerateRoute: (settings) => MaterialPageRoute<void>(builder: (_) => const SizedBox(key: contentKey)),
),
),
);
// The covered content stays mounted (maintainState) but goes offstage.
BuildContext content() => tester.element(find.byKey(contentKey, skipOffstage: false));
expect(isRouteChainCurrent(content()), isTrue, reason: 'sole route is current');
// A route above on the SAME (nested) navigator.
unawaited(
nestedNavigatorKey.currentState!.push(
MaterialPageRoute<void>(builder: (_) => const SizedBox(key: nestedAboveKey)),
),
);
await tester.pumpAndSettle();
expect(isRouteChainCurrent(content()), isFalse, reason: 'covered on own navigator');
expect(isRouteChainCurrent(tester.element(find.byKey(nestedAboveKey))), isTrue);
nestedNavigatorKey.currentState!.pop();
await tester.pumpAndSettle();
expect(isRouteChainCurrent(content()), isTrue, reason: 'uncovered after nested pop');
// A route above on the ROOT navigator — the profile-picker-over-player
// shape from #2034: the nested route's own isCurrent stays true.
unawaited(
Navigator.of(
content(),
rootNavigator: true,
).push(MaterialPageRoute<void>(builder: (_) => const SizedBox(key: rootAboveKey))),
);
await tester.pumpAndSettle();
expect(isRouteChainCurrent(content()), isFalse, reason: 'covered on ancestor navigator');
expect(isRouteChainCurrent(tester.element(find.byKey(rootAboveKey))), isTrue);
Navigator.of(content(), rootNavigator: true).pop();
await tester.pumpAndSettle();
expect(isRouteChainCurrent(content()), isTrue, reason: 'uncovered after root pop');
});
}