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/i18n/strings.g.dart'; import 'package:tane/services/profile_cache.dart'; import 'package:tane/services/social_connection.dart'; import 'package:tane/services/social_service.dart'; import 'package:tane/services/social_settings.dart'; import 'package:tane/ui/your_people_screen.dart'; import '../support/test_support.dart'; /// In-memory [TrustTransport] with a mutable certification graph. class FakeTrustTransport implements TrustTransport { FakeTrustTransport(this.selfId); final String selfId; final List certs = []; void addCert(String issuer, String subject) => certs.add(Certification( issuer: issuer, subject: subject, issuedAt: DateTime(2026), )); @override Future> allCertifications() async => List.of(certs); @override Future> certifiersOf(String subjectPubkey) async => { for (final c in certs) if (c.subject == subjectPubkey) c.issuer, }; @override Future certify({ required String subjectPubkey, Duration validity = const Duration(days: 365), String note = '', }) async => addCert(selfId, subjectPubkey); @override Future revoke({required String subjectPubkey}) async => certs .removeWhere((c) => c.issuer == selfId && c.subject == subjectPubkey); @override Future close() async {} } /// A [SocialSession] stand-in that only carries the trust transport — the /// screen under test never touches the other transports. class FakeSession implements SocialSession { FakeSession(this.trust); @override final TrustTransport trust; @override OfferTransport get offers => throw UnimplementedError(); @override MessageTransport get messages => throw UnimplementedError(); @override ProfileTransport get profile => throw UnimplementedError(); @override SyncTransport get sync => throw UnimplementedError(); @override Future close() async {} } void main() { const seedHex = '000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'; final peerA = 'aa' * 32; final peerB = 'bb' * 32; late SocialService social; setUp(() async { social = await SocialService.fromRootSeedHex(seedHex); LocaleSettings.setLocaleSync(AppLocale.en); }); Widget app(SocialConnection connection, {ProfileCache? cache}) => TranslationProvider( child: MaterialApp( locale: AppLocale.en.flutterLocale, supportedLocales: AppLocaleUtils.supportedLocales, localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], home: YourPeopleScreen( social: social, connection: connection, profileCache: cache, ), ), ); SocialConnection online(TrustTransport trust) => SocialConnection( social: social, settings: SocialSettings(InMemorySecretStore()), open: (_) async => FakeSession(trust) as SocialSession, ); testWidgets('offline shows the friendly note', (tester) async { final settings = SocialSettings(InMemorySecretStore()); await settings.setRelayUrls(const []); // offline: don't hit the network final connection = SocialConnection(social: social, settings: settings); await tester.pumpWidget(app(connection)); await tester.pumpAndSettle(); expect( find.text("You're offline — try again when you're connected"), findsOneWidget, ); }); testWidgets('lists who you vouch for and who vouches for you, with names', (tester) async { final trust = FakeTrustTransport(social.publicKeyHex) ..addCert(social.publicKeyHex, peerA) // you vouch for A ..addCert(peerB, social.publicKeyHex) // B vouches for you ..addCert(peerA, peerB); // unrelated edge, must not show final cache = ProfileCache(InMemorySecretStore()); await cache.setName(peerA, 'Alice'); await tester.pumpWidget(app(online(trust), cache: cache)); await tester.pumpAndSettle(); expect(find.text('You vouch for'), findsOneWidget); expect(find.text('They vouch for you'), findsOneWidget); expect(find.text('Alice'), findsOneWidget); // cached name wins expect(find.text(shortPubkey(peerB)), findsOneWidget); // fallback expect(find.byKey(Key('yourPeople.given.$peerA')), findsOneWidget); expect(find.byKey(Key('yourPeople.received.$peerB')), findsOneWidget); }); testWidgets('empty states show gentle notes', (tester) async { await tester .pumpWidget(app(online(FakeTrustTransport(social.publicKeyHex)))); await tester.pumpAndSettle(); expect( find.textContaining("You don't vouch for anyone yet"), findsOneWidget, ); expect(find.text('No one vouches for you yet'), findsOneWidget); }); testWidgets('revoking removes the vouch after confirming', (tester) async { final trust = FakeTrustTransport(social.publicKeyHex) ..addCert(social.publicKeyHex, peerA); await tester.pumpWidget(app(online(trust))); await tester.pumpAndSettle(); await tester.tap(find.text('Stop vouching')); await tester.pumpAndSettle(); expect(find.text('Stop vouching for this person?'), findsOneWidget); await tester.tap(find.text('Stop vouching').last); // dialog action await tester.pumpAndSettle(); expect(trust.certs, isEmpty); expect( find.textContaining("You don't vouch for anyone yet"), findsOneWidget, ); }); }