71735354made TvTextInputPresentation.automatic use native platform input for single-line Apple TV fields, and829d3745migrated the search and Add Jellyfin suites to it. These five cases still expected the Flutter on-screen keyboard widget and failed looking for a panel that no longer exists. Drives the native path instead: each case asserts input is live (readOnly false), raises the keyboard, then keeps its original regression intent — the first Back deactivates input and is consumed so it cannot also pop, and only the second Back pops or cancels.
175 lines
5.2 KiB
Dart
175 lines
5.2 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:plezy/i18n/strings.g.dart';
|
|
import 'package:plezy/screens/settings/settings_utils.dart';
|
|
import 'package:plezy/utils/platform_detector.dart';
|
|
import 'package:plezy/widgets/dialog_action_button.dart';
|
|
|
|
void main() {
|
|
tearDown(() {
|
|
TvDetectionService.debugSetAppleTVOverride(null);
|
|
});
|
|
|
|
testWidgets('settings text input survives TV keyboard back dismissal', (tester) async {
|
|
TvDetectionService.debugSetAppleTVOverride(true);
|
|
await tester.binding.setSurfaceSize(const Size(1280, 720));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
late BuildContext hostContext;
|
|
final saved = <String>[];
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Builder(
|
|
builder: (context) {
|
|
hostContext = context;
|
|
return const SizedBox.shrink();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
showRegexInputDialog(
|
|
context: hostContext,
|
|
title: 'Regex',
|
|
currentValue: 'abc',
|
|
defaultValue: '.*',
|
|
onSave: (value) async => saved.add(value),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final fieldFinder = find.byType(TextField);
|
|
|
|
expect(find.byType(AlertDialog), findsOneWidget);
|
|
expect(find.byKey(const Key('tv_virtual_keyboard_dialog')), findsNothing);
|
|
expect(tester.widget<TextField>(fieldFinder).readOnly, isFalse);
|
|
await tester.showKeyboard(fieldFinder);
|
|
|
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.escape);
|
|
await tester.pump();
|
|
|
|
expect(find.byType(AlertDialog), findsOneWidget);
|
|
expect(tester.widget<TextField>(fieldFinder).readOnly, isTrue);
|
|
|
|
await tester.sendKeyUpEvent(LogicalKeyboardKey.escape);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('tv_virtual_keyboard_dialog')), findsNothing);
|
|
expect(find.byType(AlertDialog), findsOneWidget);
|
|
expect(tester.widget<TextField>(fieldFinder).readOnly, isTrue);
|
|
expect(tester.takeException(), isNull);
|
|
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(AlertDialog), findsNothing);
|
|
expect(saved, isEmpty);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('settings save helper reports platform failures and remains retryable', (tester) async {
|
|
final context = await _pumpHost(tester);
|
|
|
|
showRegexInputDialog(
|
|
context: context,
|
|
title: 'Regex',
|
|
currentValue: 'abc',
|
|
defaultValue: '.*',
|
|
onSave: (_) async => throw PlatformException(code: 'write_failed'),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(_saveButton());
|
|
await tester.pump();
|
|
|
|
expect(find.byType(AlertDialog), findsOneWidget);
|
|
expect(find.text(t.settings.saveFailed), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('settings save helper reports filesystem failures and remains retryable', (tester) async {
|
|
final context = await _pumpHost(tester);
|
|
|
|
showRegexInputDialog(
|
|
context: context,
|
|
title: 'Regex',
|
|
currentValue: 'abc',
|
|
defaultValue: '.*',
|
|
onSave: (_) async => throw const FileSystemException('write failed'),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(_saveButton());
|
|
await tester.pump();
|
|
|
|
expect(find.byType(AlertDialog), findsOneWidget);
|
|
expect(find.text(t.settings.saveFailed), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('settings save helper closes only after a successful save', (tester) async {
|
|
final context = await _pumpHost(tester);
|
|
var saved = false;
|
|
|
|
showRegexInputDialog(
|
|
context: context,
|
|
title: 'Regex',
|
|
currentValue: 'abc',
|
|
defaultValue: '.*',
|
|
onSave: (_) async => saved = true,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(_saveButton());
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(saved, isTrue);
|
|
expect(find.byType(AlertDialog), findsNothing);
|
|
});
|
|
|
|
testWidgets('disposed settings save context does not present late feedback', (tester) async {
|
|
final context = await _pumpHost(tester);
|
|
final saveGate = Completer<void>();
|
|
|
|
showRegexInputDialog(
|
|
context: context,
|
|
title: 'Regex',
|
|
currentValue: 'abc',
|
|
defaultValue: '.*',
|
|
onSave: (_) async {
|
|
await saveGate.future;
|
|
throw PlatformException(code: 'late_write_failure');
|
|
},
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(_saveButton());
|
|
await tester.pump();
|
|
await tester.pumpWidget(const SizedBox.shrink());
|
|
saveGate.complete();
|
|
await tester.pump();
|
|
|
|
expect(find.text(t.settings.saveFailed), findsNothing);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|
|
|
|
Future<BuildContext> _pumpHost(WidgetTester tester) async {
|
|
late BuildContext context;
|
|
await tester.pumpWidget(
|
|
TranslationProvider(
|
|
child: MaterialApp(
|
|
home: Scaffold(
|
|
body: Builder(
|
|
builder: (hostContext) {
|
|
context = hostContext;
|
|
return const SizedBox.shrink();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
return context;
|
|
}
|
|
|
|
Finder _saveButton() => find.widgetWithText(DialogActionButton, t.common.save);
|