diff --git a/Experiments/Experiments/ABTest.swift b/Experiments/Experiments/ABTest.swift index 45678521f5f..25df24d7b3a 100644 --- a/Experiments/Experiments/ABTest.swift +++ b/Experiments/Experiments/ABTest.swift @@ -16,6 +16,10 @@ public enum ABTest: String, CaseIterable { /// Experiment ref: pbxNRc-1S0-p2 case aaTestLoggedOut = "woocommerceios_explat_aa_test_logged_out_202211" + /// A/B test for the Products Onboarding banner on the My Store dashboard. + /// Experiment ref: pbxNRc-26F-p2 + case productsOnboardingBanner = "woocommerceios_products_onboarding_first_product_banner" + /// Returns a variation for the given experiment public var variation: Variation { ExPlat.shared?.experiment(rawValue) ?? .control @@ -26,7 +30,7 @@ public enum ABTest: String, CaseIterable { /// When adding a new experiment, add it to the appropriate case depending on its context (logged-in or logged-out experience). public var context: ExperimentContext { switch self { - case .aaTestLoggedIn202210: + case .aaTestLoggedIn202210, .productsOnboardingBanner: return .loggedIn case .aaTestLoggedOut: return .loggedOut diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift index c64f6fee3d6..efc79e99026 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift @@ -2,6 +2,7 @@ import Yosemite import enum Networking.DotcomError import enum Storage.StatsVersion import protocol Experiments.FeatureFlagService +import enum Experiments.ABTest /// Syncs data for dashboard stats UI and determines the state of the dashboard UI based on stats version. final class DashboardViewModel { @@ -124,7 +125,7 @@ final class DashboardViewModel { if isEligible { ServiceLocator.analytics.track(event: .ProductsOnboarding.storeIsEligible()) - if self?.featureFlagService.isFeatureFlagEnabled(.productsOnboarding) == true { + if ABTest.productsOnboardingBanner.variation == .treatment(nil) { let viewModel = ProductsOnboardingAnnouncementCardViewModel(onCTATapped: { [weak self] in self?.announcementViewModel = nil // Dismiss announcement MainTabBarController.presentAddProductFlow() diff --git a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj index 65619779f5b..30678dcaa63 100644 --- a/WooCommerce/WooCommerce.xcodeproj/project.pbxproj +++ b/WooCommerce/WooCommerce.xcodeproj/project.pbxproj @@ -1388,6 +1388,7 @@ CC3B35DB28E5A6830036B097 /* ReviewReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC3B35DA28E5A6830036B097 /* ReviewReply.swift */; }; CC3B35DD28E5A6EA0036B097 /* ReviewReplyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC3B35DC28E5A6EA0036B097 /* ReviewReplyViewModel.swift */; }; CC3B35DF28E5BE6F0036B097 /* ReviewReplyViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC3B35DE28E5BE6F0036B097 /* ReviewReplyViewModelTests.swift */; }; + CC3DB1DC291188CA00425961 /* MockABTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC3DB1DB291188CA00425961 /* MockABTesting.swift */; }; CC440E1E2770C6AF0074C264 /* ProductInOrderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC440E1D2770C6AF0074C264 /* ProductInOrderViewModel.swift */; }; CC4A4E962655273D00B75DCD /* ShippingLabelPaymentMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC4A4E952655273D00B75DCD /* ShippingLabelPaymentMethods.swift */; }; CC4A4ED82655478D00B75DCD /* ShippingLabelPaymentMethodsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC4A4ED72655478D00B75DCD /* ShippingLabelPaymentMethodsViewModel.swift */; }; @@ -3338,6 +3339,7 @@ CC3B35DA28E5A6830036B097 /* ReviewReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewReply.swift; sourceTree = ""; }; CC3B35DC28E5A6EA0036B097 /* ReviewReplyViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewReplyViewModel.swift; sourceTree = ""; }; CC3B35DE28E5BE6F0036B097 /* ReviewReplyViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewReplyViewModelTests.swift; sourceTree = ""; }; + CC3DB1DB291188CA00425961 /* MockABTesting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockABTesting.swift; sourceTree = ""; }; CC440E1D2770C6AF0074C264 /* ProductInOrderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductInOrderViewModel.swift; sourceTree = ""; }; CC4A4E952655273D00B75DCD /* ShippingLabelPaymentMethods.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelPaymentMethods.swift; sourceTree = ""; }; CC4A4ED72655478D00B75DCD /* ShippingLabelPaymentMethodsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelPaymentMethodsViewModel.swift; sourceTree = ""; }; @@ -6331,6 +6333,7 @@ B958A7D728B5316A00823EEF /* MockURLOpener.swift */, EE8DCA7F28BF964700F23B23 /* MockAuthentication.swift */, AEB4DB98290AE8F300AE4340 /* MockCookieJar.swift */, + CC3DB1DB291188CA00425961 /* MockABTesting.swift */, ); path = Mocks; sourceTree = ""; @@ -10799,6 +10802,7 @@ 31F21B02263C8E150035B50A /* CardReaderSettingsSearchingViewModelTests.swift in Sources */, 45EF798624509B4C00B22BA2 /* ArrayIndexPathTests.swift in Sources */, D8610BDD256F5ABF00A5DF27 /* JetpackErrorViewModelTests.swift in Sources */, + CC3DB1DC291188CA00425961 /* MockABTesting.swift in Sources */, 746791632108D7C0007CF1DC /* WooAnalyticsTests.swift in Sources */, 2667BFDD252F61C5008099D4 /* RefundShippingDetailsViewModelTests.swift in Sources */, DE7B479727A3C4980018742E /* CouponDetailsViewModelTests.swift in Sources */, diff --git a/WooCommerce/WooCommerceTests/Mocks/MockABTesting.swift b/WooCommerce/WooCommerceTests/Mocks/MockABTesting.swift new file mode 100644 index 00000000000..924e2fe5bf3 --- /dev/null +++ b/WooCommerce/WooCommerceTests/Mocks/MockABTesting.swift @@ -0,0 +1,20 @@ +import Foundation +import enum AutomatticTracks.Variation +import enum Experiments.ABTest + +struct MockABTesting { + /// Sets the provided A/B Test variation in `UserDefaults`, to mock a given experiment assignment + /// + static func setVariation(_ variation: AutomatticTracks.Variation, for experiment: ABTest) { + let newVariation: String? + switch variation { + case .control: + newVariation = "control" + case .treatment(let type): + newVariation = type ?? "treatment" + } + + let assignment = [experiment.rawValue: newVariation] + UserDefaults.standard.setValue(assignment, forKey: "ab-testing-assignments") + } +} diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift index 5296a0b1202..d1715bf48d5 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift @@ -83,6 +83,7 @@ final class DashboardViewModelTests: XCTestCase { func test_products_onboarding_announcements_take_precedence() { // Given + MockABTesting.setVariation(.treatment(nil), for: .productsOnboardingBanner) let stores = MockStoresManager(sessionManager: .makeForTesting()) stores.whenReceivingAction(ofType: ProductAction.self) { action in switch action { @@ -164,32 +165,4 @@ final class DashboardViewModelTests: XCTestCase { // Then XCTAssertNil(viewModel.announcementViewModel) } - - func test_no_announcement_synced_when_feature_flags_disabled() { - // Given - let stores = MockStoresManager(sessionManager: .makeForTesting()) - stores.whenReceivingAction(ofType: ProductAction.self) { action in - switch action { - case let .checkProductsOnboardingEligibility(_, completion): - completion(.success(true)) - default: - XCTFail("Received unsupported action: \(action)") - } - } - stores.whenReceivingAction(ofType: JustInTimeMessageAction.self) { action in - switch action { - case let .loadMessage(_, _, _, completion): - completion(.success(Yosemite.JustInTimeMessage.fake())) - default: - XCTFail("Received unsupported action: \(action)") - } - } - let viewModel = DashboardViewModel(stores: stores, featureFlags: MockFeatureFlagService()) - - // When - viewModel.syncAnnouncements(for: sampleSiteID) - - // Then - XCTAssertNil(viewModel.announcementViewModel) - } }