diff --git a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsListEvent.kt b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsListEvent.kt index 2c3ee5a1059f..5aa630128b77 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsListEvent.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsListEvent.kt @@ -20,6 +20,12 @@ internal sealed interface PageRsListEvent { data class CopyPageUrl(val url: String) : PageRsListEvent + /** Opens the block-theme homepage in the Site Editor web view via WPWebViewActivity. */ + data class OpenSiteEditor( + val url: String, + val useWpComCredentials: Boolean + ) : PageRsListEvent + data class PromoteWithBlaze( val site: SiteModel, val page: PostModel diff --git a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsListUiState.kt b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsListUiState.kt index 1416d5677dc9..680e1b2222b1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsListUiState.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsListUiState.kt @@ -73,10 +73,18 @@ internal sealed interface PageRsListItem { ) : PageRsListItem { override val stableKey: String get() = "virtual:$kind" - enum class Kind { HOMEPAGE, POSTS_PAGE } + // HOMEPAGE / POSTS_PAGE wrap a real assigned static page; SITE_EDITOR is the block-theme + // homepage, which has no backing page and opens the Site Editor web view on tap. + enum class Kind { HOMEPAGE, POSTS_PAGE, SITE_EDITOR } } } +/** + * Sentinel [PageRsUiModel.remotePageId] for the synthetic SITE_EDITOR virtual row, which has no real + * page behind it. Real remote page ids are always positive, so a negative value can't collide. + */ +internal const val SITE_EDITOR_PAGE_ID = -1L + internal enum class PageRsDisplayState { NORMAL, FETCHING_WITH_DATA, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsTreeBuilder.kt b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsTreeBuilder.kt index 038b5d08f78d..abe67d5eac51 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsTreeBuilder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PageRsTreeBuilder.kt @@ -10,6 +10,10 @@ internal const val MAX_INDENT_LEVEL = 3 * - When hierarchy is on, the pages identified by [pageOnFront] and [pageForPosts] * (when set and present in [pages]) are prepended as Virtual rows and hidden from * their normal sorted position. + * - When [showSiteEditorHomepage] is true (block-based theme + Site Editor MVP), a single + * SITE_EDITOR virtual row is prepended in place of the static HOMEPAGE row, and the + * [pageOnFront] page (if any) is still hidden from the tree. This mirrors the legacy list, + * where a block-theme homepage opens the Site Editor instead of the block editor. * - When [applyHierarchy] is false, [pages] are wrapped as flat Real rows with * indentLevel = 0 and no virtuals. */ @@ -17,7 +21,8 @@ internal fun buildRows( pages: List, applyHierarchy: Boolean, pageOnFront: Long, - pageForPosts: Long + pageForPosts: Long, + showSiteEditorHomepage: Boolean = false ): List { if (!applyHierarchy) { return pages.map { PageRsListItem.Real(it) } @@ -29,12 +34,30 @@ internal fun buildRows( val visible = pages.filterNot { it.remotePageId in hiddenIds } val tree = flattenToTree(visible) return buildList { - homepage?.let { add(PageRsListItem.Virtual(PageRsListItem.Virtual.Kind.HOMEPAGE, it)) } + if (showSiteEditorHomepage) { + add(siteEditorHomepageRow()) + } else { + homepage?.let { add(PageRsListItem.Virtual(PageRsListItem.Virtual.Kind.HOMEPAGE, it)) } + } postsPage?.let { add(PageRsListItem.Virtual(PageRsListItem.Virtual.Kind.POSTS_PAGE, it)) } addAll(tree) } } +/** + * The synthetic SITE_EDITOR virtual row. It has no real page, so its visible text is rendered from + * string resources in the composable; the sentinel id lets the tap handler recognize it. + */ +private fun siteEditorHomepageRow() = PageRsListItem.Virtual( + kind = PageRsListItem.Virtual.Kind.SITE_EDITOR, + page = PageRsUiModel( + remotePageId = SITE_EDITOR_PAGE_ID, + title = "", + excerpt = "", + date = "" + ) +) + internal fun flattenToTree(pages: List): List { val byId = pages.associateBy { it.remotePageId } val childrenByParent = pages diff --git a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PagesRsListActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PagesRsListActivity.kt index 6a8fab57aef1..78a85f670962 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PagesRsListActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PagesRsListActivity.kt @@ -18,6 +18,7 @@ import org.wordpress.android.R import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.ui.ActivityLauncher import org.wordpress.android.ui.PagePostCreationSourcesDetail.PAGE_FROM_PAGES_LIST +import org.wordpress.android.ui.WPWebViewActivity import org.wordpress.android.ui.blaze.BlazeFlowSource import org.wordpress.android.ui.compose.theme.AppThemeM3 import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalPhaseHelper @@ -30,6 +31,7 @@ import org.wordpress.android.util.extensions.clipboardManager import org.wordpress.android.util.extensions.setContent import org.wordpress.android.viewmodel.mlp.ModalLayoutPickerViewModel import org.wordpress.android.viewmodel.observeEvent +import org.wordpress.android.viewmodel.wpwebview.WPWebViewSource import javax.inject.Inject @AndroidEntryPoint @@ -109,6 +111,7 @@ class PagesRsListActivity : BaseAppCompatActivity() { is PageRsListEvent.SharePage -> ActivityLauncher.openShareIntent(this, event.url, event.title) is PageRsListEvent.CopyPageUrl -> copyUrlToClipboard(event.url) + is PageRsListEvent.OpenSiteEditor -> openSiteEditor(event.url, event.useWpComCredentials) is PageRsListEvent.PromoteWithBlaze -> ActivityLauncher.openPromoteWithBlaze(this, event.page, BlazeFlowSource.PAGES_LIST) is PageRsListEvent.ShowToast -> ToastUtils.showToast(this, event.messageResId) @@ -116,6 +119,18 @@ class PagesRsListActivity : BaseAppCompatActivity() { } } + private fun openSiteEditor(url: String, useWpComCredentials: Boolean) { + if (useWpComCredentials) { + WPWebViewActivity.openUrlByUsingGlobalWPCOMCredentials( + this, + url, + WPWebViewSource.PAGE_LIST_EDIT_HOMEPAGE + ) + } else { + WPWebViewActivity.openURL(this, url, WPWebViewSource.PAGE_LIST_EDIT_HOMEPAGE) + } + } + private fun copyUrlToClipboard(url: String) { clipboardManager?.setPrimaryClip(ClipData.newPlainText(CLIPBOARD_URL_LABEL, url)) // Android 13+ shows its own confirmation UI when the clipboard changes. diff --git a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PagesRsListViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PagesRsListViewModel.kt index f5f207e8e967..761710f955f1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PagesRsListViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/PagesRsListViewModel.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -28,14 +29,19 @@ import org.greenrobot.eventbus.ThreadMode import org.wordpress.android.R import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.fluxc.Dispatcher +import org.wordpress.android.fluxc.generated.EditorThemeActionBuilder import org.wordpress.android.fluxc.model.SiteHomepageSettings.ShowOnFront import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.model.post.PostStatus as FluxCPostStatus import org.wordpress.android.fluxc.store.AccountStore +import org.wordpress.android.fluxc.store.EditorThemeStore +import org.wordpress.android.fluxc.store.EditorThemeStore.FetchEditorThemePayload +import org.wordpress.android.fluxc.store.EditorThemeStore.OnEditorThemeChanged import org.wordpress.android.fluxc.store.PostStore import org.wordpress.android.fluxc.store.PostStore.OnPostUploaded import org.wordpress.android.ui.blaze.BlazeFeatureUtils import org.wordpress.android.ui.mysite.SelectedSiteRepository +import org.wordpress.android.ui.pages.PageItem import org.wordpress.android.ui.posts.AuthorFilterSelection import org.wordpress.android.ui.postsrs.PostRsErrorUtils import org.wordpress.android.ui.postsrs.SnackbarMessage @@ -45,6 +51,7 @@ import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.util.AppLog import org.wordpress.android.util.NetworkUtilsWrapper import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper +import org.wordpress.android.util.config.SiteEditorMVPFeatureConfig import org.wordpress.android.viewmodel.ResourceProvider import rs.wordpress.cache.kotlin.ObservableMetadataCollection import rs.wordpress.cache.kotlin.getObservablePostMetadataCollectionWithEditContext @@ -74,6 +81,8 @@ internal class PagesRsListViewModel @Inject constructor( private val accountStore: AccountStore, private val appPrefsWrapper: AppPrefsWrapper, private val analyticsTracker: AnalyticsTrackerWrapper, + private val editorThemeStore: EditorThemeStore, + private val siteEditorMVPFeatureConfig: SiteEditorMVPFeatureConfig, ) : ViewModel() { private val _tabStates = MutableStateFlow>(emptyMap()) val tabStates: StateFlow> = _tabStates.asStateFlow() @@ -133,12 +142,25 @@ internal class PagesRsListViewModel @Inject constructor( ) val authorFilter: StateFlow = _authorFilter.asStateFlow() + // Whether the site's homepage uses a block-based theme. Seeded from the local cache and kept + // current via [onEditorThemeChanged]. When true (and the Site Editor MVP flag is on) the + // published tab shows a single SITE_EDITOR virtual row that opens the Site Editor web view. + private var isBlockBasedTheme = false + + // Guards against a rapid double-tap on the SITE_EDITOR row launching two web views. + private var isLaunchingSiteEditor = false + init { dispatcher.register(this) if (site == null) { _events.trySend(PageRsListEvent.ShowToast(R.string.blog_not_found)) _events.trySend(PageRsListEvent.Finish) } else { + // Only the SITE_EDITOR virtual row needs the block-theme state, so skip the fetch + // entirely when the Site Editor MVP flag is off to avoid a request on every visit. + if (siteEditorMVPFeatureConfig.isEnabled()) { + refreshEditorTheme(site) + } @OptIn(FlowPreview::class) viewModelScope.launch { _searchQuery @@ -350,6 +372,31 @@ internal class PagesRsListViewModel @Inject constructor( refreshAllTabs() } + /** Seeds [isBlockBasedTheme] from the local cache and dispatches a remote refresh. */ + private fun refreshEditorTheme(site: SiteModel) { + isBlockBasedTheme = editorThemeStore.getIsBlockBasedTheme(site) + dispatcher.dispatch( + EditorThemeActionBuilder.newFetchEditorThemeAction( + FetchEditorThemePayload(site, gssEnabled = true) + ) + ) + } + + @Suppress("unused") + @Subscribe(threadMode = ThreadMode.MAIN_ORDERED) + fun onEditorThemeChanged(event: OnEditorThemeChanged) { + val site = this.site ?: return + val isBlockBased = event.editorTheme?.themeSupport?.isEditorThemeBlockBased() + if (site.id != event.siteId || isBlockBased == null || isBlockBased == isBlockBasedTheme) { + return + } + isBlockBasedTheme = isBlockBased + // Rebuild the published tab from cache so the SITE_EDITOR row appears/disappears. + if (collections.containsKey(PageRsListTab.PUBLISHED)) { + viewModelScope.launch { loadItemsForTab(PageRsListTab.PUBLISHED) } + } + } + /** Refreshes all currently initialized tabs. */ @MainThread fun refreshAllTabs() { @@ -453,11 +500,15 @@ internal class PagesRsListViewModel @Inject constructor( val site = this.site if (site == null || _isOpeningPage.value) return + if (remotePageId == SITE_EDITOR_PAGE_ID) { + openSiteEditor(site) + return + } + val page = _tabStates.value[tab] ?.pages ?.firstOrNull { it.remotePageId == remotePageId } ?.page - when { tab == PageRsListTab.TRASHED || page?.isTrashed == true -> _pendingConfirmation.value = PageRsListConfirmation.MoveToDraft(remotePageId) @@ -465,6 +516,27 @@ internal class PagesRsListViewModel @Inject constructor( } } + /** Opens the block-theme homepage in the Site Editor web view, matching the legacy pages list. */ + private fun openSiteEditor(site: SiteModel) { + if (isLaunchingSiteEditor) return + isLaunchingSiteEditor = true + analyticsTracker.track(Stat.PAGES_EDIT_HOMEPAGE_ITEM_PRESSED, site) + val useWpComCredentials = site.isWPCom || site.isWPComAtomic || site.isPrivateWPComAtomic + _events.trySend( + PageRsListEvent.OpenSiteEditor( + url = PageItem.VirtualHomepage.Action.OpenSiteEditor.getUrl(site), + useWpComCredentials = useWpComCredentials + ) + ) + // The web view opens in a separate activity with no completion callback, so clear the + // guard after a short debounce: a rapid double-tap is dropped, but the row stays tappable + // when the user returns. + viewModelScope.launch { + delay(SITE_EDITOR_LAUNCH_DEBOUNCE_MS) + isLaunchingSiteEditor = false + } + } + private fun proceedOpenPage(site: SiteModel, remotePageId: Long, lastModified: String?) { analyticsTracker.track( Stat.PAGES_LIST_ITEM_SELECTED, @@ -1111,11 +1183,13 @@ internal class PagesRsListViewModel @Inject constructor( // and the construction-time [site] snapshot would pin stale pageOnFront / // pageForPosts values onto the virtual rows. val currentSite = selectedSiteRepository.getSelectedSite() ?: site + val showSiteEditorHomepage = siteEditorMVPFeatureConfig.isEnabled() && isBlockBasedTheme val rows = buildRows( pages = uiModels, applyHierarchy = applyHierarchy, pageOnFront = currentSite?.pageOnFront ?: 0L, - pageForPosts = currentSite?.pageForPosts ?: 0L + pageForPosts = currentSite?.pageForPosts ?: 0L, + showSiteEditorHomepage = showSiteEditorHomepage ).map { row -> row.withMenuActions(currentSite) } updateTabUiState(tab) { copy(pages = rows, isLoading = false, error = null, isAuthError = false) @@ -1201,14 +1275,22 @@ internal class PagesRsListViewModel @Inject constructor( } else { true } - val actions = computePageMenuActions( - status = page.status, - isHomepage = pageOnFront != 0L && page.remotePageId == pageOnFront, - isPostsPage = pageForPosts != 0L && page.remotePageId == pageForPosts, - hasPassword = page.hasPassword, - isBlazeEligibleSite = site != null && blazeFeatureUtils.isSiteBlazeEligible(site), - canManageHomepage = canManageHomepage - ) + // The SITE_EDITOR virtual has no backing page, so it gets no overflow menu. Its synthetic + // page already has empty actions, so this leaves it unchanged below. + val isSiteEditor = this is PageRsListItem.Virtual && + kind == PageRsListItem.Virtual.Kind.SITE_EDITOR + val actions = if (isSiteEditor) { + emptyList() + } else { + computePageMenuActions( + status = page.status, + isHomepage = pageOnFront != 0L && page.remotePageId == pageOnFront, + isPostsPage = pageForPosts != 0L && page.remotePageId == pageForPosts, + hasPassword = page.hasPassword, + isBlazeEligibleSite = site != null && blazeFeatureUtils.isSiteBlazeEligible(site), + canManageHomepage = canManageHomepage + ) + } if (actions == page.actions) return this val updated = page.copy(actions = actions) return when (this) { @@ -1328,6 +1410,7 @@ internal class PagesRsListViewModel @Inject constructor( companion object { private const val PAGE_SIZE = 20 private const val SEARCH_DEBOUNCE_MS = 250L + private const val SITE_EDITOR_LAUNCH_DEBOUNCE_MS = 1000L internal const val MIN_SEARCH_QUERY_LENGTH = 3 private const val THUMBNAIL_SIZE_DP = 64 private val ALL_STATUSES = PageRsListTab.entries.flatMap { it.statuses }.distinct() diff --git a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/screens/PageRsRow.kt b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/screens/PageRsRow.kt index 0d93f9ac96cc..b3fdc094ac45 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/screens/PageRsRow.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/pagesrs/screens/PageRsRow.kt @@ -115,11 +115,11 @@ private fun PageContentItem( Spacer(modifier = Modifier.width(12.dp)) } Column(modifier = Modifier.weight(1f)) { - val virtualLabel = virtualKind?.let { stringResource(it.labelResId()) } + val rowText = pageRowText(page, virtualKind) val statusLabel = page.statusLabelResId.takeIf { it != 0 }?.let { stringResource(it) } val bullet = stringResource(R.string.bullet_with_spaces) val headerText = listOfNotNull( - virtualLabel, + rowText.headerLabel, statusLabel, page.date.takeIf { it.isNotBlank() }, page.authorDisplayName?.takeIf { it.isNotBlank() } @@ -137,15 +137,15 @@ private fun PageContentItem( Spacer(modifier = Modifier.height(4.dp)) } Text( - text = page.title.ifBlank { stringResource(R.string.untitled_in_parentheses) }, + text = rowText.title, style = MaterialTheme.typography.titleMedium, maxLines = 2, overflow = TextOverflow.Ellipsis ) - if (page.excerpt.isNotBlank()) { + if (rowText.subtitle.isNotBlank()) { Spacer(modifier = Modifier.height(4.dp)) Text( - text = page.excerpt, + text = rowText.subtitle, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 2, @@ -282,14 +282,49 @@ private fun ErrorItem(modifier: Modifier = Modifier) { } } +private data class PageRowText( + val title: String, + val subtitle: String, + val headerLabel: String? +) + +/** + * Resolves the row's title, subtitle, and header label. The SITE_EDITOR row has no backing page, so + * its title/subtitle come from string resources and it shows no header label. + */ +@Composable +private fun pageRowText( + page: PageRsUiModel, + virtualKind: PageRsListItem.Virtual.Kind? +): PageRowText { + val isSiteEditor = virtualKind == PageRsListItem.Virtual.Kind.SITE_EDITOR + val title = if (isSiteEditor) { + stringResource(R.string.virtual_homepage_title) + } else { + page.title.ifBlank { stringResource(R.string.untitled_in_parentheses) } + } + val subtitle = if (isSiteEditor) { + stringResource(R.string.virtual_homepage_subtitle) + } else { + page.excerpt + } + val headerLabel = virtualKind + ?.takeUnless { isSiteEditor } + ?.let { stringResource(it.labelResId()) } + return PageRowText(title, subtitle, headerLabel) +} + private fun PageRsListItem.Virtual.Kind.icon(): ImageVector = when (this) { - PageRsListItem.Virtual.Kind.HOMEPAGE -> Icons.Filled.Home + PageRsListItem.Virtual.Kind.HOMEPAGE, + PageRsListItem.Virtual.Kind.SITE_EDITOR -> Icons.Filled.Home PageRsListItem.Virtual.Kind.POSTS_PAGE -> Icons.AutoMirrored.Filled.Article } +// SITE_EDITOR renders its own title from string resources and has no header label. private fun PageRsListItem.Virtual.Kind.labelResId(): Int = when (this) { PageRsListItem.Virtual.Kind.HOMEPAGE -> R.string.site_settings_homepage PageRsListItem.Virtual.Kind.POSTS_PAGE -> R.string.site_settings_posts_page + PageRsListItem.Virtual.Kind.SITE_EDITOR -> R.string.virtual_homepage_title } private const val INDENT_STEP_DP = 16 diff --git a/WordPress/src/test/java/org/wordpress/android/ui/pagesrs/PageRsTreeBuilderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/pagesrs/PageRsTreeBuilderTest.kt index 4dc626244712..178f4dda88ed 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/pagesrs/PageRsTreeBuilderTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/pagesrs/PageRsTreeBuilderTest.kt @@ -167,6 +167,85 @@ class PageRsTreeBuilderTest { .containsOnly(0) } + @Test + fun `showSiteEditorHomepage prepends a single SITE_EDITOR virtual`() { + val pages = listOf(page(1), page(2), page(3)) + + val rows = buildRows( + pages, + applyHierarchy = true, + pageOnFront = 0L, + pageForPosts = 0L, + showSiteEditorHomepage = true + ) + + assertThat(rows).hasSize(4) + assertThat(rows[0]).isInstanceOfSatisfying(PageRsListItem.Virtual::class.java) { virtual -> + assertThat(virtual.kind).isEqualTo(PageRsListItem.Virtual.Kind.SITE_EDITOR) + assertThat(virtual.page.remotePageId).isEqualTo(SITE_EDITOR_PAGE_ID) + } + assertThat(rows.drop(1).map { (it as PageRsListItem.Real).page.remotePageId }) + .containsExactly(1L, 2L, 3L) + } + + @Test + fun `showSiteEditorHomepage replaces the static HOMEPAGE row and hides its page`() { + val pages = listOf(page(1), page(2), page(3)) + + val rows = buildRows( + pages, + applyHierarchy = true, + pageOnFront = 2L, + pageForPosts = 0L, + showSiteEditorHomepage = true + ) + + // SITE_EDITOR is shown instead of the static HOMEPAGE virtual, and page 2 is hidden. + assertThat(rows).hasSize(3) + assertThat((rows[0] as PageRsListItem.Virtual).kind) + .isEqualTo(PageRsListItem.Virtual.Kind.SITE_EDITOR) + assertThat(rows.drop(1).map { (it as PageRsListItem.Real).page.remotePageId }) + .containsExactly(1L, 3L) + } + + @Test + fun `showSiteEditorHomepage still shows the POSTS_PAGE virtual after SITE_EDITOR`() { + val pages = listOf(page(1), page(2), page(3)) + + val rows = buildRows( + pages, + applyHierarchy = true, + pageOnFront = 0L, + pageForPosts = 3L, + showSiteEditorHomepage = true + ) + + assertThat((rows[0] as PageRsListItem.Virtual).kind) + .isEqualTo(PageRsListItem.Virtual.Kind.SITE_EDITOR) + assertThat((rows[1] as PageRsListItem.Virtual).kind) + .isEqualTo(PageRsListItem.Virtual.Kind.POSTS_PAGE) + assertThat((rows[1] as PageRsListItem.Virtual).page.remotePageId).isEqualTo(3L) + assertThat(rows.drop(2).map { (it as PageRsListItem.Real).page.remotePageId }) + .containsExactly(1L, 2L) + } + + @Test + fun `showSiteEditorHomepage is ignored when applyHierarchy is false`() { + val pages = listOf(page(1), page(2)) + + val rows = buildRows( + pages, + applyHierarchy = false, + pageOnFront = 0L, + pageForPosts = 0L, + showSiteEditorHomepage = true + ) + + assertThat(rows).allMatch { it is PageRsListItem.Real } + assertThat(rows.map { (it as PageRsListItem.Real).page.remotePageId }) + .containsExactly(1L, 2L) + } + @Test fun `self-parented page is appended as a flat row instead of dropped`() { val pages = listOf( diff --git a/WordPress/src/test/java/org/wordpress/android/ui/pagesrs/PagesRsListViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/pagesrs/PagesRsListViewModelTest.kt index 038a4ed9c820..0b1a58879f63 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/pagesrs/PagesRsListViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/pagesrs/PagesRsListViewModelTest.kt @@ -23,6 +23,7 @@ import org.wordpress.android.fluxc.Dispatcher import org.wordpress.android.fluxc.model.PostModel import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.AccountStore +import org.wordpress.android.fluxc.store.EditorThemeStore import org.wordpress.android.fluxc.store.PostStore import org.wordpress.android.fluxc.store.PostStore.OnPostUploaded import org.wordpress.android.ui.blaze.BlazeFeatureUtils @@ -33,6 +34,7 @@ import org.wordpress.android.ui.postsrs.data.WpServiceProvider import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.util.NetworkUtilsWrapper import org.wordpress.android.util.analytics.AnalyticsTrackerWrapper +import org.wordpress.android.util.config.SiteEditorMVPFeatureConfig import org.wordpress.android.viewmodel.ResourceProvider @ExperimentalCoroutinesApi @@ -50,6 +52,8 @@ internal class PagesRsListViewModelTest : BaseUnitTest(StandardTestDispatcher()) @Mock lateinit var accountStore: AccountStore @Mock lateinit var appPrefsWrapper: AppPrefsWrapper @Mock lateinit var analyticsTracker: AnalyticsTrackerWrapper + @Mock lateinit var editorThemeStore: EditorThemeStore + @Mock lateinit var siteEditorMVPFeatureConfig: SiteEditorMVPFeatureConfig private lateinit var site: SiteModel private var activeViewModel: PagesRsListViewModel? = null @@ -84,6 +88,8 @@ internal class PagesRsListViewModelTest : BaseUnitTest(StandardTestDispatcher()) accountStore = accountStore, appPrefsWrapper = appPrefsWrapper, analyticsTracker = analyticsTracker, + editorThemeStore = editorThemeStore, + siteEditorMVPFeatureConfig = siteEditorMVPFeatureConfig, ).also { activeViewModel = it } @Test @@ -297,6 +303,49 @@ internal class PagesRsListViewModelTest : BaseUnitTest(StandardTestDispatcher()) ) } + @Test + fun `openPage on the site editor row emits OpenSiteEditor`() = test { + site.setIsWPCom(true) + val viewModel = createViewModel() + + viewModel.events.test { + viewModel.openPage(SITE_EDITOR_PAGE_ID, PageRsListTab.PUBLISHED) + + val event = awaitItem() + assertThat(event).isInstanceOf(PageRsListEvent.OpenSiteEditor::class.java) + event as PageRsListEvent.OpenSiteEditor + assertThat(event.url).endsWith("site-editor.php?canvas=edit") + assertThat(event.useWpComCredentials).isTrue() + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `openPage on the site editor row tracks PAGES_EDIT_HOMEPAGE_ITEM_PRESSED`() { + val viewModel = createViewModel() + + viewModel.openPage(SITE_EDITOR_PAGE_ID, PageRsListTab.PUBLISHED) + + verify(analyticsTracker).track( + eq(Stat.PAGES_EDIT_HOMEPAGE_ITEM_PRESSED), + eq(site), + anyOrNull>() + ) + } + + @Test + fun `openPage on the site editor row does not track PAGES_LIST_ITEM_SELECTED`() { + val viewModel = createViewModel() + + viewModel.openPage(SITE_EDITOR_PAGE_ID, PageRsListTab.PUBLISHED) + + verify(analyticsTracker, never()).track( + eq(Stat.PAGES_LIST_ITEM_SELECTED), + any(), + any>() + ) + } + @Test fun `registers with the dispatcher on init and unregisters on clear`() { val viewModel = createViewModel()