Add widget tests for TaskTile and FilterChips
TaskTile tests: display, interactions, popup menu, delete confirmation, priority colors. FilterChips tests: display, selection, visual state changes.
This commit is contained in:
parent
4ed703506b
commit
a89cb3393d
100
test/features/tasks/presentation/widgets/filter_chips_test.dart
Normal file
100
test/features/tasks/presentation/widgets/filter_chips_test.dart
Normal file
@ -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<FilterChip>(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<FilterChip>(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);
|
||||
});
|
||||
});
|
||||
}
|
||||
249
test/features/tasks/presentation/widgets/task_tile_test.dart
Normal file
249
test/features/tasks/presentation/widgets/task_tile_test.dart
Normal file
@ -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<Checkbox>(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<Checkbox>(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<String>));
|
||||
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<String>));
|
||||
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<String>));
|
||||
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<String>));
|
||||
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<Text>(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<Container>(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<Container>(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<Container>(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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user