Skip to content

Commit 7228793

Browse files
committed
🧪 Added Tests for Silent Push and Custom action handlers
1 parent e5a18f1 commit 7228793

File tree

5 files changed

+360
-258
lines changed

5 files changed

+360
-258
lines changed

tests/business-critical-integration/integration-test-app/IterableSDK-Integration-Tester/App/AppDelegate.swift

Lines changed: 124 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,108 @@ import IterableSDK
1515
class AppDelegate: UIResponder, UIApplicationDelegate {
1616
var window: UIWindow?
1717

18+
// MARK: - AppDelegate Lifecycle
19+
20+
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
21+
// Create window and set root to HomeViewController programmatically for a clean test UI
22+
window = UIWindow(frame: UIScreen.main.bounds)
23+
let root = UINavigationController(rootViewController: HomeViewController())
24+
window?.rootViewController = root
25+
window?.makeKeyAndVisible()
26+
27+
setupNotifications()
28+
setupTestModeUI()
29+
30+
// Start network monitoring
31+
NetworkMonitor.shared.startMonitoring()
32+
33+
// Reset device token session state on app launch for clean testing
34+
AppDelegate.resetDeviceTokenSessionState()
35+
36+
return true
37+
}
38+
39+
func applicationWillResignActive(_: UIApplication) {
40+
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
41+
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
42+
}
43+
44+
func applicationDidEnterBackground(_: UIApplication) {
45+
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
46+
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
47+
}
48+
49+
func applicationWillEnterForeground(_: UIApplication) {
50+
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
51+
}
52+
53+
func applicationDidBecomeActive(_ application: UIApplication) {
54+
print("🚀 BREAKPOINT HERE: App became active")
55+
// Set a breakpoint here to confirm app is opening
56+
57+
// App is always in test mode - no validation needed
58+
}
59+
60+
func applicationWillTerminate(_: UIApplication) {
61+
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
62+
}
63+
64+
// MARK: - Silent Push for in-app
65+
66+
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
67+
print("🔕 [APP] Silent push notification received")
68+
print("🔕 [APP] Silent push payload: \(userInfo)")
69+
70+
// Log Iterable-specific data if present
71+
if let iterableData = userInfo["itbl"] as? [String: Any] {
72+
print("🔕 [APP] Iterable-specific data in silent push: \(iterableData)")
73+
74+
if let isGhostPush = iterableData["isGhostPush"] as? Bool {
75+
print("👻 [APP] Ghost push flag: \(isGhostPush)")
76+
}
77+
}
78+
79+
// Call completion handler
80+
completionHandler(.newData)
81+
}
82+
83+
// MARK: - Deep link
84+
85+
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
86+
return true // No deep link handling in this test app
87+
}
88+
89+
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
90+
print("🔗 App opened via direct deep link: \(url.absoluteString)")
91+
92+
if url.scheme == "tester" {
93+
print("✅ Direct deep link opened - tester:// (will be handled by Iterable SDK)")
94+
return true
95+
}
96+
97+
return false
98+
}
99+
100+
// MARK: - Notifications
101+
102+
func application(_ applicatiTon: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
103+
// Register the device token with Iterable SDK and save it
104+
print("Received device token: \(deviceToken.map { String(format: "%02.2hhx", $0) }.joined())")
105+
AppDelegate.registerDeviceToken(deviceToken)
106+
}
107+
108+
func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {}
109+
110+
// ITBL:
111+
// Setup self as delegate to listen to push notifications.
112+
// Note: This only sets up the delegate, doesn't request permissions automatically
113+
private func setupNotifications() {
114+
print("🔔 Setting up notification delegate")
115+
UNUserNotificationCenter.current().delegate = self
116+
print("🔔 Notification delegate set to: \(String(describing: UNUserNotificationCenter.current().delegate))")
117+
}
118+
119+
// MARK: - Helper Functions
18120
private func setupTestModeUI() {
19121
// Add visual indicator that we're always in integration test mode
20122
DispatchQueue.main.async {
@@ -100,92 +202,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
100202
rootViewController.present(navController, animated: true)
101203
}
102204

103-
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
104-
// Create window and set root to HomeViewController programmatically for a clean test UI
105-
window = UIWindow(frame: UIScreen.main.bounds)
106-
let root = UINavigationController(rootViewController: HomeViewController())
107-
window?.rootViewController = root
108-
window?.makeKeyAndVisible()
109-
110-
setupNotifications()
111-
setupTestModeUI()
112-
113-
// Start network monitoring
114-
NetworkMonitor.shared.startMonitoring()
115-
116-
// Reset device token session state on app launch for clean testing
117-
AppDelegate.resetDeviceTokenSessionState()
118-
119-
return true
120-
}
121-
122-
func applicationWillResignActive(_: UIApplication) {
123-
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
124-
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
125-
}
126-
127-
func applicationDidEnterBackground(_: UIApplication) {
128-
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
129-
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
130-
}
131-
132-
func applicationWillEnterForeground(_: UIApplication) {
133-
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
134-
}
135-
136-
func applicationDidBecomeActive(_ application: UIApplication) {
137-
print("🚀 BREAKPOINT HERE: App became active")
138-
// Set a breakpoint here to confirm app is opening
139-
140-
// App is always in test mode - no validation needed
141-
}
142-
143-
func applicationWillTerminate(_: UIApplication) {
144-
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
145-
}
146-
147-
// MARK: Silent Push for in-app
148-
149-
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
150-
print("🔕 [APP] Silent push notification received")
151-
print("🔕 [APP] Silent push payload: \(userInfo)")
152-
153-
// Log Iterable-specific data if present
154-
if let iterableData = userInfo["itbl"] as? [String: Any] {
155-
print("🔕 [APP] Iterable-specific data in silent push: \(iterableData)")
156-
157-
if let isGhostPush = iterableData["isGhostPush"] as? Bool {
158-
print("👻 [APP] Ghost push flag: \(isGhostPush)")
159-
}
160-
}
161-
162-
// Call completion handler
163-
completionHandler(.newData)
164-
}
165-
166-
// MARK: Deep link
167-
168-
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
169-
return false // No deep link handling in this test app
170-
}
171-
172-
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
173-
print("🔗 App opened via direct deep link: \(url.absoluteString)")
174-
175-
if url.scheme == "tester" {
176-
print("✅ Direct deep link opened - tester:// (will be handled by Iterable SDK)")
177-
return true
178-
}
179-
180-
return false
181-
}
182-
183-
private func showDeepLinkAlert(url: URL) {
205+
private func showAlert(with title: String, and message: String) {
184206
guard let rootViewController = window?.rootViewController else { return }
185207

186208
let alert = UIAlertController(
187-
title: "Iterable Deep Link Opened",
188-
message: "🔗 App was opened via Iterable SDK deep link:\n\(url.absoluteString)",
209+
title: title,
210+
message: message,
189211
preferredStyle: .alert
190212
)
191213

@@ -201,47 +223,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
201223
}
202224

203225
private func showSilentPushAlert(badgeCount: Int, contentAvailable: Int = 0) {
204-
guard let rootViewController = window?.rootViewController else { return }
205-
206-
let alert = UIAlertController(
207-
title: "Silent Push Received",
208-
message: "🔕 Silent push has been received with a badge count of \(badgeCount) and content-available: \(contentAvailable)",
209-
preferredStyle: .alert
210-
)
211-
212-
alert.addAction(UIAlertAction(title: "OK", style: .default))
213-
214-
// Find the topmost presented view controller
215-
var topViewController = rootViewController
216-
while let presentedViewController = topViewController.presentedViewController {
217-
topViewController = presentedViewController
218-
}
219-
220-
topViewController.present(alert, animated: true)
226+
showAlert(with: "Silent Push Received", and: "🔕 Silent push has been received with a badge count of \(badgeCount) and content-available: \(contentAvailable)")
221227
}
222228

223-
// MARK: Notification
224-
225-
// ITBL:
226-
func application(_ applicatiTon: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
227-
// Register the device token with Iterable SDK and save it
228-
print("Received device token: \(deviceToken.map { String(format: "%02.2hhx", $0) }.joined())")
229-
AppDelegate.registerDeviceToken(deviceToken)
230-
}
231-
232-
func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError _: Error) {}
233-
234-
// ITBL:
235-
// Setup self as delegate to listen to push notifications.
236-
// Note: This only sets up the delegate, doesn't request permissions automatically
237-
private func setupNotifications() {
238-
print("🔔 Setting up notification delegate")
239-
UNUserNotificationCenter.current().delegate = self
240-
print("🔔 Notification delegate set to: \(String(describing: UNUserNotificationCenter.current().delegate))")
229+
private func showDeepLinkAlert(url: URL) {
230+
showAlert(with: "Iterable Deep Link Opened", and: "🔗 App was opened via Iterable SDK deep link:\n\(url.absoluteString)")
241231
}
242232
}
243233

