feat: drive the reader with a D-pad

This commit is contained in:
randogoth 2026-10-12 17:59:14 +03:00
parent 7b2ddc0249
commit f386ef2c5b
5 changed files with 600 additions and 84 deletions

View file

@ -927,6 +927,184 @@ void main() {
debugDefaultTargetPlatformOverride = null;
});
group('a D-pad drives the reader on a touch host', () {
late AppDependencies stubDeps;
setUp(() {
stubDeps = AppDependencies(
database: MewsDatabase.test(NativeDatabase.memory()),
fetch: FakeFetch({
'https://example.com/a.html': _page(
'Page A',
'<h1>Page A</h1><p><a href="b.html">To page B</a></p>'
'<p><a href="c.html">To page C</a></p>'
'<form action="/sign" method="post">'
'<input type="text" name="name" />'
'<input type="submit" value="Sign" /></form>'
'${'<p>Padding paragraph to make the page scroll.</p>' * 60}'
'<p><a href="c.html">Far down to page C</a></p>'),
'https://example.com/b.html':
_page('Page B', '<h1>Page B</h1><p>The second pages content.</p>'),
'https://example.com/c.html':
_page('Page C', '<h1>Page C</h1><p>The third pages content.</p>'),
}),
strings: strings,
);
});
tearDown(() => stubDeps.db.close());
Future<void> openA(WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;
await tester.pumpWidget(MewsApp(deps: stubDeps, strings: strings));
await tester.pumpAndSettle();
await stubDeps.browser.open('https://example.com/a.html');
await tester.pumpAndSettle();
}
Future<void> press(WidgetTester tester, LogicalKeyboardKey key) async {
await tester.sendKeyDownEvent(key);
await tester.sendKeyUpEvent(key);
await tester.pumpAndSettle();
}
Future<void> hold(WidgetTester tester, LogicalKeyboardKey key) async {
await tester.sendKeyDownEvent(key);
await tester.sendKeyRepeatEvent(key);
await tester.sendKeyRepeatEvent(key);
await tester.sendKeyUpEvent(key);
await tester.pumpAndSettle();
}
String? current() => stubDeps.browser.currentPage.value?.url;
ScrollPosition pageScroll(WidgetTester tester) => tester
.state<ScrollableState>(find
.descendant(of: find.byType(ListView), matching: find.byType(Scrollable))
.first)
.position;
testWidgets('Left and Right walk the links, the center opens one',
(tester) async {
await openA(tester);
await press(tester, LogicalKeyboardKey.arrowRight);
await press(tester, LogicalKeyboardKey.arrowRight);
await press(tester, LogicalKeyboardKey.arrowLeft);
await press(tester, LogicalKeyboardKey.select);
expect(current(), 'https://example.com/b.html');
debugDefaultTargetPlatformOverride = null;
});
testWidgets('holding Left, Right and the center navigates', (tester) async {
await openA(tester);
await stubDeps.browser.open('https://example.com/b.html');
await tester.pumpAndSettle();
await hold(tester, LogicalKeyboardKey.arrowLeft);
expect(current(), 'https://example.com/a.html');
await hold(tester, LogicalKeyboardKey.arrowRight);
expect(current(), 'https://example.com/b.html');
await hold(tester, LogicalKeyboardKey.select);
expect(current(), MewsUrls.hub);
debugDefaultTargetPlatformOverride = null;
});
testWidgets('Up and Down scroll, and the walk starts from the screen',
(tester) async {
await openA(tester);
final scroll = pageScroll(tester);
await press(tester, LogicalKeyboardKey.arrowDown);
expect(scroll.pixels, greaterThan(0));
await press(tester, LogicalKeyboardKey.arrowUp);
expect(scroll.pixels, 0);
// Scrolled to the end, Right takes the first link on screen rather
// than the first of the page.
scroll.jumpTo(scroll.maxScrollExtent);
await tester.pumpAndSettle();
await press(tester, LogicalKeyboardKey.arrowRight);
await press(tester, LogicalKeyboardKey.enter);
expect(current(), 'https://example.com/c.html');
debugDefaultTargetPlatformOverride = null;
});
testWidgets('the walk passes through form controls', (tester) async {
await openA(tester);
FocusNode? focused() => FocusManager.instance.primaryFocus;
await press(tester, LogicalKeyboardKey.arrowRight);
await press(tester, LogicalKeyboardKey.arrowRight);
await press(tester, LogicalKeyboardKey.arrowRight);
final field = tester.widget<EditableText>(find.descendant(
of: find.byType(TextFormField), matching: find.byType(EditableText)));
expect(focused(), field.focusNode);
// Typed text keeps Left for its cursor until the cursor is at the start.
await tester.enterText(find.byType(TextFormField), 'Al');
await press(tester, LogicalKeyboardKey.arrowRight);
expect(focused()?.context?.findAncestorWidgetOfExactType<FilledButton>(),
isNotNull);
await press(tester, LogicalKeyboardKey.arrowLeft);
expect(focused(), field.focusNode);
await press(tester, LogicalKeyboardKey.arrowLeft);
await press(tester, LogicalKeyboardKey.arrowLeft);
expect(focused(), field.focusNode);
await press(tester, LogicalKeyboardKey.arrowLeft);
expect(field.focusNode.hasFocus, isFalse);
await press(tester, LogicalKeyboardKey.select);
expect(current(), 'https://example.com/c.html');
debugDefaultTargetPlatformOverride = null;
});
testWidgets('the arrows reach the buttons of an error page', (tester) async {
await openA(tester);
await stubDeps.browser.open('https://example.com/missing.html');
await tester.pumpAndSettle();
expect(find.text('Try again'), findsOneWidget);
// The first button takes the focus, and the arrows move between them.
BuildContext? focused() => FocusManager.instance.primaryFocus?.context;
expect(focused()?.findAncestorWidgetOfExactType<FilledButton>(), isNotNull);
await press(tester, LogicalKeyboardKey.arrowDown);
expect(focused()?.findAncestorWidgetOfExactType<OutlinedButton>(), isNotNull);
debugDefaultTargetPlatformOverride = null;
});
testWidgets('Menu shows the chrome and Down returns to the page',
(tester) async {
await openA(tester);
final scroll = pageScroll(tester);
await press(tester, LogicalKeyboardKey.arrowDown);
expect(find.byType(AddressBar), findsNothing);
await press(tester, LogicalKeyboardKey.contextMenu);
final address = tester.widget<TextField>(find.byType(TextField).first);
expect(address.focusNode!.hasFocus, isTrue);
// Right moves past the text to the bar's buttons.
await press(tester, LogicalKeyboardKey.arrowRight);
await press(tester, LogicalKeyboardKey.arrowRight);
expect(address.focusNode!.hasFocus, isFalse);
await press(tester, LogicalKeyboardKey.arrowLeft);
expect(address.focusNode!.hasFocus, isTrue);
final before = scroll.pixels;
await press(tester, LogicalKeyboardKey.arrowDown);
expect(address.focusNode!.hasFocus, isFalse);
await press(tester, LogicalKeyboardKey.arrowDown);
expect(scroll.pixels, greaterThan(before));
debugDefaultTargetPlatformOverride = null;
});
});
testWidgets('bookmarks can be searched and removed from their own page',
(tester) async {
await pumpApp(tester);