tane/apps/app_seeds/test/ui/plantare_propose_sheet_test.dart
vjrj f9c3dc018d feat(plantare): UI — propose from chat, review & sign on the ledger
The viral loop, made usable end-to-end:

- Propose sheet (plantare_propose_sheet.dart): from a chat with a peer
  (their key already in hand), pick your side, name the seed, choose what
  comes back (similar · non-GMO organic / work hours / other) and an
  optional return-by, then sign and send. Reached from the chat overflow
  menu; confirms "waiting for them to sign".
- Plantares screen: incoming proposals show Accept & sign / Decline right
  on the tile; every bilateral row wears a handshake badge — awaiting
  signature, signed by both, or declined. Accept falls back to a gentle
  "offline" nudge if the proposal isn't in hand yet.
- i18n: full en + es for the bilateral flow; other locales fall back to
  base per slang config.

Wire-up only touches human copy (no jargon) and stays behind the
optional PlantareService, so inventory-only builds are unaffected.

Tests: propose sheet drives a real sign+send; the ledger renders the
signed/awaiting badges. Fake SocialSession in your_people test gains the
new transport getter. Widget tests targeted + timed. All green.
2026-07-14 11:23:09 +02:00

100 lines
3.4 KiB
Dart

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/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 = <PlantarePledge>[];
@override
Future<void> propose(PlantarePledge pledge) async => proposed.add(pledge);
@override
Future<void> accept(PlantarePledge pledge) async {}
@override
Future<void> decline({
required String toPubkey,
required String pledgeId,
String reason = '',
}) async {}
@override
Stream<PlantareEnvelope> incoming() => const Stream.empty();
@override
Future<void> close() async {}
}
void main() {
testWidgets('filling the seed and sending proposes a signed pledge',
(tester) async {
LocaleSettings.setLocaleSync(AppLocale.en);
final db = newTestDatabase();
addTearDown(db.close);
final repo = newTestRepository(db);
final tx = _CapturingTransport();
final service = PlantareService(
repo: repo,
selfPubkey: 'a' * 64,
selfSecretKey:
'0000000000000000000000000000000000000000000000000000000000000001',
)..bindTransport(tx);
await tester.pumpWidget(
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,
peerPubkey: 'b' * 64,
peerName: 'Ana',
),
child: const Text('open'),
),
),
),
),
),
),
);
await tester.tap(find.text('open'));
await tester.pumpAndSettle();
expect(find.byKey(const Key('propose.seed')), findsOneWidget);
// The seed name is the one required field.
await tester.enterText(
find.byKey(const Key('propose.seed')), 'Tomate rosa');
await tester.pump();
expect(find.text('Tomate rosa'), findsOneWidget);
await tester.ensureVisible(find.byKey(const Key('propose.send')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('propose.send')));
await tester.pumpAndSettle();
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.
final row = await repo.plantareByPledgeId(tx.proposed.single.pledgeId);
expect(row!.remoteState, PlantareRemoteState.proposed);
await service.stop();
});
}