Todo repository protocol (#8)
This PR contains the work done to decouple the direct use of the `ModelContext` from `SwiftData` from the view model that uses it. In addition, the project transitioned to use Swift 6 and strict concurrency which brought some issues with the unit tests, so they were amended as well. Reviewed-on: rock-n-code/todos-app#8 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit was merged in pull request #8.
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
import Testing
|
||||
|
||||
@testable import Todos
|
||||
|
||||
@MainActor
|
||||
@Suite("TaskList view model tests")
|
||||
struct TaskListViewModelTests {
|
||||
|
||||
@@ -12,7 +11,7 @@ struct TaskListViewModelTests {
|
||||
|
||||
@Test(
|
||||
"properties are set correctly during initialization",
|
||||
arguments: TodoGroup.allCases
|
||||
arguments: [TodoGroup.today, .upcoming, .overdue, .completed]
|
||||
)
|
||||
func initialization(for group: TodoGroup) {
|
||||
// GIVEN
|
||||
@@ -28,7 +27,7 @@ struct TaskListViewModelTests {
|
||||
|
||||
@Test(
|
||||
"buttonCompleted returns correct values when showCompleted is true",
|
||||
arguments: TodoGroup.allCases, [true, false]
|
||||
arguments: [TodoGroup.today, .upcoming, .overdue, .completed], [true, false]
|
||||
)
|
||||
func buttonCompleted(
|
||||
for group: TodoGroup,
|
||||
@@ -56,7 +55,7 @@ struct TaskListViewModelTests {
|
||||
@Test(
|
||||
"canShowCompleted returns correct value for each group",
|
||||
arguments: zip(
|
||||
TodoGroup.allCases,
|
||||
[TodoGroup.today, .upcoming, .overdue, .completed],
|
||||
[true, true, true, false]
|
||||
)
|
||||
)
|
||||
@@ -76,7 +75,7 @@ struct TaskListViewModelTests {
|
||||
|
||||
@Test(
|
||||
"navigationTitle returns correct view title for each group",
|
||||
arguments: TodoGroup.allCases
|
||||
arguments: [TodoGroup.today, .upcoming, .overdue, .completed]
|
||||
)
|
||||
func navigationTitle(for group: TodoGroup) {
|
||||
// GIVEN
|
||||
@@ -91,43 +90,59 @@ struct TaskListViewModelTests {
|
||||
|
||||
// MARK: Method tests
|
||||
|
||||
@Test("deleteTasks removes a single task from model context")
|
||||
func deleteTasksSingle() throws {
|
||||
@Test("delete removes a single todo task from repository")
|
||||
func deleteSingleTodo() {
|
||||
// GIVEN
|
||||
let container = try ModelContainer(
|
||||
for: Todo.self,
|
||||
configurations: .init(isStoredInMemoryOnly: true)
|
||||
)
|
||||
let context = ModelContext(container)
|
||||
|
||||
let viewModel = TaskListView.ViewModel(group: .today)
|
||||
let todosToInsert: [Todo] = [
|
||||
let todos: [Todo] = [
|
||||
.init(title: "Task 1", due: .now, order: 0),
|
||||
.init(title: "Task 2", due: .now, order: 1),
|
||||
.init(title: "Task 3", due: .now, order: 2),
|
||||
]
|
||||
let countInitial = todosToInsert.count
|
||||
|
||||
for todo in todosToInsert {
|
||||
context.insert(todo)
|
||||
}
|
||||
let repository = MockTodoRepository(todos: todos)
|
||||
let viewModel = TaskListView.ViewModel(group: .today)
|
||||
|
||||
// WHEN
|
||||
viewModel.deleteTasks(
|
||||
todosToInsert,
|
||||
viewModel.delete(
|
||||
todos,
|
||||
at: IndexSet(integer: 1),
|
||||
from: context
|
||||
from: repository
|
||||
)
|
||||
|
||||
// THEN
|
||||
let descriptor = FetchDescriptor<Todo>()
|
||||
let todos = try context.fetch(descriptor)
|
||||
#expect(repository.todos.count == 2)
|
||||
#expect(repository.todos.contains { $0.title == "Task 1" })
|
||||
#expect(repository.todos.contains { $0.title == "Task 3" })
|
||||
#expect(!repository.todos.contains { $0.title == "Task 2" })
|
||||
}
|
||||
|
||||
#expect(countInitial == 3)
|
||||
#expect(todos.count == 2)
|
||||
#expect(todos.contains(where: { $0.title == "Task 1" }))
|
||||
#expect(todos.contains(where: { $0.title == "Task 3" }))
|
||||
#expect(!todos.contains(where: { $0.title == "Task 2" }))
|
||||
@Test("delete removes multiple todo tasks from repository")
|
||||
func deleteMultipleTodos() {
|
||||
// GIVEN
|
||||
let todos: [Todo] = [
|
||||
.init(title: "Task 1", due: .now, order: 0),
|
||||
.init(title: "Task 2", due: .now, order: 1),
|
||||
.init(title: "Task 3", due: .now, order: 2),
|
||||
.init(title: "Task 4", due: .now, order: 3),
|
||||
.init(title: "Task 5", due: .now, order: 4),
|
||||
]
|
||||
|
||||
let repository = MockTodoRepository(todos: todos)
|
||||
let viewModel = TaskListView.ViewModel(group: .today)
|
||||
|
||||
// WHEN
|
||||
viewModel.delete(
|
||||
todos,
|
||||
at: IndexSet([1, 3]),
|
||||
from: repository
|
||||
)
|
||||
|
||||
// THEN
|
||||
#expect(repository.todos.count == 3)
|
||||
#expect(repository.todos.contains { $0.title == "Task 1" })
|
||||
#expect(repository.todos.contains { $0.title == "Task 3" })
|
||||
#expect(repository.todos.contains { $0.title == "Task 5" })
|
||||
#expect(!repository.todos.contains { $0.title == "Task 2" })
|
||||
#expect(!repository.todos.contains { $0.title == "Task 4" })
|
||||
}
|
||||
|
||||
@Test("reorderTasks updates order property when moving task forward")
|
||||
@@ -180,7 +195,7 @@ struct TaskListViewModelTests {
|
||||
#expect(tasks[3].order == 1)
|
||||
}
|
||||
|
||||
@Test("toggleCompletedVisbility toggles the showCompleted property")
|
||||
@Test("toggleCompletedVisibility toggles the showCompleted property")
|
||||
func toggleCompletedVisibility() {
|
||||
// GIVEN
|
||||
let viewModel = TaskListView.ViewModel(group: .today)
|
||||
|
||||
Reference in New Issue
Block a user