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:
171
PlantGuideTests/Mocks/MockNotificationService.swift
Normal file
171
PlantGuideTests/Mocks/MockNotificationService.swift
Normal file
@@ -0,0 +1,171 @@
|
||||
//
|
||||
// MockNotificationService.swift
|
||||
// PlantGuideTests
|
||||
//
|
||||
// Mock implementation of NotificationServiceProtocol for unit testing.
|
||||
// Provides configurable behavior and call tracking for verification.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UserNotifications
|
||||
@testable import PlantGuide
|
||||
|
||||
// MARK: - MockNotificationService
|
||||
|
||||
/// Mock implementation of NotificationServiceProtocol for testing
|
||||
final actor MockNotificationService: NotificationServiceProtocol {
|
||||
|
||||
// MARK: - Storage
|
||||
|
||||
private var scheduledReminders: [UUID: (task: CareTask, plantName: String, plantID: UUID)] = [:]
|
||||
private var pendingNotifications: [UNNotificationRequest] = []
|
||||
|
||||
// MARK: - Call Tracking
|
||||
|
||||
private(set) var requestAuthorizationCallCount = 0
|
||||
private(set) var scheduleReminderCallCount = 0
|
||||
private(set) var cancelReminderCallCount = 0
|
||||
private(set) var cancelAllRemindersCallCount = 0
|
||||
private(set) var updateBadgeCountCallCount = 0
|
||||
private(set) var getPendingNotificationsCallCount = 0
|
||||
private(set) var removeAllDeliveredNotificationsCallCount = 0
|
||||
|
||||
// MARK: - Error Configuration
|
||||
|
||||
var shouldThrowOnRequestAuthorization = false
|
||||
var shouldThrowOnScheduleReminder = false
|
||||
|
||||
var errorToThrow: Error = NotificationError.schedulingFailed(
|
||||
NSError(domain: "MockError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Mock notification error"])
|
||||
)
|
||||
|
||||
// MARK: - Return Value Configuration
|
||||
|
||||
var authorizationGranted = true
|
||||
|
||||
// MARK: - Captured Values
|
||||
|
||||
private(set) var lastScheduledTask: CareTask?
|
||||
private(set) var lastScheduledPlantName: String?
|
||||
private(set) var lastScheduledPlantID: UUID?
|
||||
private(set) var lastCancelledTaskID: UUID?
|
||||
private(set) var lastCancelledAllPlantID: UUID?
|
||||
private(set) var lastBadgeCount: Int?
|
||||
|
||||
// MARK: - NotificationServiceProtocol
|
||||
|
||||
func requestAuthorization() async throws -> Bool {
|
||||
requestAuthorizationCallCount += 1
|
||||
if shouldThrowOnRequestAuthorization {
|
||||
throw errorToThrow
|
||||
}
|
||||
if !authorizationGranted {
|
||||
throw NotificationError.permissionDenied
|
||||
}
|
||||
return authorizationGranted
|
||||
}
|
||||
|
||||
func scheduleReminder(for task: CareTask, plantName: String, plantID: UUID) async throws {
|
||||
scheduleReminderCallCount += 1
|
||||
lastScheduledTask = task
|
||||
lastScheduledPlantName = plantName
|
||||
lastScheduledPlantID = plantID
|
||||
|
||||
if shouldThrowOnScheduleReminder {
|
||||
throw errorToThrow
|
||||
}
|
||||
|
||||
// Validate that the scheduled date is in the future
|
||||
guard task.scheduledDate > Date() else {
|
||||
throw NotificationError.invalidTriggerDate
|
||||
}
|
||||
|
||||
scheduledReminders[task.id] = (task, plantName, plantID)
|
||||
}
|
||||
|
||||
func cancelReminder(for taskID: UUID) async {
|
||||
cancelReminderCallCount += 1
|
||||
lastCancelledTaskID = taskID
|
||||
scheduledReminders.removeValue(forKey: taskID)
|
||||
}
|
||||
|
||||
func cancelAllReminders(for plantID: UUID) async {
|
||||
cancelAllRemindersCallCount += 1
|
||||
lastCancelledAllPlantID = plantID
|
||||
|
||||
// Remove all reminders for this plant
|
||||
let keysToRemove = scheduledReminders.filter { $0.value.plantID == plantID }.map { $0.key }
|
||||
for key in keysToRemove {
|
||||
scheduledReminders.removeValue(forKey: key)
|
||||
}
|
||||
}
|
||||
|
||||
func updateBadgeCount(_ count: Int) async {
|
||||
updateBadgeCountCallCount += 1
|
||||
lastBadgeCount = count
|
||||
}
|
||||
|
||||
func getPendingNotifications() async -> [UNNotificationRequest] {
|
||||
getPendingNotificationsCallCount += 1
|
||||
return pendingNotifications
|
||||
}
|
||||
|
||||
func removeAllDeliveredNotifications() async {
|
||||
removeAllDeliveredNotificationsCallCount += 1
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
|
||||
/// Resets all state for clean test setup
|
||||
func reset() {
|
||||
scheduledReminders = [:]
|
||||
pendingNotifications = []
|
||||
|
||||
requestAuthorizationCallCount = 0
|
||||
scheduleReminderCallCount = 0
|
||||
cancelReminderCallCount = 0
|
||||
cancelAllRemindersCallCount = 0
|
||||
updateBadgeCountCallCount = 0
|
||||
getPendingNotificationsCallCount = 0
|
||||
removeAllDeliveredNotificationsCallCount = 0
|
||||
|
||||
shouldThrowOnRequestAuthorization = false
|
||||
shouldThrowOnScheduleReminder = false
|
||||
|
||||
authorizationGranted = true
|
||||
|
||||
lastScheduledTask = nil
|
||||
lastScheduledPlantName = nil
|
||||
lastScheduledPlantID = nil
|
||||
lastCancelledTaskID = nil
|
||||
lastCancelledAllPlantID = nil
|
||||
lastBadgeCount = nil
|
||||
}
|
||||
|
||||
/// Gets the count of scheduled reminders
|
||||
var scheduledReminderCount: Int {
|
||||
scheduledReminders.count
|
||||
}
|
||||
|
||||
/// Gets scheduled reminders for a specific plant
|
||||
func reminders(for plantID: UUID) -> [CareTask] {
|
||||
scheduledReminders.values
|
||||
.filter { $0.plantID == plantID }
|
||||
.map { $0.task }
|
||||
}
|
||||
|
||||
/// Checks if a reminder is scheduled for a task
|
||||
func hasReminder(for taskID: UUID) -> Bool {
|
||||
scheduledReminders[taskID] != nil
|
||||
}
|
||||
|
||||
/// Gets all scheduled task IDs
|
||||
var scheduledTaskIDs: [UUID] {
|
||||
Array(scheduledReminders.keys)
|
||||
}
|
||||
|
||||
/// Adds a pending notification for testing getPendingNotifications
|
||||
func addPendingNotification(_ request: UNNotificationRequest) {
|
||||
pendingNotifications.append(request)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user