Skip to content

Commit 0c14d2f

Browse files
committed
Merge pull request #455 from Iterable/MOB-2605-read-state
[MOB-2605] persistent read state # Conflicts: # swift-sdk.xcodeproj/project.pbxproj # swift-sdk/Internal/InAppHelper.swift # tests/swift-sdk-swift-tests/InAppMessageProcessorTests.swift # tests/swift-sdk-swift-tests/InAppTests.swift
1 parent c9da09c commit 0c14d2f

File tree

6 files changed

+273
-83
lines changed

6 files changed

+273
-83
lines changed

swift-sdk.xcodeproj/project.pbxproj

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
556FB1EA244FAF6A00EDF6BD /* InAppPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 556FB1E9244FAF6A00EDF6BD /* InAppPresenter.swift */; };
1919
557AE6BF24A56E5E00B57750 /* Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 557AE6BE24A56E5E00B57750 /* Auth.swift */; };
2020
5585DF8F22A73390000A32B9 /* IterableInboxViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5585DF8E22A73390000A32B9 /* IterableInboxViewControllerTests.swift */; };
21+
55AEA95925F05B7D00B38CED /* InAppMessageProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55AEA95825F05B7D00B38CED /* InAppMessageProcessorTests.swift */; };
2122
55B3119B251015CF0056E4FC /* AuthManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55298B222501A5AB00190BAE /* AuthManager.swift */; };
2223
55B37FC1229620D20042F13A /* CommerceItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B37FC0229620D20042F13A /* CommerceItemTests.swift */; };
2324
55B37FC42297135F0042F13A /* NotificationMetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55B37FC32297135F0042F13A /* NotificationMetadataTests.swift */; };
@@ -366,6 +367,7 @@
366367
557AE6BE24A56E5E00B57750 /* Auth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Auth.swift; sourceTree = "<group>"; };
367368
5585DF8E22A73390000A32B9 /* IterableInboxViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableInboxViewControllerTests.swift; sourceTree = "<group>"; };
368369
5585DF9022A877E6000A32B9 /* IterableInboxViewControllerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableInboxViewControllerUITests.swift; sourceTree = "<group>"; };
370+
55AEA95825F05B7D00B38CED /* InAppMessageProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageProcessorTests.swift; sourceTree = "<group>"; };
369371
55B37FC0229620D20042F13A /* CommerceItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommerceItemTests.swift; sourceTree = "<group>"; };
370372
55B37FC32297135F0042F13A /* NotificationMetadataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMetadataTests.swift; sourceTree = "<group>"; };
371373
55B37FC5229752DD0042F13A /* OrderedDictionaryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedDictionaryTests.swift; sourceTree = "<group>"; };
@@ -676,6 +678,67 @@
676678
path = "swift-sdk/misc";
677679
sourceTree = "<group>";
678680
};
681+
557059B125A691D1009BDEE2 /* in-app-tests */ = {
682+
isa = PBXGroup;
683+
children = (
684+
ACC3FD9D2536D7A30004A2E0 /* InAppFilePersistenceTests.swift */,
685+
AC750A49234CD67900561902 /* InAppHelperTests.swift */,
686+
55AEA95825F05B7D00B38CED /* InAppMessageProcessorTests.swift */,
687+
AC6FDD8B20F56309005D811E /* InAppParsingTests.swift */,
688+
55B37FED229F59290042F13A /* InAppPersistenceTests.swift */,
689+
55CC257A2462064F00A77FD5 /* InAppPresenterTests.swift */,
690+
55705A6825A78BF3009BDEE2 /* InAppPriorityTests.swift */,
691+
ACA8D1A821965B7D001B1332 /* InAppTests.swift */,
692+
);
693+
path = "in-app-tests";
694+
sourceTree = "<group>";
695+
};
696+
557059BC25A69352009BDEE2 /* inbox-tests */ = {
697+
isa = PBXGroup;
698+
children = (
699+
55B549852397462300243E87 /* InboxImpressionTrackerTests.swift */,
700+
55B37FC722975A840042F13A /* InboxMessageViewModelTests.swift */,
701+
55B5498323973B5C00243E87 /* InboxSessionManagerTests.swift */,
702+
AC1670CC2230A91C00989F8E /* InboxTests.swift */,
703+
AC8F35A1239806B500302994 /* InboxViewControllerViewModelTests.swift */,
704+
5585DF8E22A73390000A32B9 /* IterableInboxViewControllerTests.swift */,
705+
);
706+
path = "inbox-tests";
707+
sourceTree = "<group>";
708+
};
709+
557059C525A6938B009BDEE2 /* foundational-tests */ = {
710+
isa = PBXGroup;
711+
children = (
712+
00B6FACB210E8484007535CF /* APNSTypeCheckerTests.swift */,
713+
5531CDAD22A9C992000D05E2 /* ClassExtensionsTests.swift */,
714+
55B37FC0229620D20042F13A /* CommerceItemTests.swift */,
715+
5536781E2576FF9000DB3652 /* IterableUtilTests.swift */,
716+
ACE34AB4213776CB00691224 /* LocalStorageTests.swift */,
717+
ACED4C00213F50B30055A497 /* LoggingTests.swift */,
718+
55B37FC5229752DD0042F13A /* OrderedDictionaryTests.swift */,
719+
ACEDF41E2183C436000B9BFE /* PromiseTests.swift */,
720+
);
721+
path = "foundational-tests";
722+
sourceTree = "<group>";
723+
};
724+
557059EE25A69752009BDEE2 /* deep-linking-tests */ = {
725+
isa = PBXGroup;
726+
children = (
727+
55E6F45E238E066400808BCE /* DeepLinkTests.swift */,
728+
55E6F45D238E066400808BCE /* DeferredDeepLinkTests.swift */,
729+
);
730+
path = "deep-linking-tests";
731+
sourceTree = "<group>";
732+
};
733+
557059FF25A69780009BDEE2 /* request-tests */ = {
734+
isa = PBXGroup;
735+
children = (
736+
ACC362B724D17005002C67BA /* IterableRequestTests.swift */,
737+
AC776DA3211A17C700C27C27 /* IterableRequestUtilTests.swift */,
738+
);
739+
path = "request-tests";
740+
sourceTree = "<group>";
741+
};
679742
AC0248062279132400495FB9 /* Dwifft */ = {
680743
isa = PBXGroup;
681744
children = (
@@ -1752,6 +1815,7 @@
17521815
buildActionMask = 2147483647;
17531816
files = (
17541817
ACA8D1A62196309C001B1332 /* Common.swift in Sources */,
1818+
55AEA95925F05B7D00B38CED /* InAppMessageProcessorTests.swift in Sources */,
17551819
ACC362B824D17005002C67BA /* IterableRequestTests.swift in Sources */,
17561820
AC2C668720D3435700D46CC9 /* IterableActionRunnerTests.swift in Sources */,
17571821
00CB31B621096129004ACDEC /* TestUtils.swift in Sources */,

swift-sdk/Internal/InAppHelper.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
// Ported to Swift by Tapash Majumder on 6/7/18.
44
// Copyright © 2018 Iterable. All rights reserved.
55
//
6-
// Utility Methods for in-app
7-
// All classes/structs are internal.
86

97
import UIKit
108

11-
// This is Internal Struct, no public methods
9+
/// Utility Methods for in-app
10+
/// All classes/structs are internal.
11+
1212
struct InAppHelper {
1313
static func getInAppMessagesFromServer(apiClient: ApiClientProtocol, number: Int) -> Future<[IterableInAppMessage], SendRequestError> {
1414
apiClient.getInAppMessages(NSNumber(value: number)).map {

swift-sdk/Internal/InAppManager+Functions.swift

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -53,28 +53,28 @@ struct MessagesProcessor {
5353

5454
ITBDebug("processing message with id: \(message.messageId)")
5555

56-
if inAppDisplayChecker.isOkToShowNow(message: message) {
57-
ITBDebug("isOkToShowNow")
58-
if inAppDelegate.onNew(message: message) == .show {
59-
ITBDebug("delegate returned show")
60-
return .show(message)
61-
} else {
62-
ITBDebug("delegate returned skip")
63-
return .skip(message)
64-
}
65-
} else {
56+
guard inAppDisplayChecker.isOkToShowNow(message: message) else {
6657
ITBDebug("Not ok to show now")
67-
6858
return .wait
6959
}
60+
61+
ITBDebug("isOkToShowNow")
62+
63+
if inAppDelegate.onNew(message: message) == .show {
64+
ITBDebug("delegate returned show")
65+
return .show(message)
66+
} else {
67+
ITBDebug("delegate returned skip")
68+
return .skip(message)
69+
}
7070
}
7171

7272
private func getFirstProcessableTriggeredMessage() -> IterableInAppMessage? {
7373
messagesMap.values.filter(MessagesProcessor.isProcessableTriggeredMessage).first
7474
}
7575

7676
private static func isProcessableTriggeredMessage(_ message: IterableInAppMessage) -> Bool {
77-
message.didProcessTrigger == false && message.trigger.type == .immediate
77+
!message.didProcessTrigger && message.trigger.type == .immediate && !message.read
7878
}
7979

8080
private mutating func updateMessage(_ message: IterableInAppMessage, didProcessTrigger: Bool? = nil, consumed: Bool? = nil) {
@@ -120,20 +120,34 @@ struct MessagesObtainedHandler {
120120
let removedInboxCount = removedMessages.reduce(0) { $1.saveToInbox ? $0 + 1 : $0 }
121121
let addedInboxCount = addedMessages.reduce(0) { $1.saveToInbox ? $0 + 1 : $0 }
122122

123+
var messagesOverwritten = 0
123124
var newMessagesMap = OrderedDictionary<String, IterableInAppMessage>()
124-
messages.forEach {
125-
if let existingMessage = messagesMap[$0.messageId] {
126-
newMessagesMap[$0.messageId] = existingMessage
125+
messages.forEach { serverMessage in
126+
let messageId = serverMessage.messageId
127+
if let existingMessage = messagesMap[messageId] {
128+
if Self.shouldOverwrite(clientMessage: existingMessage, withServerMessage: serverMessage) {
129+
newMessagesMap[messageId] = serverMessage
130+
messagesOverwritten += 1
131+
} else {
132+
newMessagesMap[messageId] = existingMessage
133+
}
127134
} else {
128-
newMessagesMap[$0.messageId] = $0
135+
newMessagesMap[messageId] = serverMessage
129136
}
130137
}
131138

132-
return MergeMessagesResult(inboxChanged: removedInboxCount + addedInboxCount > 0,
139+
return MergeMessagesResult(inboxChanged: removedInboxCount + addedInboxCount + messagesOverwritten > 0,
133140
messagesMap: newMessagesMap,
134141
deliveredMessages: addedMessages)
135142
}
136143

137144
private let messagesMap: OrderedDictionary<String, IterableInAppMessage>
138145
private let messages: [IterableInAppMessage]
146+
147+
// We should only overwrite if the server is read and client is not read.
148+
// This is because some client changes may not have propagated to server yet.
149+
private static func shouldOverwrite(clientMessage: IterableInAppMessage,
150+
withServerMessage serverMessage: IterableInAppMessage) -> Bool {
151+
serverMessage.read && !clientMessage.read
152+
}
139153
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// Copyright © 2021 Iterable. All rights reserved.
3+
//
4+
5+
import XCTest
6+
7+
@testable import IterableSDK
8+
9+
class InAppMessageProcessorTests: XCTestCase {
10+
func testMessagesObtainedShouldOverwriteForReadState() {
11+
let messageId = "1"
12+
13+
let localMessage = Self.makeEmptyInboxMessage(messageId)
14+
15+
let serverMessage = Self.makeEmptyInboxMessage(messageId)
16+
serverMessage.read = true
17+
18+
let messagesMap: OrderedDictionary<String, IterableInAppMessage> = [messageId: localMessage]
19+
let newMessages = [serverMessage]
20+
21+
let result = MessagesObtainedHandler(messagesMap: messagesMap,
22+
messages: newMessages).handle()
23+
24+
XCTAssertTrue(result.inboxChanged)
25+
}
26+
27+
private static let emptyInAppContent = IterableHtmlInAppContent(edgeInsets: .zero, html: "")
28+
29+
private static func makeEmptyMessage() -> IterableInAppMessage {
30+
IterableInAppMessage(messageId: "", campaignId: nil, content: emptyInAppContent)
31+
}
32+
33+
private static func makeEmptyInboxMessage(_ messageId: String = "") -> IterableInAppMessage {
34+
IterableInAppMessage(messageId: messageId, campaignId: nil, content: emptyInAppContent, saveToInbox: true, read: false)
35+
}
36+
}

tests/swift-sdk-swift-tests/InAppPersistenceTests.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,40 @@ class InAppPersistenceTests: XCTestCase {
5757
XCTFail("conversion to string failed")
5858
}
5959
}
60+
61+
func testPersistentReadStateFromServerPayload() {
62+
let expectation1 = expectation(description: #function)
63+
let mockInAppFetcher = MockInAppFetcher()
64+
65+
let internalAPI = IterableAPIInternal.initializeForTesting(inAppFetcher: mockInAppFetcher)
66+
67+
mockInAppFetcher.mockMessagesAvailableFromServer(internalApi: internalAPI, messages: [Self.getInboxMessage(id: "1", read: false)])
68+
.flatMap { _ in
69+
return mockInAppFetcher.mockMessagesAvailableFromServer(internalApi: internalAPI, messages: [Self.getInboxMessage(id: "1", read: true)])
70+
}
71+
.onSuccess { [weak internalAPI = internalAPI] count in
72+
guard let firstMessage = internalAPI?.inAppManager.getMessages().first else {
73+
XCTFail("could not get in-app message for test")
74+
return
75+
}
76+
XCTAssertTrue(firstMessage.read)
77+
expectation1.fulfill()
78+
}
79+
80+
wait(for: [expectation1], timeout: testExpectationTimeout)
81+
}
82+
83+
private static func getInboxMessage(id: String = "", read: Bool) -> IterableInAppMessage {
84+
return IterableInAppMessage(messageId: id,
85+
campaignId: nil,
86+
trigger: .neverTrigger,
87+
createdAt: nil,
88+
expiresAt: nil,
89+
content: IterableHtmlInAppContent(edgeInsets: .zero, html: ""),
90+
saveToInbox: true,
91+
inboxMetadata: nil,
92+
customPayload: nil,
93+
read: read,
94+
priorityLevel: Const.PriorityLevel.unassigned)
95+
}
6096
}

0 commit comments

Comments
 (0)