diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsEvent.swift b/WooCommerce/Classes/Analytics/WooAnalyticsEvent.swift index 68f4e2a2b12..13aa0edf3f2 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsEvent.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsEvent.swift @@ -619,6 +619,29 @@ extension WooAnalyticsEvent { } } +// MARK: - Just In Time Messages +// +extension WooAnalyticsEvent { + enum JustInTimeMessage { + private enum Keys { + static let source = "source" + static let justInTimeMessageID = "jitm_id" + static let justInTimeMessageGroup = "jitm_group" + } + + static func callToActionTapped(source: String, + messageID: String, + featureClass: String) -> WooAnalyticsEvent { + WooAnalyticsEvent(statName: .justInTimeMessageCallToActionTapped, + properties: [ + Keys.source: source, + Keys.justInTimeMessageID: messageID, + Keys.justInTimeMessageGroup: featureClass + ]) + } + } +} + // MARK: - Simple Payments // extension WooAnalyticsEvent { diff --git a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift index e43c57bec56..377bca1c7da 100644 --- a/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift +++ b/WooCommerce/Classes/Analytics/WooAnalyticsStat.swift @@ -627,6 +627,9 @@ public enum WooAnalyticsStat: String { case featureCardDismissed = "feature_card_dismissed" case featureCardCtaTapped = "feature_card_cta_tapped" + // MARK: Just In Time Messages events + case justInTimeMessageCallToActionTapped = "jitm_cta_tapped" + // MARK: Simple Payments events // case simplePaymentsFlowStarted = "simple_payments_flow_started" diff --git a/WooCommerce/Classes/ViewModels/Feature Announcement Cards/JustInTimeMessageAnnouncementCardViewModel.swift b/WooCommerce/Classes/ViewModels/Feature Announcement Cards/JustInTimeMessageAnnouncementCardViewModel.swift index 0dcaf922b21..5a495374a8b 100644 --- a/WooCommerce/Classes/ViewModels/Feature Announcement Cards/JustInTimeMessageAnnouncementCardViewModel.swift +++ b/WooCommerce/Classes/ViewModels/Feature Announcement Cards/JustInTimeMessageAnnouncementCardViewModel.swift @@ -1,36 +1,94 @@ import Foundation +import WooFoundation import UIKit +import Yosemite +import Combine -struct JustInTimeMessageAnnouncementCardViewModel: AnnouncementCardViewModelProtocol { - var showDividers: Bool = false +final class JustInTimeMessageAnnouncementCardViewModel: AnnouncementCardViewModelProtocol { + private let siteID: Int64 - var badgeType: BadgeView.BadgeType = .tip + private let analytics: Analytics - var title: String + // MARK: - Message properties + let title: String - var message: String + let message: String - var buttonTitle: String? + let buttonTitle: String? - var image: UIImage = .paymentsFeatureBannerImage + private let url: URL? - func onAppear() { - // No-op - } + private let messageID: String - let onCTATapped: (() -> Void)? + private let featureClass: String - func ctaTapped() { - onCTATapped?() + private let screenName: String + + init(justInTimeMessage: YosemiteJustInTimeMessage, + screenName: String, + siteID: Int64, + analytics: Analytics = ServiceLocator.analytics) { + self.siteID = siteID + self.analytics = analytics + let utmProvider = WooCommerceComUTMProvider( + campaign: "jitm_group_\(justInTimeMessage.featureClass)", + source: screenName, + content: "jitm_\(justInTimeMessage.messageID)", + siteID: siteID) + self.url = utmProvider.urlWithUtmParams(string: justInTimeMessage.url) + self.messageID = justInTimeMessage.messageID + self.featureClass = justInTimeMessage.featureClass + self.screenName = screenName + self.title = justInTimeMessage.title + self.message = justInTimeMessage.detail + self.buttonTitle = justInTimeMessage.buttonTitle } + // MARK: - output streams + @Published private(set) var showWebViewSheet: WebViewSheetViewModel? + + // MARK: - default AnnouncementCardViewModelProtocol conformance + let showDividers: Bool = false + + let badgeType: BadgeView.BadgeType = .tip + + let image: UIImage = .paymentsFeatureBannerImage + var showDismissButton: Bool = true - var showDismissConfirmation: Bool = false + let showDismissConfirmation: Bool = false + + let dismissAlertTitle: String = "" - var dismissAlertTitle: String = "" + let dismissAlertMessage: String = "" - var dismissAlertMessage: String = "" + // MARK: - AnnouncementCardViewModelProtocol methods + func onAppear() { + // No-op + } + + func ctaTapped() { + analytics.track(event: WooAnalyticsEvent.JustInTimeMessage.callToActionTapped( + source: screenName, + messageID: messageID, + featureClass: featureClass)) + + guard let url = url else { + return + } + let webViewModel = WebViewSheetViewModel( + url: url, + navigationTitle: title, + wpComAuthenticated: needsAuthenticatedWebView(url: url)) + showWebViewSheet = webViewModel + } + + private func needsAuthenticatedWebView(url: URL) -> Bool { + guard let host = url.host else { + return false + } + return Constants.trustedDomains.contains(host) + } func dontShowAgainTapped() { // No-op @@ -39,5 +97,10 @@ struct JustInTimeMessageAnnouncementCardViewModel: AnnouncementCardViewModelProt func remindLaterTapped() { // No-op } +} +extension JustInTimeMessageAnnouncementCardViewModel { + enum Constants { + static let trustedDomains = ["woocommerce.com", "wordpress.com"] + } } diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift index 734da8fa8dd..69ce3e5aa9b 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift @@ -160,33 +160,17 @@ final class DashboardViewModel { switch result { case let .success(.some(message)): let viewModel = JustInTimeMessageAnnouncementCardViewModel( - title: message.title, - message: message.detail, - buttonTitle: message.buttonTitle, - onCTATapped: { [weak self] in - guard let self = self, - let url = URL(string: message.url) - else { return } - let webViewModel = WebViewSheetViewModel( - url: url, - navigationTitle: message.title, - wpComAuthenticated: self.needsAuthenticatedWebView(url: url)) - self.showWebViewSheet = webViewModel - }) + justInTimeMessage: message, + screenName: Constants.dashboardScreenName, + siteID: siteID) self.announcementViewModel = viewModel + viewModel.$showWebViewSheet.assign(to: &self.$showWebViewSheet) default: break } } stores.dispatch(action) } - - private func needsAuthenticatedWebView(url: URL) -> Bool { - guard let host = url.host else { - return false - } - return Constants.trustedDomains.contains(host) - } } // MARK: - Constants @@ -195,6 +179,5 @@ private extension DashboardViewModel { enum Constants { static let topEarnerStatsLimit: Int = 5 static let dashboardScreenName = "my_store" - static let trustedDomains = ["woocommerce.com", "wordpress.com"] } } diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 506ab842251..b97138de53d 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -446,6 +446,7 @@ 03191AE628E1DF0600670723 /* WooCommercePluginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03191AE528E1DF0600670723 /* WooCommercePluginViewModel.swift */; }; 03191AE928E20C9200670723 /* PluginDetailsRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03191AE828E20C9200670723 /* PluginDetailsRowView.swift */; }; 031B10E3274FE2AE007390BA /* CardPresentModalConnectionFailedUpdateAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031B10E2274FE2AE007390BA /* CardPresentModalConnectionFailedUpdateAddress.swift */; }; + 035BA3A8291000E90056F0AD /* JustInTimeMessageAnnouncementCardViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035BA3A7291000E90056F0AD /* JustInTimeMessageAnnouncementCardViewModelTests.swift */; }; 035C6DEB273EA12D00F70406 /* SoftwareUpdateTypeProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035C6DEA273EA12D00F70406 /* SoftwareUpdateTypeProperty.swift */; }; 035F2308275690970019E1B0 /* CardPresentModalConnectingFailedUpdatePostalCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035F2307275690970019E1B0 /* CardPresentModalConnectingFailedUpdatePostalCode.swift */; }; 0366EAE12909A37800B51755 /* JustInTimeMessageAnnouncementCardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0366EAE02909A37800B51755 /* JustInTimeMessageAnnouncementCardViewModel.swift */; }; @@ -2392,6 +2393,7 @@ 03191AE528E1DF0600670723 /* WooCommercePluginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooCommercePluginViewModel.swift; sourceTree = ""; }; 03191AE828E20C9200670723 /* PluginDetailsRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginDetailsRowView.swift; sourceTree = ""; }; 031B10E2274FE2AE007390BA /* CardPresentModalConnectionFailedUpdateAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalConnectionFailedUpdateAddress.swift; sourceTree = ""; }; + 035BA3A7291000E90056F0AD /* JustInTimeMessageAnnouncementCardViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustInTimeMessageAnnouncementCardViewModelTests.swift; sourceTree = ""; }; 035C6DEA273EA12D00F70406 /* SoftwareUpdateTypeProperty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftwareUpdateTypeProperty.swift; sourceTree = ""; }; 035F2307275690970019E1B0 /* CardPresentModalConnectingFailedUpdatePostalCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardPresentModalConnectingFailedUpdatePostalCode.swift; sourceTree = ""; }; 0366EAE02909A37800B51755 /* JustInTimeMessageAnnouncementCardViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustInTimeMessageAnnouncementCardViewModel.swift; sourceTree = ""; }; @@ -4932,6 +4934,7 @@ isa = PBXGroup; children = ( 0371C3692876DBCA00277E2C /* FeatureAnnouncementCardViewModelTests.swift */, + 035BA3A7291000E90056F0AD /* JustInTimeMessageAnnouncementCardViewModelTests.swift */, ); path = "Feature Announcement Cards"; sourceTree = ""; @@ -10655,6 +10658,7 @@ B958A7D328B52A2300823EEF /* MockRoute.swift in Sources */, 02153211242376B5003F2BBD /* ProductPriceSettingsViewModelTests.swift in Sources */, 45C8B25D231529410002FA77 /* CustomerInfoTableViewCellTests.swift in Sources */, + 035BA3A8291000E90056F0AD /* JustInTimeMessageAnnouncementCardViewModelTests.swift in Sources */, 023EC2E624DAB1270021DA91 /* EditableProductVariationModelTests.swift in Sources */, 09BE3A9127C921A70070B69D /* BulkUpdatePriceSettingsViewModelTests.swift in Sources */, 095A077E27CF486C007A61D2 /* ValueOneTableViewCellTests.swift in Sources */, diff --git a/WooCommerce/WooCommerceTests/ViewModels/Feature Announcement Cards/JustInTimeMessageAnnouncementCardViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewModels/Feature Announcement Cards/JustInTimeMessageAnnouncementCardViewModelTests.swift new file mode 100644 index 00000000000..c4f8107ca18 --- /dev/null +++ b/WooCommerce/WooCommerceTests/ViewModels/Feature Announcement Cards/JustInTimeMessageAnnouncementCardViewModelTests.swift @@ -0,0 +1,110 @@ +import XCTest +import TestKit +import Fakes +import Yosemite +import Combine + +@testable import WooCommerce + +final class JustInTimeMessageAnnouncementCardViewModelTests: XCTestCase { + private var subscriptions = Set() + private var webviewPublishes: [WebViewSheetViewModel]! + private var analyticsProvider: MockAnalyticsProvider! + private var analytics: Analytics! + private var sut: JustInTimeMessageAnnouncementCardViewModel! + + override func setUp() { + subscriptions = Set() + webviewPublishes = [WebViewSheetViewModel]() + analyticsProvider = MockAnalyticsProvider() + analytics = WooAnalytics(analyticsProvider: analyticsProvider) + } + + func setUp(with message: YosemiteJustInTimeMessage) { + sut = JustInTimeMessageAnnouncementCardViewModel(justInTimeMessage: message, + screenName: "my_store", + siteID: 1234, + analytics: analytics) + + sut.$showWebViewSheet + .sink { [weak self] webViewSheetViewModel in + if let webViewSheetViewModel = webViewSheetViewModel { + self?.webviewPublishes.append(webViewSheetViewModel) + } + } + .store(in: &self.subscriptions) + } + + func test_ctaTapped_presents_a_webview_with_the_url_adding_correct_utm_parameters() throws { + // Given + setUp(with: YosemiteJustInTimeMessage.fake().copy(messageID: "message_id", + featureClass: "feature_class", + url: "https://woocommerce.com/take-action")) + + // When + sut.ctaTapped() + + // Then + let actualUrl = try XCTUnwrap(webviewPublishes.last?.url) + let query = try XCTUnwrap(URLComponents(url: actualUrl, resolvingAgainstBaseURL: false)?.query) + assertThat(query, contains: "utm_source=my_store") + assertThat(query, contains: "utm_campaign=jitm_group_feature_class") + assertThat(query, contains: "utm_content=jitm_message_id") + assertThat(query, contains: "utm_term=1234") + } + + func test_ctaTapped_presents_an_authenticated_webview_for_woocommerce() throws { + // Given + setUp(with: YosemiteJustInTimeMessage.fake().copy(url: "https://woocommerce.com/take-action")) + + // When + sut.ctaTapped() + + // Then + let webViewViewModel = try XCTUnwrap(webviewPublishes.last) + XCTAssertTrue(webViewViewModel.wpComAuthenticated) + } + + func test_ctaTapped_presents_an_authenticated_webview_for_wordpress() throws { + // Given + setUp(with: YosemiteJustInTimeMessage.fake().copy(url: "https://wordpress.com/take-action")) + + // When + sut.ctaTapped() + + // Then + let webViewViewModel = try XCTUnwrap(webviewPublishes.last) + XCTAssertTrue(webViewViewModel.wpComAuthenticated) + } + + func test_ctaTapped_presents_an_unauthenticated_webview_for_other_url() throws { + // Given + setUp(with: YosemiteJustInTimeMessage.fake().copy(url: "https://example.com/take-action")) + + // When + sut.ctaTapped() + + // Then + let webViewViewModel = try XCTUnwrap(webviewPublishes.last) + XCTAssertFalse(webViewViewModel.wpComAuthenticated) + } + + func test_ctaTapped_tracks_jitm_cta_tapped_event() { + // Given + setUp(with: YosemiteJustInTimeMessage.fake().copy(messageID: "test-message-id", featureClass: "test-feature-class")) + + // When + sut.ctaTapped() + + // Then + guard let eventIndex = analyticsProvider.receivedEvents.firstIndex(of: "jitm_cta_tapped") + else { + return XCTFail("Analytics not logged") + } + let properties = analyticsProvider.receivedProperties[eventIndex] as? [String: String] + let expectedProperties = ["jitm_id": "test-message-id", + "jitm_group": "test-feature-class", + "source": "my_store"] + assertEqual(expectedProperties, properties) + } +} diff --git a/WooFoundation/WooFoundation.xcodeproj/project.pbxproj b/WooFoundation/WooFoundation.xcodeproj/project.pbxproj index 3e18bf6d9b9..5f429afa15b 100644 --- a/WooFoundation/WooFoundation.xcodeproj/project.pbxproj +++ b/WooFoundation/WooFoundation.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 03597A9928F8799C005E4A98 /* MockUTMParameterProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03597A9728F87948005E4A98 /* MockUTMParameterProvider.swift */; }; 03597A9B28F87BFC005E4A98 /* WooCommerceComUTMProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03597A9A28F87BFC005E4A98 /* WooCommerceComUTMProvider.swift */; }; 03597A9D28F93409005E4A98 /* WooCommerceComUTMProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03597A9C28F93409005E4A98 /* WooCommerceComUTMProviderTests.swift */; }; + 035BA3A6290FF98D0056F0AD /* UTMProviderProtocolExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035BA3A5290FF98D0056F0AD /* UTMProviderProtocolExtensionTests.swift */; }; 036563D728F93F8D00D84BFD /* TestKit in Frameworks */ = {isa = PBXBuildFile; productRef = 036563D628F93F8D00D84BFD /* TestKit */; }; 265C99D828B93F04005E6117 /* ColorPalette.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 265C99D728B93F04005E6117 /* ColorPalette.xcassets */; }; 265C99DD28B941D5005E6117 /* UIColor+Muriel-Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 265C99DC28B941D5005E6117 /* UIColor+Muriel-Tests.swift */; }; @@ -54,6 +55,7 @@ 03597A9728F87948005E4A98 /* MockUTMParameterProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUTMParameterProvider.swift; sourceTree = ""; }; 03597A9A28F87BFC005E4A98 /* WooCommerceComUTMProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooCommerceComUTMProvider.swift; sourceTree = ""; }; 03597A9C28F93409005E4A98 /* WooCommerceComUTMProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WooCommerceComUTMProviderTests.swift; sourceTree = ""; }; + 035BA3A5290FF98D0056F0AD /* UTMProviderProtocolExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMProviderProtocolExtensionTests.swift; sourceTree = ""; }; 265C99D728B93F04005E6117 /* ColorPalette.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = ColorPalette.xcassets; sourceTree = ""; }; 265C99DC28B941D5005E6117 /* UIColor+Muriel-Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Muriel-Tests.swift"; sourceTree = ""; }; 265C99DE28B94271005E6117 /* MurielColorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MurielColorTests.swift; sourceTree = ""; }; @@ -160,6 +162,7 @@ 265C99DB28B941D5005E6117 /* Colors */, 686BE914288EE2CA00967C86 /* TypedPredicateTests.swift */, 03597A9C28F93409005E4A98 /* WooCommerceComUTMProviderTests.swift */, + 035BA3A5290FF98D0056F0AD /* UTMProviderProtocolExtensionTests.swift */, ); path = Utilities; sourceTree = ""; @@ -500,6 +503,7 @@ 265C99DF28B94271005E6117 /* MurielColorTests.swift in Sources */, AE948D0D28CF6D50009F3246 /* DateStartAndEndTests.swift in Sources */, 265C99DD28B941D5005E6117 /* UIColor+Muriel-Tests.swift in Sources */, + 035BA3A6290FF98D0056F0AD /* UTMProviderProtocolExtensionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/WooFoundation/WooFoundation/Mocks/MockUTMParameterProvider.swift b/WooFoundation/WooFoundation/Mocks/MockUTMParameterProvider.swift index ee73cf413d1..af001eec7d2 100644 --- a/WooFoundation/WooFoundation/Mocks/MockUTMParameterProvider.swift +++ b/WooFoundation/WooFoundation/Mocks/MockUTMParameterProvider.swift @@ -1,6 +1,8 @@ import Foundation public struct MockUTMParameterProvider: UTMParametersProviding { + public var limitToHosts: [String]? + public var parameters: [UTMParameterKey: String?] public init(parameters: [UTMParameterKey: String?] = [.medium: "woo_ios"]) { diff --git a/WooFoundation/WooFoundation/Utilities/UTMParameters.swift b/WooFoundation/WooFoundation/Utilities/UTMParameters.swift index 8cb8c8f2e0a..c0edb59a5b1 100644 --- a/WooFoundation/WooFoundation/Utilities/UTMParameters.swift +++ b/WooFoundation/WooFoundation/Utilities/UTMParameters.swift @@ -1,6 +1,7 @@ import Foundation public protocol UTMParametersProviding { + var limitToHosts: [String]? { get } var parameters: [UTMParameterKey: String?] { get } var utmQueryItems: [URLQueryItem] { get } } @@ -26,9 +27,28 @@ public extension UTMParametersProviding { } func urlWithUtmParams(string urlString: String) -> URL? { - guard var components = URLComponents(string: urlString) else { + guard let components = URLComponents(string: urlString) else { return nil } + + if let limitToHosts = limitToHosts { + return urlAddingUTMParamsForAllowedHosts(limitToHosts, urlComponents: components) + } else { + return urlAddingUTMParams(urlComponents: components) + } + } + + private func urlAddingUTMParamsForAllowedHosts(_ allowedHosts: [String], urlComponents: URLComponents) -> URL? { + if let host = urlComponents.host, + allowedHosts.contains(host) { + return urlAddingUTMParams(urlComponents: urlComponents) + } else { + return urlComponents.url + } + } + + private func urlAddingUTMParams(urlComponents: URLComponents) -> URL? { + var components = urlComponents var queryItems = components.queryItems ?? [URLQueryItem]() let newQueryItems = utmQueryItems diff --git a/WooFoundation/WooFoundation/Utilities/WooCommerceComUTMProvider.swift b/WooFoundation/WooFoundation/Utilities/WooCommerceComUTMProvider.swift index 6c32a529fa6..f8c19f2eeae 100644 --- a/WooFoundation/WooFoundation/Utilities/WooCommerceComUTMProvider.swift +++ b/WooFoundation/WooFoundation/Utilities/WooCommerceComUTMProvider.swift @@ -1,6 +1,8 @@ import Foundation public struct WooCommerceComUTMProvider: UTMParametersProviding { + public var limitToHosts: [String]? = ["woocommerce.com"] + public let parameters: [UTMParameterKey: String?] public init(campaign: String, diff --git a/WooFoundation/WooFoundationTests/Utilities/UTMProviderProtocolExtensionTests.swift b/WooFoundation/WooFoundationTests/Utilities/UTMProviderProtocolExtensionTests.swift new file mode 100644 index 00000000000..7514114bf20 --- /dev/null +++ b/WooFoundation/WooFoundationTests/Utilities/UTMProviderProtocolExtensionTests.swift @@ -0,0 +1,152 @@ +import Foundation +import TestKit +import XCTest + +@testable import WooFoundation + +final class UTMProviderProtocolExtensionTests: XCTestCase { + + func test_init_sets_utm_medium_to_woo_ios() { + // Given, When + let sut = MockUTMParameterProvider() + + // Then + assertEqual("woo_ios", sut.parameters[.medium]) + } + + func test_query_string_has_all_passed_utm_parameters() throws { + // Given + let sut = MockUTMParameterProvider(parameters: [.medium: "woo_ios", + .campaign: "campaign_name", + .source: "source_name", + .content: "content_details", + .term: "12345"]) + + let urlString = "https://example.com" + + // When + let url = try XCTUnwrap(sut.urlWithUtmParams(string: urlString)) + let query = try XCTUnwrap(url.query) + + // Then + assertThat(query, contains: "utm_medium=woo_ios") + assertThat(query, contains: "utm_campaign=campaign_name") + assertThat(query, contains: "utm_source=source_name") + assertThat(query, contains: "utm_content=content_details") + assertThat(query, contains: "utm_term=12345") + } + + func test_query_string_excludes_nils_passed_in_utm_parameters() throws { + // Given + let sut = MockUTMParameterProvider(parameters: [.medium: "woo_ios", + .campaign: "campaign_name", + .source: "source_name", + .content: nil, + .term: nil]) + + let urlString = "https://example.com" + + // When + let url = try XCTUnwrap(sut.urlWithUtmParams(string: urlString)) + let query = try XCTUnwrap(url.query) + + // Then + XCTAssertFalse(query.contains("utm_content")) + XCTAssertFalse(query.contains("utm_term")) + } + + func test_query_string_overwrites_using_passed_in_utm_parameters() throws { + // Given + let sut = MockUTMParameterProvider(parameters: [.medium: "woo_ios", + .campaign: "campaign_name", + .source: "source_name", + .content: nil, + .term: nil]) + + let urlString = "https://example.com?utm_campaign=existing_campaign" + + // When + let url = try XCTUnwrap(sut.urlWithUtmParams(string: urlString)) + let query = try XCTUnwrap(url.query) + + // Then + assertThat(query, contains: "utm_campaign=campaign_name") + XCTAssertFalse(query.contains("utm_campaign=existing_campaign")) + } + + func test_query_string_preserves_existing_when_nil_is_passed_in_utm_parameters() throws { + // Given + let sut = MockUTMParameterProvider(parameters: [.medium: "woo_ios", + .campaign: "campaign_name", + .source: "source_name", + .content: nil, + .term: nil]) + + let urlString = "https://example.com?utm_term=keyword" + + // When + let url = try XCTUnwrap(sut.urlWithUtmParams(string: urlString)) + let query = try XCTUnwrap(url.query) + + // Then + assertThat(query, contains: "utm_term=keyword") + } + + func test_urlWithUtmParams_does_not_add_params_to_urls_not_in_limitToHosts_when_set() throws { + // Given + var sut = MockUTMParameterProvider(parameters: [.medium: "woo_ios", + .campaign: "campaign_name", + .source: "source_name", + .content: nil, + .term: nil]) + sut.limitToHosts = ["wordpress.com", "woocommerce.com"] + + let urlString = "https://example.com" + + // When + let url = try XCTUnwrap(sut.urlWithUtmParams(string: urlString)) + + // Then + XCTAssertNil(url.query) + assertEqual(URL(string: "https://example.com"), url) + } + + func test_urlWithUtmParams_does_not_add_params_to_urls_not_in_limitToHosts_even_when_empty() throws { + // Given + var sut = MockUTMParameterProvider(parameters: [.medium: "woo_ios", + .campaign: "campaign_name", + .source: "source_name", + .content: nil, + .term: nil]) + sut.limitToHosts = [] + + let urlString = "https://example.com" + + // When + let url = try XCTUnwrap(sut.urlWithUtmParams(string: urlString)) + + // Then + XCTAssertNil(url.query) + assertEqual(URL(string: "https://example.com"), url) + } + + func test_urlWithUtmParams_adds_params_to_urls_in_limitToHosts() throws { + // Given + var sut = MockUTMParameterProvider(parameters: [.medium: "woo_ios", + .campaign: "campaign_name", + .source: "source_name", + .content: nil, + .term: nil]) + sut.limitToHosts = ["woocommerce.com", "example.com"] + + let urlString = "https://example.com" + + // When + let url = try XCTUnwrap(sut.urlWithUtmParams(string: urlString)) + let query = try XCTUnwrap(url.query) + + // Then + assertThat(query, contains: "utm_campaign=campaign_name") + } + +} diff --git a/WooFoundation/WooFoundationTests/Utilities/WooCommerceComUTMProviderTests.swift b/WooFoundation/WooFoundationTests/Utilities/WooCommerceComUTMProviderTests.swift index ca498c61825..a2db32d682f 100644 --- a/WooFoundation/WooFoundationTests/Utilities/WooCommerceComUTMProviderTests.swift +++ b/WooFoundation/WooFoundationTests/Utilities/WooCommerceComUTMProviderTests.swift @@ -6,14 +6,6 @@ import XCTest final class WooCommerceComUTMProviderTests: XCTestCase { - func test_init_sets_utm_medium_to_woo_ios() { - // Given, When - let sut = WooCommerceComUTMProvider(campaign: "", source: "", content: nil, siteID: nil) - - // Then - assertEqual("woo_ios", sut.parameters[.medium]) - } - func test_query_string_has_all_passed_utm_parameters() throws { // Given let sut = WooCommerceComUTMProvider(campaign: "campaign_name", @@ -35,57 +27,19 @@ final class WooCommerceComUTMProviderTests: XCTestCase { assertThat(query, contains: "utm_term=12345") } - func test_query_string_excludes_nils_passed_in_utm_parameters() throws { - // Given - let sut = WooCommerceComUTMProvider(campaign: "campaign_name", - source: "source_name", - content: nil, - siteID: nil) - - let urlString = "https://woocommerce.com" - - // When - let url = try XCTUnwrap(sut.urlWithUtmParams(string: urlString)) - let query = try XCTUnwrap(url.query) - - // Then - XCTAssertFalse(query.contains("utm_content")) - XCTAssertFalse(query.contains("utm_term")) - } - - func test_query_string_overwrites_using_passed_in_utm_parameters() throws { - // Given - let sut = WooCommerceComUTMProvider(campaign: "campaign_name", - source: "source_name", - content: nil, - siteID: nil) - - let urlString = "https://woocommerce.com?utm_campaign=existing_campaign" - - // When - let url = try XCTUnwrap(sut.urlWithUtmParams(string: urlString)) - let query = try XCTUnwrap(url.query) - - // Then - assertThat(query, contains: "utm_campaign=campaign_name") - XCTAssertFalse(query.contains("utm_campaign=existing_campaign")) - } - - func test_query_string_preserves_existing_when_nil_is_passed_in_utm_parameters() throws { + func test_urlWithUtmParams_does_not_add_params_to_urls_other_than_woocommerce_com() throws { // Given let sut = WooCommerceComUTMProvider(campaign: "campaign_name", source: "source_name", content: nil, siteID: nil) - let urlString = "https://woocommerce.com?utm_term=keyword" + let urlString = "https://wordpress.com" // When let url = try XCTUnwrap(sut.urlWithUtmParams(string: urlString)) - let query = try XCTUnwrap(url.query) // Then - assertThat(query, contains: "utm_term=keyword") + XCTAssertNil(url.query) } - }