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())); + }); + }); +}