From 5b520eb5ae2f7228127d7f697a202d5388ac74f1 Mon Sep 17 00:00:00 2001 From: hunteraraujo Date: Mon, 21 Aug 2023 00:03:39 +0200 Subject: [PATCH] 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. --- lib/viewmodels/mock_data.dart | 20 ++++++++ lib/viewmodels/task_viewmodel.dart | 68 +++++++++++++++++++++++++ pubspec.lock | 40 +++++++-------- pubspec.yaml | 1 + test/task_viewmodel_test.dart | 79 ++++++++++++++++++++++++++++++ 5 files changed, 188 insertions(+), 20 deletions(-) create mode 100644 lib/viewmodels/mock_data.dart create mode 100644 test/task_viewmodel_test.dart diff --git a/lib/viewmodels/mock_data.dart b/lib/viewmodels/mock_data.dart new file mode 100644 index 00000000..7f553d78 --- /dev/null +++ b/lib/viewmodels/mock_data.dart @@ -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 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); +} diff --git a/lib/viewmodels/task_viewmodel.dart b/lib/viewmodels/task_viewmodel.dart index e69de29b..96c68cbf 100644 --- a/lib/viewmodels/task_viewmodel.dart +++ b/lib/viewmodels/task_viewmodel.dart @@ -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 _tasks = []; + Task? _selectedTask; // This will store the currently selected task + + /// Returns the list of tasks. + List 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); + } + } +} diff --git a/pubspec.lock b/pubspec.lock index e2e249ec..1368f2b3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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" diff --git a/pubspec.yaml b/pubspec.yaml index 5a18aaa6..939d0d04 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,7 @@ environment: dependencies: flutter: sdk: flutter + collection: ^1.15.0 # The following adds the Cupertino Icons font to your application. diff --git a/test/task_viewmodel_test.dart b/test/task_viewmodel_test.dart new file mode 100644 index 00000000..13828574 --- /dev/null +++ b/test/task_viewmodel_test.dart @@ -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())); + }); + }); +}