Add PlantGuide iOS app with plant identification and care management
- Implement camera capture and plant identification workflow - Add Core Data persistence for plants, care schedules, and cached API data - Create collection view with grid/list layouts and filtering - Build plant detail views with care information display - Integrate Trefle botanical API for plant care data - Add local image storage for captured plant photos - Implement dependency injection container for testability - Include accessibility support throughout the app Bug fixes in this commit: - Fix Trefle API decoding by removing duplicate CodingKeys - Fix LocalCachedImage to load from correct PlantImages directory - Set dateAdded when saving plants for proper collection sorting Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
166
PlantGuideTests/Mocks/MockCareScheduleRepository.swift
Normal file
166
PlantGuideTests/Mocks/MockCareScheduleRepository.swift
Normal file
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
// MARK: - Error Configuration
|
||||
|
||||
var shouldThrowOnSave = false
|
||||
var shouldThrowOnFetch = false
|
||||
var shouldThrowOnFetchAll = false
|
||||
var shouldThrowOnFetchAllTasks = false
|
||||
var shouldThrowOnUpdateTask = false
|
||||
var shouldThrowOnDelete = 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?
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
shouldThrowOnSave = false
|
||||
shouldThrowOnFetch = false
|
||||
shouldThrowOnFetchAll = false
|
||||
shouldThrowOnFetchAllTasks = false
|
||||
shouldThrowOnUpdateTask = false
|
||||
shouldThrowOnDelete = false
|
||||
|
||||
lastSavedSchedule = nil
|
||||
lastFetchedPlantID = nil
|
||||
lastUpdatedTask = nil
|
||||
lastDeletedPlantID = 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 }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user