Skip to content

Commit 4abf32e

Browse files
committed
🧪 Embedded Messaging integration tests
1 parent 2730071 commit 4abf32e

File tree

10 files changed

+1313
-559
lines changed

10 files changed

+1313
-559
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ extension AppDelegate {
8181
config.inAppDisplayInterval = 1
8282
config.autoPushRegistration = false // Disable automatic push registration for testing control
8383
config.allowedProtocols = ["tester"] // Allow our custom tester:// deep link scheme
84+
config.enableEmbeddedMessaging = true
8485

8586
let apiKey = loadApiKeyFromConfig()
8687
IterableAPI.initialize(apiKey: apiKey,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// EmbeddedMessageTestHostingController.swift
3+
// IterableSDK-Integration-Tester
4+
//
5+
6+
import UIKit
7+
import SwiftUI
8+
9+
final class EmbeddedMessageTestHostingController: UIViewController {
10+
11+
override func viewDidLoad() {
12+
super.viewDidLoad()
13+
14+
// Create SwiftUI view
15+
let embeddedTestView = EmbeddedMessageTestView()
16+
let hostingController = UIHostingController(rootView: embeddedTestView)
17+
18+
// Add hosting controller as child
19+
addChild(hostingController)
20+
view.addSubview(hostingController.view)
21+
22+
// Setup constraints
23+
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
24+
NSLayoutConstraint.activate([
25+
hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
26+
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
27+
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
28+
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
29+
])
30+
31+
hostingController.didMove(toParent: self)
32+
}
33+
}
34+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
//
2+
// EmbeddedMessageTestView.swift
3+
// IterableSDK-Integration-Tester
4+
//
5+
6+
import SwiftUI
7+
import IterableSDK
8+
9+
struct EmbeddedMessageTestView: View {
10+
@StateObject private var viewModel = EmbeddedMessageTestViewModel()
11+
@Environment(\.dismiss) private var dismiss
12+
@State private var showMessagesModal = false
13+
14+
var body: some View {
15+
ScrollView {
16+
VStack(spacing: 24) {
17+
// Status Section
18+
statusSection
19+
20+
// User Profile Section
21+
userProfileSection
22+
23+
// Campaign Triggers
24+
// campaignTriggersSection
25+
26+
// Embedded Message Display
27+
embeddedMessagesSection
28+
29+
// Control Buttons
30+
controlButtonsSection
31+
32+
Spacer(minLength: 20)
33+
}
34+
.padding(20)
35+
}
36+
.navigationTitle("Embedded Messages")
37+
.navigationBarTitleDisplayMode(.inline)
38+
.toolbar {
39+
ToolbarItem(placement: .navigationBarLeading) {
40+
Button("Back") {
41+
dismiss()
42+
}
43+
.accessibilityIdentifier("back-to-home-button")
44+
}
45+
}
46+
.onAppear {
47+
viewModel.startMonitoring()
48+
}
49+
.onDisappear {
50+
viewModel.stopMonitoring()
51+
}
52+
.alert(item: $viewModel.alertMessage) { alertMessage in
53+
Alert(
54+
title: Text(alertMessage.title),
55+
message: Text(alertMessage.message),
56+
dismissButton: .default(Text("OK"))
57+
)
58+
}
59+
}
60+
61+
// MARK: - Status Section
62+
63+
private var statusSection: some View {
64+
VStack(alignment: .leading, spacing: 12) {
65+
Text("Status")
66+
.font(.headline)
67+
68+
StatusRow(title: "Embedded Enabled", value: viewModel.isEmbeddedEnabled ? "✓ Enabled" : "✗ Disabled")
69+
.accessibilityIdentifier("embedded-enabled-value")
70+
71+
StatusRow(title: "Messages Count", value: "\(viewModel.messagesCount)")
72+
.accessibilityIdentifier("embedded-messages-count")
73+
74+
StatusRow(title: "User Eligibility", value: viewModel.userEligibilityStatus)
75+
.accessibilityIdentifier("user-eligibility-status")
76+
}
77+
.padding()
78+
.background(Color.secondary.opacity(0.1))
79+
.cornerRadius(12)
80+
}
81+
82+
// MARK: - User Profile Section
83+
84+
private var userProfileSection: some View {
85+
VStack(alignment: .leading, spacing: 12) {
86+
Text("User Profile Controls")
87+
.font(.headline)
88+
89+
HStack {
90+
Button {
91+
viewModel.isPremiumMember = false
92+
viewModel.updateUserProfile()
93+
} label: {
94+
Text("Disable Premium Member")
95+
.padding()
96+
.background(Color.red)
97+
.foregroundColor(.white)
98+
.cornerRadius(8)
99+
}
100+
101+
Button {
102+
viewModel.isPremiumMember = true
103+
viewModel.updateUserProfile()
104+
} label: {
105+
Text("Enable Premium Member")
106+
.padding()
107+
.background(Color.blue)
108+
.foregroundColor(.white)
109+
.cornerRadius(8)
110+
}
111+
}
112+
.accessibilityIdentifier("premium-member-toggle")
113+
114+
HStack {
115+
Text("Premium Member: ")
116+
Text(viewModel.isPremiumMember ? "Yes" : "No")
117+
.foregroundColor(viewModel.isPremiumMember ? .green : .gray)
118+
}
119+
120+
StatusRow(title: "Profile Status", value: viewModel.profileUpdateStatus)
121+
.accessibilityIdentifier("profile-update-status")
122+
}
123+
.padding()
124+
.background(Color.secondary.opacity(0.1))
125+
.cornerRadius(12)
126+
}
127+
128+
// MARK: - Campaign Triggers Section
129+
130+
private var campaignTriggersSection: some View {
131+
VStack(alignment: .leading, spacing: 12) {
132+
Text("Campaign Triggers")
133+
.font(.headline)
134+
135+
Button(action: {
136+
viewModel.sendSilentPushForSync()
137+
}) {
138+
HStack {
139+
Image(systemName: "bell.badge.fill")
140+
Text("Send Silent Push (Sync)")
141+
}
142+
.frame(maxWidth: .infinity)
143+
.padding()
144+
.background(Color.purple)
145+
.foregroundColor(.white)
146+
.cornerRadius(8)
147+
}
148+
.accessibilityIdentifier("send-silent-push-sync-button")
149+
150+
StatusRow(title: "Campaign Status", value: viewModel.campaignStatus)
151+
.accessibilityIdentifier("campaign-status-value")
152+
}
153+
.padding()
154+
.background(Color.secondary.opacity(0.1))
155+
.cornerRadius(12)
156+
}
157+
158+
// MARK: - Embedded Messages Display Section
159+
160+
private var embeddedMessagesSection: some View {
161+
VStack(alignment: .leading, spacing: 12) {
162+
Text("Embedded Messages")
163+
.font(.headline)
164+
165+
Button(action: {
166+
viewModel.syncMessages()
167+
}) {
168+
HStack {
169+
Image(systemName: "arrow.clockwise")
170+
Text("Sync Messages")
171+
}
172+
.frame(maxWidth: .infinity)
173+
.padding()
174+
.background(Color.green)
175+
.foregroundColor(.white)
176+
.cornerRadius(8)
177+
}
178+
.accessibilityIdentifier("sync-embedded-messages-button")
179+
180+
if viewModel.embeddedMessages.isEmpty {
181+
Text("No embedded messages")
182+
.foregroundColor(.gray)
183+
.frame(maxWidth: .infinity)
184+
.padding()
185+
.accessibilityIdentifier("no-embedded-messages-label")
186+
} else {
187+
Button(action: {
188+
showMessagesModal = true
189+
}) {
190+
HStack {
191+
Image(systemName: "rectangle.stack.fill")
192+
Text("View Messages (\(viewModel.embeddedMessages.count))")
193+
}
194+
.frame(maxWidth: .infinity)
195+
.padding()
196+
.background(Color.blue)
197+
.foregroundColor(.white)
198+
.cornerRadius(8)
199+
}
200+
.accessibilityIdentifier("view-embedded-messages-button")
201+
.sheet(isPresented: $showMessagesModal) {
202+
EmbeddedMessagesModalView(messages: viewModel.embeddedMessages)
203+
}
204+
}
205+
}
206+
.padding()
207+
.background(Color.secondary.opacity(0.1))
208+
.cornerRadius(12)
209+
}
210+
211+
// MARK: - Control Buttons Section
212+
213+
private var controlButtonsSection: some View {
214+
VStack(spacing: 12) {
215+
Button(action: {
216+
viewModel.clearMessages()
217+
}) {
218+
HStack {
219+
Image(systemName: "trash.fill")
220+
Text("Clear All Messages")
221+
}
222+
.frame(maxWidth: .infinity)
223+
.padding()
224+
.background(Color.red)
225+
.foregroundColor(.white)
226+
.cornerRadius(8)
227+
}
228+
.accessibilityIdentifier("clear-embedded-messages-button")
229+
}
230+
}
231+
}
232+
233+
// MARK: - Supporting Views
234+
235+
struct EmbeddedMessagesModalView: UIViewControllerRepresentable {
236+
let messages: [IterableEmbeddedMessage]
237+
238+
func makeUIViewController(context: Context) -> UINavigationController {
239+
let vc = EmbeddedMessagesViewController(messages: messages)
240+
let nav = UINavigationController(rootViewController: vc)
241+
return nav
242+
}
243+
244+
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
245+
if let vc = uiViewController.viewControllers.first as? EmbeddedMessagesViewController {
246+
vc.updateMessages(messages)
247+
}
248+
}
249+
}
250+
251+
class EmbeddedMessagesViewController: UIViewController {
252+
private var messages: [IterableEmbeddedMessage]
253+
private let scrollView = UIScrollView()
254+
private let stackView = UIStackView()
255+
256+
init(messages: [IterableEmbeddedMessage]) {
257+
self.messages = messages
258+
super.init(nibName: nil, bundle: nil)
259+
}
260+
261+
required init?(coder: NSCoder) {
262+
fatalError("init(coder:) has not been implemented")
263+
}
264+
265+
override func viewDidLoad() {
266+
super.viewDidLoad()
267+
setupUI()
268+
displayMessages()
269+
}
270+
271+
private func setupUI() {
272+
view.backgroundColor = .systemBackground
273+
274+
// Navigation bar
275+
navigationItem.title = "Embedded Messages"
276+
navigationItem.rightBarButtonItem = UIBarButtonItem(
277+
barButtonSystemItem: .done,
278+
target: self,
279+
action: #selector(dismissModal)
280+
)
281+
282+
// ScrollView
283+
scrollView.translatesAutoresizingMaskIntoConstraints = false
284+
view.addSubview(scrollView)
285+
286+
// StackView
287+
stackView.axis = .vertical
288+
stackView.spacing = 16
289+
stackView.distribution = .equalSpacing
290+
stackView.translatesAutoresizingMaskIntoConstraints = false
291+
scrollView.addSubview(stackView)
292+
293+
NSLayoutConstraint.activate([
294+
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
295+
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
296+
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
297+
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
298+
299+
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 16),
300+
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16),
301+
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -16),
302+
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -16),
303+
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -32)
304+
])
305+
}
306+
307+
private func displayMessages() {
308+
// Clear existing views
309+
stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
310+
311+
// Add message views
312+
for (index, message) in messages.enumerated() {
313+
let embeddedView = IterableEmbeddedView(message: message, viewType: .card, config: nil)
314+
embeddedView.translatesAutoresizingMaskIntoConstraints = false
315+
embeddedView.accessibilityIdentifier = "embedded-message-\(index)"
316+
stackView.addArrangedSubview(embeddedView)
317+
318+
NSLayoutConstraint.activate([
319+
embeddedView.heightAnchor.constraint(greaterThanOrEqualToConstant: 100)
320+
])
321+
}
322+
}
323+
324+
func updateMessages(_ messages: [IterableEmbeddedMessage]) {
325+
self.messages = messages
326+
if isViewLoaded {
327+
displayMessages()
328+
}
329+
}
330+
331+
@objc private func dismissModal() {
332+
dismiss(animated: true)
333+
}
334+
}
335+
336+
337+
#Preview {
338+
NavigationView {
339+
EmbeddedMessageTestView()
340+
}
341+
}
342+

0 commit comments

Comments
 (0)