import 'package:commons_core/commons_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:tane/db/enums.dart'; import 'package:tane/i18n/strings.g.dart'; import 'package:tane/services/profile_store.dart'; import 'package:tane/services/social_account_store.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/about_screen.dart'; import 'package:tane/ui/home_screen.dart'; import 'package:tane/ui/intro_screen.dart'; import 'package:tane/ui/inventory_list_screen.dart'; import 'package:tane/ui/profile_screen.dart'; import 'package:tane/ui/settings_screen.dart'; import '../support/test_support.dart'; /// Small-phone layout guard. A Flutter `RenderFlex overflowed` (a clipped title, /// a save button pushed off a non-scrolling column, a Row too wide) is reported /// as an error and FAILS the test — so pumping each screen at a tiny viewport, /// in the languages whose text runs longest (es/pt/ast), catches exactly the /// "the save button / title isn't fully visible on a small phone" reports. /// /// FULL SCREENS ONLY. Modal bottom sheets (quick-add, the lot editor) are NOT /// checked here: `showModalBottomSheet` gets an unbounded height in a widget /// test, which fabricates a huge false overflow. Those sheets already wrap their /// body in a `SingleChildScrollView`, so their save buttons stay reachable; /// verify them on a real small device. void main() { // iPhone-SE-class logical viewport: narrow AND short, where clipping shows. const small = Size(320, 568); // The locales whose strings are longest — English rarely overflows first. const longLocales = [AppLocale.es, AppLocale.pt, AppLocale.ast]; setUpAll(() => PackageInfo.setMockInitialValues( appName: 'Tanemaki', packageName: 'org.comunes.tane', version: '0.1.0', buildNumber: '1', buildSignature: '', )); Future pumpSmall(WidgetTester tester, Widget widget) async { tester.view.physicalSize = small; tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(widget); await tester.pumpAndSettle(); } for (final locale in longLocales) { final tag = locale.languageCode; testWidgets('home menu fits a small screen ($tag)', (tester) async { final db = newTestDatabase(); addTearDown(db.close); await pumpSmall( tester, wrapScreen( repository: newTestRepository(db), locale: locale, child: const HomeScreen(marketEnabled: true), ), ); await disposeTree(tester); }); testWidgets('settings (language, backup, about) fits ($tag)', (tester) async { final db = newTestDatabase(); addTearDown(db.close); await pumpSmall( tester, wrapScreen( repository: newTestRepository(db), locale: locale, child: const SettingsScreen(), ), ); await disposeTree(tester); }); testWidgets('about (title, version, license) fits ($tag)', (tester) async { final db = newTestDatabase(); addTearDown(db.close); await pumpSmall( tester, wrapScreen( repository: newTestRepository(db), locale: locale, child: const AboutScreen(), ), ); await disposeTree(tester); }); testWidgets('the intro carousel fits ($tag)', (tester) async { final db = newTestDatabase(); addTearDown(db.close); await pumpSmall( tester, wrapScreen( repository: newTestRepository(db), locale: locale, child: IntroScreen(onDone: () {}), ), ); await disposeTree(tester); }); } testWidgets('profile (identity card + save button) fits a small screen', (tester) async { final db = newTestDatabase(); addTearDown(db.close); final social = await SocialService.fromRootSeedHex('00' * 32); final connection = SocialConnection( social: social, settings: SocialSettings(InMemorySecretStore()), open: (_) async => throw StateError('offline'), online: const Stream.empty(), ); await pumpSmall( tester, wrapScreen( repository: newTestRepository(db), locale: AppLocale.es, child: ProfileScreen( social: social, connection: connection, profileStore: ProfileStore(InMemorySecretStore()), accounts: SocialAccountStore(InMemorySecretStore()), ), ), ); await disposeTree(tester); }); testWidgets('inventory (empty) fits a small screen', (tester) async { final db = newTestDatabase(); addTearDown(db.close); await pumpSmall( tester, wrapScreen( repository: newTestRepository(db), locale: AppLocale.es, child: const InventoryListScreen(), ), ); await disposeTree(tester); }); testWidgets('inventory with a long-named seed fits a small screen', (tester) async { final db = newTestDatabase(); addTearDown(db.close); final repo = newTestRepository(db); final id = await repo.addQuickVariety( label: 'Tomate rosa de Barbastro extraordinariamente larguísimo', category: 'Solanáceas', ); await repo.addLot( varietyId: id, harvestYear: 2024, quantity: const Quantity(kind: QuantityKind.handful), offerStatus: OfferStatus.shared, ); await pumpSmall( tester, wrapScreen( repository: repo, locale: AppLocale.es, child: const InventoryListScreen(), ), ); await disposeTree(tester); }); // NOTE: the variety-detail screen is deliberately NOT overflow-checked here. // It reports a ~2px overflow on a RenderFlex that is already DISPOSED/DEFUNCT // — a transient stale frame while its cubit's Drift stream rebuilds during // pumpAndSettle, not the stable rendered layout. Guarding it would be flaky; // its RTL/build coverage lives in rtl_smoke_test.dart. }