test: aggiungi suite di test Flutter

Struttura specchia lib/features/:
- test/features/meal_planner/ — unit (DayPlan, MealPlan, provider) + widget (MealCard)
- test/features/converter/ — unit (ConversionEntry, formule rawToCooked/cookedToRaw)
- test/features/shopping_list/ — unit (ShoppingItem, provider) + widget (ShoppingItemTile)
- test/helpers/pump_app.dart — estensione PumpApp condivisa tra widget test
- integration_test/app_test.dart — e2e navigazione, shopping list, convertitore, meal planner

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 17:41:38 +02:00
parent eb7912421c
commit 9a865a1479
9 changed files with 809 additions and 0 deletions
+225
View File
@@ -0,0 +1,225 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:provider/provider.dart';
import 'package:biteplan/app.dart';
import 'package:biteplan/features/meal_planner/providers/meal_planner_provider.dart';
import 'package:biteplan/features/converter/providers/converter_provider.dart';
import 'package:biteplan/features/shopping_list/providers/shopping_list_provider.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUp(() async {
SharedPreferences.setMockInitialValues({});
});
Widget buildApp() => MultiProvider(
providers: [
ChangeNotifierProvider(
create: (_) => MealPlannerProvider()..load()),
ChangeNotifierProvider(
create: (_) => ConverterProvider()..loadDb()),
ChangeNotifierProvider(
create: (_) => ShoppingListProvider()..load()),
],
child: const BitePlanApp(),
);
// ── Navigazione ────────────────────────────────────────────────────────────
group('Navigazione', () {
testWidgets('il tab iniziale è Piano Pasti', (tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(find.text('Piano Pasti'), findsOneWidget);
});
testWidgets('passa al tab Convertitore', (tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Converti'));
await tester.pumpAndSettle();
expect(find.text('Convertitore'), findsOneWidget);
});
testWidgets('passa al tab Lista della spesa', (tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Spesa'));
await tester.pumpAndSettle();
expect(find.text('Lista della spesa'), findsOneWidget);
});
testWidgets('naviga tra tutti e tre i tab', (tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Converti'));
await tester.pumpAndSettle();
expect(find.text('Convertitore'), findsOneWidget);
await tester.tap(find.text('Spesa'));
await tester.pumpAndSettle();
expect(find.text('Lista della spesa'), findsOneWidget);
await tester.tap(find.text('Pasti'));
await tester.pumpAndSettle();
expect(find.text('Piano Pasti'), findsOneWidget);
});
});
// ── Lista della spesa ──────────────────────────────────────────────────────
group('Lista della spesa', () {
testWidgets('aggiunge un elemento e lo mostra', (tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Spesa'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Pollo');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
expect(find.text('Pollo'), findsOneWidget);
});
testWidgets('rimuove un elemento con il pulsante chiudi', (tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Spesa'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Riso');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.close).first);
await tester.pumpAndSettle();
expect(find.text('Riso'), findsNothing);
});
testWidgets('spuntare un elemento lo sposta in Completati', (tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Spesa'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Pasta');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
await tester.tap(find.byType(Checkbox).first);
await tester.pumpAndSettle();
expect(find.text('COMPLETATI (1)'), findsOneWidget);
});
testWidgets('aggiorna il contatore completati/totale', (tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Spesa'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Pollo');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Riso');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
await tester.tap(find.byType(Checkbox).first);
await tester.pumpAndSettle();
expect(find.text('1 / 2 completato'), findsOneWidget);
});
});
// ── Convertitore ──────────────────────────────────────────────────────────
group('Convertitore', () {
testWidgets('mostra hint iniziale', (tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Converti'));
await tester.pumpAndSettle();
expect(find.text('Cerca un alimento per iniziare'), findsOneWidget);
});
testWidgets('la ricerca mostra risultati pertinenti', (tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Converti'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'pollo');
await tester.pumpAndSettle();
expect(find.textContaining('Pollo'), findsWidgets);
});
testWidgets('selezionare un alimento mostra la card del convertitore',
(tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Converti'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'riso basmati');
await tester.pumpAndSettle();
await tester.tap(find.textContaining('Riso basmati').first);
await tester.pumpAndSettle();
expect(find.text('CRUDO'), findsOneWidget);
expect(find.text('COTTO'), findsOneWidget);
expect(find.text('Cambia'), findsOneWidget);
});
testWidgets('il pulsante Cambia resetta la selezione', (tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
await tester.tap(find.text('Converti'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'riso basmati');
await tester.pumpAndSettle();
await tester.tap(find.textContaining('Riso basmati').first);
await tester.pumpAndSettle();
await tester.tap(find.text('Cambia'));
await tester.pumpAndSettle();
expect(find.text('Cerca un alimento per iniziare'), findsOneWidget);
});
});
// ── Meal Planner ───────────────────────────────────────────────────────────
group('Piano Pasti', () {
testWidgets('genera lista della spesa e naviga al tab Spesa',
(tester) async {
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
// Trova il giorno corrente espanso e aggiungi un elemento
final addButtons = find.byIcon(Icons.add);
if (addButtons.evaluate().isNotEmpty) {
final firstTextField = find.byType(TextField).first;
await tester.enterText(firstTextField, 'Broccoli');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
}
await tester.tap(find.text('Genera lista della spesa'));
await tester.pumpAndSettle();
// Deve essere atterrato sul tab Spesa
expect(find.text('Lista della spesa'), findsOneWidget);
});
});
}
@@ -0,0 +1,65 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:biteplan/features/converter/models/conversion_entry.dart';
void main() {
group('ConversionEntry', () {
test('rawToCooked moltiplica per il fattore di resa', () {
const entry = ConversionEntry(
food: 'riso basmati',
method: 'bollitura',
yieldFactor: 3.0,
);
expect(entry.rawToCooked(100), closeTo(300.0, 0.001));
expect(entry.rawToCooked(70), closeTo(210.0, 0.001));
});
test('cookedToRaw divide per il fattore di resa', () {
const entry = ConversionEntry(
food: 'riso basmati',
method: 'bollitura',
yieldFactor: 3.0,
);
expect(entry.cookedToRaw(300), closeTo(100.0, 0.001));
expect(entry.cookedToRaw(150), closeTo(50.0, 0.001));
});
test('conversione bidirezionale è consistente', () {
const entry = ConversionEntry(
food: 'pollo petto',
method: 'forno',
yieldFactor: 0.67,
);
final cooked = entry.rawToCooked(140);
final backToRaw = entry.cookedToRaw(cooked);
expect(backToRaw, closeTo(140, 0.001));
});
test('yield > 1: i cereali aumentano di peso con la cottura', () {
const entry = ConversionEntry(
food: 'pasta',
method: 'bollitura',
yieldFactor: 2.1,
);
expect(entry.rawToCooked(80), greaterThan(80));
});
test('yield < 1: la carne perde peso con la cottura', () {
const entry = ConversionEntry(
food: 'pollo petto',
method: 'forno',
yieldFactor: 0.67,
);
expect(entry.rawToCooked(100), lessThan(100));
});
test('valore limite: 0 grammi restituisce 0', () {
const entry = ConversionEntry(
food: 'riso',
method: 'bollitura',
yieldFactor: 3.0,
);
expect(entry.rawToCooked(0), 0.0);
expect(entry.cookedToRaw(0), 0.0);
});
});
}
@@ -0,0 +1,100 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:biteplan/features/meal_planner/models/meal_plan.dart';
void main() {
group('DayPlan', () {
test('crea con liste vuote di default', () {
final day = DayPlan();
expect(day.colazione, isEmpty);
expect(day.pranzo, isEmpty);
expect(day.cena, isEmpty);
});
test('slot() ritorna la lista corretta', () {
final day = DayPlan(
colazione: ['latte'],
pranzo: ['pasta'],
cena: ['pollo'],
);
expect(day.slot('colazione'), ['latte']);
expect(day.slot('pranzo'), ['pasta']);
expect(day.slot('cena'), ['pollo']);
expect(day.slot('unknown'), isEmpty);
});
test('copyWith() non modifica lorigine', () {
final day = DayPlan(colazione: ['latte']);
final updated = day.copyWith(colazione: ['latte', 'caffè']);
expect(updated.colazione, ['latte', 'caffè']);
expect(day.colazione, ['latte']);
});
test('copyWith() preserva i campi non aggiornati', () {
final day = DayPlan(colazione: ['latte'], pranzo: ['pasta']);
final updated = day.copyWith(colazione: ['caffè']);
expect(updated.pranzo, ['pasta']);
});
test('JSON round-trip preserva i dati', () {
final day = DayPlan(colazione: ['latte', 'biscotti'], cena: ['pollo']);
final restored = DayPlan.fromJson(day.toJson());
expect(restored.colazione, ['latte', 'biscotti']);
expect(restored.cena, ['pollo']);
expect(restored.pranzo, isEmpty);
});
});
group('MealPlan', () {
test('empty() crea piano con tutti i giorni', () {
const days = ['lunedi', 'martedi', 'mercoledi'];
final plan = MealPlan.empty(days);
expect(plan.days.keys, containsAll(days));
for (final d in days) {
expect(plan.days[d]!.colazione, isEmpty);
}
});
test('hasAnyMeal è false con piano vuoto', () {
final plan = MealPlan.empty(['lunedi', 'martedi']);
expect(plan.hasAnyMeal, false);
});
test('hasAnyMeal è true con almeno un elemento', () {
final plan = MealPlan({'lunedi': DayPlan(colazione: ['latte'])});
expect(plan.hasAnyMeal, true);
});
test('allItems ritorna lista appiattita', () {
final plan = MealPlan({
'lunedi': DayPlan(colazione: ['latte'], pranzo: ['pasta']),
'martedi': DayPlan(cena: ['pollo']),
});
expect(plan.allItems, containsAll(['latte', 'pasta', 'pollo']));
expect(plan.allItems.length, 3);
});
test('allItems è vuota con piano vuoto', () {
final plan = MealPlan.empty(['lunedi']);
expect(plan.allItems, isEmpty);
});
test('withUpdatedDay() aggiorna solo il giorno specificato', () {
final plan = MealPlan.empty(['lunedi', 'martedi']);
final updated =
plan.withUpdatedDay('lunedi', DayPlan(colazione: ['latte']));
expect(updated.days['lunedi']!.colazione, ['latte']);
expect(updated.days['martedi']!.colazione, isEmpty);
});
test('JSON round-trip preserva tutti i dati', () {
final original = MealPlan({
'lunedi': DayPlan(colazione: ['latte', 'biscotti'], cena: ['pollo']),
'martedi': DayPlan(pranzo: ['pasta al pomodoro']),
});
final restored = MealPlan.fromJsonString(original.toJsonString());
expect(restored.days['lunedi']!.colazione, ['latte', 'biscotti']);
expect(restored.days['lunedi']!.cena, ['pollo']);
expect(restored.days['martedi']!.pranzo, ['pasta al pomodoro']);
});
});
}
@@ -0,0 +1,88 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:biteplan/features/meal_planner/providers/meal_planner_provider.dart';
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
});
test('inizia con piano vuoto', () async {
final provider = MealPlannerProvider();
await provider.load();
expect(provider.plan.hasAnyMeal, false);
});
test('addItem aggiunge nello slot corretto', () async {
final provider = MealPlannerProvider();
await provider.load();
provider.addItem('lunedi', 'colazione', 'latte');
expect(provider.plan.days['lunedi']!.colazione, ['latte']);
expect(provider.plan.days['lunedi']!.pranzo, isEmpty);
});
test('addItem aggiunge in tutti e tre gli slot', () async {
final provider = MealPlannerProvider();
await provider.load();
provider.addItem('martedi', 'colazione', 'yogurt');
provider.addItem('martedi', 'pranzo', 'pasta');
provider.addItem('martedi', 'cena', 'pollo');
expect(provider.plan.days['martedi']!.colazione, ['yogurt']);
expect(provider.plan.days['martedi']!.pranzo, ['pasta']);
expect(provider.plan.days['martedi']!.cena, ['pollo']);
});
test('addItem ignora testo vuoto o solo spazi', () async {
final provider = MealPlannerProvider();
await provider.load();
provider.addItem('lunedi', 'colazione', ' ');
provider.addItem('lunedi', 'colazione', '');
expect(provider.plan.days['lunedi']!.colazione, isEmpty);
});
test('addItem esegue trim del testo', () async {
final provider = MealPlannerProvider();
await provider.load();
provider.addItem('lunedi', 'pranzo', ' pasta ');
expect(provider.plan.days['lunedi']!.pranzo, ['pasta']);
});
test('removeItem rimuove dalla posizione corretta', () async {
final provider = MealPlannerProvider();
await provider.load();
provider.addItem('lunedi', 'pranzo', 'pasta');
provider.addItem('lunedi', 'pranzo', 'insalata');
provider.addItem('lunedi', 'pranzo', 'frutta');
provider.removeItem('lunedi', 'pranzo', 1);
expect(provider.plan.days['lunedi']!.pranzo, ['pasta', 'frutta']);
});
test('clearAll svuota tutti i giorni', () async {
final provider = MealPlannerProvider();
await provider.load();
provider.addItem('lunedi', 'colazione', 'latte');
provider.addItem('venerdi', 'cena', 'pesce');
provider.clearAll();
expect(provider.plan.hasAnyMeal, false);
});
test('persiste e ricarica i dati', () async {
final provider1 = MealPlannerProvider();
await provider1.load();
provider1.addItem('lunedi', 'cena', 'pollo');
await Future.delayed(const Duration(milliseconds: 50));
final provider2 = MealPlannerProvider();
await provider2.load();
expect(provider2.plan.days['lunedi']!.cena, ['pollo']);
});
test('notifica i listener dopo addItem', () async {
final provider = MealPlannerProvider();
await provider.load();
var notified = false;
provider.addListener(() => notified = true);
provider.addItem('lunedi', 'colazione', 'latte');
expect(notified, true);
});
}
@@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:biteplan/features/meal_planner/presentation/widgets/meal_card.dart';
import 'package:biteplan/features/meal_planner/models/meal_plan.dart';
import '../../../helpers/pump_app.dart';
Widget _card({
DayPlan? dayPlan,
bool initiallyExpanded = false,
void Function(String, String)? onAdd,
void Function(String, int)? onRemove,
}) =>
Scaffold(
body: SingleChildScrollView(
child: MealCard(
dayId: 'lunedi',
dayLabel: 'Lunedì',
dayPlan: dayPlan ?? DayPlan(),
initiallyExpanded: initiallyExpanded,
onAdd: onAdd ?? (_, __) {},
onRemove: onRemove ?? (_, __) {},
),
),
);
void main() {
testWidgets('mostra il nome del giorno', (tester) async {
await tester.pumpApp(_card());
expect(find.text('Lunedì'), findsOneWidget);
});
testWidgets('mostra il conteggio voci nel sottotitolo', (tester) async {
await tester.pumpApp(_card(
dayPlan: DayPlan(colazione: ['latte', 'caffè'], pranzo: ['pasta']),
));
expect(find.text('3 voci'), findsOneWidget);
});
testWidgets('usa forma singolare per 1 voce', (tester) async {
await tester.pumpApp(_card(dayPlan: DayPlan(colazione: ['latte'])));
expect(find.text('1 voce'), findsOneWidget);
});
testWidgets('nessun sottotitolo quando il giorno è vuoto', (tester) async {
await tester.pumpApp(_card());
expect(find.text('0 voci'), findsNothing);
expect(find.text('voce'), findsNothing);
});
testWidgets('mostra gli elementi quando espanso', (tester) async {
await tester.pumpApp(_card(
dayPlan: DayPlan(colazione: ['latte'], cena: ['pollo al forno']),
initiallyExpanded: true,
));
await tester.pumpAndSettle();
expect(find.text('latte'), findsOneWidget);
expect(find.text('pollo al forno'), findsOneWidget);
});
testWidgets('chiama onAdd con slot e testo corretti', (tester) async {
String? capturedSlot;
String? capturedText;
await tester.pumpApp(_card(
initiallyExpanded: true,
onAdd: (slot, text) {
capturedSlot = slot;
capturedText = text;
},
));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField).first, 'yogurt');
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pump();
expect(capturedSlot, 'colazione');
expect(capturedText, 'yogurt');
});
testWidgets('chiama onRemove quando si preme il pulsante elimina',
(tester) async {
String? removedSlot;
int? removedIndex;
await tester.pumpApp(_card(
dayPlan: DayPlan(colazione: ['latte']),
initiallyExpanded: true,
onRemove: (slot, index) {
removedSlot = slot;
removedIndex = index;
},
));
await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.close).first);
await tester.pump();
expect(removedSlot, 'colazione');
expect(removedIndex, 0);
});
}
@@ -0,0 +1,46 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:biteplan/features/shopping_list/models/shopping_item.dart';
void main() {
group('ShoppingItem', () {
test('checked è false di default', () {
final item = ShoppingItem(id: '1', name: 'Pollo');
expect(item.checked, false);
});
test('copyWith() aggiorna solo il campo specificato', () {
final item = ShoppingItem(id: '1', name: 'Pollo');
final checked = item.copyWith(checked: true);
expect(checked.checked, true);
expect(checked.name, 'Pollo');
expect(checked.id, '1');
});
test('copyWith() non modifica lorigine', () {
final item = ShoppingItem(id: '1', name: 'Pollo');
item.copyWith(checked: true);
expect(item.checked, false);
});
test('JSON round-trip preserva tutti i campi', () {
const item = ShoppingItem(id: '42', name: 'Riso', checked: true);
final restored = ShoppingItem.fromJson(item.toJson());
expect(restored.id, '42');
expect(restored.name, 'Riso');
expect(restored.checked, true);
});
test('fromJson gestisce campo checked assente (default false)', () {
final item = ShoppingItem.fromJson({'id': '1', 'name': 'Pasta'});
expect(item.checked, false);
});
test('toJson produce la mappa corretta', () {
const item = ShoppingItem(id: '5', name: 'Zucchine', checked: false);
final json = item.toJson();
expect(json['id'], '5');
expect(json['name'], 'Zucchine');
expect(json['checked'], false);
});
});
}
@@ -0,0 +1,109 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:biteplan/features/shopping_list/providers/shopping_list_provider.dart';
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
});
test('inizia vuota', () async {
final provider = ShoppingListProvider();
await provider.load();
expect(provider.items, isEmpty);
});
test('add() aggiunge un elemento', () async {
final provider = ShoppingListProvider();
await provider.load();
provider.add('Pollo');
expect(provider.items.length, 1);
expect(provider.items.first.name, 'Pollo');
expect(provider.items.first.checked, false);
});
test('add() ignora stringa vuota o solo spazi', () async {
final provider = ShoppingListProvider();
await provider.load();
provider.add(' ');
provider.add('');
expect(provider.items, isEmpty);
});
test('add() esegue trim del testo', () async {
final provider = ShoppingListProvider();
await provider.load();
provider.add(' Riso ');
expect(provider.items.first.name, 'Riso');
});
test('toggle() inverte lo stato checked', () async {
final provider = ShoppingListProvider();
await provider.load();
provider.add('Riso');
final id = provider.items.first.id;
provider.toggle(id);
expect(provider.items.first.checked, true);
provider.toggle(id);
expect(provider.items.first.checked, false);
});
test('pendingItems e checkedItems separano correttamente', () async {
final provider = ShoppingListProvider();
await provider.load();
provider.add('Riso');
provider.add('Pasta');
provider.add('Pollo');
provider.toggle(provider.items[0].id);
provider.toggle(provider.items[2].id);
expect(provider.pendingItems.length, 1);
expect(provider.checkedItems.length, 2);
});
test('remove() elimina per id', () async {
final provider = ShoppingListProvider();
await provider.load();
provider.add('Pollo');
provider.add('Riso');
final id = provider.items.first.id;
provider.remove(id);
expect(provider.items.length, 1);
expect(provider.items.first.name, 'Riso');
});
test('addAll() non aggiunge duplicati (case-insensitive)', () async {
final provider = ShoppingListProvider();
await provider.load();
provider.add('Pollo');
provider.addAll(['pollo', 'Riso', 'RISO', 'Pasta']);
expect(provider.items.length, 3); // Pollo, Riso, Pasta
});
test('addAll() ignora elementi già presenti', () async {
final provider = ShoppingListProvider();
await provider.load();
provider.add('Riso');
provider.addAll(['Riso', 'Pasta']);
expect(provider.items.length, 2);
});
test('clearAll() svuota la lista', () async {
final provider = ShoppingListProvider();
await provider.load();
provider.add('Pollo');
provider.add('Riso');
provider.clearAll();
expect(provider.items, isEmpty);
});
test('persiste e ricarica i dati', () async {
final provider1 = ShoppingListProvider();
await provider1.load();
provider1.add('Merluzzo');
await Future.delayed(const Duration(milliseconds: 50));
final provider2 = ShoppingListProvider();
await provider2.load();
expect(provider2.items.first.name, 'Merluzzo');
});
}
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:biteplan/features/shopping_list/presentation/widgets/shopping_item_tile.dart';
import 'package:biteplan/features/shopping_list/models/shopping_item.dart';
import '../../../helpers/pump_app.dart';
Widget _tile({
required ShoppingItem item,
VoidCallback? onToggle,
VoidCallback? onRemove,
}) =>
Scaffold(
body: ShoppingItemTile(
item: item,
onToggle: onToggle ?? () {},
onRemove: onRemove ?? () {},
),
);
void main() {
testWidgets('mostra il nome dell\'elemento', (tester) async {
await tester.pumpApp(_tile(item: const ShoppingItem(id: '1', name: 'Pollo')));
expect(find.text('Pollo'), findsOneWidget);
});
testWidgets('checkbox non spuntato quando checked è false', (tester) async {
await tester.pumpApp(_tile(item: const ShoppingItem(id: '1', name: 'Riso')));
final checkbox = tester.widget<Checkbox>(find.byType(Checkbox));
expect(checkbox.value, false);
});
testWidgets('checkbox spuntato quando checked è true', (tester) async {
await tester.pumpApp(
_tile(item: const ShoppingItem(id: '1', name: 'Riso', checked: true)));
final checkbox = tester.widget<Checkbox>(find.byType(Checkbox));
expect(checkbox.value, true);
});
testWidgets('chiama onToggle al tap sul checkbox', (tester) async {
var toggled = false;
await tester.pumpApp(_tile(
item: const ShoppingItem(id: '1', name: 'Pollo'),
onToggle: () => toggled = true,
));
await tester.tap(find.byType(Checkbox));
expect(toggled, true);
});
testWidgets('chiama onRemove al tap sul pulsante chiudi', (tester) async {
var removed = false;
await tester.pumpApp(_tile(
item: const ShoppingItem(id: '1', name: 'Pasta'),
onRemove: () => removed = true,
));
await tester.tap(find.byIcon(Icons.close));
expect(removed, true);
});
testWidgets('testo barrato quando elemento è completato', (tester) async {
await tester.pumpApp(
_tile(item: const ShoppingItem(id: '1', name: 'Tonno', checked: true)));
final textWidget = tester.widget<Text>(find.text('Tonno'));
expect(textWidget.style?.decoration, TextDecoration.lineThrough);
});
}
+9
View File
@@ -0,0 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:biteplan/core/theme/app_theme.dart';
extension PumpApp on WidgetTester {
Future<void> pumpApp(Widget widget) => pumpWidget(
MaterialApp(theme: AppTheme.light(), home: widget),
);
}