- 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>
172 lines
5.2 KiB
Swift
172 lines
5.2 KiB
Swift
//
|
|
// 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)
|
|
}
|
|
}
|