Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
adcc10b
Add small onboarding
subsymbolic Mar 2, 2020
7e9946e
Add expanded view
subsymbolic Mar 3, 2020
8426f7a
Design feedback
subsymbolic Mar 3, 2020
de4f8de
Add landscape layout
subsymbolic Mar 4, 2020
d3b44b4
Make title bar color primary color so it is continuous with omnibar
subsymbolic Mar 4, 2020
66105ce
Remove landscape
subsymbolic Mar 4, 2020
4be1e94
Reduce hint text size
subsymbolic Mar 4, 2020
f657d56
Remove custom tap feedback
subsymbolic Mar 4, 2020
cecd155
Remove duplicate id
subsymbolic Mar 4, 2020
a438e6d
Add separation line
subsymbolic Mar 5, 2020
086ac47
Improve scroll logic to account for greater range of views
subsymbolic Mar 5, 2020
55aadc0
Persist onboarding option
subsymbolic Mar 5, 2020
77655b5
Add test dependency
subsymbolic Mar 6, 2020
5c58e0c
Merge branch 'develop' into feature/mia/interstitial_onboarding
subsymbolic Mar 6, 2020
2371597
Add onboarding to bottom and update asset name
subsymbolic Mar 6, 2020
8f53a3c
Add tests
subsymbolic Mar 7, 2020
d094231
Fix typos and lint
subsymbolic Mar 7, 2020
4c7f27f
Merge branch 'develop' into feature/mia/interstitial_onboarding
subsymbolic Mar 7, 2020
21fee58
Pixels and Variant
subsymbolic Mar 9, 2020
409d740
Move pixels into view model for improved testability
subsymbolic Mar 9, 2020
d02fb2b
Merge branch 'develop' into feature/mia/interstitial_onboarding
subsymbolic Mar 9, 2020
2eb6262
Delete dead code, switch from post to set value and reduce layout mar…
subsymbolic Mar 10, 2020
c4f4759
Revert setValue as we do access this from a background thread
subsymbolic Mar 10, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.duckduckgo.app.statistics

import com.duckduckgo.app.statistics.VariantManager.Companion.RESERVED_EU_AUCTION_VARIANT
import com.duckduckgo.app.statistics.store.StatisticsDataStore
import com.nhaarman.mockitokotlin2.*
import org.junit.Assert.*
Expand Down Expand Up @@ -166,12 +167,19 @@ class ExperimentationVariantManagerTest {
}

