import 'package:commons_core/commons_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tane/data/variety_repository.dart'; import 'package:tane/db/database.dart'; import 'package:tane/db/enums.dart'; import 'package:tane/i18n/strings.g.dart'; import 'package:tane/services/plantare_service.dart'; import 'package:tane/ui/plantare_propose_sheet.dart'; import '../support/test_support.dart'; /// A records-only transport so the sheet can be driven without a relay. class _CapturingTransport implements PlantareTransport { final proposed = []; @override Future propose(PlantarePledge pledge) async => proposed.add(pledge); @override Future accept(PlantarePledge pledge) async {} @override Future decline({ required String toPubkey, required String pledgeId, String reason = '', }) async {} @override Stream incoming() => const Stream.empty(); @override Future close() async {} } void main() { // NEVER pumpAndSettle these: the sheet subscribes to a live Drift stream // (watchInventory) and a focused TextField blinks its cursor forever, so // pumpAndSettle never returns (see CLAUDE.md testing note). Use a tall surface // so the whole sheet fits without scrolling, and bounded pumps only. PlantareService serviceFor(AppDatabase db, _CapturingTransport tx) => PlantareService( repo: newTestRepository(db), selfPubkey: 'a' * 64, selfSecretKey: '0000000000000000000000000000000000000000000000000000000000000001', )..bindTransport(tx); Widget host(PlantareService service, VarietyRepository repo) => TranslationProvider( child: MaterialApp( locale: const Locale('en'), supportedLocales: AppLocaleUtils.supportedLocales, localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], home: Builder( builder: (context) => Scaffold( body: Center( child: ElevatedButton( onPressed: () => showProposePlantareSheet( context, service: service, repository: repo, peerPubkey: 'b' * 64, peerName: 'Ana', ), child: const Text('open'), ), ), ), ), ), ); Future useTallSurface(WidgetTester tester) async { tester.view.physicalSize = const Size(1400, 3200); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); } /// Opens the sheet and lets the slide-in + inventory load settle (bounded). Future openSheet(WidgetTester tester) async { await tester.tap(find.text('open')); await tester.pump(); // start the sheet route await tester.pump(const Duration(milliseconds: 400)); // slide-in + load await tester.pump(const Duration(milliseconds: 200)); } /// Taps send and lets the async propose (DB write + sign + pop) drain. Future tapSend(WidgetTester tester) async { await tester.tap(find.byKey(const Key('propose.send'))); for (var i = 0; i < 6; i++) { await tester.pump(const Duration(milliseconds: 100)); } } testWidgets('a new seed name proposes a signed pledge linked to a Variety', (tester) async { LocaleSettings.setLocaleSync(AppLocale.en); await useTallSurface(tester); final db = newTestDatabase(); addTearDown(db.close); final repo = newTestRepository(db); final tx = _CapturingTransport(); final service = serviceFor(db, tx); await tester.pumpWidget(host(service, repo)); await openSheet(tester); expect(find.byKey(const Key('propose.seed')), findsOneWidget); await tester.enterText( find.byKey(const Key('propose.seed')), 'Tomate rosa'); await tester.pump(); await tapSend(tester); expect(tx.proposed, hasLength(1)); expect(tx.proposed.single.label, 'Tomate rosa'); // Default direction is owedToMe → I'm the creditor and signed my stub. expect(tx.proposed.single.creditorSignature, isNotNull); expect(tx.proposed.single.debtorSignature, isNull); // The local row is recorded as proposed AND tied to a real Variety — a new // name created one on send. final row = await repo.plantareByPledgeId(tx.proposed.single.pledgeId); expect(row!.remoteState, PlantareRemoteState.proposed); expect(row.varietyId, isNotNull); final linked = await repo.varietyLabels(); expect(linked.map((v) => v.id), contains(row.varietyId)); await service.stop(); }); testWidgets('typing an existing seed links the pledge to that variety', (tester) async { LocaleSettings.setLocaleSync(AppLocale.en); await useTallSurface(tester); final db = newTestDatabase(); addTearDown(db.close); final repo = newTestRepository(db); final existingId = await repo.addQuickVariety(label: 'Maíz rojo'); final tx = _CapturingTransport(); final service = serviceFor(db, tx); await tester.pumpWidget(host(service, repo)); await openSheet(tester); // A matching name resolves to the existing variety — no duplicate created. await tester.enterText(find.byKey(const Key('propose.seed')), 'Maíz rojo'); await tester.pump(); await tapSend(tester); expect(tx.proposed, hasLength(1)); final row = await repo.plantareByPledgeId(tx.proposed.single.pledgeId); expect(row!.varietyId, existingId); // linked, not duplicated expect((await repo.varietyLabels()).length, 1); await service.stop(); }); }