fix(tv): keyboard no longer opens over jellyfin quick connect screen

Auto-starting quick connect from inside the probe's runAsync let the
probe's finally clear busy mid-initiate; the re-enabled form then took
focus fallback on the URL field, auto-opening the TV keyboard. Sequence
the two busy scopes and debug-assert against overlapping runAsync.
This commit is contained in:
edde746
2026-06-12 13:35:55 +02:00
parent 7f1eb3a818
commit 892dfee381
3 changed files with 96 additions and 8 deletions
@@ -17,7 +17,7 @@ import '../../test_helpers/prefs.dart';
Profile _profile(String id) =>
Profile.local(id: id, displayName: id, sortOrder: 0, createdAt: DateTime.fromMillisecondsSinceEpoch(0));
JellyfinConnectionAuthService _jellyfinAuthService({bool quickConnectEnabled = false}) {
JellyfinConnectionAuthService _jellyfinAuthService({bool quickConnectEnabled = false, Duration? initiateDelay}) {
return JellyfinConnectionAuthService(
clientName: 'Plezy',
clientVersion: 'test',
@@ -33,6 +33,7 @@ JellyfinConnectionAuthService _jellyfinAuthService({bool quickConnectEnabled = f
case '/QuickConnect/Enabled':
return http.Response(jsonEncode(quickConnectEnabled), 200, headers: {'content-type': 'application/json'});
case '/QuickConnect/Initiate':
if (initiateDelay != null) await Future<void>.delayed(initiateDelay);
return http.Response(
jsonEncode({'Code': '123456', 'Secret': 'qc-secret'}),
200,
@@ -267,6 +268,67 @@ void main() {
await tester.pump(const Duration(seconds: 6));
});
testWidgets('TV auto Quick Connect never opens the keyboard across the panel swap', (tester) async {
resetSharedPreferencesForTest();
TvDetectionService.debugSetAppleTVOverride(null);
await TvDetectionService.getInstance(forceTv: true);
TvDetectionService.setForceTVSync(true);
await tester.pumpWidget(
InputModeTracker(
child: MaterialApp(
home: AddJellyfinScreen(
// Hold /QuickConnect/Initiate open so the frames between probe
// success and the panel swap are observable — that window is
// where the focus fallback used to auto-open the keyboard.
authServiceFactory: () =>
_jellyfinAuthService(quickConnectEnabled: true, initiateDelay: const Duration(milliseconds: 50)),
localDiscoveryFactory: () async => [
DiscoveredJellyfinServer(address: 'http://192.168.1.20:8096', id: 'srv-1', name: 'Home'),
],
),
),
),
);
await tester.pumpAndSettle();
expect(FocusManager.instance.primaryFocus?.debugLabel, 'AddJellyfin:Url');
// D-pad to the discovered server and select it — on TV the probe
// auto-starts Quick Connect.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pump();
expect(FocusManager.instance.primaryFocus?.debugLabel, 'AddJellyfin:Discovered:srv-1');
await tester.sendKeyEvent(LogicalKeyboardKey.select);
await tester.pump();
await tester.pump();
// Pre-swap frames: probe done, initiate in flight — no keyboard.
expect(find.byKey(const Key('tv_virtual_keyboard_panel')), findsNothing);
await tester.pump(const Duration(milliseconds: 60));
await tester.pump();
// Quick Connect panel swapped in: code shown, Cancel focused, no keyboard.
expect(find.text('123456'), findsOneWidget);
expect(find.byKey(const Key('tv_virtual_keyboard_panel')), findsNothing);
expect(FocusManager.instance.primaryFocus?.debugLabel, 'AddJellyfin:CancelQuickConnect');
await tester.tap(find.text('Cancel'));
await tester.pump();
await tester.pump();
// Form returns; the URL field's autofocus re-fires on a fresh host whose
// first-focus suppression keeps the keyboard closed.
expect(find.text('123456'), findsNothing);
expect(FocusManager.instance.primaryFocus?.debugLabel, 'AddJellyfin:Url');
expect(find.byKey(const Key('tv_virtual_keyboard_panel')), findsNothing);
// Let the cancelled poll's backoff timer fire so the test ends clean.
await tester.pump(const Duration(seconds: 6));
});
testWidgets('selecting a discovered Jellyfin server probes that address', (tester) async {
await tester.pumpWidget(
MaterialApp(