feat(inventory): external links on the variety detail

Add Wikipedia/forum-style URLs to a variety:
- Repository: addExternalLink/removeExternalLink (soft delete); watchVariety
  loads links and re-emits on external_links changes; ExternalLinkItem model
  (title preferred over URL for display).
- Cubit: addExternalLink/removeExternalLink.
- Detail: a "Links" section with tappable link chips (open via url_launcher,
  http(s):// prepended if missing), delete per chip, and an "Add link" dialog
  (URL + optional title).

Tests: repo add/remove + widget add/remove. 52 green; Linux runs.
This commit is contained in:
vjrj 2026-07-08 13:38:31 +02:00
parent 02a9d5bf39
commit b23f4f1c77
14 changed files with 315 additions and 2 deletions

View file

@ -212,4 +212,20 @@ void main() {
await queue.cancel();
});
test('add and remove external links', () async {
final id = await repo.addQuickVariety(label: 'Maize');
final linkId = await repo.addExternalLink(
id,
'https://en.wikipedia.org/wiki/Maize',
title: 'Wikipedia',
);
final detail = await repo.watchVariety(id).first;
expect(detail!.links.single.url, 'https://en.wikipedia.org/wiki/Maize');
expect(detail.links.single.display, 'Wikipedia'); // title preferred
await repo.removeExternalLink(linkId);
expect((await repo.watchVariety(id).first)!.links, isEmpty);
});
}

View file

@ -329,4 +329,33 @@ void main() {
expect(find.text('Maíz'), findsNothing);
await disposeTree(tester);
});
testWidgets('adding and removing an external link', (tester) async {
final repo = newTestRepository(db);
final id = await repo.addQuickVariety(label: 'Maize');
await tester.pumpWidget(
wrapDetail(
repository: repo,
varietyId: id,
species: newTestSpeciesRepository(db),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('link.add')));
await tester.pumpAndSettle();
await tester.enterText(find.byKey(const Key('link.url')), 'wikipedia.org');
await tester.enterText(find.byKey(const Key('link.title')), 'Wiki');
await tester.tap(find.byKey(const Key('link.save')));
await tester.pumpAndSettle();
expect(find.text('Wiki'), findsOneWidget);
await tester.tap(find.byIcon(Icons.close)); // the link chip's delete icon
await tester.pumpAndSettle();
expect(find.text('Wiki'), findsNothing);
await disposeTree(tester);
});
}