style: dart format reflow (no behavior change)
This commit is contained in:
parent
cf5d302cc1
commit
e3ec855630
17 changed files with 197 additions and 139 deletions
|
|
@ -267,7 +267,10 @@ class InventoryCsvCodec {
|
|||
);
|
||||
}
|
||||
|
||||
CsvImportLot? _parseLot(List<String> row, String? Function(List<String>, String) cell) {
|
||||
CsvImportLot? _parseLot(
|
||||
List<String> row,
|
||||
String? Function(List<String>, String) cell,
|
||||
) {
|
||||
final typeCell = cell(row, 'lot_type');
|
||||
final year = cell(row, 'harvest_year');
|
||||
final month = cell(row, 'harvest_month');
|
||||
|
|
@ -452,7 +455,8 @@ class InventoryCsvCodec {
|
|||
i++;
|
||||
}
|
||||
row.add(field.toString());
|
||||
final trailingEmpty = rows.isNotEmpty && row.length == 1 && row.first.isEmpty;
|
||||
final trailingEmpty =
|
||||
rows.isNotEmpty && row.length == 1 && row.first.isEmpty;
|
||||
if (!trailingEmpty) rows.add(row);
|
||||
return rows;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,11 +51,7 @@ class InventorySnapshot {
|
|||
/// newer incoming version (LWW), and rows skipped (already present and at
|
||||
/// least as recent, or not decodable).
|
||||
class ImportSummary {
|
||||
const ImportSummary({
|
||||
this.inserted = 0,
|
||||
this.updated = 0,
|
||||
this.skipped = 0,
|
||||
});
|
||||
const ImportSummary({this.inserted = 0, this.updated = 0, this.skipped = 0});
|
||||
|
||||
final int inserted;
|
||||
final int updated;
|
||||
|
|
|
|||
|
|
@ -65,13 +65,11 @@ class SpeciesRepository {
|
|||
if (existing != null) {
|
||||
// Backfill bundled reference data added after the row was seeded.
|
||||
if (existing.viabilityYears == null && seed.viabilityYears != null) {
|
||||
await (_db.update(_db.species)
|
||||
..where((s) => s.id.equals(existing.id)))
|
||||
.write(
|
||||
SpeciesCompanion(
|
||||
viabilityYears: Value(seed.viabilityYears),
|
||||
),
|
||||
);
|
||||
await (_db.update(
|
||||
_db.species,
|
||||
)..where((s) => s.id.equals(existing.id))).write(
|
||||
SpeciesCompanion(viabilityYears: Value(seed.viabilityYears)),
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,9 +77,24 @@ class TesseractLabelExtractor implements LabelTextExtractor {
|
|||
/// Words in an hOCR document ignored as packet boilerplate (lower-cased,
|
||||
/// accent-stripped compare). Kept deliberately small and generic.
|
||||
const _boilerplate = <String>{
|
||||
'organic', 'product', 'products', 'herb', 'herbs', 'seed', 'seeds',
|
||||
'bio', 'net', 'weight', 'wt', 'www', 'com', 'gr', 'grs', 'grams',
|
||||
'variety', 'quality',
|
||||
'organic',
|
||||
'product',
|
||||
'products',
|
||||
'herb',
|
||||
'herbs',
|
||||
'seed',
|
||||
'seeds',
|
||||
'bio',
|
||||
'net',
|
||||
'weight',
|
||||
'wt',
|
||||
'www',
|
||||
'com',
|
||||
'gr',
|
||||
'grs',
|
||||
'grams',
|
||||
'variety',
|
||||
'quality',
|
||||
};
|
||||
|
||||
/// Selects the variety name from Tesseract [hocr]: keep the words whose glyph
|
||||
|
|
@ -112,7 +127,13 @@ HocrLabel? readHocrLabel(String hocr, {int minConfidence = 20}) {
|
|||
final text = _unescape(m.group(6)!).trim();
|
||||
if (text.isEmpty) continue;
|
||||
words.add(
|
||||
_Word(x0: x0, y0: y0, height: y1 - y0, text: text, confidence: confidence),
|
||||
_Word(
|
||||
x0: x0,
|
||||
y0: y0,
|
||||
height: y1 - y0,
|
||||
text: text,
|
||||
confidence: confidence,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (words.isEmpty) return null;
|
||||
|
|
|
|||
|
|
@ -130,6 +130,8 @@ class QuickAddCubit extends Cubit<QuickAddState> {
|
|||
quantity: state.quantity,
|
||||
photoBytes: state.photoBytes,
|
||||
);
|
||||
emit(QuickAddState(lotType: state.lotType, addedCount: state.addedCount + 1));
|
||||
emit(
|
||||
QuickAddState(lotType: state.lotType, addedCount: state.addedCount + 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,10 +76,8 @@ class _TriageSheet extends StatelessWidget {
|
|||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: drafts.length,
|
||||
itemBuilder: (context, i) => _DraftTile(
|
||||
draft: drafts[i],
|
||||
repository: repository,
|
||||
),
|
||||
itemBuilder: (context, i) =>
|
||||
_DraftTile(draft: drafts[i], repository: repository),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -18,35 +18,39 @@ void main() {
|
|||
|
||||
Uint8List photo(int seed) => Uint8List.fromList([seed, seed, seed]);
|
||||
|
||||
test('a captured draft is hidden from the list but shown in the tray',
|
||||
() async {
|
||||
final id = await repo.addDraftVariety(photo(1));
|
||||
test(
|
||||
'a captured draft is hidden from the list but shown in the tray',
|
||||
() async {
|
||||
final id = await repo.addDraftVariety(photo(1));
|
||||
|
||||
// Not in the main inventory…
|
||||
expect(await repo.watchInventory().first, isEmpty);
|
||||
// Not in the main inventory…
|
||||
expect(await repo.watchInventory().first, isEmpty);
|
||||
|
||||
// …but in the "to catalogue" tray, with its photo and no name yet.
|
||||
final drafts = await repo.watchDrafts().first;
|
||||
expect(drafts, hasLength(1));
|
||||
expect(drafts.single.id, id);
|
||||
expect(drafts.single.isDraft, isTrue);
|
||||
expect(drafts.single.label, '');
|
||||
expect(drafts.single.photo, isNotNull);
|
||||
});
|
||||
// …but in the "to catalogue" tray, with its photo and no name yet.
|
||||
final drafts = await repo.watchDrafts().first;
|
||||
expect(drafts, hasLength(1));
|
||||
expect(drafts.single.id, id);
|
||||
expect(drafts.single.isDraft, isTrue);
|
||||
expect(drafts.single.label, '');
|
||||
expect(drafts.single.photo, isNotNull);
|
||||
},
|
||||
);
|
||||
|
||||
test('naming a draft promotes it into the list and out of the tray',
|
||||
() async {
|
||||
final id = await repo.addDraftVariety(photo(2));
|
||||
await repo.nameDraft(id, 'Grandma tomato');
|
||||
test(
|
||||
'naming a draft promotes it into the list and out of the tray',
|
||||
() async {
|
||||
final id = await repo.addDraftVariety(photo(2));
|
||||
await repo.nameDraft(id, 'Grandma tomato');
|
||||
|
||||
expect(await repo.watchDrafts().first, isEmpty);
|
||||
final list = await repo.watchInventory().first;
|
||||
expect(list.single.id, id);
|
||||
expect(list.single.label, 'Grandma tomato');
|
||||
expect(list.single.isDraft, isFalse);
|
||||
// The captured photo survived the promotion.
|
||||
expect(list.single.photo, isNotNull);
|
||||
});
|
||||
expect(await repo.watchDrafts().first, isEmpty);
|
||||
final list = await repo.watchInventory().first;
|
||||
expect(list.single.id, id);
|
||||
expect(list.single.label, 'Grandma tomato');
|
||||
expect(list.single.isDraft, isFalse);
|
||||
// The captured photo survived the promotion.
|
||||
expect(list.single.photo, isNotNull);
|
||||
},
|
||||
);
|
||||
|
||||
test('the tray is ordered newest-capture first', () async {
|
||||
final first = await repo.addDraftVariety(photo(1));
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ void main() {
|
|||
await newTestSpeciesRepository(db).seedBundled(const [tomato]);
|
||||
final repo = newTestRepository(db);
|
||||
|
||||
const csv = 'variety_label,category,species_scientific_name,'
|
||||
const csv =
|
||||
'variety_label,category,species_scientific_name,'
|
||||
'cultivar_name,lot_type,harvest_year,quantity_kind,quantity_precise\r\n'
|
||||
'Grandma tomato,Solanaceae,Solanum lycopersicum,Marmande,seed,2024,cob,3\r\n';
|
||||
final summary = await repo.importCsv(codec.decode(csv));
|
||||
|
|
@ -44,7 +45,8 @@ void main() {
|
|||
|
||||
test('an unmatched species name leaves speciesId null', () async {
|
||||
final repo = newTestRepository(db); // no bundled catalog
|
||||
const csv = 'variety_label,species_scientific_name\r\n'
|
||||
const csv =
|
||||
'variety_label,species_scientific_name\r\n'
|
||||
'Mystery bean,Phaseolus nonexistus\r\n';
|
||||
await repo.importCsv(codec.decode(csv));
|
||||
final variety = await db.select(db.varieties).getSingle();
|
||||
|
|
@ -60,19 +62,26 @@ void main() {
|
|||
expect(varieties, hasLength(2));
|
||||
});
|
||||
|
||||
test('stores vernacular names and links attached to the new variety', () async {
|
||||
final repo = newTestRepository(db);
|
||||
const csv = 'variety_label,vernacular_names,external_links\r\n'
|
||||
'Tomato,"tomate (es); granny tomato","Wiki|https://example.org"\r\n';
|
||||
await repo.importCsv(codec.decode(csv));
|
||||
test(
|
||||
'stores vernacular names and links attached to the new variety',
|
||||
() async {
|
||||
final repo = newTestRepository(db);
|
||||
const csv =
|
||||
'variety_label,vernacular_names,external_links\r\n'
|
||||
'Tomato,"tomate (es); granny tomato","Wiki|https://example.org"\r\n';
|
||||
await repo.importCsv(codec.decode(csv));
|
||||
|
||||
final variety = await db.select(db.varieties).getSingle();
|
||||
final names = await db.select(db.varietyVernacularNames).get();
|
||||
expect(names.map((n) => n.name), containsAll(['tomate', 'granny tomato']));
|
||||
expect(names.every((n) => n.varietyId == variety.id), isTrue);
|
||||
final variety = await db.select(db.varieties).getSingle();
|
||||
final names = await db.select(db.varietyVernacularNames).get();
|
||||
expect(
|
||||
names.map((n) => n.name),
|
||||
containsAll(['tomate', 'granny tomato']),
|
||||
);
|
||||
expect(names.every((n) => n.varietyId == variety.id), isTrue);
|
||||
|
||||
final links = await db.select(db.externalLinks).get();
|
||||
expect(links.single.url, 'https://example.org');
|
||||
expect(links.single.parentId, variety.id);
|
||||
});
|
||||
final links = await db.select(db.externalLinks).get();
|
||||
expect(links.single.url, 'https://example.org');
|
||||
expect(links.single.parentId, variety.id);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,14 +50,16 @@ void main() {
|
|||
seedbankId: null,
|
||||
);
|
||||
|
||||
List<String> rowsOf(String csv) =>
|
||||
csv.trimRight().split('\r\n');
|
||||
List<String> rowsOf(String csv) => csv.trimRight().split('\r\n');
|
||||
|
||||
test('emits a header and one row per lot', () {
|
||||
final csv = codec.encode(
|
||||
InventorySnapshot(
|
||||
varieties: [variety()],
|
||||
lots: [lot(), lot(id: 'lot-2')],
|
||||
lots: [
|
||||
lot(),
|
||||
lot(id: 'lot-2'),
|
||||
],
|
||||
speciesNamesById: const {'sp-1': 'Solanum lycopersicum'},
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ void main() {
|
|||
});
|
||||
|
||||
test('skips rows without a variety_label', () {
|
||||
final csv = 'variety_label,category\r\n,Solanaceae\r\nTomato,Solanaceae\r\n';
|
||||
final csv =
|
||||
'variety_label,category\r\n,Solanaceae\r\nTomato,Solanaceae\r\n';
|
||||
final result = codec.decode(csv);
|
||||
expect(result.skippedRows, 1);
|
||||
expect(result.varieties, hasLength(1));
|
||||
|
|
@ -29,7 +30,8 @@ void main() {
|
|||
});
|
||||
|
||||
test('maps columns by header name regardless of order', () {
|
||||
final csv = 'category,variety_label,harvest_year,lot_type\r\n'
|
||||
final csv =
|
||||
'category,variety_label,harvest_year,lot_type\r\n'
|
||||
'Solanaceae,Tomato,2024,seed\r\n';
|
||||
final v = codec.decode(csv).varieties.single;
|
||||
expect(v.label, 'Tomato');
|
||||
|
|
@ -46,7 +48,8 @@ void main() {
|
|||
|
||||
test('unknown enums fall back: lot_type→seed, offer_status→private, '
|
||||
'quantity_kind dropped', () {
|
||||
final csv = 'variety_label,lot_type,offer_status,quantity_kind\r\n'
|
||||
final csv =
|
||||
'variety_label,lot_type,offer_status,quantity_kind\r\n'
|
||||
'Tomato,spaceship,gift,truckload\r\n';
|
||||
final lot = codec.decode(csv).varieties.single.lots.single;
|
||||
expect(lot.type, LotType.seed);
|
||||
|
|
@ -55,7 +58,8 @@ void main() {
|
|||
});
|
||||
|
||||
test('keeps a valid quantity_kind and parses precise/label', () {
|
||||
final csv = 'variety_label,quantity_kind,quantity_precise,quantity_label\r\n'
|
||||
final csv =
|
||||
'variety_label,quantity_kind,quantity_precise,quantity_label\r\n'
|
||||
'Maize,cob,3,a handful\r\n';
|
||||
final lot = codec.decode(csv).varieties.single.lots.single;
|
||||
expect(lot.quantityKind, 'cob');
|
||||
|
|
@ -63,16 +67,20 @@ void main() {
|
|||
expect(lot.quantityLabel, 'a handful');
|
||||
});
|
||||
|
||||
test('rows sharing a variety_id collapse into one variety with many lots', () {
|
||||
final csv = 'variety_id,variety_label,lot_type,harvest_year\r\n'
|
||||
'v1,Tomato,seed,2023\r\n'
|
||||
'v1,Tomato,seed,2024\r\n'
|
||||
'v2,Carrot,seed,2024\r\n';
|
||||
final result = codec.decode(csv);
|
||||
expect(result.varieties, hasLength(2));
|
||||
final tomato = result.varieties.firstWhere((v) => v.label == 'Tomato');
|
||||
expect(tomato.lots.map((l) => l.harvestYear), [2023, 2024]);
|
||||
});
|
||||
test(
|
||||
'rows sharing a variety_id collapse into one variety with many lots',
|
||||
() {
|
||||
final csv =
|
||||
'variety_id,variety_label,lot_type,harvest_year\r\n'
|
||||
'v1,Tomato,seed,2023\r\n'
|
||||
'v1,Tomato,seed,2024\r\n'
|
||||
'v2,Carrot,seed,2024\r\n';
|
||||
final result = codec.decode(csv);
|
||||
expect(result.varieties, hasLength(2));
|
||||
final tomato = result.varieties.firstWhere((v) => v.label == 'Tomato');
|
||||
expect(tomato.lots.map((l) => l.harvestYear), [2023, 2024]);
|
||||
},
|
||||
);
|
||||
|
||||
test('rows without a variety_id each become their own variety', () {
|
||||
final csv = 'variety_label,lot_type\r\nTomato,seed\r\nTomato,plant\r\n';
|
||||
|
|
@ -81,7 +89,8 @@ void main() {
|
|||
});
|
||||
|
||||
test('parses quoted fields with commas, doubled quotes and newlines', () {
|
||||
final csv = 'variety_label,variety_notes\r\n'
|
||||
final csv =
|
||||
'variety_label,variety_notes\r\n'
|
||||
'"Bean, ""the best""","line one\nline two"\r\n';
|
||||
final v = codec.decode(csv).varieties.single;
|
||||
expect(v.label, 'Bean, "the best"');
|
||||
|
|
@ -89,7 +98,8 @@ void main() {
|
|||
});
|
||||
|
||||
test('splits vernacular names and links back out', () {
|
||||
final csv = 'variety_label,vernacular_names,external_links\r\n'
|
||||
final csv =
|
||||
'variety_label,vernacular_names,external_links\r\n'
|
||||
'Tomato,"tomate (es); granny tomato","Wiki|https://example.org"\r\n';
|
||||
final v = codec.decode(csv).varieties.single;
|
||||
expect(v.vernacularNames.map((n) => n.name), ['tomate', 'granny tomato']);
|
||||
|
|
@ -152,7 +162,10 @@ void main() {
|
|||
final csv = codec.encode(
|
||||
InventorySnapshot(
|
||||
varieties: [variety()],
|
||||
lots: [lot(), lot(id: 'lot-2')],
|
||||
lots: [
|
||||
lot(),
|
||||
lot(id: 'lot-2'),
|
||||
],
|
||||
speciesNamesById: const {'sp-1': 'Solanum lycopersicum'},
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -170,7 +170,8 @@ void main() {
|
|||
test('unknown fields are ignored (forward compatibility, §5.2)', () {
|
||||
final root = jsonDecode(codec.encode(snapshot)) as Map<String, dynamic>;
|
||||
root['someFutureSection'] = {'x': 1};
|
||||
((root['varieties'] as List).single as Map<String, dynamic>)['futureField'] =
|
||||
((root['varieties'] as List).single
|
||||
as Map<String, dynamic>)['futureField'] =
|
||||
'ignored';
|
||||
final decoded = codec.decode(jsonEncode(root));
|
||||
expect(decoded.varieties, [variety]);
|
||||
|
|
|
|||
|
|
@ -114,8 +114,12 @@ void main() {
|
|||
final sourceRows = await source.exportInventory();
|
||||
final targetRows = await target.exportInventory();
|
||||
expect(
|
||||
targetRows.varieties.map((v) => v.copyWith(speciesId: const Value(null))),
|
||||
sourceRows.varieties.map((v) => v.copyWith(speciesId: const Value(null))),
|
||||
targetRows.varieties.map(
|
||||
(v) => v.copyWith(speciesId: const Value(null)),
|
||||
),
|
||||
sourceRows.varieties.map(
|
||||
(v) => v.copyWith(speciesId: const Value(null)),
|
||||
),
|
||||
);
|
||||
expect(targetRows.lots, sourceRows.lots);
|
||||
expect(targetRows.vernacularNames, sourceRows.vernacularNames);
|
||||
|
|
@ -130,9 +134,9 @@ void main() {
|
|||
expect(targetRows.varieties.single.speciesId, targetSpecies.id);
|
||||
|
||||
// Photo bytes and cover order survived.
|
||||
final detail = await target.watchVariety(
|
||||
targetRows.varieties.single.id,
|
||||
).first;
|
||||
final detail = await target
|
||||
.watchVariety(targetRows.varieties.single.id)
|
||||
.first;
|
||||
expect(detail!.photos.first.bytes, [40, 50, 60]);
|
||||
expect(detail.photos.last.bytes, [10, 20, 30]);
|
||||
},
|
||||
|
|
@ -229,10 +233,7 @@ void main() {
|
|||
final written = await (targetDb.select(
|
||||
targetDb.varieties,
|
||||
)..where((v) => v.label.equals('Written after import'))).getSingle();
|
||||
expect(
|
||||
Hlc.parse(written.updatedAt).compareTo(maxImported),
|
||||
greaterThan(0),
|
||||
);
|
||||
expect(Hlc.parse(written.updatedAt).compareTo(maxImported), greaterThan(0));
|
||||
});
|
||||
|
||||
test('unmatched species leaves speciesId null instead of dangling', () async {
|
||||
|
|
|
|||
|
|
@ -49,13 +49,15 @@ void main() {
|
|||
await db.close();
|
||||
});
|
||||
|
||||
test('upgrades v5 → v8 (adds Variety.isDraft) and matches the fresh schema',
|
||||
() async {
|
||||
final connection = await verifier.startAt(5);
|
||||
final db = AppDatabase(connection);
|
||||
await verifier.migrateAndValidate(db, 8);
|
||||
await db.close();
|
||||
});
|
||||
test(
|
||||
'upgrades v5 → v8 (adds Variety.isDraft) and matches the fresh schema',
|
||||
() async {
|
||||
final connection = await verifier.startAt(5);
|
||||
final db = AppDatabase(connection);
|
||||
await verifier.migrateAndValidate(db, 8);
|
||||
await db.close();
|
||||
},
|
||||
);
|
||||
|
||||
test('upgrades v6 → v8 (adds Variety.isOrganic, Species.viabilityYears) and '
|
||||
'matches the fresh schema', () async {
|
||||
|
|
|
|||
|
|
@ -12,21 +12,24 @@ String page(List<String> words) =>
|
|||
|
||||
void main() {
|
||||
group('pickLabelFromHocr picks the largest print, not the longest line', () {
|
||||
test('Aubergine Black Beauty packet → the variety name, not boilerplate', () {
|
||||
// "Organic Product" (14 letters) is longer than "Black Beauty" (11) but
|
||||
// printed smaller — the old longest-line heuristic wrongly picked it.
|
||||
final hocr = page([
|
||||
word('BonPrime', 120, 300, 300, 335), // brand, tiny
|
||||
word('Organic', 130, 1050, 320, 1095), // boilerplate, small
|
||||
word('Product', 130, 1100, 340, 1145),
|
||||
word('AUBERGINE', 380, 1180, 900, 1300), // the title (tallest)
|
||||
word('BLACK', 380, 1330, 620, 1410),
|
||||
word('BEAUTY', 640, 1330, 900, 1410),
|
||||
word('HERB', 430, 1470, 560, 1510), // boilerplate, small
|
||||
word('SEEDS', 580, 1470, 740, 1510),
|
||||
]);
|
||||
expect(pickLabelFromHocr(hocr), 'Aubergine Black Beauty');
|
||||
});
|
||||
test(
|
||||
'Aubergine Black Beauty packet → the variety name, not boilerplate',
|
||||
() {
|
||||
// "Organic Product" (14 letters) is longer than "Black Beauty" (11) but
|
||||
// printed smaller — the old longest-line heuristic wrongly picked it.
|
||||
final hocr = page([
|
||||
word('BonPrime', 120, 300, 300, 335), // brand, tiny
|
||||
word('Organic', 130, 1050, 320, 1095), // boilerplate, small
|
||||
word('Product', 130, 1100, 340, 1145),
|
||||
word('AUBERGINE', 380, 1180, 900, 1300), // the title (tallest)
|
||||
word('BLACK', 380, 1330, 620, 1410),
|
||||
word('BEAUTY', 640, 1330, 900, 1410),
|
||||
word('HERB', 430, 1470, 560, 1510), // boilerplate, small
|
||||
word('SEEDS', 580, 1470, 740, 1510),
|
||||
]);
|
||||
expect(pickLabelFromHocr(hocr), 'Aubergine Black Beauty');
|
||||
},
|
||||
);
|
||||
|
||||
test('Cucumber Supermarketer packet → the variety name', () {
|
||||
final hocr = page([
|
||||
|
|
|
|||
|
|
@ -63,10 +63,7 @@ void main() {
|
|||
|
||||
// Explanation text and kanji are shown above the fold.
|
||||
expect(find.text('種まき'), findsOneWidget);
|
||||
expect(
|
||||
find.textContaining('helps people and collectives'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.textContaining('helps people and collectives'), findsOneWidget);
|
||||
expect(find.textContaining('digital Plantare'), findsOneWidget);
|
||||
|
||||
// License and version live below the fold; scroll them into view.
|
||||
|
|
|
|||
|
|
@ -48,27 +48,30 @@ void main() {
|
|||
0x42, 0x60, 0x82,
|
||||
]);
|
||||
|
||||
testWidgets('a supported engine offers a suggestion that pre-fills the name',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
_host(
|
||||
NameDraftDialog(
|
||||
photo: photo,
|
||||
extractor: _FakeExtractor(result: 'Aubergine Black Beauty'),
|
||||
testWidgets(
|
||||
'a supported engine offers a suggestion that pre-fills the name',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
_host(
|
||||
NameDraftDialog(
|
||||
photo: photo,
|
||||
extractor: _FakeExtractor(result: 'Aubergine Black Beauty'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('draft.suggestFromPhoto')), findsOneWidget);
|
||||
await tester.tap(find.byKey(const Key('draft.suggestFromPhoto')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const Key('draft.suggestFromPhoto')), findsOneWidget);
|
||||
await tester.tap(find.byKey(const Key('draft.suggestFromPhoto')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Aubergine Black Beauty'), findsOneWidget);
|
||||
});
|
||||
expect(find.text('Aubergine Black Beauty'), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('an unsupported engine hides the suggestion button',
|
||||
(tester) async {
|
||||
testWidgets('an unsupported engine hides the suggestion button', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
_host(
|
||||
NameDraftDialog(
|
||||
|
|
@ -81,8 +84,9 @@ void main() {
|
|||
expect(find.byKey(const Key('draft.suggestFromPhoto')), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('a null suggestion leaves the field untouched (no crash)',
|
||||
(tester) async {
|
||||
testWidgets('a null suggestion leaves the field untouched (no crash)', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
_host(NameDraftDialog(photo: photo, extractor: _FakeExtractor())),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,10 @@ void main() {
|
|||
// The two captures are in the tray, not the main list.
|
||||
expect(find.byKey(const Key('inventory.triageBanner')), findsOneWidget);
|
||||
expect(find.text('2 to catalogue'), findsOneWidget);
|
||||
expect(find.text('No seeds yet. Tap + to add your first.'), findsOneWidget);
|
||||
expect(
|
||||
find.text('No seeds yet. Tap + to add your first.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
// Open the tray and name the first draft.
|
||||
await tester.tap(find.byKey(const Key('inventory.triageBanner')));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue