From 937941f50b5f7ab2bdcf9f2313953f37fe44ed94 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 10 Feb 2026 03:18:56 +0100 Subject: [PATCH] Implemented the view refresh after data updates for the TaskGroupListView view in the app target. --- .../View Models/TaskGroupListViewModel.swift | 51 ++++++++++--------- App/Sources/Views/TaskGroupListView.swift | 14 +++-- .../TaskGroupListViewModelTests.swift | 35 +++++-------- 3 files changed, 45 insertions(+), 55 deletions(-) diff --git a/App/Sources/View Models/TaskGroupListViewModel.swift b/App/Sources/View Models/TaskGroupListViewModel.swift index 8537307..e420f98 100644 --- a/App/Sources/View Models/TaskGroupListViewModel.swift +++ b/App/Sources/View Models/TaskGroupListViewModel.swift @@ -45,41 +45,42 @@ extension TaskGroupListView { // MARK: Functions - /// Calculates the number of tasks for a given task group using SwiftData. + /// Calculates the number of tasks for a given task group from an in-memory collection. /// - /// This method performs an efficient database-level count using SwiftData's `fetchCount` method with predicate-based filtering. - /// Tasks are categorized based on their due date and completion status. + /// This method filters the provided tasks array to count only those tasks that belong + /// to the specified group based on their due date and completion status. Tasks are + /// categorized as follows: + /// + /// - **Completed**: Tasks with a non-nil `completed` date + /// - **Overdue**: Incomplete tasks due before today + /// - **Today**: Incomplete tasks due today (from start of day to end of day) + /// - **Upcoming**: Incomplete tasks due tomorrow or later /// /// - Parameters: + /// - tasks: The array of tasks to filter and count. /// - group: The task group to count tasks for. - /// - modelContext: The SwiftData model context to query. - /// - Returns: The number of tasks in the specified group, or 0 if an error occurs. + /// - Returns: The number of tasks in the specified group. func count( - for group: TodoGroup, - in modelContext: ModelContext + _ tasks: [Todo], + for group: TodoGroup ) -> Int { let today = calendar.startOfDay(for: .now) let tomorrow = calendar.date(byAdding: .day, value: 1, to: today) ?? .now - let fetchDescriptor: FetchDescriptor = - switch group { - case .completed: - .init(predicate: #Predicate { $0.completed != nil }) - case .overdue: - .init(predicate: #Predicate { $0.completed == nil && $0.due < today }) - case .today: - .init(predicate: #Predicate { $0.completed == nil && $0.due >= today && $0.due < tomorrow }) - case .upcoming: - .init(predicate: #Predicate { $0.completed == nil && $0.due >= tomorrow }) + return tasks + .filter { task in + switch group { + case .completed: + task.completed != nil + case .overdue: + task.completed == nil && task.due < today + case .today: + task.completed == nil && task.due >= today && task.due < tomorrow + case .upcoming: + task.completed == nil && task.due >= tomorrow + } } - - do { - return try modelContext.fetchCount(fetchDescriptor) - } - catch { - print("Error fetching count for group \(group): \(error)") - return 0 - } + .count } } diff --git a/App/Sources/Views/TaskGroupListView.swift b/App/Sources/Views/TaskGroupListView.swift index a334709..da3a86d 100644 --- a/App/Sources/Views/TaskGroupListView.swift +++ b/App/Sources/Views/TaskGroupListView.swift @@ -12,11 +12,12 @@ struct TaskGroupListView: View { // MARK: Properties - /// The SwiftData model context for database operations. + /// Query to monitor all todo tasks for changes. /// - /// This context is used by the view model to perform efficient database-level queries for counting tasks in each group. - @Environment(\.modelContext) - private var modelContext + /// This query triggers view updates whenever todo tasks are inserted, updated, or deleted, + /// ensuring the task counts displayed in the view remain accurate. + @Query + private var tasks: [Todo] /// The view model managing the state and logic for this view. /// @@ -43,10 +44,7 @@ struct TaskGroupListView: View { NavigationLink(destination: TaskListView(group: group)) { Card( group: group, - count: viewModel.count( - for: group, - in: modelContext - ) + count: viewModel.count(tasks, for: group) ) } .buttonStyle(.plain) diff --git a/UnitTests/Tests/View Models/TaskGroupListViewModelTests.swift b/UnitTests/Tests/View Models/TaskGroupListViewModelTests.swift index f3251c9..7ee98fd 100644 --- a/UnitTests/Tests/View Models/TaskGroupListViewModelTests.swift +++ b/UnitTests/Tests/View Models/TaskGroupListViewModelTests.swift @@ -31,58 +31,49 @@ struct TaskGroupListViewModelTests { // MARK: Functions tests - @MainActor @Test( "count returns zero when empty data", arguments: TodoGroup.allCases ) func countWhenEmptyData(for group: TodoGroup) throws { // GIVEN - let container = try ModelContainer( - for: Todo.self, - configurations: .init(isStoredInMemoryOnly: true) - ) + let tasks: [Todo] = [] let viewModel = TaskGroupListView.ViewModel() // WHEN - let count = viewModel.count(for: group, in: container.mainContext) + let count = viewModel.count(tasks, for: group) // THEN #expect(count == 0) } - @MainActor @Test( "count returns correct count when filled data", arguments: TodoGroup.allCases ) func countWhenFilledData(for group: TodoGroup) throws { // GIVEN - let container = try ModelContainer( - for: Todo.self, - configurations: .init(isStoredInMemoryOnly: true) - ) - let context = container.mainContext - let today: Date = .now let tomorrow = calendar.date(byAdding: .day, value: 1, to: today) ?? today let yesterday = calendar.date(byAdding: .day, value: -1, to: today) ?? today let nextWeek = calendar.date(byAdding: .day, value: 7, to: today) ?? today let lastWeek = calendar.date(byAdding: .day, value: -7, to: today) ?? today - context.insert(Todo(title: "Today task #1", due: today)) - context.insert(Todo(title: "Today task #2", due: today)) - context.insert(Todo(title: "Tomorrow task #1", due: tomorrow)) - context.insert(Todo(title: "Next week task #1", due: nextWeek)) - context.insert(Todo(title: "Yesterday task #1", due: yesterday)) - context.insert(Todo(title: "Last week task #1", due: lastWeek)) - context.insert(Todo(title: "Completed task #1", due: tomorrow, completed: today)) - context.insert(Todo(title: "Completed task #2", due: yesterday, completed: today)) + let tasks = [ + Todo(title: "Today task #1", due: today), + Todo(title: "Today task #2", due: today), + Todo(title: "Tomorrow task #1", due: tomorrow), + Todo(title: "Next week task #1", due: nextWeek), + Todo(title: "Yesterday task #1", due: yesterday), + Todo(title: "Last week task #1", due: lastWeek), + Todo(title: "Completed task #1", due: tomorrow, completed: today), + Todo(title: "Completed task #2", due: yesterday, completed: today) + ] let viewModel = TaskGroupListView.ViewModel() // WHEN - let count = viewModel.count(for: group, in: context) + let count = viewModel.count(tasks, for: group) // THEN #expect(count == 2)