diff --git a/test/app_test.dart b/test/app_test.dart new file mode 100644 index 0000000..44f7174 --- /dev/null +++ b/test/app_test.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:biteplan/app.dart'; +import 'package:biteplan/core/constants/app_constants.dart'; +import 'package:biteplan/features/converter/providers/converter_provider.dart'; +import 'package:biteplan/features/meal_planner/providers/meal_planner_provider.dart'; +import 'package:biteplan/features/shopping_list/providers/shopping_list_provider.dart'; + +Future _pumpApp(WidgetTester tester) async { + await tester.binding.setSurfaceSize(const Size(800, 1600)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => MealPlannerProvider()..load()), + ChangeNotifierProvider(create: (_) => ConverterProvider()..loadDb()), + ChangeNotifierProvider(create: (_) => ShoppingListProvider()..load()), + ], + child: const BitePlanApp(), + ), + ); + await tester.pumpAndSettle(); +} + +Finder _appBarTitle(String text) => find.descendant( + of: find.byType(AppBar), matching: find.text(text)); + +void main() { + setUp(() { + SharedPreferences.setMockInitialValues({}); + }); + + group('BitePlanApp', () { + testWidgets('parte sul tab Piano Pasti', (tester) async { + await _pumpApp(tester); + expect(_appBarTitle('Piano Pasti'), findsOneWidget); + expect(find.byType(NavigationBar), findsOneWidget); + }); + + testWidgets('naviga al tab Convertitore', (tester) async { + await _pumpApp(tester); + await tester.tap(find.text('Converti')); + await tester.pumpAndSettle(); + expect(_appBarTitle('Convertitore'), findsOneWidget); + expect(find.text('Cerca un alimento per iniziare'), findsOneWidget); + }); + + testWidgets('naviga al tab Lista della spesa', (tester) async { + await _pumpApp(tester); + await tester.tap(find.text('Spesa')); + await tester.pumpAndSettle(); + expect(_appBarTitle('Lista della spesa'), findsOneWidget); + expect(find.text('Lista vuota'), findsOneWidget); + }); + + testWidgets('il bottone info apre il bottom sheet informazioni', + (tester) async { + await _pumpApp(tester); + await tester.tap(find.byIcon(Icons.info_outline)); + await tester.pumpAndSettle(); + expect(find.text('Versione $kAppVersion'), findsOneWidget); + }); + }); +} diff --git a/test/coverage_helper_test.dart b/test/coverage_helper_test.dart new file mode 100644 index 0000000..fadfad5 --- /dev/null +++ b/test/coverage_helper_test.dart @@ -0,0 +1,35 @@ +// Importa tutti i file di lib/ così il report di coverage include anche +// i file non toccati da alcun test (altrimenti lcov li ignora del tutto). +// ignore_for_file: unused_import +import 'package:biteplan/app.dart'; +import 'package:biteplan/core/constants/app_constants.dart'; +import 'package:biteplan/core/theme/app_theme.dart'; +import 'package:biteplan/features/converter/models/conversion_entry.dart'; +import 'package:biteplan/features/converter/presentation/pages/converter_page.dart'; +import 'package:biteplan/features/converter/providers/converter_provider.dart'; +import 'package:biteplan/features/guide/presentation/pages/guide_page.dart'; +import 'package:biteplan/features/guide/presentation/widgets/info_bottom_sheet.dart'; +import 'package:biteplan/features/meal_planner/models/meal_plan.dart'; +import 'package:biteplan/features/meal_planner/presentation/pages/meal_planner_page.dart'; +import 'package:biteplan/features/meal_planner/presentation/pages/qr_scan_page.dart'; +import 'package:biteplan/features/meal_planner/presentation/widgets/meal_card.dart'; +import 'package:biteplan/features/meal_planner/presentation/widgets/qr_share_sheet.dart'; +import 'package:biteplan/features/meal_planner/providers/meal_planner_provider.dart'; +import 'package:biteplan/features/meal_planner/qr_codec.dart'; +import 'package:biteplan/features/shopping_list/models/shopping_item.dart'; +import 'package:biteplan/features/shopping_list/presentation/pages/shopping_list_page.dart'; +import 'package:biteplan/features/shopping_list/presentation/widgets/shopping_item_tile.dart'; +import 'package:biteplan/features/shopping_list/providers/shopping_list_provider.dart'; +import 'package:biteplan/main.dart'; +import 'package:biteplan/shared/services/storage_service.dart'; +import 'package:biteplan/shared/services/update_service.dart'; +import 'package:biteplan/shared/services/url_launcher_service.dart'; +import 'package:biteplan/shared/widgets/empty_state.dart'; +import 'package:biteplan/shared/widgets/update_dialog.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('coverage helper', () { + expect(true, isTrue); + }); +} diff --git a/test/features/converter/pages/converter_page_test.dart b/test/features/converter/pages/converter_page_test.dart new file mode 100644 index 0000000..01cd071 --- /dev/null +++ b/test/features/converter/pages/converter_page_test.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:biteplan/features/converter/models/conversion_entry.dart'; +import 'package:biteplan/features/converter/presentation/pages/converter_page.dart'; +import 'package:biteplan/features/converter/providers/converter_provider.dart'; +import '../../../helpers/pump_app.dart'; + +Widget _page(ConverterProvider provider) => ChangeNotifierProvider.value( + value: provider, + child: const Scaffold(body: ConverterPage()), + ); + +const _riso = + ConversionEntry(food: 'riso', method: 'bollitura', yieldFactor: 3.0); + +void main() { + late ConverterProvider provider; + + setUp(() { + provider = ConverterProvider(); + }); + + group('ConverterPage — ricerca', () { + testWidgets('mostra lo stato vuoto iniziale', (tester) async { + await tester.pumpApp(_page(provider)); + expect(find.text('Cerca un alimento per iniziare'), findsOneWidget); + }); + + testWidgets('la ricerca mostra risultati dal database', (tester) async { + await provider.loadDb(); + await tester.pumpApp(_page(provider)); + await tester.enterText(find.byType(TextField), 'riso'); + await tester.pump(); + expect(find.byType(ListTile), findsWidgets); + }); + + testWidgets('ricerca senza corrispondenze mostra "Nessun risultato"', + (tester) async { + await provider.loadDb(); + await tester.pumpApp(_page(provider)); + await tester.enterText(find.byType(TextField), 'xyzabc'); + await tester.pump(); + expect(find.text('Nessun risultato'), findsOneWidget); + }); + + testWidgets('tap su un risultato apre la card di conversione', + (tester) async { + await provider.loadDb(); + await tester.pumpApp(_page(provider)); + await tester.enterText(find.byType(TextField), 'riso'); + await tester.pump(); + await tester.tap(find.byType(ListTile).first); + await tester.pump(); + expect(find.text('Cambia'), findsOneWidget); + expect(find.text('CRUDO'), findsOneWidget); + expect(find.text('COTTO'), findsOneWidget); + }); + }); + + group('ConverterPage — card di conversione', () { + testWidgets('calcola e mostra il risultato formattato', (tester) async { + provider.select(_riso); + await tester.pumpApp(_page(provider)); + await tester.enterText(find.byType(TextField), '100'); + await tester.pump(); + // 100 * 3.0 = 300, intero → senza decimali + expect(find.text('300'), findsOneWidget); + expect(find.textContaining('fattore di resa 3.0'), findsOneWidget); + }); + + testWidgets('input non numerico mostra il placeholder —', (tester) async { + provider.select(_riso); + await tester.pumpApp(_page(provider)); + await tester.enterText(find.byType(TextField), 'abc'); + await tester.pump(); + expect(find.text('—'), findsOneWidget); + }); + + testWidgets('swap inverte la direzione e azzera il risultato', + (tester) async { + provider.select(_riso); + await tester.pumpApp(_page(provider)); + await tester.enterText(find.byType(TextField), '100'); + await tester.pump(); + expect(find.text('300'), findsOneWidget); + + await tester.tap(find.byIcon(Icons.swap_horiz)); + await tester.pump(); + expect(provider.rawToCooked, isFalse); + expect(find.text('300'), findsNothing); + }); + + testWidgets('"Cambia" torna alla ricerca', (tester) async { + provider.select(_riso); + await tester.pumpApp(_page(provider)); + await tester.tap(find.text('Cambia')); + await tester.pump(); + expect(find.text('Cerca un alimento per iniziare'), findsOneWidget); + expect(provider.selected, isNull); + }); + }); +} diff --git a/test/features/guide/pages/guide_page_test.dart b/test/features/guide/pages/guide_page_test.dart new file mode 100644 index 0000000..1f673d7 --- /dev/null +++ b/test/features/guide/pages/guide_page_test.dart @@ -0,0 +1,36 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:biteplan/features/guide/presentation/pages/guide_page.dart'; +import '../../../helpers/pump_app.dart'; + +void main() { + group('GuidePage', () { + testWidgets('mostra titolo e le tre tab', (tester) async { + await tester.pumpApp(const GuidePage()); + expect(find.text('Guida'), findsOneWidget); + expect(find.text('Pasti'), findsOneWidget); + expect(find.text('Converti'), findsOneWidget); + expect(find.text('Spesa'), findsOneWidget); + }); + + testWidgets('la tab iniziale è Pasti', (tester) async { + await tester.pumpApp(const GuidePage()); + expect(find.text('Aggiungere un alimento'), findsOneWidget); + }); + + testWidgets('la tab Converti mostra i suoi contenuti', (tester) async { + await tester.pumpApp(const GuidePage()); + await tester.tap(find.text('Converti')); + await tester.pumpAndSettle(); + expect(find.text('Come usarlo'), findsOneWidget); + expect(find.text('Alimenti disponibili'), findsOneWidget); + }); + + testWidgets('la tab Spesa mostra i suoi contenuti', (tester) async { + await tester.pumpApp(const GuidePage()); + await tester.tap(find.text('Spesa')); + await tester.pumpAndSettle(); + expect(find.text('Aggiungere un elemento'), findsOneWidget); + expect(find.text('Spuntare un elemento'), findsOneWidget); + }); + }); +} diff --git a/test/features/guide/widgets/info_bottom_sheet_test.dart b/test/features/guide/widgets/info_bottom_sheet_test.dart new file mode 100644 index 0000000..d7e2625 --- /dev/null +++ b/test/features/guide/widgets/info_bottom_sheet_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:biteplan/core/constants/app_constants.dart'; +import 'package:biteplan/features/guide/presentation/widgets/info_bottom_sheet.dart'; +import '../../../helpers/pump_app.dart'; + +Future _openSheet(WidgetTester tester) async { + await tester.pumpApp(Scaffold( + body: Builder( + builder: (context) => Center( + child: FilledButton( + onPressed: () => showInfoBottomSheet(context), + child: const Text('apri'), + ), + ), + ), + )); + await tester.tap(find.text('apri')); + await tester.pumpAndSettle(); +} + +void main() { + group('InfoBottomSheet', () { + testWidgets('mostra nome app, versione, autore e licenza', + (tester) async { + await _openSheet(tester); + expect(find.text('BitePlan'), findsOneWidget); + expect(find.text('Versione $kAppVersion'), findsOneWidget); + expect(find.text('Autore'), findsOneWidget); + expect(find.text('Licenza'), findsOneWidget); + }); + + testWidgets('"Apri" sulla riga Guida naviga alla GuidePage', + (tester) async { + await _openSheet(tester); + await tester.tap(find.text('Apri')); + await tester.pumpAndSettle(); + expect(find.text('Aggiungere un alimento'), findsOneWidget); + }); + }); +} diff --git a/test/features/meal_planner/pages/meal_planner_page_test.dart b/test/features/meal_planner/pages/meal_planner_page_test.dart new file mode 100644 index 0000000..222be28 --- /dev/null +++ b/test/features/meal_planner/pages/meal_planner_page_test.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:biteplan/features/meal_planner/presentation/pages/meal_planner_page.dart'; +import 'package:biteplan/features/meal_planner/presentation/widgets/meal_card.dart'; +import 'package:biteplan/features/meal_planner/providers/meal_planner_provider.dart'; +import 'package:biteplan/features/shopping_list/providers/shopping_list_provider.dart'; +import '../../../helpers/pump_app.dart'; + +Widget _page({ + required MealPlannerProvider meals, + required ShoppingListProvider shopping, + VoidCallback? onGoShopping, +}) => + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: meals), + ChangeNotifierProvider.value(value: shopping), + ], + child: Scaffold( + body: MealPlannerPage(onGoShopping: onGoShopping ?? () {}), + ), + ); + +String get _todayId { + const ids = [ + '', + 'lunedi', + 'martedi', + 'mercoledi', + 'giovedi', + 'venerdi', + 'sabato', + 'domenica', + ]; + return ids[DateTime.now().weekday]; +} + +void main() { + late MealPlannerProvider meals; + late ShoppingListProvider shopping; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + meals = MealPlannerProvider(); + shopping = ShoppingListProvider(); + await meals.load(); + await shopping.load(); + }); + + Future pumpPage(WidgetTester tester, {VoidCallback? onGoShopping}) async { + await tester.binding.setSurfaceSize(const Size(800, 1800)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + await tester.pumpApp( + _page(meals: meals, shopping: shopping, onGoShopping: onGoShopping)); + } + + group('MealPlannerPage', () { + testWidgets('mostra le 7 card dei giorni', (tester) async { + await pumpPage(tester); + expect(find.byType(MealCard), findsNWidgets(7)); + }); + + testWidgets('solo la card di oggi è inizialmente espansa', (tester) async { + await pumpPage(tester); + for (final card in + tester.widgetList(find.byType(MealCard))) { + expect(card.initiallyExpanded, card.dayId == _todayId, + reason: card.dayId); + } + }); + + testWidgets('"Condividi" è disabilitato con piano vuoto', (tester) async { + await pumpPage(tester); + final button = tester.widget( + find.widgetWithText(OutlinedButton, 'Condividi')); + expect(button.onPressed, isNull); + }); + + testWidgets('"Condividi" è abilitato con almeno un pasto', (tester) async { + meals.addItem('lunedi', 'pranzo', 'pasta'); + await pumpPage(tester); + final button = tester.widget( + find.widgetWithText(OutlinedButton, 'Condividi')); + expect(button.onPressed, isNotNull); + }); + + testWidgets('"Svuota piano" appare solo con pasti presenti', + (tester) async { + await pumpPage(tester); + expect(find.text('Svuota piano'), findsNothing); + + meals.addItem('lunedi', 'pranzo', 'pasta'); + await tester.pump(); + expect(find.text('Svuota piano'), findsOneWidget); + }); + + testWidgets('"Genera lista" popola la spesa e chiama onGoShopping', + (tester) async { + meals.addItem('lunedi', 'pranzo', 'pasta'); + meals.addItem('martedi', 'cena', 'pollo'); + var navigated = false; + await pumpPage(tester, onGoShopping: () => navigated = true); + + await tester.tap(find.text('Genera lista della spesa')); + await tester.pump(); + + expect(navigated, isTrue); + expect(shopping.items.map((i) => i.name), + containsAll(['pasta', 'pollo'])); + }); + + testWidgets('"Svuota piano" con Annulla mantiene i pasti', (tester) async { + meals.addItem('lunedi', 'pranzo', 'pasta'); + await pumpPage(tester); + await tester.tap(find.widgetWithText(OutlinedButton, 'Svuota piano')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Annulla')); + await tester.pumpAndSettle(); + expect(meals.plan.hasAnyMeal, isTrue); + }); + + testWidgets('"Svuota piano" con Svuota azzera il piano', (tester) async { + meals.addItem('lunedi', 'pranzo', 'pasta'); + await pumpPage(tester); + await tester.tap(find.widgetWithText(OutlinedButton, 'Svuota piano')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Svuota')); + await tester.pumpAndSettle(); + expect(meals.plan.hasAnyMeal, isFalse); + }); + }); +} diff --git a/test/features/meal_planner/widgets/qr_share_sheet_test.dart b/test/features/meal_planner/widgets/qr_share_sheet_test.dart new file mode 100644 index 0000000..da87649 --- /dev/null +++ b/test/features/meal_planner/widgets/qr_share_sheet_test.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:biteplan/core/constants/app_constants.dart'; +import 'package:biteplan/features/meal_planner/models/meal_plan.dart'; +import 'package:biteplan/features/meal_planner/presentation/widgets/qr_share_sheet.dart'; +import '../../../helpers/pump_app.dart'; + +MealPlan _planWith(Map overrides) => MealPlan({ + ...Map.fromEntries(kDayIds.map((d) => MapEntry(d, DayPlan()))), + ...overrides, + }); + +Future _openSheet(WidgetTester tester, MealPlan plan) async { + await tester.pumpApp(Scaffold( + body: Builder( + builder: (context) => Center( + child: FilledButton( + onPressed: () => showQrShareSheet(context, plan), + child: const Text('apri'), + ), + ), + ), + )); + await tester.tap(find.text('apri')); + await tester.pumpAndSettle(); +} + +void main() { + group('QrShareSheet', () { + testWidgets('piano valido mostra il QR code', (tester) async { + final plan = _planWith({ + 'lunedi': DayPlan(pranzo: ['pasta']) + }); + await _openSheet(tester, plan); + expect(find.text('Condividi piano'), findsOneWidget); + expect(find.byType(QrImageView), findsOneWidget); + }); + + testWidgets('piano troppo grande mostra il messaggio di errore', + (tester) async { + final plan = _planWith({ + for (final d in kDayIds) + d: DayPlan( + colazione: List.generate( + 30, (i) => 'alimento molto lungo numero $i della colazione'), + pranzo: List.generate( + 30, (i) => 'alimento molto lungo numero $i del pranzo'), + ), + }); + await _openSheet(tester, plan); + expect(find.textContaining('Dati troppo grandi'), findsOneWidget); + expect(find.byType(QrImageView), findsNothing); + }); + + testWidgets('il pulsante chiudi fa scomparire il foglio', (tester) async { + final plan = _planWith({ + 'lunedi': DayPlan(pranzo: ['pasta']) + }); + await _openSheet(tester, plan); + await tester.tap(find.byIcon(Icons.close)); + await tester.pumpAndSettle(); + expect(find.text('Condividi piano'), findsNothing); + }); + }); +} diff --git a/test/features/shopping_list/pages/shopping_list_page_test.dart b/test/features/shopping_list/pages/shopping_list_page_test.dart new file mode 100644 index 0000000..b6a61e2 --- /dev/null +++ b/test/features/shopping_list/pages/shopping_list_page_test.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:biteplan/features/shopping_list/presentation/pages/shopping_list_page.dart'; +import 'package:biteplan/features/shopping_list/providers/shopping_list_provider.dart'; +import '../../../helpers/pump_app.dart'; + +Widget _page(ShoppingListProvider provider) => ChangeNotifierProvider.value( + value: provider, + child: const Scaffold(body: ShoppingListPage()), + ); + +void main() { + late ShoppingListProvider provider; + + setUp(() async { + SharedPreferences.setMockInitialValues({}); + provider = ShoppingListProvider(); + await provider.load(); + }); + + group('ShoppingListPage', () { + testWidgets('lista vuota mostra EmptyState', (tester) async { + await tester.pumpApp(_page(provider)); + expect(find.text('Lista vuota'), findsOneWidget); + }); + + testWidgets('aggiunge un elemento con il pulsante +', (tester) async { + await tester.pumpApp(_page(provider)); + await tester.enterText(find.byType(TextField), 'pane'); + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + expect(find.text('pane'), findsOneWidget); + expect(provider.items, hasLength(1)); + }); + + testWidgets('aggiunge un elemento con Invio', (tester) async { + await tester.pumpApp(_page(provider)); + await tester.enterText(find.byType(TextField), 'latte'); + await tester.testTextInput.receiveAction(TextInputAction.done); + await tester.pump(); + expect(find.text('latte'), findsOneWidget); + }); + + testWidgets('testo vuoto o solo spazi non aggiunge nulla', (tester) async { + await tester.pumpApp(_page(provider)); + await tester.enterText(find.byType(TextField), ' '); + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + expect(find.text('Lista vuota'), findsOneWidget); + expect(provider.items, isEmpty); + }); + + testWidgets('il contatore usa il singolare con 1 completato', + (tester) async { + provider.add('pane'); + provider.add('latte'); + provider.toggle(provider.items.first.id); + await tester.pumpApp(_page(provider)); + expect(find.text('1 / 2 completato'), findsOneWidget); + }); + + testWidgets('il contatore usa il plurale con più completati', + (tester) async { + provider.add('pane'); + provider.add('latte'); + for (final item in [...provider.items]) { + provider.toggle(item.id); + } + await tester.pumpApp(_page(provider)); + expect(find.text('2 / 2 completati'), findsOneWidget); + }); + + testWidgets('la sezione COMPLETATI appare solo con elementi spuntati', + (tester) async { + provider.add('pane'); + await tester.pumpApp(_page(provider)); + expect(find.textContaining('COMPLETATI'), findsNothing); + + provider.toggle(provider.items.first.id); + await tester.pump(); + expect(find.text('COMPLETATI (1)'), findsOneWidget); + }); + + testWidgets('"Svuota lista" con Annulla non svuota', (tester) async { + provider.add('pane'); + await tester.pumpApp(_page(provider)); + await tester.tap(find.widgetWithText(OutlinedButton, 'Svuota lista')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Annulla')); + await tester.pumpAndSettle(); + expect(provider.items, hasLength(1)); + expect(find.text('pane'), findsOneWidget); + }); + + testWidgets('"Svuota lista" con Svuota svuota tutto', (tester) async { + provider.add('pane'); + await tester.pumpApp(_page(provider)); + await tester.tap(find.widgetWithText(OutlinedButton, 'Svuota lista')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Svuota')); + await tester.pumpAndSettle(); + expect(provider.items, isEmpty); + expect(find.text('Lista vuota'), findsOneWidget); + }); + }); +} diff --git a/test/shared/widgets/empty_state_test.dart b/test/shared/widgets/empty_state_test.dart new file mode 100644 index 0000000..6931951 --- /dev/null +++ b/test/shared/widgets/empty_state_test.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:biteplan/shared/widgets/empty_state.dart'; +import '../../helpers/pump_app.dart'; + +void main() { + group('EmptyState', () { + testWidgets('mostra icona e titolo', (tester) async { + await tester.pumpApp(const Scaffold( + body: EmptyState(icon: Icons.search, title: 'Nessun elemento'), + )); + expect(find.byIcon(Icons.search), findsOneWidget); + expect(find.text('Nessun elemento'), findsOneWidget); + }); + + testWidgets('mostra il sottotitolo quando presente', (tester) async { + await tester.pumpApp(const Scaffold( + body: EmptyState( + icon: Icons.search, + title: 'Titolo', + subtitle: 'Sottotitolo di prova', + ), + )); + expect(find.text('Sottotitolo di prova'), findsOneWidget); + }); + + testWidgets('nessun sottotitolo quando assente', (tester) async { + await tester.pumpApp(const Scaffold( + body: EmptyState(icon: Icons.search, title: 'Titolo'), + )); + expect(find.byType(Text), findsOneWidget); + }); + }); +}