diff --git a/test/features/tasks/presentation/widgets/filter_chips_test.dart b/test/features/tasks/presentation/widgets/filter_chips_test.dart new file mode 100644 index 0000000..a5fb19e --- /dev/null +++ b/test/features/tasks/presentation/widgets/filter_chips_test.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:agenda_tasks/features/tasks/presentation/viewmodels/daily_tasks_viewmodel.dart'; +import 'package:agenda_tasks/features/tasks/presentation/widgets/filter_chips.dart'; + +import '../../../../helpers/test_helpers.dart'; + +void main() { + group('FilterChips', () { + late TaskFilter selectedFilter; + + Widget buildFilterChips({TaskFilter initialFilter = TaskFilter.all}) { + selectedFilter = initialFilter; + return createTestableWidget( + StatefulBuilder( + builder: (context, setState) { + return FilterChips( + currentFilter: selectedFilter, + onFilterChanged: (filter) { + setState(() { + selectedFilter = filter; + }); + }, + ); + }, + ), + ); + } + + testWidgets('should display all three filter options', (tester) async { + await tester.pumpWidget(buildFilterChips()); + await tester.pumpAndSettle(); + + expect(find.text('All'), findsOneWidget); + expect(find.text('Active'), findsOneWidget); + expect(find.text('Completed'), findsOneWidget); + }); + + testWidgets('should have "All" chip selected by default', (tester) async { + await tester.pumpWidget(buildFilterChips()); + await tester.pumpAndSettle(); + + final chips = tester.widgetList(find.byType(FilterChip)); + final allChip = chips.first; + + expect(allChip.selected, true); + }); + + testWidgets('should select "Active" chip when tapped', (tester) async { + await tester.pumpWidget(buildFilterChips()); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Active')); + await tester.pumpAndSettle(); + + expect(selectedFilter, TaskFilter.active); + }); + + testWidgets('should select "Completed" chip when tapped', (tester) async { + await tester.pumpWidget(buildFilterChips()); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Completed')); + await tester.pumpAndSettle(); + + expect(selectedFilter, TaskFilter.completed); + }); + + testWidgets('should select "All" chip when tapped from another filter', (tester) async { + await tester.pumpWidget(buildFilterChips(initialFilter: TaskFilter.active)); + await tester.pumpAndSettle(); + + await tester.tap(find.text('All')); + await tester.pumpAndSettle(); + + expect(selectedFilter, TaskFilter.all); + }); + + testWidgets('should visually indicate selected filter', (tester) async { + await tester.pumpWidget(buildFilterChips(initialFilter: TaskFilter.completed)); + await tester.pumpAndSettle(); + + final chips = tester.widgetList(find.byType(FilterChip)).toList(); + + // All chip should not be selected + expect(chips[0].selected, false); + // Active chip should not be selected + expect(chips[1].selected, false); + // Completed chip should be selected + expect(chips[2].selected, true); + }); + + testWidgets('should be horizontally scrollable', (tester) async { + await tester.pumpWidget(buildFilterChips()); + await tester.pumpAndSettle(); + + expect(find.byType(SingleChildScrollView), findsOneWidget); + }); + }); +} diff --git a/test/features/tasks/presentation/widgets/task_tile_test.dart b/test/features/tasks/presentation/widgets/task_tile_test.dart new file mode 100644 index 0000000..5b5d9a0 --- /dev/null +++ b/test/features/tasks/presentation/widgets/task_tile_test.dart @@ -0,0 +1,249 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:agenda_tasks/features/tasks/domain/entities/task_entity.dart'; +import 'package:agenda_tasks/features/tasks/domain/enums/priority.dart'; +import 'package:agenda_tasks/features/tasks/presentation/widgets/task_tile.dart'; + +import '../../../../helpers/test_helpers.dart'; + +void main() { + group('TaskTile', () { + late TaskEntity testTask; + late bool toggleCalled; + late bool tapCalled; + late bool deleteCalled; + late bool rescheduleCalled; + + setUp(() { + toggleCalled = false; + tapCalled = false; + deleteCalled = false; + rescheduleCalled = false; + + testTask = TaskEntity( + id: 'test-task-1', + title: 'Test Task Title', + description: 'Test task description', + date: DateTime(2026, 2, 3), + priority: Priority.medium, + isDone: false, + ); + }); + + Widget buildTaskTile({TaskEntity? task}) { + return createTestableWidget( + TaskTile( + task: task ?? testTask, + onToggle: () => toggleCalled = true, + onTap: () => tapCalled = true, + onDelete: () => deleteCalled = true, + onReschedule: () => rescheduleCalled = true, + ), + ); + } + + testWidgets('should display task title', (tester) async { + await tester.pumpWidget(buildTaskTile()); + await tester.pumpAndSettle(); + + expect(find.text('Test Task Title'), findsOneWidget); + }); + + testWidgets('should display task description', (tester) async { + await tester.pumpWidget(buildTaskTile()); + await tester.pumpAndSettle(); + + expect(find.text('Test task description'), findsOneWidget); + }); + + testWidgets('should not display description when null', (tester) async { + final taskWithoutDesc = TaskEntity( + id: 'test-task-2', + title: 'Task Without Description', + date: DateTime(2026, 2, 3), + ); + + await tester.pumpWidget(buildTaskTile(task: taskWithoutDesc)); + await tester.pumpAndSettle(); + + expect(find.text('Task Without Description'), findsOneWidget); + // Only title should be visible, no description text + }); + + testWidgets('should display checkbox unchecked when task is not done', (tester) async { + await tester.pumpWidget(buildTaskTile()); + await tester.pumpAndSettle(); + + final checkbox = tester.widget(find.byType(Checkbox)); + expect(checkbox.value, false); + }); + + testWidgets('should display checkbox checked when task is done', (tester) async { + final doneTask = testTask.copyWith(isDone: true); + + await tester.pumpWidget(buildTaskTile(task: doneTask)); + await tester.pumpAndSettle(); + + final checkbox = tester.widget(find.byType(Checkbox)); + expect(checkbox.value, true); + }); + + testWidgets('should call onToggle when checkbox is tapped', (tester) async { + await tester.pumpWidget(buildTaskTile()); + await tester.pumpAndSettle(); + + await tester.tap(find.byType(Checkbox)); + await tester.pumpAndSettle(); + + expect(toggleCalled, true); + }); + + testWidgets('should call onTap when card is tapped', (tester) async { + await tester.pumpWidget(buildTaskTile()); + await tester.pumpAndSettle(); + + // Tap on the task title area (InkWell) + await tester.tap(find.text('Test Task Title')); + await tester.pumpAndSettle(); + + expect(tapCalled, true); + }); + + testWidgets('should show popup menu when more button is tapped', (tester) async { + await tester.pumpWidget(buildTaskTile()); + await tester.pumpAndSettle(); + + // Find and tap the popup menu button + await tester.tap(find.byType(PopupMenuButton)); + await tester.pumpAndSettle(); + + // Menu items should be visible + expect(find.text('Move to tomorrow'), findsOneWidget); + expect(find.text('Delete'), findsOneWidget); + }); + + testWidgets('should call onReschedule when reschedule menu item is tapped', (tester) async { + await tester.pumpWidget(buildTaskTile()); + await tester.pumpAndSettle(); + + // Open popup menu + await tester.tap(find.byType(PopupMenuButton)); + await tester.pumpAndSettle(); + + // Tap reschedule option + await tester.tap(find.text('Move to tomorrow')); + await tester.pumpAndSettle(); + + expect(rescheduleCalled, true); + }); + + testWidgets('should show delete confirmation dialog when delete menu item is tapped', (tester) async { + await tester.pumpWidget(buildTaskTile()); + await tester.pumpAndSettle(); + + // Open popup menu + await tester.tap(find.byType(PopupMenuButton)); + await tester.pumpAndSettle(); + + // Tap delete option - find the one in the popup menu (second occurrence) + final deleteTexts = find.text('Delete'); + await tester.tap(deleteTexts.last); + await tester.pumpAndSettle(); + + // Confirmation dialog should appear + expect(find.text('Delete Task'), findsOneWidget); + expect(find.text('Are you sure you want to delete this task?'), findsOneWidget); + }); + + testWidgets('should call onDelete when delete is confirmed', (tester) async { + await tester.pumpWidget(buildTaskTile()); + await tester.pumpAndSettle(); + + // Open popup menu + await tester.tap(find.byType(PopupMenuButton)); + await tester.pumpAndSettle(); + + // Tap delete option in popup menu + final deleteTexts = find.text('Delete'); + await tester.tap(deleteTexts.last); + await tester.pumpAndSettle(); + + // Dialog should be visible - find Delete button in dialog (the TextButton) + final dialogDeleteButtons = find.widgetWithText(TextButton, 'Delete'); + await tester.tap(dialogDeleteButtons); + await tester.pumpAndSettle(); + + expect(deleteCalled, true); + }); + + testWidgets('should display strikethrough text when task is done', (tester) async { + final doneTask = testTask.copyWith(isDone: true); + + await tester.pumpWidget(buildTaskTile(task: doneTask)); + await tester.pumpAndSettle(); + + final titleFinder = find.text('Test Task Title'); + expect(titleFinder, findsOneWidget); + + final titleWidget = tester.widget(titleFinder); + expect(titleWidget.style?.decoration, TextDecoration.lineThrough); + }); + + group('priority colors', () { + testWidgets('should display red indicator for high priority', (tester) async { + final highPriorityTask = testTask.copyWith(priority: Priority.high); + + await tester.pumpWidget(buildTaskTile(task: highPriorityTask)); + await tester.pumpAndSettle(); + + // Find the priority indicator container + final containers = tester.widgetList(find.byType(Container)); + final priorityIndicator = containers.where((c) { + final decoration = c.decoration; + if (decoration is BoxDecoration && c.constraints?.maxWidth == 5) { + return decoration.color == Colors.red.shade700; + } + return false; + }); + + expect(priorityIndicator, isNotEmpty); + }); + + testWidgets('should display orange indicator for medium priority', (tester) async { + final mediumPriorityTask = testTask.copyWith(priority: Priority.medium); + + await tester.pumpWidget(buildTaskTile(task: mediumPriorityTask)); + await tester.pumpAndSettle(); + + final containers = tester.widgetList(find.byType(Container)); + final priorityIndicator = containers.where((c) { + final decoration = c.decoration; + if (decoration is BoxDecoration && c.constraints?.maxWidth == 5) { + return decoration.color == Colors.orange.shade700; + } + return false; + }); + + expect(priorityIndicator, isNotEmpty); + }); + + testWidgets('should display green indicator for low priority', (tester) async { + final lowPriorityTask = testTask.copyWith(priority: Priority.low); + + await tester.pumpWidget(buildTaskTile(task: lowPriorityTask)); + await tester.pumpAndSettle(); + + final containers = tester.widgetList(find.byType(Container)); + final priorityIndicator = containers.where((c) { + final decoration = c.decoration; + if (decoration is BoxDecoration && c.constraints?.maxWidth == 5) { + return decoration.color == Colors.green.shade700; + } + return false; + }); + + expect(priorityIndicator, isNotEmpty); + }); + }); + }); +}