From ca975d6f946b391bf08cd18935fb817dff905265 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Wed, 10 Sep 2025 17:22:18 +0300 Subject: [PATCH 1/7] Hide "Stock" card from dashboard and customization options for CIAB sites --- .../Dashboard/DashboardViewModel.swift | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift index df7015b71f7..78d3e68e187 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift @@ -69,6 +69,8 @@ final class DashboardViewModel: ObservableObject { @Published private(set) var isEligibleForInbox = false + @Published private(set) var isEligibleForStock = false + @Published var showingCustomization = false @Published private(set) var showNewCardsNotice = false @@ -164,6 +166,7 @@ final class DashboardViewModel: ObservableObject { self?.onInAppFeedbackCardAction() } + observeStockEligibility() configureOrdersResultController() setupDashboardCards() observeWPCOMSiteSuspendedState() @@ -187,6 +190,7 @@ final class DashboardViewModel: ObservableObject { canShowBlaze: blazeCampaignDashboardViewModel.canShowInDashboard, canShowGoogle: googleAdsDashboardCardViewModel.canShowOnDashboard, canShowInbox: isEligibleForInbox, + canSowStock: isEligibleForStock, hasOrders: hasOrders) await reloadCardsWithBackgroundUpdateSupportIfNeeded() @@ -487,18 +491,20 @@ private extension DashboardViewModel { private extension DashboardViewModel { func observeValuesForDashboardCards() { storeOnboardingViewModel.$canShowInDashboard - .combineLatest(blazeCampaignDashboardViewModel.$canShowInDashboard) + .combineLatest(blazeCampaignDashboardViewModel.$canShowInDashboard, + $isEligibleForStock) .combineLatest(googleAdsDashboardCardViewModel.$canShowOnDashboard, $hasOrders, $isEligibleForInbox) .receive(on: DispatchQueue.main) .sink { [weak self] combinedResult in guard let self else { return } - let ((canShowOnboarding, canShowBlaze), canShowGoogle, hasOrders, isEligibleForInbox) = combinedResult + let ((canShowOnboarding, canShowBlaze, canShowStock), canShowGoogle, hasOrders, isEligibleForInbox) = combinedResult updateDashboardCards(canShowOnboarding: canShowOnboarding, canShowBlaze: canShowBlaze, canShowGoogle: canShowGoogle, canShowInbox: isEligibleForInbox, + canSowStock: canShowStock, hasOrders: hasOrders) } .store(in: &subscriptions) @@ -531,6 +537,27 @@ private extension DashboardViewModel { isEligibleForInbox = inboxEligibilityChecker.isEligibleForInbox(siteID: siteID) } + func observeStockEligibility() { + stores.site + .removeDuplicates() + .map { [weak self] in + guard + let self, + let site = $0 + else { + return false + } + + return CIABEligibilityChecker( + stores: stores + ).isFeatureSupported( + .productsStockDashboardCard, + for: site + ) + } + .assign(to: &$isEligibleForStock) + } + func configureOrdersResultController() { func refreshHasOrders() { /// Upon logging out, `CoreDataManager` clears the storage triggering data change. @@ -586,6 +613,7 @@ private extension DashboardViewModel { canShowGoogle: Bool, canShowAnalytics: Bool, canShowLastOrders: Bool, + canSowStock: Bool, canShowInbox: Bool) -> [DashboardCard] { var cards = [DashboardCard]() @@ -616,7 +644,14 @@ private extension DashboardViewModel { enabled: false)) cards.append(DashboardCard(type: .reviews, availability: .show, enabled: false)) cards.append(DashboardCard(type: .coupons, availability: .show, enabled: false)) - cards.append(DashboardCard(type: .stock, availability: .show, enabled: false)) + + cards.append( + DashboardCard( + type: .stock, + availability: canSowStock ? .show : .hide, + enabled: false + ) + ) // When not available, Last orders cards need to be hidden from Dashboard, but appear on Customize as "Unavailable" cards.append(DashboardCard(type: .lastOrders, @@ -634,6 +669,7 @@ private extension DashboardViewModel { canShowBlaze: Bool, canShowGoogle: Bool, canShowInbox: Bool, + canSowStock: Bool, hasOrders: Bool) { let canShowAnalytics = hasOrders @@ -645,6 +681,7 @@ private extension DashboardViewModel { canShowGoogle: canShowGoogle, canShowAnalytics: canShowAnalytics, canShowLastOrders: canShowLastOrders, + canSowStock: canSowStock, canShowInbox: canShowInbox) // Next, get saved cards and preserve existing enabled state for all available cards. From 11f8342ba38554ff4066f0b15fbe5f17705fd59b Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Wed, 10 Sep 2025 18:39:57 +0300 Subject: [PATCH 2/7] Use injected CIAB checker --- .../ViewRelated/Dashboard/DashboardViewModel.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift index 78d3e68e187..9d343a7856d 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift @@ -85,6 +85,7 @@ final class DashboardViewModel: ObservableObject { private let userDefaults: UserDefaults private let storageManager: StorageManagerType private let inboxEligibilityChecker: InboxEligibilityChecker + private let siteIsCIABEligibilityChecker: CIABEligibilityCheckerProtocol private let usageTracksEventEmitter: StoreStatsUsageTracksEventEmitter private let blazeLocalNotificationScheduler: BlazeLocalNotificationScheduler private let tapToPayAwarenessMomentDeterminer: TapToPayAwarenessMomentDetermining @@ -120,6 +121,7 @@ final class DashboardViewModel: ObservableObject { blazeEligibilityChecker: BlazeEligibilityCheckerProtocol = BlazeEligibilityChecker(), inboxEligibilityChecker: InboxEligibilityChecker = InboxEligibilityUseCase(), googleAdsEligibilityChecker: GoogleAdsEligibilityChecker = DefaultGoogleAdsEligibilityChecker(), + siteIsCIABEligibilityChecker: CIABEligibilityCheckerProtocol = CIABEligibilityChecker(), localNotificationScheduler: BlazeLocalNotificationScheduler? = nil, tapToPayAwarenessMomentDeterminer: TapToPayAwarenessMomentDetermining = TapToPayAwarenessMomentDeterminer()) { self.siteID = siteID @@ -149,6 +151,7 @@ final class DashboardViewModel: ObservableObject { ) self.inboxEligibilityChecker = inboxEligibilityChecker + self.siteIsCIABEligibilityChecker = siteIsCIABEligibilityChecker self.usageTracksEventEmitter = usageTracksEventEmitter self.blazeLocalNotificationScheduler = localNotificationScheduler ?? DefaultBlazeLocalNotificationScheduler(siteID: siteID, @@ -548,12 +551,11 @@ private extension DashboardViewModel { return false } - return CIABEligibilityChecker( - stores: stores - ).isFeatureSupported( - .productsStockDashboardCard, - for: site - ) + return siteIsCIABEligibilityChecker + .isFeatureSupported( + .productsStockDashboardCard, + for: site + ) } .assign(to: &$isEligibleForStock) } From dce7449eb66511942738b8c04c6e35552de5f004 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Wed, 10 Sep 2025 18:42:56 +0300 Subject: [PATCH 3/7] Implement site blaze eligibility observe in `BlazeCampaignDashboardViewModel` --- .../BlazeCampaignDashboardViewModel.swift | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModel.swift index c3c11ca90e2..c61c98ec4de 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModel.swift @@ -73,7 +73,7 @@ final class BlazeCampaignDashboardViewModel: ObservableObject { private let storageManager: StorageManagerType private let analytics: Analytics - private var isSiteEligibleForBlaze = false + @Published private var isSiteEligibleForBlaze = false private let blazeEligibilityChecker: BlazeEligibilityCheckerProtocol /// Blaze campaign ResultsController. @@ -103,12 +103,13 @@ final class BlazeCampaignDashboardViewModel: ObservableObject { ) }() - private(set) var latestPublishedProduct: BlazeCampaignProduct? + @Published private(set) var latestPublishedProduct: BlazeCampaignProduct? private var subscriptions: Set = [] @Published private var syncingError: Error? - + + private var cancellables = Set() init(siteID: Int64, stores: StoresManager = ServiceLocator.stores, @@ -122,15 +123,33 @@ final class BlazeCampaignDashboardViewModel: ObservableObject { self.blazeEligibilityChecker = blazeEligibilityChecker self.state = .loading + observeIsSiteEligibleForBlaze() + observeAvailability() observeSectionVisibility() configureResultsController() } + func observeIsSiteEligibleForBlaze() { + stores.site + .removeDuplicates() + .sink { site in + Task { [weak self] in + guard + let self, + let site + else { + return + } + + isSiteEligibleForBlaze = await checkSiteEligibility(site) + } + } + .store(in: &cancellables) + } + @MainActor func checkAvailability() async { - isSiteEligibleForBlaze = await checkSiteEligibility() try? await synchronizePublishedProducts() - updateAvailability() } @MainActor @@ -140,8 +159,6 @@ final class BlazeCampaignDashboardViewModel: ObservableObject { analytics.track(event: .DynamicDashboard.cardLoadingStarted(type: .blaze)) - isSiteEligibleForBlaze = await checkSiteEligibility() - guard isSiteEligibleForBlaze else { update(state: .empty) return @@ -221,10 +238,7 @@ private extension BlazeCampaignDashboardViewModel { }) } - func checkSiteEligibility() async -> Bool { - guard let site = stores.sessionManager.defaultSite else { - return false - } + func checkSiteEligibility(_ site: Site) async -> Bool { return await blazeEligibilityChecker.isSiteEligible(site) } @@ -288,8 +302,13 @@ private extension BlazeCampaignDashboardViewModel { onStateChange?() } - func updateAvailability() { - canShowInDashboard = isSiteEligibleForBlaze && latestPublishedProduct != nil + func observeAvailability() { + $isSiteEligibleForBlaze + .combineLatest($latestPublishedProduct) + .map { (isSiteEligibleForBlaze, latestPublishedProduct) in + return isSiteEligibleForBlaze && latestPublishedProduct != nil + } + .assign(to: &$canShowInDashboard) } func updateResults() { @@ -321,11 +340,9 @@ private extension BlazeCampaignDashboardViewModel { productResultsController.onDidChangeContent = { [weak self] in guard let self else { return } latestPublishedProduct = productResultsController.fetchedObjects.first - updateAvailability() updateResults() } productResultsController.onDidResetContent = { [weak self] in - self?.updateAvailability() self?.updateResults() } From 9d687270be77d60b0a0b2b714d5a57d34cc12993 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Thu, 11 Sep 2025 18:49:48 +0300 Subject: [PATCH 4/7] Add tests for BlazeCampaignDashboardViewModel to account CIAB sites --- ...BlazeCampaignDashboardViewModelTests.swift | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModelTests.swift index 0ed80d09a79..e982320e7fd 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModelTests.swift @@ -105,6 +105,60 @@ final class BlazeCampaignDashboardViewModelTests: XCTestCase { XCTAssertFalse(sut.canShowInDashboard) } + @MainActor + func test_canShowInDashboard_returns_false_if_store_is_ciab_and_other_requirements_met() async { + // Given + let siteCIABChecker = MockCIABEligibilityChecker( + mockedIsCurrentSiteCIAB: true, + mockedCIABSites: [stores.sessionManager.defaultSite ?? .fake()] + ) + let blazeEligibilityChecker = BlazeEligibilityChecker( + stores: stores, + siteCIABEligibilityChecker: siteCIABChecker + ) + let sut = BlazeCampaignDashboardViewModel( + siteID: sampleSiteID, + stores: stores, + storageManager: storageManager, + blazeEligibilityChecker: blazeEligibilityChecker + ) + + mockSynchronizeProducts() + mockSynchronizeCampaignsList() + + // When + await sut.checkAvailability() + + // Then + XCTAssertFalse(sut.canShowInDashboard) + } + + @MainActor + func test_canShowInDashboard_returns_true_if_store_is_non_ciab_and_other_requirements_met() async { + // Given + let siteCIABChecker = MockCIABEligibilityChecker(mockedIsCurrentSiteCIAB: false) + let blazeEligibilityChecker = BlazeEligibilityChecker( + stores: stores, + siteCIABEligibilityChecker: siteCIABChecker + ) + + let sut = BlazeCampaignDashboardViewModel( + siteID: sampleSiteID, + stores: stores, + storageManager: storageManager, + blazeEligibilityChecker: blazeEligibilityChecker + ) + + mockSynchronizeProducts() + mockSynchronizeCampaignsList() + + // When + await sut.checkAvailability() + + // Then + XCTAssertFalse(sut.canShowInDashboard) + } + // MARK: Published product @MainActor From 7e73f66e440f5d7d3d565e934cd629a7147ebfd8 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Fri, 12 Sep 2025 15:22:30 +0300 Subject: [PATCH 5/7] Rework model and tests to handle published default site --- .../BlazeCampaignDashboardViewModel.swift | 32 ++++++++++----- ...BlazeCampaignDashboardViewModelTests.swift | 39 +++++++++++++++++-- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModel.swift index c61c98ec4de..805d402c028 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModel.swift @@ -108,7 +108,7 @@ final class BlazeCampaignDashboardViewModel: ObservableObject { private var subscriptions: Set = [] @Published private var syncingError: Error? - + private var cancellables = Set() init(siteID: Int64, @@ -124,7 +124,6 @@ final class BlazeCampaignDashboardViewModel: ObservableObject { self.state = .loading observeIsSiteEligibleForBlaze() - observeAvailability() observeSectionVisibility() configureResultsController() } @@ -141,14 +140,24 @@ final class BlazeCampaignDashboardViewModel: ObservableObject { return } - isSiteEligibleForBlaze = await checkSiteEligibility(site) + await updateIsSiteEligibleForBlaze(site) + updateAvailability() } } .store(in: &cancellables) } + func updateIsSiteEligibleForBlaze() async { + guard let site = stores.sessionManager.defaultSite else { + return + } + + await updateIsSiteEligibleForBlaze(site) + } + @MainActor func checkAvailability() async { + await updateIsSiteEligibleForBlaze() try? await synchronizePublishedProducts() } @@ -159,6 +168,8 @@ final class BlazeCampaignDashboardViewModel: ObservableObject { analytics.track(event: .DynamicDashboard.cardLoadingStarted(type: .blaze)) + await updateIsSiteEligibleForBlaze() + guard isSiteEligibleForBlaze else { update(state: .empty) return @@ -242,6 +253,10 @@ private extension BlazeCampaignDashboardViewModel { return await blazeEligibilityChecker.isSiteEligible(site) } + func updateIsSiteEligibleForBlaze(_ site: Site) async { + isSiteEligibleForBlaze = await checkSiteEligibility(site) + } + @MainActor func synchronizeBlazeCampaigns() async throws { try await withCheckedThrowingContinuation({ continuation in @@ -302,13 +317,8 @@ private extension BlazeCampaignDashboardViewModel { onStateChange?() } - func observeAvailability() { - $isSiteEligibleForBlaze - .combineLatest($latestPublishedProduct) - .map { (isSiteEligibleForBlaze, latestPublishedProduct) in - return isSiteEligibleForBlaze && latestPublishedProduct != nil - } - .assign(to: &$canShowInDashboard) + func updateAvailability() { + canShowInDashboard = isSiteEligibleForBlaze && latestPublishedProduct != nil } func updateResults() { @@ -340,9 +350,11 @@ private extension BlazeCampaignDashboardViewModel { productResultsController.onDidChangeContent = { [weak self] in guard let self else { return } latestPublishedProduct = productResultsController.fetchedObjects.first + updateAvailability() updateResults() } productResultsController.onDidResetContent = { [weak self] in + self?.updateAvailability() self?.updateResults() } diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModelTests.swift index e982320e7fd..0dd6286081c 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/Blaze/BlazeCampaignDashboardViewModelTests.swift @@ -108,6 +108,17 @@ final class BlazeCampaignDashboardViewModelTests: XCTestCase { @MainActor func test_canShowInDashboard_returns_false_if_store_is_ciab_and_other_requirements_met() async { // Given + + let site = Site.fake().copy( + siteID: sampleSiteID, + isJetpackThePluginInstalled: true, + isJetpackConnected: true, + canBlaze: true, + isAdmin: true, + ) + + stores.updateDefaultStore(site) + let siteCIABChecker = MockCIABEligibilityChecker( mockedIsCurrentSiteCIAB: true, mockedCIABSites: [stores.sessionManager.defaultSite ?? .fake()] @@ -123,7 +134,13 @@ final class BlazeCampaignDashboardViewModelTests: XCTestCase { blazeEligibilityChecker: blazeEligibilityChecker ) - mockSynchronizeProducts() + mockSynchronizeProducts( + insertProductToStorage: .fake().copy( + siteID: sampleSiteID, + statusKey: (ProductStatus.published.rawValue) + ) + ) + mockSynchronizeCampaignsList() // When @@ -136,6 +153,16 @@ final class BlazeCampaignDashboardViewModelTests: XCTestCase { @MainActor func test_canShowInDashboard_returns_true_if_store_is_non_ciab_and_other_requirements_met() async { // Given + let site = Site.fake().copy( + siteID: sampleSiteID, + isJetpackThePluginInstalled: true, + isJetpackConnected: true, + canBlaze: true, + isAdmin: true, + ) + + stores.updateDefaultStore(site) + let siteCIABChecker = MockCIABEligibilityChecker(mockedIsCurrentSiteCIAB: false) let blazeEligibilityChecker = BlazeEligibilityChecker( stores: stores, @@ -149,14 +176,20 @@ final class BlazeCampaignDashboardViewModelTests: XCTestCase { blazeEligibilityChecker: blazeEligibilityChecker ) - mockSynchronizeProducts() + mockSynchronizeProducts( + insertProductToStorage: .fake().copy( + siteID: sampleSiteID, + statusKey: (ProductStatus.published.rawValue) + ) + ) + mockSynchronizeCampaignsList() // When await sut.checkAvailability() // Then - XCTAssertFalse(sut.canShowInDashboard) + XCTAssertTrue(sut.canShowInDashboard) } // MARK: Published product From df4536fd0284a439361f600e895f8b7e2976ba38 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Fri, 12 Sep 2025 17:35:18 +0300 Subject: [PATCH 6/7] Add tests for dashboard stock card availability --- .../Dashboard/DashboardViewModelTests.swift | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift index 5d0ba0534a7..a17ee44143c 100644 --- a/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift +++ b/WooCommerce/WooCommerceTests/ViewRelated/Dashboard/DashboardViewModelTests.swift @@ -27,12 +27,19 @@ final class DashboardViewModelTests: XCTestCase { storageManager.viewStorage } + private lazy var site = Site.fake().copy( + siteID: sampleSiteID + ) + override func setUpWithError() throws { analyticsProvider = MockAnalyticsProvider() analytics = WooAnalytics(analyticsProvider: analyticsProvider) stores = MockStoresManager(sessionManager: .makeForTesting(authenticated: true)) userDefaults = try XCTUnwrap(UserDefaults(suiteName: "DashboardViewModelTests")) storageManager = MockStorageManager() + + stores.updateDefaultStore(storeID: sampleSiteID) + stores.updateDefaultStore(site) } @MainActor @@ -538,6 +545,57 @@ final class DashboardViewModelTests: XCTestCase { assertEqual([.performance, .googleAds], viewModel.showOnDashboardSecondColumn.map(\.type)) } + @MainActor + func test_dashboard_cards_contain_stock_card_when_store_is_eligible_and_non_ciab() async throws { + // Given + let siteCIABChecker = MockCIABEligibilityChecker(mockedIsCurrentSiteCIAB: false) + let userDefaults = try XCTUnwrap(UserDefaults(suiteName: UUID().uuidString)) + + let viewModel = DashboardViewModel(siteID: sampleSiteID, + stores: stores, + storageManager: storageManager, + userDefaults: userDefaults, + siteIsCIABEligibilityChecker: siteCIABChecker) + + mockReloadingData() + + // Stock card need to be set with availability: .show and enabled: true by default if available. + let expectedStockCard = DashboardCard(type: .stock, availability: .show, enabled: false) + + // When + await viewModel.reloadAllData() + + // Then + XCTAssertTrue(viewModel.dashboardCards.contains(expectedStockCard)) + } + + @MainActor + func test_dashboard_cards_does_not_contain_stock_card_when_store_is_eligible_and_ciab() async throws { + // Given + let siteCIABChecker = MockCIABEligibilityChecker( + mockedIsCurrentSiteCIAB: true, + mockedCIABSites: [site] + ) + let userDefaults = try XCTUnwrap(UserDefaults(suiteName: UUID().uuidString)) + + let viewModel = DashboardViewModel(siteID: sampleSiteID, + stores: stores, + storageManager: storageManager, + userDefaults: userDefaults, + siteIsCIABEligibilityChecker: siteCIABChecker) + + mockReloadingData() + + // Stock card need to be set with availability: .show and enabled: true by default if available. + let expectedStockCard = DashboardCard(type: .stock, availability: .show, enabled: false) + + // When + await viewModel.reloadAllData() + + // Then + XCTAssertFalse(viewModel.dashboardCards.contains(expectedStockCard)) + } + // MARK: Show New Cards Notice @MainActor From a604522c98d711ac85f919d3ee268679e06ca716 Mon Sep 17 00:00:00 2001 From: RafaelKayumov Date: Mon, 15 Sep 2025 12:26:16 +0300 Subject: [PATCH 7/7] Fix typo --- .../ViewRelated/Dashboard/DashboardViewModel.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift index 9d343a7856d..5ffe12aace6 100644 --- a/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Dashboard/DashboardViewModel.swift @@ -193,7 +193,7 @@ final class DashboardViewModel: ObservableObject { canShowBlaze: blazeCampaignDashboardViewModel.canShowInDashboard, canShowGoogle: googleAdsDashboardCardViewModel.canShowOnDashboard, canShowInbox: isEligibleForInbox, - canSowStock: isEligibleForStock, + canShowStock: isEligibleForStock, hasOrders: hasOrders) await reloadCardsWithBackgroundUpdateSupportIfNeeded() @@ -507,7 +507,7 @@ private extension DashboardViewModel { canShowBlaze: canShowBlaze, canShowGoogle: canShowGoogle, canShowInbox: isEligibleForInbox, - canSowStock: canShowStock, + canShowStock: canShowStock, hasOrders: hasOrders) } .store(in: &subscriptions) @@ -615,7 +615,7 @@ private extension DashboardViewModel { canShowGoogle: Bool, canShowAnalytics: Bool, canShowLastOrders: Bool, - canSowStock: Bool, + canShowStock: Bool, canShowInbox: Bool) -> [DashboardCard] { var cards = [DashboardCard]() @@ -650,7 +650,7 @@ private extension DashboardViewModel { cards.append( DashboardCard( type: .stock, - availability: canSowStock ? .show : .hide, + availability: canShowStock ? .show : .hide, enabled: false ) ) @@ -671,7 +671,7 @@ private extension DashboardViewModel { canShowBlaze: Bool, canShowGoogle: Bool, canShowInbox: Bool, - canSowStock: Bool, + canShowStock: Bool, hasOrders: Bool) { let canShowAnalytics = hasOrders @@ -683,7 +683,7 @@ private extension DashboardViewModel { canShowGoogle: canShowGoogle, canShowAnalytics: canShowAnalytics, canShowLastOrders: canShowLastOrders, - canSowStock: canSowStock, + canShowStock: canShowStock, canShowInbox: canShowInbox) // Next, get saved cards and preserve existing enabled state for all available cards.