feat(block1): bundled species catalog + autocomplete link

Add a small curated catalog of Iberian horticultural species and let a variety
be linked to it from the edit sheet.

- assets/catalog/species.json: 14 species with botanical family and ES/EN
  common names (wikidata_qid/gbif_key deferred to the varilla enrichment).
- SpeciesRepository: idempotent seedBundled (is_bundled rows, keyed by
  scientific name) + search by scientific/common name with a locale-best label.
  Seeded on startup from DI.
- VarietyRepository.linkSpecies: sets species_id and prefills category from the
  species' family when empty (never overwrites an existing category).
  VarietyDetail now carries the scientific name.
- Edit sheet gains a live species-search field; the detail view shows the
  scientific name (italic). i18n strings added (ES/EN).

Tests: catalog parse, idempotent seeding, search by scientific/common name,
linkSpecies prefill semantics, and a widget test for the autocomplete → link →
scientific-name-shown flow. Full suite: 32 passing, 0 skipped.
This commit is contained in:
vjrj 2026-07-07 21:21:59 +02:00
parent 7ff4e38a15
commit 4e8b8293e0
22 changed files with 700 additions and 47 deletions

View file

@ -1,11 +1,20 @@
import 'package:async/async.dart';
import 'package:commons_core/commons_core.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tane/data/species_repository.dart';
import 'package:tane/data/variety_repository.dart';
import 'package:tane/db/database.dart';
import '../support/test_support.dart';
const _tomatoSeed = SpeciesSeed(
scientificName: 'Solanum lycopersicum',
family: 'Solanaceae',
commonNames: {
'en': ['Tomato'],
},
);
void main() {
late AppDatabase db;
late VarietyRepository repo;
@ -60,4 +69,32 @@ void main() {
expect(await repo.watchInventory().first, isEmpty);
},
);
test(
'linkSpecies exposes the scientific name and prefills empty category',
() async {
final species = newTestSpeciesRepository(db);
await species.seedBundled(const [_tomatoSeed]);
final match = (await species.search('tomato')).single;
final id = await repo.addQuickVariety(label: "Grandma's tomato");
await repo.linkSpecies(id, match.id);
final detail = await repo.watchVariety(id).first;
expect(detail!.scientificName, 'Solanum lycopersicum');
expect(detail.category, 'Solanaceae'); // prefilled from family
},
);
test('linkSpecies does not overwrite an existing category', () async {
final species = newTestSpeciesRepository(db);
await species.seedBundled(const [_tomatoSeed]);
final match = (await species.search('tomato')).single;
final id = await repo.addQuickVariety(label: 'T', category: 'My tomatoes');
await repo.linkSpecies(id, match.id);
final detail = await repo.watchVariety(id).first;
expect(detail!.category, 'My tomatoes');
});
}