191 lines
6.6 KiB
Dart
191 lines
6.6 KiB
Dart
|
|
import 'dart:io' show File;
|
||
|
|
|
||
|
|
import 'package:drift/native.dart';
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:flutter_test/flutter_test.dart';
|
||
|
|
|
||
|
|
import 'package:mews/application/controllers/browser_controller.dart'
|
||
|
|
show BrokenPage, LoadFailed;
|
||
|
|
import 'package:mews/application/controllers/reader_settings_controller.dart'
|
||
|
|
show ReaderSettingsController;
|
||
|
|
import 'package:mews/database/database.dart';
|
||
|
|
import 'package:mews/l10n/strings.dart';
|
||
|
|
import 'package:mews/mews/document.dart';
|
||
|
|
import 'package:mews/mews/parser.dart';
|
||
|
|
import 'package:mews/presentation/app.dart';
|
||
|
|
import 'package:mews/presentation/reader/address_bar.dart';
|
||
|
|
import 'package:mews/presentation/reader/fob.dart';
|
||
|
|
import 'package:mews/presentation/reader/reader_screen.dart';
|
||
|
|
import 'package:mews/presentation/reader/settings_sheet.dart';
|
||
|
|
|
||
|
|
const pageSource = '''
|
||
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
||
|
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||
|
|
<head>
|
||
|
|
<title>Signal Bars</title>
|
||
|
|
<meta name="mews-profile" content="0.1" />
|
||
|
|
</head>
|
||
|
|
<body class="mews-green">
|
||
|
|
<h1>Signal Bars</h1>
|
||
|
|
<p>Notes on old phones.</p>
|
||
|
|
<ul>
|
||
|
|
<li><a href="2026-10-08-retro.html">2026-10-08 Collecting retro phones</a></li>
|
||
|
|
<li><a href="2026-09-21-first.html">2026-09-21 First post</a></li>
|
||
|
|
</ul>
|
||
|
|
<form action="/sign" method="post">
|
||
|
|
<label for="name">Your name</label>
|
||
|
|
<input type="text" id="name" name="name" />
|
||
|
|
<input type="submit" value="Sign guestbook" />
|
||
|
|
</form>
|
||
|
|
</body>
|
||
|
|
</html>
|
||
|
|
''';
|
||
|
|
|
||
|
|
void main() {
|
||
|
|
late MewsDatabase db;
|
||
|
|
late AppDependencies deps;
|
||
|
|
|
||
|
|
setUp(() async {
|
||
|
|
db = MewsDatabase.test(NativeDatabase.memory());
|
||
|
|
deps = AppDependencies(database: db);
|
||
|
|
});
|
||
|
|
|
||
|
|
tearDown(() async {
|
||
|
|
await db.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
Future<void> pumpApp(WidgetTester tester) async {
|
||
|
|
final strings = Strings();
|
||
|
|
strings.loadSource(File('locales/en-US/main.ftl').readAsStringSync());
|
||
|
|
await tester.pumpWidget(MewsApp(deps: deps, strings: strings));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
}
|
||
|
|
|
||
|
|
testWidgets('renders a page through the full app', (tester) async {
|
||
|
|
await pumpApp(tester);
|
||
|
|
|
||
|
|
final page = parseSource(pageSource, 'https://example.com/index.html');
|
||
|
|
deps.browser.currentPage.value = page;
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
expect(find.text('Signal Bars'), findsWidgets);
|
||
|
|
expect(find.textContaining('Collecting retro phones'), findsOneWidget);
|
||
|
|
expect(find.textContaining('Sign guestbook'), findsOneWidget);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('shows the broken-page error and its text view', (tester) async {
|
||
|
|
await pumpApp(tester);
|
||
|
|
deps.browser.currentPage.value = null;
|
||
|
|
deps.browser.error.value = const BrokenPage('<p>Half a page & more');
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
expect(find.textContaining('markup error'), findsOneWidget);
|
||
|
|
await tester.tap(find.textContaining('Show page text'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
expect(find.textContaining('Half a page & more'), findsOneWidget);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('shows the load-failed error page', (tester) async {
|
||
|
|
await pumpApp(tester);
|
||
|
|
deps.browser.error.value = const LoadFailed();
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
expect(find.textContaining('could not be loaded'), findsOneWidget);
|
||
|
|
expect(find.textContaining('Try again'), findsOneWidget);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('settings sheet changes presentation and toggles library',
|
||
|
|
(tester) async {
|
||
|
|
await pumpApp(tester);
|
||
|
|
|
||
|
|
final page = parseSource(pageSource, 'https://example.com/index.html');
|
||
|
|
deps.browser.currentPage.value = page;
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
showReaderSettingsSheet(tester.element(find.byType(ReaderScreen)));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
// Typeface, size, theme, colors.
|
||
|
|
await tester.tap(find.text('Atkinson Hyperlegible'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
await tester.tap(find.byIcon(Icons.add));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
await tester.tap(find.text('Dark'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
await tester.tap(find.text('Warm'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
expect(deps.settings.fontScale.value, 1.125);
|
||
|
|
expect(deps.settings.themeMode.value, 'dark');
|
||
|
|
expect(deps.settings.variant.value, 'warm');
|
||
|
|
|
||
|
|
// Bookmark and subscribe the current page; both tiles sit at the bottom
|
||
|
|
// of the sheet, so scroll the sheet down to the end first.
|
||
|
|
final sheet = find.byType(Scrollable).last;
|
||
|
|
await tester.drag(sheet, const Offset(0, -1000));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
await tester.tap(find.text('Add bookmark'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
expect(deps.library.isBookmarked('https://example.com/index.html'), isTrue);
|
||
|
|
|
||
|
|
await tester.tap(find.text('Subscribe to this site'));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
expect(deps.library.isSubscribed('https://example.com/index.html'), isTrue);
|
||
|
|
|
||
|
|
// Settings survive a reload of the controller.
|
||
|
|
final reloaded = ReaderSettingsController(db);
|
||
|
|
await reloaded.load();
|
||
|
|
expect(reloaded.themeMode.value, 'dark');
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('the FOB hides on scroll down and returns on scroll up',
|
||
|
|
(tester) async {
|
||
|
|
await pumpApp(tester);
|
||
|
|
|
||
|
|
// A page long enough to scroll.
|
||
|
|
final tall = pageSource.replaceFirst(
|
||
|
|
'</body>',
|
||
|
|
'<p>Padding paragraph to make the page scroll.</p>' * 80 + '</body>');
|
||
|
|
final page = parseSource(tall, 'https://example.com/index.html');
|
||
|
|
deps.browser.currentPage.value = page;
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
Fob fob() => tester.widget<Fob>(find.byType(Fob));
|
||
|
|
expect(fob().visible, isTrue);
|
||
|
|
|
||
|
|
// Scrolling down hides the address bar and the FOB.
|
||
|
|
await tester.drag(find.byType(ListView), const Offset(0, -300));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
expect(fob().visible, isFalse);
|
||
|
|
expect(find.byType(AddressBar), findsNothing);
|
||
|
|
|
||
|
|
// Scrolling up brings both back.
|
||
|
|
await tester.drag(find.byType(ListView), const Offset(0, 300));
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
expect(fob().visible, isTrue);
|
||
|
|
expect(find.byType(AddressBar), findsOneWidget);
|
||
|
|
});
|
||
|
|
|
||
|
|
testWidgets('reports new posts once per page', (tester) async {
|
||
|
|
await pumpApp(tester);
|
||
|
|
|
||
|
|
final page = parseSource(pageSource, 'https://example.com/index.html');
|
||
|
|
deps.library.subscribedUrls.value = {'https://example.com/index.html'};
|
||
|
|
deps.library.newEntries.value = {
|
||
|
|
'https://example.com/index.html': [
|
||
|
|
MewsEntry(
|
||
|
|
url: 'https://example.com/2026-10-08-retro.html',
|
||
|
|
date: DateTime(2026, 10, 8),
|
||
|
|
title: 'Collecting retro phones'),
|
||
|
|
],
|
||
|
|
};
|
||
|
|
deps.browser.currentPage.value = page;
|
||
|
|
await tester.pumpAndSettle();
|
||
|
|
|
||
|
|
expect(find.textContaining('1 new post'), findsOneWidget);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
MewsPage parseSource(String source, String url) =>
|
||
|
|
parseMewsPage(source: source, url: url);
|