Implement TaskViewModel with tests and mock data

This commit introduces the TaskViewModel, which manages the business logic for tasks. The ViewModel interacts with a mock data source, providing functionalities like fetching tasks, selecting a task, creating, and deleting tasks.

Additionally, comprehensive tests for TaskViewModel have been added to ensure its behavior aligns with expectations. The mock data source has also been updated to support the new functionalities.

Key Features:
- Task management in TaskViewModel.
- Tests for each major functionality in TaskViewModel.
- Mock data source to simulate data interactions.
This commit is contained in:
hunteraraujo
2023-08-21 00:03:39 +02:00
parent fb946e3d57
commit 5b520eb5ae
5 changed files with 188 additions and 20 deletions

View File

@@ -0,0 +1,20 @@
import 'package:auto_gpt_flutter_client/models/task.dart';
/// A list of mock tasks for the application.
/// TODO: Remove this file when we implement TaskService
List<Task> mockTasks = [
Task(id: 1, title: 'Task 1'),
Task(id: 2, title: 'Task 2'),
Task(id: 3, title: 'Task 3'),
// ... add more mock tasks as needed
];
/// Adds a task to the mock data.
void addTask(Task task) {
mockTasks.add(task);
}
/// Removes a task from the mock data based on its ID.
void removeTask(int id) {
mockTasks.removeWhere((task) => task.id == id);
}

View File

@@ -0,0 +1,68 @@
import 'package:auto_gpt_flutter_client/models/task.dart';
import 'package:auto_gpt_flutter_client/viewmodels/mock_data.dart';
import 'package:flutter/foundation.dart';
import 'package:collection/collection.dart';
// TODO: Update whole class once we have created TaskService
class TaskViewModel with ChangeNotifier {
List<Task> _tasks = [];
Task? _selectedTask; // This will store the currently selected task
/// Returns the list of tasks.
List<Task> get tasks => _tasks;
/// Returns the currently selected task.
Task? get selectedTask => _selectedTask;
/// Adds a task.
void createTask(String title) {
// Generate an ID (This is a simplistic approach for mock data)
final id = _tasks.length + 1;
final newTask = Task(id: id, title: title);
// Add to data source
addTask(newTask);
// Update local tasks list and notify listeners
_tasks.add(newTask);
notifyListeners();
}
/// Deletes a task.
void deleteTask(int id) {
// Remove from data source
removeTask(id);
// Update local tasks list and notify listeners
_tasks.removeWhere((task) => task.id == id);
notifyListeners();
}
/// Fetches tasks from the data source.
void fetchTasks() {
try {
_tasks = mockTasks;
notifyListeners(); // Notify listeners to rebuild UI
print("Tasks fetched successfully!");
} catch (error) {
print("Error fetching tasks: $error");
// TODO: Handle additional error scenarios or log them as required
}
}
/// Handles the selection of a task by its ID.
void selectTask(int id) {
final task = _tasks.firstWhereOrNull((t) => t.id == id);
if (task != null) {
_selectedTask = task;
print("Selected task with ID: ${task.id} and Title: ${task.title}");
notifyListeners(); // Notify listeners to rebuild UI
} else {
final errorMessage =
"Error: Attempted to select a task with ID: $id that does not exist in the data source.";
print(errorMessage);
throw ArgumentError(errorMessage);
}
}
}

View File

@@ -34,13 +34,13 @@ packages:
source: hosted
version: "1.1.1"
collection:
dependency: transitive
dependency: "direct main"
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev"
source: hosted
version: "1.17.1"
version: "1.17.2"
cupertino_icons:
dependency: "direct main"
description:
@@ -75,14 +75,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
lints:
dependency: transitive
description:
@@ -95,18 +87,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
version: "0.12.16"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.5.0"
meta:
dependency: transitive
description:
@@ -132,10 +124,10 @@ packages:
dependency: transitive
description:
name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
stack_trace:
dependency: transitive
description:
@@ -172,10 +164,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
version: "0.6.0"
vector_math:
dependency: transitive
description:
@@ -184,5 +176,13 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
sdks:
dart: ">=3.0.0-417.2.beta <4.0.0"
dart: ">=3.1.0-185.0.dev <4.0.0"

View File

@@ -30,6 +30,7 @@ environment:
dependencies:
flutter:
sdk: flutter
collection: ^1.15.0
# The following adds the Cupertino Icons font to your application.

View File

@@ -0,0 +1,79 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:auto_gpt_flutter_client/viewmodels/task_viewmodel.dart';
import 'package:auto_gpt_flutter_client/viewmodels/mock_data.dart';
void main() {
group('TaskViewModel', () {
late TaskViewModel viewModel;
setUp(() {
viewModel = TaskViewModel();
});
test('Fetches tasks successfully', () {
viewModel.fetchTasks();
expect(viewModel.tasks, isNotEmpty);
});
test('Selects a task successfully', () {
viewModel.fetchTasks();
viewModel.selectTask(1);
expect(viewModel.selectedTask, isNotNull);
});
test(
'Notifiers are properly telling UI to update after fetching a task or selecting a task',
() {
bool hasNotified = false;
viewModel.addListener(() {
hasNotified = true;
});
viewModel.fetchTasks();
expect(hasNotified, true);
hasNotified = false; // Reset for next test
viewModel.selectTask(1);
expect(hasNotified, true);
});
test('Simulate error happening while fetching a task', () {
// TODO: Implement once you have error handling in place in fetchTasks.
});
test('No tasks are fetched', () {
// Clear mock data for this test
mockTasks.clear();
viewModel.fetchTasks();
expect(viewModel.tasks, isEmpty);
});
test('No task is selected', () {
expect(viewModel.selectedTask, isNull);
});
test('Creates a task successfully', () {
final initialCount = viewModel.tasks.length;
viewModel.createTask('New Task');
expect(viewModel.tasks.length, initialCount + 1);
});
test('Deletes a task successfully', () {
viewModel.fetchTasks();
final initialCount = viewModel.tasks.length;
viewModel.deleteTask(1);
expect(viewModel.tasks.length, initialCount - 1);
});
test('Deletes a task with invalid id', () {
final initialCount = viewModel.tasks.length;
viewModel.deleteTask(9999); // Assuming no task with this id exists
expect(viewModel.tasks.length, initialCount); // Count remains same
});
test('Select a task that doesn\'t exist', () {
expect(() => viewModel.selectTask(9999), throwsA(isA<ArgumentError>()));
});
});
}