// Unit tests for the `friendlyError` mapper. These check the // duck-typed shape contract — `GrpcError` from package:grpc is // expected to expose `.code` (int), `.message` (String?), and // `.codeName` (String) — without taking grpc as a Studio // dependency. The fakes here mimic that shape. import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fai_studio/data/friendly_error.dart'; import 'package:fai_studio/l10n/app_localizations.dart'; class _FakeGrpcError { final int code; final String? message; final String codeName; _FakeGrpcError(this.code, this.codeName, [this.message]); } Future _loadL10n(Locale locale) async { return await AppLocalizations.delegate.load(locale); } void main() { WidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { // Force loading the gen-l10n delegate before tests so each // call to `_loadL10n` is fast. await _loadL10n(const Locale('en')); }); test('UNAVAILABLE maps to hub-not-reachable copy', () async { final l = await _loadL10n(const Locale('en')); final r = friendlyError(_FakeGrpcError(14, 'UNAVAILABLE', 'connection refused'), l); expect(r.headline, l.errUnavailable); expect(r.hint, l.errUnavailableHint); expect(r.detail, 'connection refused'); }); test('FAILED_PRECONDITION carries module detail in body', () async { final l = await _loadL10n(const Locale('en')); final r = friendlyError( _FakeGrpcError(9, 'FAILED_PRECONDITION', 'install error: no store entry for system.approval'), l, ); expect(r.headline, l.errFailedPrecondition); expect(r.hint, l.errFailedPreconditionHint); expect(r.detail, contains('system.approval')); }); test('NOT_FOUND has a recovery hint', () async { final l = await _loadL10n(const Locale('en')); final r = friendlyError(_FakeGrpcError(5, 'NOT_FOUND', 'no such module'), l); expect(r.headline, l.errNotFound); expect(r.hint, isNotNull); }); test('Unknown code falls back to codeName: message', () async { final l = await _loadL10n(const Locale('en')); final r = friendlyError(_FakeGrpcError(99, 'CUSTOM_CODE', 'something'), l); expect(r.headline, contains('CUSTOM_CODE')); expect(r.headline, contains('something')); expect(r.hint, isNull); }); test('Non-gRPC error renders toString as headline', () async { final l = await _loadL10n(const Locale('en')); final r = friendlyError(FormatException('bad url'), l); expect(r.headline, contains('bad url')); }); test('Locale switches the headline language', () async { final lDe = await _loadL10n(const Locale('de')); final r = friendlyError(_FakeGrpcError(14, 'UNAVAILABLE', 'connection refused'), lDe); expect(r.headline, lDe.errUnavailable); // Sanity: the DE headline really is different from the EN // one — guards against the test silently passing if l10n // regen returned EN for both. final lEn = await _loadL10n(const Locale('en')); expect(lDe.errUnavailable, isNot(equals(lEn.errUnavailable))); }); }