Implement batch task completion feature allowing users to select and complete multiple care tasks at once. Adds edit mode to Today View with selection checkmarks, floating BatchActionBar, and confirmation dialog for completing more than 3 tasks. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
202 lines
5.8 KiB
Swift
202 lines
5.8 KiB
Swift
//
|
|
// MockCareScheduleRepository.swift
|
|
// PlantGuideTests
|
|
//
|
|
// Mock implementation of CareScheduleRepositoryProtocol for unit testing.
|
|
// Provides configurable behavior and call tracking for verification.
|
|
//
|
|
|
|
import Foundation
|
|
@testable import PlantGuide
|
|
|
|
// MARK: - MockCareScheduleRepository
|
|
|
|
/// Mock implementation of CareScheduleRepositoryProtocol for testing
|
|
final class MockCareScheduleRepository: CareScheduleRepositoryProtocol, @unchecked Sendable {
|
|
|
|
// MARK: - Storage
|
|
|
|
var schedules: [UUID: PlantCareSchedule] = [:]
|
|
|
|
// MARK: - Call Tracking
|
|
|
|
var saveCallCount = 0
|
|
var fetchForPlantCallCount = 0
|
|
var fetchAllCallCount = 0
|
|
var fetchAllTasksCallCount = 0
|
|
var updateTaskCallCount = 0
|
|
var deleteCallCount = 0
|
|
var batchCompleteCallCount = 0
|
|
|
|
// MARK: - Error Configuration
|
|
|
|
var shouldThrowOnSave = false
|
|
var shouldThrowOnFetch = false
|
|
var shouldThrowOnFetchAll = false
|
|
var shouldThrowOnFetchAllTasks = false
|
|
var shouldThrowOnUpdateTask = false
|
|
var shouldThrowOnDelete = false
|
|
var shouldThrowOnBatchComplete = false
|
|
|
|
var errorToThrow: Error = NSError(
|
|
domain: "MockError",
|
|
code: -1,
|
|
userInfo: [NSLocalizedDescriptionKey: "Mock care schedule repository error"]
|
|
)
|
|
|
|
// MARK: - Captured Values
|
|
|
|
var lastSavedSchedule: PlantCareSchedule?
|
|
var lastFetchedPlantID: UUID?
|
|
var lastUpdatedTask: CareTask?
|
|
var lastDeletedPlantID: UUID?
|
|
var lastBatchCompletedTaskIDs: Set<UUID>?
|
|
|
|
// MARK: - CareScheduleRepositoryProtocol
|
|
|
|
func save(_ schedule: PlantCareSchedule) async throws {
|
|
saveCallCount += 1
|
|
lastSavedSchedule = schedule
|
|
if shouldThrowOnSave {
|
|
throw errorToThrow
|
|
}
|
|
schedules[schedule.plantID] = schedule
|
|
}
|
|
|
|
func fetch(for plantID: UUID) async throws -> PlantCareSchedule? {
|
|
fetchForPlantCallCount += 1
|
|
lastFetchedPlantID = plantID
|
|
if shouldThrowOnFetch {
|
|
throw errorToThrow
|
|
}
|
|
return schedules[plantID]
|
|
}
|
|
|
|
func fetchAll() async throws -> [PlantCareSchedule] {
|
|
fetchAllCallCount += 1
|
|
if shouldThrowOnFetchAll {
|
|
throw errorToThrow
|
|
}
|
|
return Array(schedules.values)
|
|
}
|
|
|
|
func fetchAllTasks() async throws -> [CareTask] {
|
|
fetchAllTasksCallCount += 1
|
|
if shouldThrowOnFetchAllTasks {
|
|
throw errorToThrow
|
|
}
|
|
return schedules.values.flatMap { $0.tasks }
|
|
}
|
|
|
|
func updateTask(_ task: CareTask) async throws {
|
|
updateTaskCallCount += 1
|
|
lastUpdatedTask = task
|
|
if shouldThrowOnUpdateTask {
|
|
throw errorToThrow
|
|
}
|
|
|
|
// Find and update the task in the appropriate schedule
|
|
for (plantID, var schedule) in schedules {
|
|
if let index = schedule.tasks.firstIndex(where: { $0.id == task.id }) {
|
|
schedule.tasks[index] = task
|
|
schedules[plantID] = schedule
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func delete(for plantID: UUID) async throws {
|
|
deleteCallCount += 1
|
|
lastDeletedPlantID = plantID
|
|
if shouldThrowOnDelete {
|
|
throw errorToThrow
|
|
}
|
|
schedules.removeValue(forKey: plantID)
|
|
}
|
|
|
|
func batchCompleteTasks(_ taskIDs: Set<UUID>) async throws {
|
|
batchCompleteCallCount += 1
|
|
lastBatchCompletedTaskIDs = taskIDs
|
|
if shouldThrowOnBatchComplete {
|
|
throw errorToThrow
|
|
}
|
|
|
|
let now = Date()
|
|
for (plantID, var schedule) in schedules {
|
|
var modified = false
|
|
for (index, task) in schedule.tasks.enumerated() {
|
|
if taskIDs.contains(task.id) && !task.isCompleted {
|
|
schedule.tasks[index] = CareTask(
|
|
id: task.id,
|
|
plantID: task.plantID,
|
|
type: task.type,
|
|
scheduledDate: task.scheduledDate,
|
|
completedDate: now,
|
|
notes: task.notes
|
|
)
|
|
modified = true
|
|
}
|
|
}
|
|
if modified {
|
|
schedules[plantID] = schedule
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Helper Methods
|
|
|
|
/// Resets all state for clean test setup
|
|
func reset() {
|
|
schedules = [:]
|
|
|
|
saveCallCount = 0
|
|
fetchForPlantCallCount = 0
|
|
fetchAllCallCount = 0
|
|
fetchAllTasksCallCount = 0
|
|
updateTaskCallCount = 0
|
|
deleteCallCount = 0
|
|
batchCompleteCallCount = 0
|
|
|
|
shouldThrowOnSave = false
|
|
shouldThrowOnFetch = false
|
|
shouldThrowOnFetchAll = false
|
|
shouldThrowOnFetchAllTasks = false
|
|
shouldThrowOnUpdateTask = false
|
|
shouldThrowOnDelete = false
|
|
shouldThrowOnBatchComplete = false
|
|
|
|
lastSavedSchedule = nil
|
|
lastFetchedPlantID = nil
|
|
lastUpdatedTask = nil
|
|
lastDeletedPlantID = nil
|
|
lastBatchCompletedTaskIDs = nil
|
|
}
|
|
|
|
/// Adds a schedule directly to storage (bypasses save method)
|
|
func addSchedule(_ schedule: PlantCareSchedule) {
|
|
schedules[schedule.plantID] = schedule
|
|
}
|
|
|
|
/// Adds multiple schedules directly to storage
|
|
func addSchedules(_ schedulesToAdd: [PlantCareSchedule]) {
|
|
for schedule in schedulesToAdd {
|
|
schedules[schedule.plantID] = schedule
|
|
}
|
|
}
|
|
|
|
/// Gets all tasks for a specific plant
|
|
func getTasks(for plantID: UUID) -> [CareTask] {
|
|
schedules[plantID]?.tasks ?? []
|
|
}
|
|
|
|
/// Gets overdue tasks across all schedules
|
|
func getOverdueTasks() -> [CareTask] {
|
|
schedules.values.flatMap { $0.overdueTasks }
|
|
}
|
|
|
|
/// Gets pending tasks across all schedules
|
|
func getPendingTasks() -> [CareTask] {
|
|
schedules.values.flatMap { $0.pendingTasks }
|
|
}
|
|
}
|