import 'dart:typed_data'; 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 'package:tane/db/enums.dart'; import '../support/test_support.dart'; void main() { late AppDatabase db; late VarietyRepository repo; setUp(() { db = newTestDatabase(); repo = newTestRepository(db); }); tearDown(() => db.close()); test( 'addQuickVariety persists a variety that appears in the stream', () async { await repo.addQuickVariety( label: 'Grandma tomato', category: 'Solanaceae', ); final items = await repo.watchInventory().first; expect(items.single.label, 'Grandma tomato'); expect(items.single.category, 'Solanaceae'); }, ); test( 'a quantity creates a Lot and a photo creates an encrypted-BLOB Attachment', () async { final id = await repo.addQuickVariety( label: 'Maize', quantity: const Quantity(kind: QuantityKind.cob), photoBytes: Uint8List.fromList([1, 2, 3]), ); final lot = await db.select(db.lots).getSingle(); expect(lot.varietyId, id); expect(lot.quantityKind, 'cob'); final attachment = await db.select(db.attachments).getSingle(); expect(attachment.parentType, ParentType.variety); expect(attachment.parentId, id); expect(attachment.kind, AttachmentKind.photo); expect(attachment.bytes, [1, 2, 3]); }, ); test('watchInventory carries the first photo bytes for the avatar', () async { await repo.addQuickVariety( label: 'With photo', photoBytes: Uint8List.fromList([9, 8, 7]), ); await repo.addQuickVariety(label: 'No photo'); final items = await repo.watchInventory().first; expect(items.firstWhere((i) => i.label == 'With photo').photo, [9, 8, 7]); expect(items.firstWhere((i) => i.label == 'No photo').photo, isNull); }); test( 'stream is ordered by category then label and excludes soft-deleted rows', () async { await repo.addQuickVariety(label: 'Bean', category: 'Fabaceae'); await repo.addQuickVariety(label: 'Apple', category: 'Rosaceae'); await repo.addQuickVariety(label: 'Almond', category: 'Rosaceae'); final items = await repo.watchInventory().first; expect(items.map((i) => i.label), ['Bean', 'Almond', 'Apple']); }, ); test('writes stamp a monotonic HLC into updated_at', () async { await repo.addQuickVariety(label: 'First'); await repo.addQuickVariety(label: 'Second'); final rows = await db.select(db.varieties).get(); final stamps = rows.map((r) => r.updatedAt).toList()..sort(); // Distinct, parseable, ordered HLC stamps. expect(stamps.toSet().length, 2); expect(() => stamps.map(Hlc.parse).toList(), returnsNormally); }); group('labelRows', () { test('expands one entry per lot and resolves the locale common name', () async { final speciesRepo = newTestSpeciesRepository(db); await speciesRepo.seedBundled(const [ SpeciesSeed( scientificName: 'Beta vulgaris', family: 'Amaranthaceae', commonNames: { 'en': ['Chard'], 'es': ['Acelga'], }, ), ]); final speciesId = (await db.select(db.species).getSingle()).id; final v = await repo.addQuickVariety( label: 'Acelga de Perales', category: 'Amaranthaceae', ); await repo.linkSpecies(v, speciesId); await repo.addLot(varietyId: v, harvestYear: 2006, originName: 'Perales'); await repo.addLot(varietyId: v, harvestYear: 2008); final rows = await repo.labelRows({v}, languageCode: 'es'); expect(rows.length, 2); // one per lot expect(rows.first.commonName, 'Acelga'); // locale-picked, not 'Chard' expect(rows.first.scientificName, 'Beta vulgaris'); expect(rows.map((r) => r.harvestYear), [2006, 2008]); expect(rows.first.originName, 'Perales'); }); test('yields a single name-only entry for a variety with no lots', () async { final v = await repo.addQuickVariety(label: 'Alubia de Tolosa'); final rows = await repo.labelRows({v}, languageCode: 'en'); expect(rows.single.varietyLabel, 'Alubia de Tolosa'); expect(rows.single.harvestYear, isNull); expect(rows.single.commonName, isNull); }); test('includes only varieties in the selection', () async { final selected = await repo.addQuickVariety(label: 'Selected'); await repo.addQuickVariety(label: 'Not selected'); final rows = await repo.labelRows({selected}, languageCode: 'en'); expect(rows.map((r) => r.varietyLabel), ['Selected']); }); test('suggests copies from the latest container count', () async { final v = await repo.addQuickVariety(label: 'Tomate'); final withJars = await repo.addLot(varietyId: v, harvestYear: 2024); final noCheck = await repo.addLot(varietyId: v, harvestYear: 2025); // Two checks on the same lot: the most recent (3 jars) wins. await repo.addConditionCheck(lotId: withJars, checkedOn: 1, containerCount: 2); await repo.addConditionCheck(lotId: withJars, checkedOn: 2, containerCount: 3); final rows = await repo.labelRows({v}, languageCode: 'en'); final byYear = {for (final r in rows) r.harvestYear: r}; expect(byYear[2024]!.suggestedCopies, 3); // latest check expect(byYear[2025]!.suggestedCopies, 1); // no check → default 1 expect(noCheck, isNotEmpty); }); test('a variety with no lots suggests a single copy', () async { final v = await repo.addQuickVariety(label: 'Alubia'); final rows = await repo.labelRows({v}, languageCode: 'en'); expect(rows.single.suggestedCopies, 1); }); }); }