- SocialSession grows a ratings transport (fifth interface on the one shared channel). - PeerRatingCubit: count, locale-aware average, and circleCount — how many ratings come from your own circle (reuses the ego-centric trust rule), the signal that makes strangers' review-stuffing irrelevant. - Chat: slim rating strip under the trust banner; you can rate only someone you've talked with (soft anchor until the signed bilateral exchange form lands). Sheet with 5 stars + optional comment, pre-filled for editing, with a take-back action. - Offer detail: author's stars under their name, with 'N from people you know' when your circle rated them; nothing shown when unrated. - i18n ratings.* (en/es/pt/ast); tests for cubit and sheet.
176 lines
5.8 KiB
Dart
176 lines
5.8 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/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<Certification> certs = [];
|
|
|
|
void addCert(String issuer, String subject) => certs.add(Certification(
|
|
issuer: issuer,
|
|
subject: subject,
|
|
issuedAt: DateTime(2026),
|
|
));
|
|
|
|
@override
|
|
Future<List<Certification>> allCertifications() async => List.of(certs);
|
|
|
|
@override
|
|
Future<Set<String>> certifiersOf(String subjectPubkey) async => {
|
|
for (final c in certs)
|
|
if (c.subject == subjectPubkey) c.issuer,
|
|
};
|
|
|
|
@override
|
|
Future<void> certify({
|
|
required String subjectPubkey,
|
|
Duration validity = const Duration(days: 365),
|
|
String note = '',
|
|
}) async =>
|
|
addCert(selfId, subjectPubkey);
|
|
|
|
@override
|
|
Future<void> revoke({required String subjectPubkey}) async => certs
|
|
.removeWhere((c) => c.issuer == selfId && c.subject == subjectPubkey);
|
|
|
|
@override
|
|
Future<void> 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
|
|
RatingTransport get ratings => throw UnimplementedError();
|
|
@override
|
|
ProfileTransport get profile => throw UnimplementedError();
|
|
@override
|
|
SyncTransport get sync => throw UnimplementedError();
|
|
@override
|
|
Future<void> 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,
|
|
);
|
|
});
|
|
}
|