diff --git a/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutCategoryModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutCategoryModel.kt index d0ab70af2e29..95a300198517 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutCategoryModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutCategoryModel.kt @@ -11,7 +11,8 @@ import org.wordpress.android.fluxc.network.rest.wpcom.theme.StarterDesignCategor class LayoutCategoryModel( private val starterDesignCategory: StarterDesignCategory? = null, private val blockLayoutCategory: GutenbergLayoutCategory? = null, - val isRecommended: Boolean = false + val isRecommended: Boolean = false, + val randomizeOrder: Boolean = false ) : Parcelable { val slug: String get() = starterDesignCategory?.slug ?: blockLayoutCategory?.slug ?: "" @@ -24,8 +25,14 @@ class LayoutCategoryModel( } @JvmName("starterDesignToLayoutCategories") -fun List.toLayoutCategories(recommended: Boolean = false) = - map { LayoutCategoryModel(starterDesignCategory = it, isRecommended = recommended) } +fun List.toLayoutCategories(recommended: Boolean = false, randomizeOrder: Boolean = false) = + map { + LayoutCategoryModel( + starterDesignCategory = it, + isRecommended = recommended, + randomizeOrder = randomizeOrder + ) + } @JvmName("gutenbergLayoutToLayoutCategories") fun List.toLayoutCategories() = map { LayoutCategoryModel(blockLayoutCategory = it) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutPickerUiState.kt b/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutPickerUiState.kt index 44f2dbb00651..8f39db02d30a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutPickerUiState.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutPickerUiState.kt @@ -17,6 +17,7 @@ sealed class LayoutPickerUiState( override val isToolbarVisible: Boolean = false, val selectedCategoriesSlugs: ArrayList = arrayListOf(), val selectedLayoutSlug: String? = null, + val isSelectedLayoutRecommended: Boolean = false, val loadedThumbnailSlugs: ArrayList = arrayListOf(), val categories: List = listOf(), val layoutCategories: List = listOf(), diff --git a/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutPickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutPickerViewModel.kt index ef43a92b8f6e..9bfcdde0a2dc 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutPickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/layoutpicker/LayoutPickerViewModel.kt @@ -69,6 +69,9 @@ abstract class LayoutPickerViewModel( var nestedScrollStates: Bundle = Bundle() + // Map that holds the ordered/randomised layouts list per category (key: slug) + val orderedLayouts: MutableMap> = mutableMapOf() + abstract fun fetchLayouts(preferCache: Boolean = false) open fun onPreviewChooseTapped() = onDismissPreview() @@ -155,8 +158,16 @@ abstract class LayoutPickerViewModel( } selectedCategories.forEach { category -> - - val layouts = layouts.getFilteredLayouts(category.slug).map { layout -> + val ordered = orderedLayouts[category.slug] ?: if (category.randomizeOrder) { + val randomised = layouts.getFilteredLayouts(category.slug).shuffled() + orderedLayouts[category.slug] = randomised + randomised + } else { + val ordered = layouts.getFilteredLayouts(category.slug) + orderedLayouts[category.slug] = ordered + ordered + } + val layouts = ordered.map { layout -> val preview = when (_previewMode.value) { MOBILE -> layout.previewMobile TABLET -> layout.previewTablet @@ -170,7 +181,7 @@ abstract class LayoutPickerViewModel( mShotPreview = thumbnailPreview, selected = layout.slug == state.selectedLayoutSlug, tapOpensPreview = thumbnailTapOpensPreview, - onItemTapped = { onLayoutTapped(layoutSlug = layout.slug) }, + onItemTapped = { onLayoutTapped(layoutSlug = layout.slug, category.isRecommended) }, onThumbnailReady = { onThumbnailReady(layoutSlug = layout.slug) } ) } @@ -194,7 +205,7 @@ abstract class LayoutPickerViewModel( * Layout tapped * @param layoutSlug the slug of the tapped layout */ - open fun onLayoutTapped(layoutSlug: String) { + open fun onLayoutTapped(layoutSlug: String, isRecommended: Boolean = false) { (uiState.value as? Content)?.let { state -> if (!state.loadedThumbnailSlugs.contains(layoutSlug)) return // No action if (layoutSlug == state.selectedLayoutSlug) { // deselect diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationActivity.kt index fd95bbc0d263..b9c4276453f7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/SiteCreationActivity.kt @@ -124,6 +124,7 @@ class SiteCreationActivity : LocaleAwareActivity(), finish() }) mainViewModel.onBackPressedObservable.observe(this, Observer { + ActivityUtils.hideKeyboard(this) super.onBackPressed() }) siteCreationIntentsViewModel.onBackButtonPressed.observe(this, Observer { @@ -181,7 +182,7 @@ class SiteCreationActivity : LocaleAwareActivity(), val fragment = when (target.wizardStep) { INTENTS -> SiteCreationIntentsFragment() SITE_NAME -> SiteCreationSiteNameFragment.newInstance(target.wizardState.siteIntent) - SITE_DESIGNS -> HomePagePickerFragment() + SITE_DESIGNS -> HomePagePickerFragment.newInstance(target.wizardState.siteIntent) DOMAINS -> SiteCreationDomainsFragment.newInstance( screenTitle ) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/misc/SiteCreationTracker.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/misc/SiteCreationTracker.kt index 33423b588c96..09f2fd446f3e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/misc/SiteCreationTracker.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/misc/SiteCreationTracker.kt @@ -9,6 +9,7 @@ import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker.PROPERTY.C import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker.PROPERTY.FILTER import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker.PROPERTY.LOCATION import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker.PROPERTY.PREVIEW_MODE +import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker.PROPERTY.RECOMMENDED import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker.PROPERTY.SEARCH_TERM import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker.PROPERTY.SEGMENT_ID import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker.PROPERTY.SEGMENT_NAME @@ -47,7 +48,8 @@ class SiteCreationTracker @Inject constructor(val tracker: AnalyticsTrackerWrapp SELECTED_FILTERS("selected_filters"), VERTICAL_SLUG("vertical_slug"), VARIATION("variation"), - SITE_NAME("site_name") + SITE_NAME("site_name"), + RECOMMENDED("recommended") } private var designSelectionSkipped: Boolean = false @@ -172,11 +174,11 @@ class SiteCreationTracker @Inject constructor(val tracker: AnalyticsTrackerWrapp tracker.track(AnalyticsTracker.Stat.ENHANCED_SITE_CREATION_SITE_DESIGN_SKIPPED) } - fun trackSiteDesignSelected(template: String) { + fun trackSiteDesignSelected(template: String, recommended: Boolean) { designSelectionSkipped = false tracker.track( AnalyticsTracker.Stat.ENHANCED_SITE_CREATION_SITE_DESIGN_SELECTED, - mapOf(TEMPLATE.key to template) + mapOf(TEMPLATE.key to template, RECOMMENDED.key to recommended) ) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerFragment.kt index c73537e416ee..68ac36c93b4f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerFragment.kt @@ -42,6 +42,9 @@ class HomePagePickerFragment : Fragment() { @Inject lateinit var recommendedDimensionProvider: SiteDesignRecommendedDimensionProvider private lateinit var viewModel: HomePagePickerViewModel + private val siteIntent: String? + get() = arguments?.getString(ARG_SITE_INTENT) + override fun onAttach(context: Context) { super.onAttach(context) (requireActivity().applicationContext as WordPress).component().inject(this) @@ -128,7 +131,7 @@ class HomePagePickerFragment : Fragment() { } } - viewModel.start(displayUtils.isTablet()) + viewModel.start(siteIntent, displayUtils.isTablet()) } private fun HomePagePickerFragmentBinding.setHeaderVisibility(visible: Boolean) { @@ -160,4 +163,18 @@ class HomePagePickerFragment : Fragment() { }) viewModel.onAppBarOffsetChanged(0, scrollThreshold) } + + companion object { + private const val ARG_SITE_INTENT = "arg_site_intent" + + fun newInstance(siteIntent: String?): HomePagePickerFragment { + val bundle = Bundle().apply { + putString(ARG_SITE_INTENT, siteIntent) + } + + return HomePagePickerFragment().apply { + arguments = bundle + } + } + } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt index cbb2ed932072..5e4416c8c1f7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModel.kt @@ -5,10 +5,8 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext import org.wordpress.android.R import org.wordpress.android.fluxc.Dispatcher -import org.wordpress.android.fluxc.network.rest.wpcom.theme.StarterDesignCategory import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.modules.UI_THREAD -import org.wordpress.android.ui.layoutpicker.LayoutCategoryModel import org.wordpress.android.ui.sitecreation.misc.SiteCreationErrorType.INTERNET_UNAVAILABLE_ERROR import org.wordpress.android.ui.sitecreation.misc.SiteCreationErrorType.UNKNOWN import org.wordpress.android.ui.sitecreation.misc.SiteCreationTracker @@ -16,11 +14,8 @@ import org.wordpress.android.ui.layoutpicker.LayoutPickerUiState.Content import org.wordpress.android.ui.layoutpicker.LayoutPickerUiState.Loading import org.wordpress.android.ui.layoutpicker.LayoutPickerUiState.Error import org.wordpress.android.ui.layoutpicker.LayoutPickerViewModel -import org.wordpress.android.ui.layoutpicker.toLayoutCategories -import org.wordpress.android.ui.layoutpicker.toLayoutModels import org.wordpress.android.ui.sitecreation.usecases.FetchHomePageLayoutsUseCase import org.wordpress.android.util.NetworkUtilsWrapper -import org.wordpress.android.viewmodel.ResourceProvider import org.wordpress.android.viewmodel.SingleLiveEvent import javax.inject.Inject import javax.inject.Named @@ -36,7 +31,7 @@ class HomePagePickerViewModel @Inject constructor( private val analyticsTracker: SiteCreationTracker, @Named(BG_THREAD) override val bgDispatcher: CoroutineDispatcher, @Named(UI_THREAD) override val mainDispatcher: CoroutineDispatcher, - private val resourceProvider: ResourceProvider + private val recommendationProvider: SiteDesignRecommendationProvider ) : LayoutPickerViewModel(mainDispatcher, bgDispatcher, networkUtils, analyticsTracker) { private val _onDesignActionPressed = SingleLiveEvent() val onDesignActionPressed: LiveData = _onDesignActionPressed @@ -44,6 +39,8 @@ class HomePagePickerViewModel @Inject constructor( private val _onBackButtonPressed = SingleLiveEvent() val onBackButtonPressed: LiveData = _onBackButtonPressed + private lateinit var vertical: String + override val useCachedData: Boolean = false override val shouldUseMobileThumbnail = true override val thumbnailTapOpensPreview = true @@ -62,7 +59,8 @@ class HomePagePickerViewModel @Inject constructor( dispatcher.unregister(fetchHomePageLayoutsUseCase) } - fun start(isTablet: Boolean = false) { + fun start(intent: String? = null, isTablet: Boolean = false) { + vertical = intent ?: "" initializePreviewMode(isTablet) if (uiState.value !is Content) { analyticsTracker.trackSiteDesignViewed(selectedPreviewMode().key) @@ -85,25 +83,21 @@ class HomePagePickerViewModel @Inject constructor( analyticsTracker.trackErrorShown(ERROR_CONTEXT, UNKNOWN, "Error fetching designs") updateUiState(Error()) } else { - handleResponse(event.designs.toLayoutModels(), categoriesWithRecommendations(event.categories)) + recommendationProvider.handleResponse( + vertical, + event.designs, + event.categories, + this@HomePagePickerViewModel::handleResponse + ) } } } } - private fun categoriesWithRecommendations(categories: List): List { - val defaultVertical = resourceProvider.getString(R.string.hpp_recommended_default_vertical) - val recommendedVertical = resourceProvider.getString(R.string.hpp_recommended_title, defaultVertical) - // TODO: The link with the selected vertical and actual fallback recommendations will be implemented separately - val recommendedCategory = categories.first { it.slug == "blog" } - .copy(title = recommendedVertical, description = recommendedVertical) - return listOf(recommendedCategory).toLayoutCategories(true) + categories.toLayoutCategories() - } - - override fun onLayoutTapped(layoutSlug: String) { + override fun onLayoutTapped(layoutSlug: String, isRecommended: Boolean) { (uiState.value as? Content)?.let { if (it.loadedThumbnailSlugs.contains(layoutSlug)) { - updateUiState(it.copy(selectedLayoutSlug = layoutSlug)) + updateUiState(it.copy(selectedLayoutSlug = layoutSlug, isSelectedLayoutRecommended = isRecommended)) onPreviewTapped() loadLayouts() } @@ -111,15 +105,15 @@ class HomePagePickerViewModel @Inject constructor( } override fun onPreviewChooseTapped() { - super.onPreviewChooseTapped() onChooseTapped() } fun onChooseTapped() { - // TODO: adapt this to the new flow selectedLayout?.let { layout -> + super.onPreviewChooseTapped() val template = layout.slug - analyticsTracker.trackSiteDesignSelected(template) + val isRecommended = (uiState.value as? Content)?.isSelectedLayoutRecommended == true + analyticsTracker.trackSiteDesignSelected(template, isRecommended) _onDesignActionPressed.value = DesignSelectionAction.Choose(template) return } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/SiteDesignRecommendationProvider.kt b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/SiteDesignRecommendationProvider.kt new file mode 100644 index 000000000000..6d2b1ce7bd46 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/sitecreation/theme/SiteDesignRecommendationProvider.kt @@ -0,0 +1,76 @@ +package org.wordpress.android.ui.sitecreation.theme + +import org.wordpress.android.R +import org.wordpress.android.fluxc.network.rest.wpcom.theme.StarterDesign +import org.wordpress.android.fluxc.network.rest.wpcom.theme.StarterDesignCategory +import org.wordpress.android.ui.layoutpicker.LayoutCategoryModel +import org.wordpress.android.ui.layoutpicker.LayoutModel +import org.wordpress.android.ui.layoutpicker.toLayoutCategories +import org.wordpress.android.ui.layoutpicker.toLayoutModels +import org.wordpress.android.viewmodel.ResourceProvider +import javax.inject.Inject + +class SiteDesignRecommendationProvider @Inject constructor(private val resourceProvider: ResourceProvider) { + fun handleResponse( + vertical: String, + designs: List, + categories: List, + responseHandler: (layouts: List, categories: List) -> Unit + ) { + val verticalSlug: String? = if (vertical.isNullOrEmpty()) null else getVerticalSlug(vertical) + val hasRecommendations = !verticalSlug.isNullOrEmpty() && + designs.any { it.group != null && it.group.contains(verticalSlug) } + + if (hasRecommendations) { + val recommendedTitle = resourceProvider.getString(R.string.hpp_recommended_title, vertical) + // Create a new category for the recommendations + val recommendedCategory = StarterDesignCategory( + slug = "recommended_$verticalSlug", // The slug is not used but should not already exist + title = recommendedTitle, + description = recommendedTitle, + emoji = "" + ) + val designsWithRecommendations = designs.map { + // Add the new category to the recommended designs so that they are filtered correctly + // in the `LayoutPickerViewModel.loadLayouts()` method + if (it.group.contains(verticalSlug)) { + it.copy(categories = it.categories + recommendedCategory) + } else { + it + } + }.toLayoutModels() + val categoriesWithRecommendations = + listOf(recommendedCategory).toLayoutCategories(recommended = true) + + categories.toLayoutCategories(randomizeOrder = true) + responseHandler(designsWithRecommendations, categoriesWithRecommendations) + } else { + // If no designs are recommended for the selected vertical recommend the blog category + val recommendedTitle = resourceProvider.getString( + R.string.hpp_recommended_title, + resourceProvider.getString(R.string.hpp_recommended_default_vertical) + ) + val recommendedCategory = categories.firstOrNull { it.slug == "blog" }?.copy( + title = recommendedTitle, + description = recommendedTitle + ) + if (recommendedCategory == null) { + // If there is no blog category do not show a recommendation + responseHandler(designs.toLayoutModels(), categories.toLayoutCategories(randomizeOrder = true)) + } else { + val categoriesWithRecommendations = + listOf(recommendedCategory).toLayoutCategories(recommended = true, randomizeOrder = true) + + categories.toLayoutCategories(randomizeOrder = true) + responseHandler(designs.toLayoutModels(), categoriesWithRecommendations) + } + } + } + + private fun getVerticalSlug(vertical: String): String? { + val slugsArray = resourceProvider.getStringArray(R.array.site_creation_intents_slugs) + val verticalArray = resourceProvider.getStringArray(R.array.site_creation_intents_strings) + if (slugsArray.size != verticalArray.size) { + throw IllegalStateException("Intents arrays size mismatch") + } + return slugsArray.getOrNull(verticalArray.indexOf(vertical)) + } +} diff --git a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt index 36fc67aebc11..9d71fe54a6cd 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/HomePagePickerViewModelTest.kt @@ -56,6 +56,7 @@ class HomePagePickerViewModelTest { @Mock lateinit var analyticsTracker: SiteCreationTracker @Mock lateinit var resourceProvider: ResourceProvider + private lateinit var recommendationProvider: SiteDesignRecommendationProvider private lateinit var viewModel: HomePagePickerViewModel private val mockCategory = StarterDesignCategory( @@ -67,6 +68,7 @@ class HomePagePickerViewModelTest { @Before fun setUp() { + recommendationProvider = SiteDesignRecommendationProvider(resourceProvider) viewModel = HomePagePickerViewModel( networkUtils, dispatcher, @@ -74,7 +76,7 @@ class HomePagePickerViewModelTest { analyticsTracker, NoDelayCoroutineDispatcher(), NoDelayCoroutineDispatcher(), - resourceProvider + recommendationProvider ) viewModel.uiState.observeForever(uiStateObserver) viewModel.onDesignActionPressed.observeForever(onDesignActionObserver) @@ -212,4 +214,22 @@ class HomePagePickerViewModelTest { verify(onDesignActionObserver).onChanged(captor.capture()) assertThat(captor.value.template).isEqualTo(mockedDesignSlug) } + + @Test + fun `when the user chooses a recommended design the recommended information is emitted`() = mockResponse { + viewModel.start() + viewModel.onThumbnailReady(mockedDesignSlug) + viewModel.onLayoutTapped(mockedDesignSlug, true) + viewModel.onPreviewChooseTapped() + verify(analyticsTracker).trackSiteDesignSelected(mockedDesignSlug, true) + } + + @Test + fun `when the user chooses a design that is not recommended the correct information is emitted`() = mockResponse { + viewModel.start() + viewModel.onThumbnailReady(mockedDesignSlug) + viewModel.onLayoutTapped(mockedDesignSlug, false) + viewModel.onPreviewChooseTapped() + verify(analyticsTracker).trackSiteDesignSelected(mockedDesignSlug, false) + } } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/SiteDesignRecommendationProviderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/SiteDesignRecommendationProviderTest.kt new file mode 100644 index 000000000000..e21a280c14a0 --- /dev/null +++ b/WordPress/src/test/java/org/wordpress/android/ui/sitecreation/theme/SiteDesignRecommendationProviderTest.kt @@ -0,0 +1,155 @@ +package org.wordpress.android.ui.sitecreation.theme + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.nhaarman.mockitokotlin2.any +import com.nhaarman.mockitokotlin2.whenever +import kotlinx.coroutines.InternalCoroutinesApi +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner +import org.wordpress.android.R +import org.wordpress.android.fluxc.network.rest.wpcom.theme.StarterDesign +import org.wordpress.android.fluxc.network.rest.wpcom.theme.StarterDesignCategory +import org.wordpress.android.ui.layoutpicker.LayoutCategoryModel +import org.wordpress.android.ui.layoutpicker.LayoutModel +import org.wordpress.android.viewmodel.ResourceProvider + +@RunWith(MockitoJUnitRunner::class) +@InternalCoroutinesApi +class SiteDesignRecommendationProviderTest { + @Rule + @JvmField val rule = InstantTaskExecutorRule() + + @Mock lateinit var resourceProvider: ResourceProvider + + private lateinit var recommendationProvider: SiteDesignRecommendationProvider + + private val mockedVerticalSlug = "mockedVerticalSlug" + private val mockedVerticalTitle = "mockedVerticalTitle" + private val recommendedDesignSlug = "recommendedDesignSlug" + private val blogCategory = StarterDesignCategory( + slug = "blog", + title = "Blog", + description = "Blogging designs", + emoji = "" + ) + private val anotherCategory = StarterDesignCategory( + slug = "another", + title = "Another", + description = "Random designs", + emoji = "" + ) + private val blogDesign = StarterDesign( + "blog", + "title", + 1L, + listOf(blogCategory), + "url", + "theme", + listOf("any"), + "desktopThumbnail", + "tabletThumbnail", + "mobileThumbnail" + ) + private val anotherDesign = StarterDesign( + recommendedDesignSlug, + "title", + 1L, + listOf(anotherCategory), + "url", + "theme", + listOf("any", mockedVerticalSlug), + "desktopThumbnail", + "tabletThumbnail", + "mobileThumbnail" + ) + private val allDesigns = listOf(blogDesign, anotherDesign) + private val allCategories = listOf(blogCategory, anotherCategory) + + private val slugsArray = arrayOf("art", "automotive", "beauty", mockedVerticalSlug) + private val verticalArray = arrayOf("Art", "Automotive", "Beauty", mockedVerticalTitle) + + private class ResponseHandler { + var layouts: List? = null + var categories: List? = null + fun handle(layouts: List, categories: List) { + this.layouts = layouts + this.categories = categories + } + } + + @Before + fun setUp() { + recommendationProvider = SiteDesignRecommendationProvider(resourceProvider) + whenever(resourceProvider.getString(any())).thenReturn("Blogging") + whenever(resourceProvider.getString(any(), any())).thenReturn("Best for Blogging") + whenever(resourceProvider.getStringArray(R.array.site_creation_intents_slugs)).thenReturn(slugsArray) + whenever(resourceProvider.getStringArray(R.array.site_creation_intents_strings)).thenReturn(verticalArray) + } + + @Test + fun `All non recommended categories are randomised`() { + val handler = ResponseHandler() + recommendationProvider.handleResponse("", allDesigns, allCategories, handler::handle) + assertThat(requireNotNull(handler.categories?.filter { !it.isRecommended })).allMatch { it.randomizeOrder } + } + + @Test + fun `when no vertical is selected the blog category is recommended`() { + val handler = ResponseHandler() + recommendationProvider.handleResponse("", allDesigns, allCategories, handler::handle) + assertThat(requireNotNull(handler.categories?.filter { it.isRecommended }?.size)).isEqualTo(1) + assertThat(requireNotNull(handler.categories?.first()?.isRecommended)).isEqualTo(true) + assertThat(requireNotNull(handler.categories?.first()?.randomizeOrder)).isEqualTo(true) + assertThat(requireNotNull(handler.categories?.first()?.slug)).isEqualTo(blogCategory.slug) + } + + @Test + fun `when a vertical is selected and there are no recommendations, the blog category is recommended`() { + val handler = ResponseHandler() + recommendationProvider.handleResponse("art", allDesigns, allCategories, handler::handle) + assertThat(requireNotNull(handler.categories?.filter { it.isRecommended }?.size)).isEqualTo(1) + assertThat(requireNotNull(handler.categories?.first()?.isRecommended)).isEqualTo(true) + assertThat(requireNotNull(handler.categories?.first()?.randomizeOrder)).isEqualTo(true) + assertThat(requireNotNull(handler.categories?.first()?.slug)).isEqualTo(blogCategory.slug) + } + + @Test + fun `when a vertical is selected and there are recommendations, a recommended category is created`() { + val handler = ResponseHandler() + recommendationProvider.handleResponse(mockedVerticalTitle, allDesigns, allCategories, handler::handle) + assertThat(requireNotNull(handler.categories?.filter { it.isRecommended }?.size)).isEqualTo(1) + assertThat(requireNotNull(handler.categories?.first()?.isRecommended)).isEqualTo(true) + assertThat(requireNotNull(handler.categories?.first()?.randomizeOrder)).isEqualTo(false) + assertThat(requireNotNull(handler.categories?.first()?.slug)).isEqualTo("recommended_$mockedVerticalSlug") + } + + @Test + fun `when a vertical is selected the associated design is recommended`() { + val handler = ResponseHandler() + recommendationProvider.handleResponse(mockedVerticalTitle, allDesigns, allCategories, handler::handle) + assertThat(requireNotNull(handler.layouts?.filter { it.slug == recommendedDesignSlug }?.size)).isEqualTo(1) + } + + @Test + fun `when no vertical is selected and there is no blog category skip the recommendation`() { + val handler = ResponseHandler() + val noBlogDesigns = listOf(anotherDesign) + val noBlogCategories = listOf(anotherCategory) + recommendationProvider.handleResponse("", noBlogDesigns, noBlogCategories, handler::handle) + assertThat(requireNotNull(handler.categories?.filter { it.isRecommended }?.size)).isEqualTo(0) + } + + @Test + fun `when a vertical is selected and there are no recommendations or blog category skip the recommendation`() { + val handler = ResponseHandler() + val noBlogDesigns = listOf(anotherDesign) + val noBlogCategories = listOf(anotherCategory) + recommendationProvider.handleResponse("art", noBlogDesigns, noBlogCategories, handler::handle) + assertThat(requireNotNull(handler.categories?.filter { it.isRecommended }?.size)).isEqualTo(0) + } +}