feat: drive the reader with a D-pad
This commit is contained in:
parent
7b2ddc0249
commit
f386ef2c5b
5 changed files with 600 additions and 84 deletions
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue