From 9bacb2997aa67e3e2f5f335ab54f3c86add75d9f Mon Sep 17 00:00:00 2001 From: Alessandro Boron Date: Thu, 26 Sep 2024 19:50:15 +1000 Subject: [PATCH] Onboarding Highlights - Experiment Setup (#3390) Task/Issue URL: https://app.asana.com/0/1206329551987282/1208084960727003/f **Description**: Setup 3 branches experiment for Onboarding according to [Asana comment](https://app.asana.com/0/0/1208314441219794/1208151149115582/f). --- .maestro/shared/onboarding.yaml | 10 + Core/DefaultVariantManager.swift | 7 +- DuckDuckGo.xcodeproj/project.pbxproj | 8 + DuckDuckGo/DaxDialogs.swift | 3 +- DuckDuckGo/HomeViewController.swift | 8 +- DuckDuckGo/MainViewController+Segues.swift | 2 +- DuckDuckGo/MainViewController.swift | 11 +- DuckDuckGo/NewTabPageViewController.swift | 6 +- .../ContextualOnboardingPresenter.swift | 7 +- .../DefaultVariantManager+Onboarding.swift | 43 ++++ .../Manager/OnboardingManager.swift | 7 +- DuckDuckGo/TabDelegate.swift | 2 - DuckDuckGo/TabSwitcherViewController.swift | 31 ++- DuckDuckGo/TabViewController.swift | 2 +- DuckDuckGo/TabsBarViewController.swift | 11 +- .../ContextualOnboardingPresenterTests.swift | 43 +++- DuckDuckGoTests/DaxDialogTests.swift | 31 ++- ...DefaultVariantManagerOnboardingTests.swift | 198 ++++++++++++++++++ .../HomeViewControllerDaxDialogTests.swift | 74 ++++++- DuckDuckGoTests/MockTabDelegate.swift | 2 - DuckDuckGoTests/OnboardingManagerTests.swift | 33 ++- 21 files changed, 479 insertions(+), 60 deletions(-) create mode 100644 DuckDuckGo/OnboardingExperiment/DefaultVariantManager+Onboarding.swift create mode 100644 DuckDuckGoTests/DefaultVariantManagerOnboardingTests.swift diff --git a/.maestro/shared/onboarding.yaml b/.maestro/shared/onboarding.yaml index 17bf8ffb35..2117d4205b 100644 --- a/.maestro/shared/onboarding.yaml +++ b/.maestro/shared/onboarding.yaml @@ -14,3 +14,13 @@ appId: com.duckduckgo.mobile.ios # - assertVisible: "Make DuckDuckGo your default browser." - tapOn: text: "Skip" + +- runFlow: + when: + visible: "Which color looks best on me?" + commands: + - assertVisible: "Next" + - tapOn: "Next" + - assertVisible: "Where should I put your address bar?" + - assertVisible: "Next" + - tapOn: "Next" diff --git a/Core/DefaultVariantManager.swift b/Core/DefaultVariantManager.swift index 5ce128d6ec..76f0f8475a 100644 --- a/Core/DefaultVariantManager.swift +++ b/Core/DefaultVariantManager.swift @@ -28,6 +28,8 @@ extension FeatureName { // public static let experimentalFeature = FeatureName(rawValue: "experimentalFeature") public static let newOnboardingIntro = FeatureName(rawValue: "newOnboardingIntro") + public static let newOnboardingIntroHighlights = FeatureName(rawValue: "newOnboardingIntroHighlights") + public static let contextualDaxDialogs = FeatureName(rawValue: "contextualDaxDialogs") } public struct VariantIOS: Variant { @@ -56,8 +58,9 @@ public struct VariantIOS: Variant { VariantIOS(name: "sd", weight: doNotAllocate, isIncluded: When.always, features: []), VariantIOS(name: "se", weight: doNotAllocate, isIncluded: When.always, features: []), - VariantIOS(name: "ma", weight: 1, isIncluded: When.always, features: []), - VariantIOS(name: "mb", weight: 1, isIncluded: When.always, features: [.newOnboardingIntro]), + VariantIOS(name: "ms", weight: 1, isIncluded: When.always, features: [.newOnboardingIntro]), + VariantIOS(name: "mu", weight: 1, isIncluded: When.always, features: [.newOnboardingIntro, .contextualDaxDialogs]), + VariantIOS(name: "mx", weight: 1, isIncluded: When.always, features: [.newOnboardingIntroHighlights, .contextualDaxDialogs]), returningUser ] diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index cf258c6f4a..6464a479d3 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -696,6 +696,8 @@ 98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */; }; 98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */; }; 9F16230B2CA0F0190093C4FC /* DebouncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */; }; + 9F1061652C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1061642C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift */; }; + 9F1623092C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1623082C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift */; }; 9F23B8012C2BC94400950875 /* OnboardingBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8002C2BC94400950875 /* OnboardingBackground.swift */; }; 9F23B8032C2BCD0000950875 /* DaxDialogStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8022C2BCD0000950875 /* DaxDialogStyles.swift */; }; 9F23B8062C2BE22700950875 /* OnboardingIntroViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8052C2BE22700950875 /* OnboardingIntroViewModelTests.swift */; }; @@ -2506,6 +2508,8 @@ 98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesLists.swift; sourceTree = ""; }; 98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemableNavigationController.swift; sourceTree = ""; }; 9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebouncerTests.swift; sourceTree = ""; }; + 9F1061642C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultVariantManager+Onboarding.swift"; sourceTree = ""; }; + 9F1623082C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultVariantManagerOnboardingTests.swift; sourceTree = ""; }; 9F23B8002C2BC94400950875 /* OnboardingBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackground.swift; sourceTree = ""; }; 9F23B8022C2BCD0000950875 /* DaxDialogStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxDialogStyles.swift; sourceTree = ""; }; 9F23B8052C2BE22700950875 /* OnboardingIntroViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingIntroViewModelTests.swift; sourceTree = ""; }; @@ -4777,6 +4781,7 @@ 9F7CFF7C2C89B69A0012833E /* AppIconPickerViewModelTests.swift */, 9FDEC7B32C8FD62F00C7A692 /* OnboardingAddressBarPositionPickerViewModelTests.swift */, 9FDEC7B92C9006E000C7A692 /* BrowserComparisonModelTests.swift */, + 9F1623082C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift */, ); name = Onboarding; sourceTree = ""; @@ -4902,6 +4907,7 @@ 9F23B7FF2C2BABE000950875 /* OnboardingIntro */, 9F5E5AAA2C3D0FAA00165F54 /* ContextualOnboarding */, 9FCFCD842C75C91A006EB7A0 /* ProgressBarView.swift */, + 9F1061642C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift */, ); path = OnboardingExperiment; sourceTree = ""; @@ -7434,6 +7440,7 @@ 6FBF0F8B2BD7C0A900136CF0 /* AllProtectedCell.swift in Sources */, 9F4CC5242C4A4F0D006A96EB /* SwiftUITestUtilities.swift in Sources */, 6FDC64032C92F4D600DB71B3 /* NewTabPageSettingsPersistentStore.swift in Sources */, + 9F1061652C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift in Sources */, 1E4F4A5A297193DE00625985 /* MainViewController+CookiesManaged.swift in Sources */, C12324C32C4697C900FBB26B /* AutofillBreakageReportTableViewCell.swift in Sources */, 8586A10D24CBA7070049720E /* FindInPageActivity.swift in Sources */, @@ -7954,6 +7961,7 @@ 5694372B2BE3F2D900C0881B /* SyncErrorHandlerTests.swift in Sources */, 987130C7294AAB9F00AB05E0 /* MenuBookmarksViewModelTests.swift in Sources */, 858650D32469BFAD00C36F8A /* DaxDialogTests.swift in Sources */, + 9F1623092C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift in Sources */, 31C138B227A4097800FFD4B2 /* DownloadTestsHelper.swift in Sources */, 1E1D8B5D2994FFE100C96994 /* AutoconsentMessageProtocolTests.swift in Sources */, 85C11E532090B23A00BFFEB4 /* UserDefaultsHomeRowReminderStorageTests.swift in Sources */, diff --git a/DuckDuckGo/DaxDialogs.swift b/DuckDuckGo/DaxDialogs.swift index 8ea9004161..8fc63f8e3a 100644 --- a/DuckDuckGo/DaxDialogs.swift +++ b/DuckDuckGo/DaxDialogs.swift @@ -229,7 +229,7 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic { } private var isNewOnboarding: Bool { - variantManager.isSupported(feature: .newOnboardingIntro) + variantManager.isContextualDaxDialogsEnabled } private var firstBrowsingMessageSeen: Bool { @@ -279,6 +279,7 @@ final class DaxDialogs: NewTabDialogSpecProvider, ContextualOnboardingLogic { var isEnabled: Bool { // skip dax dialogs in integration tests guard ProcessInfo.processInfo.environment["DAXDIALOGS"] != "false" else { return false } + guard variantManager.shouldShowDaxDialogs else { return false } return !settings.isDismissed } diff --git a/DuckDuckGo/HomeViewController.swift b/DuckDuckGo/HomeViewController.swift index 4cffd017af..71bb361a88 100644 --- a/DuckDuckGo/HomeViewController.swift +++ b/DuckDuckGo/HomeViewController.swift @@ -217,7 +217,7 @@ class HomeViewController: UIViewController, NewTabPage { func openedAsNewTab(allowingKeyboard: Bool) { collectionView.openedAsNewTab(allowingKeyboard: allowingKeyboard) - if !variantManager.isSupported(feature: .newOnboardingIntro) { + if !variantManager.isContextualDaxDialogsEnabled { // In the new onboarding this gets called twice (viewDidAppear in Tab) which then reset the spec to nil. presentNextDaxDialog() } @@ -258,7 +258,9 @@ class HomeViewController: UIViewController, NewTabPage { } func presentNextDaxDialog() { - if variantManager.isSupported(feature: .newOnboardingIntro) { + guard variantManager.shouldShowDaxDialogs else { return } + + if variantManager.isContextualDaxDialogsEnabled { showNextDaxDialogNew(dialogProvider: newTabDialogTypeProvider, factory: newTabDialogFactory) } else { showNextDaxDialog(dialogProvider: newTabDialogTypeProvider) @@ -266,7 +268,7 @@ class HomeViewController: UIViewController, NewTabPage { } func showNextDaxDialog() { - showNextDaxDialog(dialogProvider: newTabDialogTypeProvider) + presentNextDaxDialog() } func reloadFavorites() { diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 324aaf1716..cd05ecddb1 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -35,7 +35,7 @@ extension MainViewController { var controller: (Onboarding & UIViewController)? - if DefaultVariantManager().isSupported(feature: .newOnboardingIntro) { + if DefaultVariantManager().isNewIntroFlow { controller = OnboardingIntroViewController(onboardingPixelReporter: contextualOnboardingPixelReporter) } else { let storyboard = UIStoryboard(name: "DaxOnboarding", bundle: nil) diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 9816927c3e..5b1f33e12f 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -852,7 +852,7 @@ class MainViewController: UIViewController { hideNotificationBarIfBrokenSitePromptShown() wakeLazyFireButtonAnimator() - if DefaultVariantManager().isSupported(feature: .newOnboardingIntro) { + if variantManager.isContextualDaxDialogsEnabled { // Dismiss dax dialog and pulse animation when the user taps on the Fire Button. currentTab?.dismissContextualDaxFireDialog() ViewHighlighter.hideAll() @@ -2418,10 +2418,6 @@ extension MainViewController: TabDelegate { } } - func tabDidRequestForgetAll(tab: TabViewController) { - forgetAllWithAnimation(showNextDaxDialog: true) - } - func tabDidRequestFireButtonPulse(tab: TabViewController) { showFireButtonPulse() } @@ -2741,7 +2737,8 @@ extension MainViewController: AutoClearWorker { self.privacyProDataReporter.saveFireCount() // Ideally this should happen once data clearing has finished AND the animation is finished - if showNextDaxDialog { + // `showNextDaxDialog: true` only set from old onboarding FireDialog ActionSheet + if showNextDaxDialog && self.variantManager.shouldShowDaxDialogs { self.homeController?.showNextDaxDialog() } else if KeyboardSettings().onNewTab { let showKeyboardAfterFireButton = DispatchWorkItem { @@ -2751,7 +2748,7 @@ extension MainViewController: AutoClearWorker { self.showKeyboardAfterFireButton = showKeyboardAfterFireButton } - if self.variantManager.isSupported(feature: .newOnboardingIntro) { + if self.variantManager.isContextualDaxDialogsEnabled { DaxDialogs.shared.clearedBrowserData() } } diff --git a/DuckDuckGo/NewTabPageViewController.swift b/DuckDuckGo/NewTabPageViewController.swift index fabd1ee3ed..c8ae9157bb 100644 --- a/DuckDuckGo/NewTabPageViewController.swift +++ b/DuckDuckGo/NewTabPageViewController.swift @@ -165,7 +165,9 @@ final class NewTabPageViewController: UIHostingController DetectedRequest { let entity = entityProvider.entity(forHost: url.host!) return DetectedRequest(url: url.absoluteString, @@ -1111,7 +1137,10 @@ final class DaxDialog: XCTestCase { } private func makeExperimentSUT(settings: DaxDialogsSettings) -> DaxDialogs { - let mockVariantManager = MockVariantManager(isSupportedReturns: true) + var mockVariantManager = MockVariantManager() + mockVariantManager.isSupportedBlock = { feature in + feature == .contextualDaxDialogs + } return DaxDialogs(settings: settings, entityProviding: entityProvider, variantManager: mockVariantManager) } } diff --git a/DuckDuckGoTests/DefaultVariantManagerOnboardingTests.swift b/DuckDuckGoTests/DefaultVariantManagerOnboardingTests.swift new file mode 100644 index 0000000000..b9e460038d --- /dev/null +++ b/DuckDuckGoTests/DefaultVariantManagerOnboardingTests.swift @@ -0,0 +1,198 @@ +// +// DefaultVariantManagerOnboardingTests.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +import BrowserServicesKit +@testable import Core +@testable import DuckDuckGo + +final class DefaultVariantManagerOnboardingTests: XCTestCase { + + // MARK: - Is New Intro Flow + + func testWhenIsNewIntroFlow_AndFeatureIsNewOnboardingIntro_ThenReturnTrue() { + // GIVEN + let sut = makeVariantManager(features: [.newOnboardingIntro]) + + // WHEN + let result = sut.isNewIntroFlow + + // THEN + XCTAssertTrue(result) + } + + func testWhenIsNewIntroFlow_AndFeaturesContainNewOnboardingIntroHighlights_ThenReturnTrue() { + // GIVEN + let sut = makeVariantManager(features: [.newOnboardingIntroHighlights]) + + // WHEN + let result = sut.isNewIntroFlow + + // THEN + XCTAssertTrue(result) + } + + func testWhenIsNewIntroFlow_AndFeaturesDoNotContainNewOnboardingIntroOrNewOnboardingIntroHighlights_ThenReturnFalse() { + // GIVEN + let sut = makeVariantManager(features: [.contextualDaxDialogs]) + + // WHEN + let result = sut.isNewIntroFlow + + // THEN + XCTAssertFalse(result) + } + + // MARK: - Is Onboarding Highlights + + func testWhenIsOnboardingHighlights_AndFeaturesContainOnboardingHighlights_ThenReturnTrue() { + // GIVEN + let sut = makeVariantManager(features: [.newOnboardingIntroHighlights]) + + // WHEN + let result = sut.isOnboardingHighlightsExperiment + + // THEN + XCTAssertTrue(result) + } + + func testWhenIsOnboardingHighlights_AndFeaturesDoNotContainOnboardingHighlights_ThenReturnFalse() { + // GIVEN + let sut = makeVariantManager(features: [.newOnboardingIntro, .contextualDaxDialogs]) + + // WHEN + let result = sut.isOnboardingHighlightsExperiment + + // THEN + XCTAssertFalse(result) + } + + func testWhenIsOnboardingHighlights_AndFeaturesIsEmpty_ThenReturnFalse() { + // GIVEN + let sut = makeVariantManager(features: []) + + // WHEN + let result = sut.isOnboardingHighlightsExperiment + + // THEN + XCTAssertFalse(result) + } + + // MARK: - Should Show Dax Dialogs + + func testWhenShouldShowDaxDialogs_AndFeaturesContainOnboardingIntro_ThenReturnFalse() { + // GIVEN + let sut = makeVariantManager(features: [.newOnboardingIntro]) + + // WHEN + let result = sut.shouldShowDaxDialogs + + // THEN + XCTAssertFalse(result) + } + + func testWhenShouldShowDaxDialogs_AndFeaturesContainOnboardingIntroAndContextualDaxDialogs_ThenReturnTrue() { + // GIVEN + let sut = makeVariantManager(features: [.newOnboardingIntro, .contextualDaxDialogs]) + + // WHEN + let result = sut.shouldShowDaxDialogs + + // THEN + XCTAssertTrue(result) + } + + func testWhenShouldShowDaxDialogs_AndFeaturesContainOnboardingHighlights_ThenReturnTrue() { + // GIVEN + let sut = makeVariantManager(features: [.newOnboardingIntroHighlights]) + + // WHEN + let result = sut.shouldShowDaxDialogs + + // THEN + XCTAssertTrue(result) + } + + func testWhenShouldShowDaxDialogs_AndFeaturesIsEmpty_ThenReturnTrue() { + // GIVEN + let sut = makeVariantManager(features: []) + + // WHEN + let result = sut.shouldShowDaxDialogs + + // THEN + XCTAssertTrue(result) + } + + // MARK: - Is Contextual Dax Dialogs Enabled + + func testWhenIsContextualDaxDialogsEnabled_AndFeaturesContainContextualDaxDialogs_ThenReturnTrue() { + // GIVEN + let sut = makeVariantManager(features: [.contextualDaxDialogs]) + + // WHEN + let result = sut.isContextualDaxDialogsEnabled + + // THEN + XCTAssertTrue(result) + } + + func testWhenIsContextualDaxDialogsEnabled_AndFeaturesDoNotContainContextualDaxDialogs_ThenReturnFalse() { + // GIVEN + let sut = makeVariantManager(features: [.newOnboardingIntro, .newOnboardingIntroHighlights]) + + // WHEN + let result = sut.isContextualDaxDialogsEnabled + + // THEN + XCTAssertFalse(result) + } + + func testWhenIsContextualDaxDialogsEnabled_AndFeaturesIsEmpty_ThenReturnFalse() { + // GIVEN + let sut = makeVariantManager(features: []) + + // WHEN + let result = sut.isContextualDaxDialogsEnabled + + // THEN + XCTAssertFalse(result) + } + +} + +// MARK: Helpers + +private extension DefaultVariantManagerOnboardingTests { + + func makeVariantManager(features: [FeatureName]) -> DefaultVariantManager { + let mockStatisticStore = MockStatisticsStore() + mockStatisticStore.variant = #function + let variantManager = DefaultVariantManager( + variants: [VariantIOS(name: #function, weight: 1, isIncluded: VariantIOS.When.always, features: features)], + storage: mockStatisticStore, + rng: MockVariantRNG(returnValue: 500), + returningUserMeasurement: MockReturningUserMeasurement(), + variantNameOverride: MockVariantNameOverride() + ) + variantManager.assignVariantIfNeeded { _ in } + return variantManager + } + +} diff --git a/DuckDuckGoTests/HomeViewControllerDaxDialogTests.swift b/DuckDuckGoTests/HomeViewControllerDaxDialogTests.swift index 1f1db8b0ca..e9d3ebf36b 100644 --- a/DuckDuckGoTests/HomeViewControllerDaxDialogTests.swift +++ b/DuckDuckGoTests/HomeViewControllerDaxDialogTests.swift @@ -92,9 +92,9 @@ final class HomeViewControllerDaxDialogTests: XCTestCase { hvc = nil } - func testWhenNewOnboarding_OnDidAppear_CorrectTypePassedToDialogFactory() throws { + func testWhenContextualDaxDialogsSupported_OnDidAppear_CorrectTypePassedToDialogFactory() throws { // GIVEN - variantManager.isSupported = true + variantManager.supportedFeatures = [.contextualDaxDialogs] let expectedSpec = randomDialogType() specProvider.specToReturn = expectedSpec @@ -102,16 +102,33 @@ final class HomeViewControllerDaxDialogTests: XCTestCase { hvc.viewDidAppear(false) // THEN - XCTAssertEqual(self.variantManager.capturedFeatureName?.rawValue, FeatureName.newOnboardingIntro.rawValue) + XCTAssertEqual(self.variantManager.capturedFeatureName?.rawValue, FeatureName.contextualDaxDialogs.rawValue) XCTAssertFalse(self.specProvider.nextHomeScreenMessageCalled) XCTAssertTrue(self.specProvider.nextHomeScreenMessageNewCalled) XCTAssertEqual(self.dialogFactory.homeDialog, expectedSpec) XCTAssertNotNil(self.dialogFactory.onDismiss) } + func testWhenDaxDialogsAreNotEnabled_OnDidAppear_NothingHappens() throws { + // GIVEN + variantManager.currentVariant = MockVariant(features: [.newOnboardingIntro]) + let expectedSpec = randomDialogType() + specProvider.specToReturn = expectedSpec + + // WHEN + hvc.viewDidAppear(false) + + // THEN + XCTAssertNil(self.variantManager.capturedFeatureName) + XCTAssertFalse(self.specProvider.nextHomeScreenMessageCalled) + XCTAssertFalse(self.specProvider.nextHomeScreenMessageNewCalled) + XCTAssertNil(self.dialogFactory.homeDialog) + XCTAssertNil(self.dialogFactory.onDismiss) + } + func testWhenOldOnboarding_OnDidAppear_NothingPassedDialogFactory() throws { // GIVEN - variantManager.isSupported = false + variantManager.supportedFeatures = [] // WHEN hvc.viewDidAppear(false) @@ -123,9 +140,9 @@ final class HomeViewControllerDaxDialogTests: XCTestCase { XCTAssertNil(dialogFactory.onDismiss) } - func testWhenNewOnboarding_OnOnboardingComplete_CorrectTypePassedToDialogFactory() throws { + func testWhenContextualDaxDialogsSupported_OnOnboardingComplete_CorrectTypePassedToDialogFactory() throws { // GIVEN - variantManager.isSupported = true + variantManager.supportedFeatures = [.contextualDaxDialogs] let expectedSpec = randomDialogType() specProvider.specToReturn = expectedSpec @@ -133,7 +150,7 @@ final class HomeViewControllerDaxDialogTests: XCTestCase { hvc.onboardingCompleted() // THEN - XCTAssertEqual(self.variantManager.capturedFeatureName?.rawValue, FeatureName.newOnboardingIntro.rawValue) + XCTAssertEqual(self.variantManager.capturedFeatureName?.rawValue, FeatureName.contextualDaxDialogs.rawValue) XCTAssertFalse(self.specProvider.nextHomeScreenMessageCalled) XCTAssertTrue(self.specProvider.nextHomeScreenMessageNewCalled) XCTAssertEqual(self.dialogFactory.homeDialog, expectedSpec) @@ -142,7 +159,7 @@ final class HomeViewControllerDaxDialogTests: XCTestCase { func testWhenOldOnboarding_OnOnboardingComplete_NothingPassedDialogFactory() throws { // GIVEN - variantManager.isSupported = false + variantManager.supportedFeatures = [] // WHEN hvc.onboardingCompleted() @@ -156,7 +173,7 @@ final class HomeViewControllerDaxDialogTests: XCTestCase { func testWhenOldOnboarding_OnOpenedAsNewTab_NothingPassedDialogFactory() throws { // GIVEN - variantManager.isSupported = false + variantManager.supportedFeatures = [] // WHEN hvc.openedAsNewTab(allowingKeyboard: true) @@ -168,6 +185,30 @@ final class HomeViewControllerDaxDialogTests: XCTestCase { XCTAssertNil(dialogFactory.onDismiss) } + func testWhenShowNextDaxDialog_AndShouldShowDaxDialogs_ThenReturnTrue() { + // GIVEN + variantManager.supportedFeatures = [] + + // WHEN + hvc.showNextDaxDialog() + + // THEN + XCTAssertTrue(specProvider.nextHomeScreenMessageCalled) + XCTAssertFalse(specProvider.nextHomeScreenMessageNewCalled) + } + + func testWhenShowNextDaxDialog_AndShouldNotShowDaxDialogs_ThenReturnFalse() { + // GIVEN + variantManager.currentVariant = MockVariant(features: [.newOnboardingIntro]) + + // WHEN + hvc.showNextDaxDialog() + + // THEN + XCTAssertFalse(specProvider.nextHomeScreenMessageCalled) + XCTAssertFalse(specProvider.nextHomeScreenMessageNewCalled) + } + private func randomDialogType() -> DaxDialogs.HomeScreenSpec { let specs: [DaxDialogs.HomeScreenSpec] = [.initial, .subsequent, .final, .addFavorite] return specs.randomElement()! @@ -177,14 +218,14 @@ final class HomeViewControllerDaxDialogTests: XCTestCase { class CapturingVariantManager: VariantManager { var currentVariant: Variant? var capturedFeatureName: FeatureName? - var isSupported = false + var supportedFeatures: [FeatureName] = [] func assignVariantIfNeeded(_ newInstallCompletion: (BrowserServicesKit.VariantManager) -> Void) { } func isSupported(feature: FeatureName) -> Bool { capturedFeatureName = feature - return isSupported + return supportedFeatures.contains(feature) } } @@ -233,3 +274,14 @@ class MockNewTabDialogSpecProvider: NewTabDialogSpecProvider { dismissCalled = true } } + +struct MockVariant: Variant { + var name: String = "" + var weight: Int = 0 + var isIncluded: () -> Bool = { false } + var features: [BrowserServicesKit.FeatureName] = [] + + init(features: [BrowserServicesKit.FeatureName]) { + self.features = features + } +} diff --git a/DuckDuckGoTests/MockTabDelegate.swift b/DuckDuckGoTests/MockTabDelegate.swift index 4b9fa4919c..3cd9a7c74c 100644 --- a/DuckDuckGoTests/MockTabDelegate.swift +++ b/DuckDuckGoTests/MockTabDelegate.swift @@ -76,8 +76,6 @@ final class MockTabDelegate: TabDelegate { func tabContentProcessDidTerminate(tab: DuckDuckGo.TabViewController) {} - func tabDidRequestForgetAll(tab: DuckDuckGo.TabViewController) {} - func tabDidRequestFireButtonPulse(tab: DuckDuckGo.TabViewController) { didRequestFireButtonPulseCalled = true } diff --git a/DuckDuckGoTests/OnboardingManagerTests.swift b/DuckDuckGoTests/OnboardingManagerTests.swift index ee456fb41d..b0f90803d5 100644 --- a/DuckDuckGoTests/OnboardingManagerTests.swift +++ b/DuckDuckGoTests/OnboardingManagerTests.swift @@ -25,17 +25,20 @@ final class OnboardingManagerTests: XCTestCase { private var sut: OnboardingManager! private var appSettingsMock: AppSettingsMock! private var featureFlaggerMock: MockFeatureFlagger! + private var variantManagerMock: MockVariantManager! override func setUpWithError() throws { try super.setUpWithError() appSettingsMock = AppSettingsMock() featureFlaggerMock = MockFeatureFlagger() - sut = OnboardingManager(appDefaults: appSettingsMock, featureFlagger: featureFlaggerMock) + variantManagerMock = MockVariantManager() + sut = OnboardingManager(appDefaults: appSettingsMock, featureFlagger: featureFlaggerMock, variantManager: variantManagerMock) } override func tearDownWithError() throws { appSettingsMock = nil featureFlaggerMock = nil + variantManagerMock = nil sut = nil try super.tearDownWithError() } @@ -119,4 +122,32 @@ final class OnboardingManagerTests: XCTestCase { // THEN XCTAssertTrue(result) } + + func testWhenIsOnboardingHiglightsEnabledAndVariantManagerSupportOnboardingHighlightsReturnTrue() { + // GIVEN + variantManagerMock.isSupportedBlock = { _ in true } + appSettingsMock.onboardingHighlightsEnabled = false + featureFlaggerMock.enabledFeatureFlags = [FeatureFlag.onboardingHighlights] + sut = OnboardingManager(appDefaults: appSettingsMock, featureFlagger: featureFlaggerMock, variantManager: variantManagerMock) + + // WHEN + let result = sut.isOnboardingHighlightsEnabled + + // THEN + XCTAssertTrue(result) + } + + func testWhenIsOnboardingHiglightsEnabledAndVariantManagerSupportOnboardingHighlightsReturnFalse() { + // GIVEN + variantManagerMock.isSupportedBlock = { _ in false } + appSettingsMock.onboardingHighlightsEnabled = false + featureFlaggerMock.enabledFeatureFlags = [FeatureFlag.onboardingHighlights] + sut = OnboardingManager(appDefaults: appSettingsMock, featureFlagger: featureFlaggerMock, variantManager: variantManagerMock) + + // WHEN + let result = sut.isOnboardingHighlightsEnabled + + // THEN + XCTAssertFalse(result) + } }