From 5d99212ca699a827969a956b414fa5557c617505 Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Mon, 4 May 2026 09:22:38 -0600 Subject: [PATCH] Skip repeat editor capability fetches in MySiteViewModel Follow-up to #22785: the capability fetch (apiRoot + themes?status=active) was firing on every onResume of the My Site tab. Dedup with an in-memory set keyed by site.id so it only runs once per ViewModel session. Pull-to-refresh bypasses the gate so users can still force a fresh fetch. --- .../android/ui/mysite/MySiteViewModel.kt | 12 +++ .../android/ui/mysite/MySiteViewModelTest.kt | 83 +++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt index 479884efdfcf..c70fd73993c4 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt @@ -78,6 +78,12 @@ class MySiteViewModel @Inject constructor( as they're already built on site select. */ private var isSiteSelected = false + /* Editor capabilities rarely change, so once we've successfully fetched them for a site we + skip subsequent non-user-initiated fetches in this ViewModel session. Failed fetches do + not populate this set, so a transient network failure recovers on the next onResume. + User-initiated refreshes (e.g. pull-to-refresh) always bypass this gate. */ + private val fetchedCapabilitiesForSite = mutableSetOf() + val onScrollTo: MutableLiveData> = MutableLiveData() val onSnackbarMessage = merge( @@ -202,8 +208,14 @@ class MySiteViewModel @Inject constructor( site: SiteModel, isUserInitiated: Boolean ) { + if (site.id in fetchedCapabilitiesForSite && !isUserInitiated) { + return + } val ok = editorSettingsRepository .fetchEditorCapabilitiesForSite(site) + if (ok) { + fetchedCapabilitiesForSite.add(site.id) + } val hasCache = editorSettingsRepository .hasCachedCapabilities(site) if (!ok && (isUserInitiated || !hasCache)) { diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt index c6c0ffc6c628..ea600c3742f5 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/MySiteViewModelTest.kt @@ -362,6 +362,89 @@ class MySiteViewModelTest : BaseUnitTest() { verify(dashboardCardsViewModelSlice).clearValue() } + @Test + fun `given selected site, when onResume invoked twice, then editor capabilities are fetched once`() = test { + initSelectedSite() + + viewModel.onResume() + advanceUntilIdle() + viewModel.onResume() + advanceUntilIdle() + + verify(editorSettingsRepository, times(1)).fetchEditorCapabilitiesForSite(siteTest) + } + + @Test + fun `given selected site, when onResume then non-PTR refresh, then editor capabilities are fetched once`() = + test { + initSelectedSite() + + viewModel.onResume() + advanceUntilIdle() + viewModel.refresh(isPullToRefresh = false) + advanceUntilIdle() + + verify(editorSettingsRepository, times(1)).fetchEditorCapabilitiesForSite(siteTest) + } + + @Test + fun `given selected site, when onResume then PTR refresh, then editor capabilities are fetched twice`() = test { + initSelectedSite() + + viewModel.onResume() + advanceUntilIdle() + viewModel.refresh(isPullToRefresh = true) + advanceUntilIdle() + + verify(editorSettingsRepository, times(2)).fetchEditorCapabilitiesForSite(siteTest) + } + + @Test + fun `given PTR refresh, when onResume invoked after, then editor capabilities are not re-fetched`() = test { + initSelectedSite() + + viewModel.refresh(isPullToRefresh = true) + advanceUntilIdle() + viewModel.onResume() + advanceUntilIdle() + + verify(editorSettingsRepository, times(1)).fetchEditorCapabilitiesForSite(siteTest) + } + + @Test + fun `given fetch failed, when onResume invoked again, then editor capabilities are re-fetched`() = test { + initSelectedSite() + whenever(editorSettingsRepository.fetchEditorCapabilitiesForSite(siteTest)).thenReturn(false, true) + + viewModel.onResume() + advanceUntilIdle() + viewModel.onResume() + advanceUntilIdle() + + verify(editorSettingsRepository, times(2)).fetchEditorCapabilitiesForSite(siteTest) + } + + @Test + fun `given site switched, when onResume invoked, then editor capabilities are fetched for the new site`() = + test { + initSelectedSite() + val otherSite = SiteModel().apply { + id = TEST_SITE_ID + 1 + url = TEST_URL + name = TEST_SITE_NAME + siteId = (TEST_SITE_ID + 1).toLong() + } + + viewModel.onResume() + advanceUntilIdle() + whenever(selectedSiteRepository.getSelectedSite()).thenReturn(otherSite) + viewModel.onResume() + advanceUntilIdle() + + verify(editorSettingsRepository, times(1)).fetchEditorCapabilitiesForSite(siteTest) + verify(editorSettingsRepository, times(1)).fetchEditorCapabilitiesForSite(otherSite) + } + /* LAND ON THE EDITOR A/B EXPERIMENT */