import Testing import UserNotifications @testable import Rosetta // MARK: - Foreground Notification Suppression Tests /// Tests for foreground notification suppression logic. /// `willPresent` returns `[.banner, .sound]` by default, `[]` for active/muted chats. @MainActor struct ForegroundNotificationTests { private func clearActiveDialogs() { for key in MessageRepository.shared.activeDialogKeys { MessageRepository.shared.setDialogActive(key, isActive: false) } } // MARK: - System Banner Presentation @Test("Telegram parity: all foreground notifications suppressed") func nonSuppressedShowsBanner() { clearActiveDialogs() let userInfo: [AnyHashable: Any] = ["dialog": "02aaa", "title": "Alice"] let options = AppDelegate.foregroundPresentationOptions(for: userInfo) // Telegram parity: willPresent returns [] — no banners/sound in foreground. #expect(options == []) } @Test("Active chat suppresses system banner") func activeChatSuppressesBanner() { clearActiveDialogs() MessageRepository.shared.setDialogActive("02bbb", isActive: true) let userInfo: [AnyHashable: Any] = ["dialog": "02bbb"] #expect(AppDelegate.foregroundPresentationOptions(for: userInfo) == []) MessageRepository.shared.setDialogActive("02bbb", isActive: false) } // MARK: - In-App Banner: Active Chat → Suppress @Test("Active chat is suppressed by shouldSuppress") func activeChatSuppressed() { clearActiveDialogs() MessageRepository.shared.setDialogActive("02aaa", isActive: true) #expect(InAppNotificationManager.shouldSuppress(senderKey: "02aaa") == true) MessageRepository.shared.setDialogActive("02aaa", isActive: false) } @Test("Inactive chat is NOT suppressed") func inactiveChatNotSuppressed() { clearActiveDialogs() #expect(InAppNotificationManager.shouldSuppress(senderKey: "02bbb") == false) } @Test("Only active chat suppressed, other chats shown") func onlyActiveSuppressed() { clearActiveDialogs() MessageRepository.shared.setDialogActive("02aaa", isActive: true) #expect(InAppNotificationManager.shouldSuppress(senderKey: "02aaa") == true) #expect(InAppNotificationManager.shouldSuppress(senderKey: "02bbb") == false) MessageRepository.shared.setDialogActive("02aaa", isActive: false) } // MARK: - Dialog Deactivation @Test("After closing chat, notifications from it are no longer suppressed") func deactivatedChatNotSuppressed() { clearActiveDialogs() MessageRepository.shared.setDialogActive("02aaa", isActive: true) #expect(InAppNotificationManager.shouldSuppress(senderKey: "02aaa") == true) MessageRepository.shared.setDialogActive("02aaa", isActive: false) #expect(InAppNotificationManager.shouldSuppress(senderKey: "02aaa") == false) } // MARK: - Empty / Missing Sender Key @Test("Empty sender key is suppressed (can't navigate to empty chat)") func emptySenderKeySuppressed() { clearActiveDialogs() #expect(InAppNotificationManager.shouldSuppress(senderKey: "") == true) } // MARK: - Multiple Active Dialogs @Test("Multiple active dialogs — each independently suppressed") func multipleActiveDialogs() { clearActiveDialogs() MessageRepository.shared.setDialogActive("02aaa", isActive: true) MessageRepository.shared.setDialogActive("02bbb", isActive: true) #expect(InAppNotificationManager.shouldSuppress(senderKey: "02aaa") == true) #expect(InAppNotificationManager.shouldSuppress(senderKey: "02bbb") == true) #expect(InAppNotificationManager.shouldSuppress(senderKey: "02ccc") == false) MessageRepository.shared.setDialogActive("02aaa", isActive: false) MessageRepository.shared.setDialogActive("02bbb", isActive: false) } // MARK: - Muted Chat Suppression @Test("Muted chat is suppressed") func mutedChatSuppressed() { clearActiveDialogs() // Set up muted chat in App Group let shared = UserDefaults(suiteName: "group.com.rosetta.dev") let originalMuted = shared?.stringArray(forKey: "muted_chats_keys") shared?.set(["02muted"], forKey: "muted_chats_keys") #expect(InAppNotificationManager.shouldSuppress(senderKey: "02muted") == true) // Restore shared?.set(originalMuted, forKey: "muted_chats_keys") } @Test("Non-muted chat is NOT suppressed") func nonMutedChatNotSuppressed() { clearActiveDialogs() let shared = UserDefaults(suiteName: "group.com.rosetta.dev") let originalMuted = shared?.stringArray(forKey: "muted_chats_keys") shared?.set(["02other"], forKey: "muted_chats_keys") #expect(InAppNotificationManager.shouldSuppress(senderKey: "02notmuted") == false) shared?.set(originalMuted, forKey: "muted_chats_keys") } // MARK: - Sender Key Extraction (AppDelegate) @Test("extractSenderKey reads 'dialog' field first") func extractSenderKeyDialog() { let userInfo: [AnyHashable: Any] = ["dialog": "02aaa", "sender_public_key": "02bbb"] let key = AppDelegate.extractSenderKey(from: userInfo) #expect(key == "02aaa") } @Test("extractSenderKey falls back to 'sender_public_key'") func extractSenderKeyFallback() { let userInfo: [AnyHashable: Any] = ["sender_public_key": "02ccc"] let key = AppDelegate.extractSenderKey(from: userInfo) #expect(key == "02ccc") } @Test("extractSenderKey falls back to 'fromPublicKey'") func extractSenderKeyFromPublicKey() { let userInfo: [AnyHashable: Any] = ["fromPublicKey": "02ddd"] let key = AppDelegate.extractSenderKey(from: userInfo) #expect(key == "02ddd") } @Test("extractSenderKey returns empty for missing keys") func extractSenderKeyEmpty() { let userInfo: [AnyHashable: Any] = ["type": "personal_message"] let key = AppDelegate.extractSenderKey(from: userInfo) #expect(key == "") } }