feat: search, filter and act on items from the library pages
This commit is contained in:
parent
1b426ccd8b
commit
5cd053c8a9
11 changed files with 401 additions and 43 deletions
|
|
@ -422,9 +422,9 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
expect(stubDeps.browser.currentPage.value?.url, 'mews:history');
|
||||
|
||||
// A history link navigates like any other link.
|
||||
await tester.tap(find.textContaining('Page A'));
|
||||
await tester.pumpAndSettle();
|
||||
// A history link navigates like any other link. Rows carry their own
|
||||
// actions now, so tap the title itself rather than the row's middle.
|
||||
await tapRun(tester, 'Page A');
|
||||
expect(stubDeps.browser.currentPage.value?.url, 'https://example.com/a.html');
|
||||
|
||||
// Bookmarks and subscriptions get the same treatment.
|
||||
|
|
@ -484,6 +484,102 @@ void main() {
|
|||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
|
||||
testWidgets('bookmarks can be searched and removed from their own page',
|
||||
(tester) async {
|
||||
await pumpApp(tester);
|
||||
await deps.bookmarks.load();
|
||||
await deps.bookmarks.toggle('https://a.example/retro.html', 'Retro phones');
|
||||
await deps.bookmarks.toggle('https://b.example/signal.html', 'Signal bars');
|
||||
|
||||
await deps.browser.open(MewsUrls.bookmarks);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('Retro phones'), findsOneWidget);
|
||||
expect(find.textContaining('Signal bars'), findsOneWidget);
|
||||
|
||||
// Searching narrows the list and the choice lands in the address.
|
||||
await tester.enterText(find.byType(TextFormField), 'retro');
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Search'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(deps.browser.currentPage.value?.url, 'mews:bookmarks?q=retro');
|
||||
expect(find.textContaining('Retro phones'), findsOneWidget);
|
||||
expect(find.textContaining('Signal bars'), findsNothing);
|
||||
|
||||
// A search with no matches says so rather than looking empty.
|
||||
await deps.browser.open('mews:bookmarks?q=nothinghere');
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('No bookmarks match'), findsOneWidget);
|
||||
|
||||
// Clearing the search brings the whole list back.
|
||||
await deps.browser.open('mews:bookmarks?q=retro');
|
||||
await tester.pumpAndSettle();
|
||||
await tapRun(tester, 'Clear search');
|
||||
expect(deps.browser.currentPage.value?.url, MewsUrls.bookmarks);
|
||||
expect(find.textContaining('Signal bars'), findsOneWidget);
|
||||
|
||||
// Removing one stays on the page and adds no walk history entry. What the
|
||||
// reader has typed survives, because the renderer matches form state by
|
||||
// position and the search box keeps its place in the page.
|
||||
await tester.enterText(find.byType(TextFormField), 'half typed');
|
||||
final stepsBack = deps.browser.canGoBack;
|
||||
await tapRun(tester, 'Remove bookmark');
|
||||
expect(find.widgetWithText(TextFormField, 'half typed'), findsOneWidget);
|
||||
expect(deps.browser.currentPage.value?.url, MewsUrls.bookmarks);
|
||||
expect(deps.browser.canGoBack, stepsBack);
|
||||
expect(find.textContaining('Retro phones'), findsNothing);
|
||||
expect(find.textContaining('Signal bars'), findsOneWidget);
|
||||
expect(deps.bookmarks.isBookmarked('https://a.example/retro.html'), isFalse);
|
||||
});
|
||||
|
||||
testWidgets('clearing history takes a second step', (tester) async {
|
||||
await pumpApp(tester);
|
||||
await deps.db.recordVisit('https://a.example/one.html', 'Page one');
|
||||
await deps.db.recordVisit('https://a.example/one.html', 'Page one');
|
||||
await deps.db.recordVisit('https://b.example/two.html', 'Page two');
|
||||
|
||||
await deps.browser.open(MewsUrls.history);
|
||||
await tester.pumpAndSettle();
|
||||
// A page visited twice is listed once.
|
||||
expect(find.textContaining('Page one'), findsOneWidget);
|
||||
expect(find.textContaining('Page two'), findsOneWidget);
|
||||
|
||||
// Backing out of the confirmation leaves history alone.
|
||||
await tapRun(tester, 'Clear history');
|
||||
expect(deps.browser.currentPage.value?.url, 'mews:history?confirm=clear');
|
||||
expect(find.textContaining("can't undo"), findsOneWidget);
|
||||
await tapRun(tester, 'Keep my history');
|
||||
expect(deps.browser.currentPage.value?.url, MewsUrls.history);
|
||||
expect(find.textContaining('Page one'), findsOneWidget);
|
||||
|
||||
// Removing one page leaves the other. The most recent visit heads the
|
||||
// list, so the first row is page two.
|
||||
await tapRun(tester, 'Remove from history');
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('Page two'), findsNothing);
|
||||
expect(find.textContaining('Page one'), findsOneWidget);
|
||||
|
||||
// Confirming clears the rest.
|
||||
await tapRun(tester, 'Clear history');
|
||||
await tapRun(tester, 'Clear history now');
|
||||
expect(find.textContaining('Pages you visit appear here'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('a site can be left from the subscriptions page', (tester) async {
|
||||
await pumpApp(tester);
|
||||
await deps.subscriptions.load();
|
||||
await deps.subscriptions
|
||||
.toggle(parseSource(pageSource, 'https://example.com/index.html'));
|
||||
|
||||
await deps.browser.open(MewsUrls.subscriptions);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('Signal Bars'), findsOneWidget);
|
||||
|
||||
await tapRun(tester, 'Stop subscribing');
|
||||
expect(deps.browser.currentPage.value?.url, MewsUrls.subscriptions);
|
||||
expect(deps.subscriptions.isSubscribed('https://example.com/index.html'),
|
||||
isFalse);
|
||||
expect(find.textContaining('Sites you subscribe to'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('the posts page lists, orders and filters posts',
|
||||
(tester) async {
|
||||
const siteA = 'https://a.example/';
|
||||
|
|
|
|||
|
|
@ -93,6 +93,45 @@ void main() {
|
|||
expect(await db.countHistory(), 2);
|
||||
});
|
||||
|
||||
test('a search matches titles and addresses literally', () async {
|
||||
await db.addBookmark('https://a.example/retro.html', 'Collecting retro phones');
|
||||
await db.addBookmark('https://b.example/signal.html', 'Signal bars');
|
||||
await db.addBookmark('https://c.example/100.html', '100% signal');
|
||||
|
||||
expect((await db.bookmarksByDate(query: 'retro')).map((b) => b.title),
|
||||
['Collecting retro phones']);
|
||||
// The address is searched too.
|
||||
expect((await db.bookmarksByDate(query: 'b.example')).map((b) => b.title),
|
||||
['Signal bars']);
|
||||
// A blank search is not a filter.
|
||||
expect(await db.bookmarksByDate(query: ' '), hasLength(3));
|
||||
|
||||
// The reader types text, not a pattern: LIKE's wildcards match literally.
|
||||
expect(await db.bookmarksByDate(query: '%'), hasLength(1));
|
||||
expect((await db.bookmarksByDate(query: '100%')).map((b) => b.title),
|
||||
['100% signal']);
|
||||
expect(await db.bookmarksByDate(query: '_'), isEmpty);
|
||||
});
|
||||
|
||||
test('history lists one row per address, newest visit first', () async {
|
||||
await db.recordVisit('https://a.example/one.html', 'First title');
|
||||
await db.recordVisit('https://b.example/two.html', 'Page two');
|
||||
await db.recordVisit('https://a.example/one.html', 'Newer title');
|
||||
|
||||
final pages = await db.recentPages();
|
||||
expect(pages.map((p) => p.url),
|
||||
['https://a.example/one.html', 'https://b.example/two.html']);
|
||||
// The row carries the title of the latest visit.
|
||||
expect(pages.first.title, 'Newer title');
|
||||
|
||||
await db.forgetPage('https://a.example/one.html');
|
||||
expect((await db.recentPages()).map((p) => p.url),
|
||||
['https://b.example/two.html']);
|
||||
|
||||
await db.clearHistory();
|
||||
expect(await db.recentPages(), isEmpty);
|
||||
});
|
||||
|
||||
test('an older database is recreated rather than migrated', () async {
|
||||
final dir = Directory.systemTemp.createTempSync('mews-schema');
|
||||
addTearDown(() => dir.deleteSync(recursive: true));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue