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:
2026-02-11 18:22:26 +00:00
committed by javier
parent b8e6e515dc
commit bbd7ec264b
13 changed files with 187 additions and 115 deletions
@@ -0,0 +1,12 @@
import SwiftData
/// Extends ModelContext to conform to the TodoRepository protocol.
///
/// This extension allows ModelContext from SwiftData to be used as a todo repository,
/// enabling dependency injection and facilitating unit testing by allowing mock
/// repositories to be substituted during tests.
///
/// The ModelContext methods `delete(_:)` and `insert(_:)` already match the
/// TodoRepository protocol requirements, so this extension simply declares conformance
/// without requiring additional implementation.
extension ModelContext: TodoRepository {}
@@ -0,0 +1,28 @@
import SwiftData
/// A protocol defining the interface for a todo repository.
///
/// This protocol abstracts the underlying data persistence mechanism for todo tasks,
/// allowing for dependency injection and easier testing. Implementations handle the
/// storage and retrieval of Todo objects.
protocol TodoRepository {
/// Deletes a todo task from the repository.
///
/// This method removes the specified todo from persistent storage. The deletion
/// is typically performed immediately, though the actual persistence may be deferred
/// until a save operation occurs depending on the implementation.
///
/// - Parameter todo: The todo task to delete from the repository.
func delete(_ todo: Todo)
/// Inserts a new todo task into the repository.
///
/// This method adds a new todo to persistent storage. The todo is typically
/// tracked immediately, though the actual persistence may be deferred until a
/// save operation occurs depending on the implementation.
///
/// - Parameter todo: The todo task to insert into the repository.
func insert(_ todo: Todo)
}
@@ -57,14 +57,14 @@ extension NewTaskView {
// MARK: Functions
/// Saves a new todo task to the SwiftData model context.
/// Saves a new todo task to the repository.
///
/// This function creates a new `Todo` instance with the current property values and inserts it into the provided model context.
/// This function creates a new `Todo` instance with the current property values and inserts it into the provided repository.
/// The title and note are trimmed of leading and trailing whitespace before saving.
/// The `note` property value is only included if it's not empty after trimming.
///
/// - Parameter modelContext: The SwiftData model context to insert a new todo task.
func save(to modelContext: ModelContext) {
/// - Parameter repository: The repository to insert a new todo task.
func save(to repository: TodoRepository) {
let trimmedTitle = title.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedNote = note.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -74,7 +74,7 @@ extension NewTaskView {
due: due
)
modelContext.insert(todo)
repository.insert(todo)
}
}
@@ -87,21 +87,21 @@ extension TaskListView {
/// Deletes tasks at the specified indices.
///
/// This method removes tasks from the model context after a swipe-to-delete action.
/// The deletion is performed through SwiftData's model context, which handles
/// This method removes tasks from the repository after a swipe-to-delete action.
/// The deletion is performed through the repository, which handles
/// persistence and triggers automatic UI updates.
///
/// - Parameters:
/// - tasks: The current array of tasks.
/// - offsets: The index set of tasks to delete.
/// - modelContext: The SwiftData model context to perform the deletion.
func deleteTasks(
/// - repository: The repository to perform the deletion.
func delete(
_ tasks: [Todo],
at offsets: IndexSet,
from modelContext: ModelContext
from repository: TodoRepository
) {
for index in offsets {
modelContext.delete(tasks[index])
repository.delete(tasks[index])
}
}
+1 -1
View File
@@ -45,7 +45,7 @@ struct TaskListView: View {
group: viewModel.group,
showCompleted: viewModel.showCompleted
) { tasks, offsets in
viewModel.deleteTasks(
viewModel.delete(
tasks,
at: offsets,
from: modelContext