244-
// MARK: UNUserNotificationCenterDelegate
234+
// MARK: - UNUserNotificationCenterDelegate
245235

246236
extension AppDelegate: UNUserNotificationCenterDelegate {
247237
public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
@@ -273,11 +263,11 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
273263
}
274264
// Don't show the system notification UI for silent push
275265

276-
return
277266
completionHandler([])
267+
return
278268
}
279269

280-
completionHandler([.alert, .badge, .sound])
270+
completionHandler([.banner, .badge, .sound])
281271
}
282272

283273
// The method will be called on the delegate when the user responded to the notification by opening the application, dismissing the notification or choosing a UNNotificationAction. The delegate must be set before the application returns from applicationDidFinishLaunching:.
@@ -317,7 +307,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
317307
}
318308
}
319309

320-
// MARK: IterableURLDelegate
310+
// MARK: - IterableURLDelegate
321311

322312
extension AppDelegate: IterableURLDelegate {
323313
// return true if we handled the url
@@ -336,10 +326,14 @@ extension AppDelegate: IterableURLDelegate {
336326
// Check if this is the testview deep link
337327
if url.host == "testview" {
338328
print("🎯 Testview deep link detected - showing TestViewController")
339-
DispatchQueue.main.async {
340-
self.showTestViewController()
329+
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
330+
self.showAlert(with: "Deep link to Test View", and: "Deep link handled with Success!")
341331
}
342332
return true
333+
} else if url.host == "iterableurldelegate" {
334+
DispatchQueue.main.async {
335+
self.showAlert(with: "IterableURLDelegate called", and: "Deep link handled with Iterable URL")
336+
}
343337
}
344338

345339
// Show alert for other deep links
@@ -356,50 +350,21 @@ extension AppDelegate: IterableURLDelegate {
356350
print("🔗 URL scheme '\(url.scheme ?? "nil")' not handled by our app")
357351
return false // We didn't handle this URL
358352
}
359-
360-
private func showTestViewController() {
361-
guard let rootViewController = window?.rootViewController else {
362-
print("❌ showTestViewController: no root view controller")
363-
return
364-
}
365-
366-
print("🎯 showTestViewController called - finding topmost view controller")
367-
368-
let testVC = TestViewController()
369-
testVC.modalPresentationStyle = .fullScreen
370-
371-
// Find the topmost presented view controller
372-
var topViewController = rootViewController
373-
while let presentedViewController = topViewController.presentedViewController {
374-
topViewController = presentedViewController
375-
}
376-
377-
print("🎯 Found topmost view controller: \(type(of: topViewController))")
378-
379-
// Add a small delay to ensure in-app dismissal animation completes
380-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
381-
print("🎯 Presenting TestViewController...")
382-
topViewController.present(testVC, animated: true) {
383-
print("✅ TestViewController presented successfully")
384-
}
385-
}
386-
}
387353
}
388354

389355
// MARK: IterableCustomActionDelegate
390356

391357
extension AppDelegate: IterableCustomActionDelegate {
392-
// handle the cutom action from push
358+
359+
// handle the custom action from push
393360
// return value true/false doesn't matter here, stored for future use
394361
func handle(iterableCustomAction action: IterableAction, inContext _: IterableActionContext) -> Bool {
395-
if action.type == "handleFindCoffee" {
396-
if let query = action.userInput {
397-
return false
398-
//return //DeepLinkHandler.handle(url: URL(string: "https://example.com/coffee?q=\(query)")!)
362+
if action.type == "showtestsuccess" {
363+
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
364+
self.showAlert(with: "Custom Action", and: "Custom Action Handled")
399365
}
366+
return true
400367
}
401368
return false
402369
}
403-
404-
// MARK: - Test Network Monitoring
405370
}

0 commit comments

Comments
 (0)