@Test
fun whenReferrerVariantReturnedThenNoFeaturesEnabled() {
fun whenUnknownReferrerVariantReturnedThenNoFeaturesEnabled() {
mockUpdateScenario("xx")
val variant = testee.getVariant(activeVariants)
assertTrue(variant.features.isEmpty())
}

@Test
fun whenEuAuctionReferrerVariantReturnedThenSuppressWidgetFeaturesEnabled() {
mockUpdateScenario(RESERVED_EU_AUCTION_VARIANT)
val variant = testee.getVariant(activeVariants)
assertTrue(variant.hasFeature(VariantManager.VariantFeature.SuppressHomeTabWidgetCta))
}

private fun mockUpdateScenario(key: String) {
testee.updateAppReferrerVariant(key)
whenever(mockStore.referrerVariant).thenReturn(key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import org.junit.Test
class VariantManagerTest {

private val variants = VariantManager.ACTIVE_VARIANTS +
variantWithKey(RESERVED_EU_AUCTION_VARIANT) +
VariantManager.REFERRER_VARIANTS +
DEFAULT_VARIANT

// SERP Experiment(s)
Expand Down Expand Up @@ -112,9 +112,4 @@ class VariantManagerTest {
fail("Doubles are not equal. Expected $expected but was $actual")
}
}

@Suppress("SameParameterValue")
private fun variantWithKey(key: String): Variant {
return DEFAULT_VARIANT.copy(key = key)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import com.duckduckgo.app.InstantSchedulersRule
import com.duckduckgo.app.autocomplete.api.AutoComplete
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteResult
import com.duckduckgo.app.autocomplete.api.AutoComplete.AutoCompleteSuggestion.AutoCompleteSearchSuggestion
import com.duckduckgo.app.onboarding.store.OnboardingStore
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.app.statistics.pixels.Pixel.PixelName.*
import com.duckduckgo.app.systemsearch.SystemSearchViewModel.Command
import com.duckduckgo.app.systemsearch.SystemSearchViewModel.Command.LaunchDuckDuckGo
import com.nhaarman.mockitokotlin2.argumentCaptor
Expand Down Expand Up @@ -51,8 +54,10 @@ class SystemSearchViewModelTest {
@get:Rule
var coroutineRule = CoroutineTestRule()

private val mockOnboardingStore: OnboardingStore = mock()
private val mockDeviceAppLookup: DeviceAppLookup = mock()
private val mockAutoComplete: AutoComplete = mock()
private val mockPixel: Pixel = mock()

private val commandObserver: Observer<Command> = mock()
private val commandCaptor = argumentCaptor<Command>()
Expand All @@ -65,7 +70,7 @@ class SystemSearchViewModelTest {
whenever(mockAutoComplete.autoComplete(BLANK_QUERY)).thenReturn(Observable.just(autocompleteBlankResult))
whenever(mockDeviceAppLookup.query(QUERY)).thenReturn(appQueryResult)
whenever(mockDeviceAppLookup.query(BLANK_QUERY)).thenReturn(appBlankResult)
testee = SystemSearchViewModel(mockAutoComplete, mockDeviceAppLookup, coroutineRule.testDispatcherProvider)
testee = SystemSearchViewModel(mockOnboardingStore, mockAutoComplete, mockDeviceAppLookup, mockPixel, coroutineRule.testDispatcherProvider)
testee.command.observeForever(commandObserver)
}

Expand All @@ -74,11 +79,70 @@ class SystemSearchViewModelTest {
testee.command.removeObserver(commandObserver)
}

@Test
fun whenOnboardingShouldNotShowThenViewIsNotVisibleAndUnexpanded() = runBlockingTest {
whenever(mockOnboardingStore.shouldShow).thenReturn(false)
testee.resetViewState()

val viewState = testee.onboardingViewState.value
assertFalse(viewState!!.visible)
assertFalse(viewState!!.expanded)
}

@Test
fun whenOnboardingShouldShowThenViewIsVisibleAndUnexpanded() = runBlockingTest {
whenever(mockOnboardingStore.shouldShow).thenReturn(true)
testee.resetViewState()

val viewState = testee.onboardingViewState.value
assertTrue(viewState!!.visible)
assertFalse(viewState!!.expanded)
}

@Test
fun whenOnboardingShownThenPixelSent() = runBlockingTest {
whenever(mockOnboardingStore.shouldShow).thenReturn(true)
testee.resetViewState()
verify(mockPixel).fire(INTERSTITIAL_ONBOARDING_SHOWN)
}

@Test
fun whenOnboardingIsUnexpandedAndUserPressesToggleThenItIsExpandedAndPixelSent() = runBlockingTest {
whenOnboardingShowing()
testee.userTappedOnboardingToggle()

val viewState = testee.onboardingViewState.value
assertTrue(viewState!!.expanded)
verify(mockPixel).fire(INTERSTITIAL_ONBOARDING_MORE_PRESSED)
}

@Test
fun whenOnboardingIsExpandedAndUserPressesToggleThenItIsUnexpandedAndPixelSent() = runBlockingTest {
whenOnboardingShowing()
testee.userTappedOnboardingToggle() // first press to expand
testee.userTappedOnboardingToggle() // second press to minimize

val viewState = testee.onboardingViewState.value
assertFalse(viewState!!.expanded)
verify(mockPixel).fire(INTERSTITIAL_ONBOARDING_LESS_PRESSED)
}

@Test
fun whenOnboardingIsDismissedThenViewHiddenPixelSentAndOnboardingStoreNotified() = runBlockingTest {
whenOnboardingShowing()
testee.userDismissedOnboarding()

val viewState = testee.onboardingViewState.value
assertFalse(viewState!!.visible)
verify(mockPixel).fire(INTERSTITIAL_ONBOARDING_DISMISSED)
verify(mockOnboardingStore).onboardingShown()
}

@Test
fun whenUserUpdatesQueryThenViewStateUpdated() = ruleRunBlockingTest {
testee.userUpdatedQuery(QUERY)

val newViewState = testee.viewState.value
val newViewState = testee.resultsViewState.value
assertNotNull(newViewState)
assertEquals(QUERY, newViewState?.queryText)
assertEquals(appQueryResult, newViewState?.appResults)
Expand All @@ -90,7 +154,7 @@ class SystemSearchViewModelTest {
testee.userUpdatedQuery(QUERY)
testee.userUpdatedQuery("$QUERY ")

val newViewState = testee.viewState.value
val newViewState = testee.resultsViewState.value
assertNotNull(newViewState)
assertEquals("$QUERY ", newViewState?.queryText)
assertEquals(appQueryResult, newViewState?.appResults)
Expand All @@ -102,7 +166,7 @@ class SystemSearchViewModelTest {
testee.userUpdatedQuery(QUERY)
testee.userClearedQuery()

val newViewState = testee.viewState.value
val newViewState = testee.resultsViewState.value
assertNotNull(newViewState)
assertTrue(newViewState!!.queryText.isEmpty())
assertTrue(newViewState.appResults.isEmpty())
Expand All @@ -114,39 +178,43 @@ class SystemSearchViewModelTest {
testee.userUpdatedQuery(QUERY)
testee.userUpdatedQuery(BLANK_QUERY)

val newViewState = testee.viewState.value
val newViewState = testee.resultsViewState.value
assertNotNull(newViewState)
assertTrue(newViewState!!.queryText.isEmpty())
assertTrue(newViewState.appResults.isEmpty())
assertEquals(AutoCompleteResult("", emptyList()), newViewState.autocompleteResults)
}

@Test
fun whenUserSubmitsQueryThenBrowserLaunched() {
fun whenUserSubmitsQueryThenBrowserLaunchedAndPixelSent() {
testee.userSubmittedQuery(QUERY)
verify(commandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
assertEquals(Command.LaunchBrowser(QUERY), commandCaptor.lastValue)
verify(mockPixel).fire(INTERSTITIAL_LAUNCH_BROWSER_QUERY)
}

@Test
fun whenUserSubmitsAutocompleteResultThenBrowserLaunched() {
fun whenUserSubmitsAutocompleteResultThenBrowserLaunchedAndPixelSent() {
testee.userSubmittedAutocompleteResult(AUTOCOMPLETE_RESULT)
verify(commandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
assertEquals(Command.LaunchBrowser(AUTOCOMPLETE_RESULT), commandCaptor.lastValue)
verify(mockPixel).fire(INTERSTITIAL_LAUNCH_BROWSER_QUERY)
}

@Test
fun whenUserSelectsAppResultThenAppLaunched() {
fun whenUserSelectsAppResultThenAppLaunchedAndPixelSent() {
testee.userSelectedApp(deviceApp)
verify(commandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
assertEquals(Command.LaunchDeviceApplication(deviceApp), commandCaptor.lastValue)
verify(mockPixel).fire(INTERSTITIAL_LAUNCH_DEVICE_APP)
}

@Test
fun whenUserTapsDaxThenAppLaunched() {
fun whenUserTapsDaxThenAppLaunchedAndPixelSent() {
testee.userTappedDax()
verify(commandObserver, atLeastOnce()).onChanged(commandCaptor.capture())
assertTrue(commandCaptor.lastValue is LaunchDuckDuckGo)
verify(mockPixel).fire(INTERSTITIAL_LAUNCH_DAX)
}

@Test
Expand All @@ -157,6 +225,11 @@ class SystemSearchViewModelTest {
assertEquals(Command.ShowAppNotFoundMessage(deviceApp.shortName), commandCaptor.lastValue)
}

private fun whenOnboardingShowing() {
whenever(mockOnboardingStore.shouldShow).thenReturn(true)
testee.resetViewState()
}

private fun ruleRunBlockingTest(block: suspend TestCoroutineScope.() -> Unit) =
coroutineRule.testDispatcher.runBlockingTest(block)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class ViewModelFactory @Inject constructor(
with(modelClass) {
when {
isAssignableFrom(LaunchViewModel::class.java) -> LaunchViewModel(onboardingStore, appInstallationReferrerStateListener)
isAssignableFrom(SystemSearchViewModel::class.java) -> SystemSearchViewModel(autoCompleteApi, deviceAppLookup)
isAssignableFrom(SystemSearchViewModel::class.java) -> SystemSearchViewModel(onboardingStore, autoCompleteApi, deviceAppLookup, pixel)
isAssignableFrom(OnboardingViewModel::class.java) -> onboardingViewModel()
isAssignableFrom(BrowserViewModel::class.java) -> browserViewModel()
isAssignableFrom(BrowserTabViewModel::class.java) -> browserTabViewModel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ interface DeviceInfo {

class ContextDeviceInfo @Inject constructor(private val context: Context) : DeviceInfo {

override val appVersion = "${BuildConfig.VERSION_NAME}"
override val appVersion = BuildConfig.VERSION_NAME

override val majorAppVersion = appVersion.split(".").first()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,4 @@ fun View.hideKeyboard(): Boolean {
}

fun Int.toDp(): Int = (this / Resources.getSystem().displayMetrics.density).toInt()
fun Int.toPx(): Int = (this * Resources.getSystem().displayMetrics.density).toInt()
fun Int.toPx(): Int = (this * Resources.getSystem().displayMetrics.density).toInt()
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,13 @@ interface VariantManager {
// All groups in an experiment (control and variants) MUST use the same filters
)

val REFERRER_VARIANTS = listOf(
Variant(RESERVED_EU_AUCTION_VARIANT, features = listOf(SuppressHomeTabWidgetCta), filterBy = { noFilter() })
)

fun referrerVariant(key: String): Variant {
return Variant(key, features = emptyList(), filterBy = { noFilter() })
val knownReferrer = REFERRER_VARIANTS.firstOrNull { it.key == key }
return knownReferrer ?: Variant(key, features = emptyList(), filterBy = { noFilter() })
}

private fun noFilter(): Boolean = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ interface Pixel {
INTERSTITIAL_LAUNCH_BROWSER_QUERY(pixelName = "m_i_lbq"),
INTERSTITIAL_LAUNCH_DEVICE_APP(pixelName = "m_i_sda"),
INTERSTITIAL_LAUNCH_DAX(pixelName = "m_i_ld"),
INTERSTITIAL_ONBOARDING_SHOWN(pixelName = "m_io_s"),
INTERSTITIAL_ONBOARDING_DISMISSED(pixelName = "m_io_d"),
INTERSTITIAL_ONBOARDING_LESS_PRESSED(pixelName = "m_io_l"),
INTERSTITIAL_ONBOARDING_MORE_PRESSED(pixelName = "m_io_m"),

LONG_PRESS("mlp"),
LONG_PRESS_DOWNLOAD_IMAGE("mlp_i"),
Expand Down
Loading