fix(profiles): improve pin entry
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/screens/profile/pin_entry_dialog.dart';
|
||||
import 'package:plezy/utils/platform_detector.dart';
|
||||
|
||||
void main() {
|
||||
tearDown(() {
|
||||
TvDetectionService.debugSetAppleTVOverride(null);
|
||||
});
|
||||
|
||||
testWidgets('mobile PIN entry keeps one focused text field while entering digits', (tester) async {
|
||||
TvDetectionService.debugSetAppleTVOverride(false);
|
||||
String? result;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(platform: TargetPlatform.iOS),
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
return Scaffold(
|
||||
body: TextButton(
|
||||
onPressed: () async {
|
||||
result = await showPinEntryDialog(context, 'Protected Profile');
|
||||
},
|
||||
child: const Text('Open'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Open'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final field = find.byType(TextField);
|
||||
expect(field, findsOneWidget);
|
||||
|
||||
final focusNode = tester.widget<TextField>(field).focusNode;
|
||||
expect(focusNode, isNotNull);
|
||||
expect(focusNode!.hasFocus, isTrue);
|
||||
|
||||
await tester.enterText(field, '1');
|
||||
await tester.pump();
|
||||
|
||||
expect(field, findsOneWidget);
|
||||
expect(tester.widget<TextField>(field).focusNode, same(focusNode));
|
||||
expect(focusNode.hasFocus, isTrue);
|
||||
|
||||
await tester.enterText(field, '12');
|
||||
await tester.pump();
|
||||
|
||||
expect(field, findsOneWidget);
|
||||
expect(tester.widget<TextField>(field).focusNode, same(focusNode));
|
||||
expect(focusNode.hasFocus, isTrue);
|
||||
|
||||
await tester.enterText(field, '1234');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(result, '1234');
|
||||
expect(find.byType(PinEntryDialog), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('D-pad keypad enters and submits PIN', (tester) async {
|
||||
TvDetectionService.debugSetAppleTVOverride(true);
|
||||
String? result;
|
||||
|
||||
await _pumpPinDialogLauncher(tester, onResult: (pin) => result = pin);
|
||||
await tester.tap(find.text('Open'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close_rounded), findsOneWidget);
|
||||
expect(find.byIcon(Icons.backspace_outlined), findsOneWidget);
|
||||
expect(find.byType(TextField), findsNothing);
|
||||
expect(find.widgetWithText(TextButton, 'Cancel'), findsNothing);
|
||||
expect(find.byType(FilledButton), findsNothing);
|
||||
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.select, PhysicalKeyboardKey.select); // 1
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.arrowRight, PhysicalKeyboardKey.arrowRight);
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.select, PhysicalKeyboardKey.select); // 2
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.arrowRight, PhysicalKeyboardKey.arrowRight);
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.select, PhysicalKeyboardKey.select); // 3
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.arrowLeft, PhysicalKeyboardKey.arrowLeft);
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.arrowLeft, PhysicalKeyboardKey.arrowLeft);
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.arrowDown, PhysicalKeyboardKey.arrowDown);
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.select, PhysicalKeyboardKey.select); // 4
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(result, '1234');
|
||||
expect(find.byType(PinEntryDialog), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('non-mobile PIN entry accepts physical keyboard digits', (tester) async {
|
||||
TvDetectionService.debugSetAppleTVOverride(true);
|
||||
String? result;
|
||||
|
||||
await _pumpPinDialogLauncher(tester, onResult: (pin) => result = pin);
|
||||
await tester.tap(find.text('Open'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await _pressKey(tester, LogicalKeyboardKey.digit1);
|
||||
await _pressKey(tester, LogicalKeyboardKey.digit2);
|
||||
await _pressKey(tester, LogicalKeyboardKey.backspace);
|
||||
await _pressKey(tester, LogicalKeyboardKey.digit3);
|
||||
await _pressKey(tester, LogicalKeyboardKey.digit4);
|
||||
await _pressKey(tester, LogicalKeyboardKey.digit5);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(result, '1345');
|
||||
expect(find.byType(PinEntryDialog), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('D-pad close key cancels PIN dialog', (tester) async {
|
||||
TvDetectionService.debugSetAppleTVOverride(true);
|
||||
var completed = false;
|
||||
String? result = 'unchanged';
|
||||
|
||||
await _pumpPinDialogLauncher(
|
||||
tester,
|
||||
onResult: (pin) {
|
||||
completed = true;
|
||||
result = pin;
|
||||
},
|
||||
);
|
||||
await tester.tap(find.text('Open'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.arrowDown, PhysicalKeyboardKey.arrowDown);
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.arrowDown, PhysicalKeyboardKey.arrowDown);
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.arrowDown, PhysicalKeyboardKey.arrowDown);
|
||||
await _pressDpadKey(tester, LogicalKeyboardKey.select, PhysicalKeyboardKey.select);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(completed, isTrue);
|
||||
expect(result, isNull);
|
||||
expect(find.byType(PinEntryDialog), findsNothing);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _pumpPinDialogLauncher(WidgetTester tester, {required ValueChanged<String?> onResult}) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
return Scaffold(
|
||||
body: TextButton(
|
||||
onPressed: () async {
|
||||
onResult(await showPinEntryDialog(context, 'Protected Profile'));
|
||||
},
|
||||
child: const Text('Open'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pressKey(WidgetTester tester, LogicalKeyboardKey key) async {
|
||||
await tester.sendKeyEvent(key);
|
||||
await tester.pump();
|
||||
}
|
||||
|
||||
Future<void> _pressDpadKey(WidgetTester tester, LogicalKeyboardKey logicalKey, PhysicalKeyboardKey physicalKey) async {
|
||||
_dispatchKey(
|
||||
KeyDownEvent(
|
||||
physicalKey: physicalKey,
|
||||
logicalKey: logicalKey,
|
||||
timeStamp: Duration.zero,
|
||||
deviceType: ui.KeyEventDeviceType.directionalPad,
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
}
|
||||
|
||||
KeyEventResult _dispatchKey(KeyEvent event) {
|
||||
FocusNode? node = FocusManager.instance.primaryFocus;
|
||||
while (node != null) {
|
||||
final result = node.onKeyEvent?.call(node, event) ?? KeyEventResult.ignored;
|
||||
if (result == KeyEventResult.handled) return result;
|
||||
node = node.parent;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
Reference in New Issue
Block a user