@ -7,177 +7,186 @@ package org.mozilla.fenix.home
import android.graphics.Bitmap
import android.graphics.Bitmap
import android.widget.ImageView
import android.widget.ImageView
import androidx.core.view.isVisible
import androidx.core.view.isVisible
import io.mockk.Called
import io.mockk.Runs
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.every
import io.mockk.just
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.spyk
import io.mockk.verify
import io.mockk.verify
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancel
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.libstate.ext.waitUntilIdle
import mozilla.components.support.test.libstate.ext.waitUntilIdle
import mozilla.components.support.test.rule.MainCoroutineRule
import mozilla.components.support.test.rule.runTestOnMain
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Assert.assert Null
import org.junit.Ignore
import org.junit. Rul e
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction.WallpaperAction.UpdateCurrentWallpaper
import org.mozilla.fenix.components.appstate.AppAction.WallpaperAction.UpdateCurrentWallpaper
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.wallpapers.Wallpaper
import org.mozilla.fenix.wallpapers.Wallpaper
import org.mozilla.fenix.wallpapers.WallpapersUseCases
import org.mozilla.fenix.wallpapers.WallpapersUseCases
@RunWith ( FenixRobolectricTestRunner :: class )
@RunWith ( FenixRobolectricTestRunner :: class )
class WallpapersObserverTest {
class WallpapersObserverTest {
@get : Rule
val coroutinesTestRule = MainCoroutineRule ( )
@Test
@Test
fun `WHEN the observer is created THEN start observing the store` ( ) {
fun `WHEN the observer is created THEN start observing the store` ( ) {
val appStore : AppStore = mockk ( relaxed = true ) {
val appStore : AppStore = mockk ( relaxed = true ) {
every { observeManually ( any ( ) ) } answers { mockk ( relaxed = true ) }
every { observeManually ( any ( ) ) } answers { mockk ( relaxed = true ) }
}
}
val observer = WallpapersObserver( appStore , mockk ( ) , mockk ( ) )
val observer = getObserver( appStore )
assertNotNull ( observer . observeWallpapersStoreSubscription )
assertNotNull ( observer . observeWallpapersStoreSubscription )
}
}
@Test
@Test
fun ` WHEN asked to apply the wallpaper THEN show it`( ) {
fun ` GIVEN a certain wallpaper is chosen WHEN the state is updated with that wallpaper THEN load it it`( ) = runTestOnMain {
val appStore = AppStore ( )
val appStore = AppStore ( )
val observer = spyk ( WallpapersObserver ( appStore , mockk ( ) , mockk ( ) ) ) {
val settings : Settings = mockk { every { currentWallpaperName } returns " Test " }
every { showWallpaper ( any ( ) ) } just Runs
val wallpaper : Wallpaper = mockk { every { name } returns " Test " }
}
val observer = spyk (
getObserver ( appStore , settings , mockk ( relaxed = true ) , mockk ( relaxed = true ) ) ,
observer . applyCurrentWallpaper ( )
)
verify { observer . showWallpaper ( any ( ) ) }
}
@Test
fun `GIVEN the store was observed for updates WHEN the lifecycle owner is destroyed THEN stop observing the store` ( ) {
val observer = WallpapersObserver ( mockk ( relaxed = true ) , mockk ( ) , mockk ( ) )
observer . observeWallpapersStoreSubscription = mockk ( relaxed = true )
observer . wallpapersScope = mockk {
every { cancel ( ) } just Runs
}
observer . onDestroy ( mockk ( ) )
// Ignore the call on the real instance and call again "observeWallpaperUpdates"
// on the spy to be able to verify the "showWallpaper" call in the spy.
observer . observeWallpaperUpdates ( )
appStore . dispatch ( UpdateCurrentWallpaper ( wallpaper ) ) . joinBlocking ( )
appStore . waitUntilIdle ( )
verify { observer . wallpapersScope . cancel ( ) }
coVerify { observer . loadWallpaper ( any ( ) ) }
verify { observer . observeWallpapersStoreSubscription !! . unsubscribe ( ) }
coVerify { observer . applyCurrentWallpaper ( ) }
}
}
@Test
@Test
fun ` WHEN the wallpaper is updated THEN show the wallpaper`( ) {
fun ` GIVEN a certain wallpaper is chosen WHEN the state updated with another wallpaper THEN avoid loading it`( ) = runTestOnMain {
val appStore = AppStore ( )
val appStore = AppStore ( )
val observer = spyk ( WallpapersObserver ( appStore , mockk ( relaxed = true ) , mockk ( relaxed = true ) ) ) {
val settings : Settings = mockk { every { currentWallpaperName } returns " Test " }
every { showWallpaper ( any ( ) ) } just Runs
val otherWallpaper : Wallpaper = mockk { every { name } returns " Other " }
}
val observer = spyk (
getObserver ( appStore , settings , mockk ( relaxed = true ) , mockk ( relaxed = true ) ) ,
)
// Ignore the call on the real instance and call again "observeWallpaperUpdates"
// Ignore the call on the real instance and call again "observeWallpaperUpdates"
// on the spy to be able to verify the "showWallpaper" call in the spy.
// on the spy to be able to verify the "showWallpaper" call in the spy.
observer . observeWallpaperUpdates ( )
observer . observeWallpaperUpdates ( )
appStore . dispatch ( UpdateCurrentWallpaper ( otherWallpaper ) ) . joinBlocking ( )
val newWallpaper : Wallpaper = mockk ( relaxed = true )
appStore . dispatch ( UpdateCurrentWallpaper ( newWallpaper ) )
appStore . waitUntilIdle ( )
appStore . waitUntilIdle ( )
verify { observer . showWallpaper ( newWallpaper ) }
coVerify ( exactly = 0 ) { observer . loadWallpaper ( any ( ) ) }
coVerify ( exactly = 0 ) { observer . applyCurrentWallpaper ( ) }
}
}
@Test
@Test
@Ignore ( " Intermittent test: https://github.com/mozilla-mobile/fenix/issues/26760 " )
fun `GIVEN a wallpaper is SHOWN WHEN the wallpaper is updated to the current one THEN don't try showing the same wallpaper again` ( ) {
fun `WHEN the wallpaper is updated to a new one THEN show the wallpaper` ( ) {
val appStore = AppStore ( )
val appStore = AppStore ( )
val wallpapersUseCases : WallpapersUseCases = mockk {
val settings : Settings = mockk { every { currentWallpaperName } returns " Test " }
coEvery { loadBitmap ( any ( ) ) } returns null
val wallpaper : Wallpaper = mockk { every { name } returns " Test " }
}
val wallpapersUseCases : WallpapersUseCases = mockk { coEvery { loadBitmap ( any ( ) ) } returns null }
val observer = spyk ( WallpapersObserver ( appStore , wallpapersUseCases , mockk ( relaxed = true ) ) ) {
val observer = spyk ( getObserver ( appStore , settings , wallpapersUseCases , mockk ( relaxed = true ) ) ) {
every { showWallpaper ( any ( ) ) } just Runs
coEvery { loadWallpaper ( any ( ) ) } just Runs
coEvery { applyCurrentWallpaper ( ) } just Runs
}
}
// Ignore the call on the real instance and call again "observeWallpaperUpdates"
// Ignore the call on the real instance and call again "observeWallpaperUpdates"
// on the spy to be able to verify the "showWallpaper" call in the spy.
// on the spy to be able to verify the "showWallpaper" call in the spy.
observer . observeWallpaperUpdates ( )
observer . observeWallpaperUpdates ( )
verify { observer . showWallpaper ( Wallpaper . Default ) }
appStore . dispatch ( UpdateCurrentWallpaper ( wallpaper ) ) . joinBlocking ( )
appStore . waitUntilIdle ( )
coVerify ( exactly = 1 ) { observer . loadWallpaper ( any ( ) ) }
coVerify ( exactly = 1 ) { observer . applyCurrentWallpaper ( ) }
val wallpaper : Wallpaper = mockk ( relaxed = true )
appStore . dispatch ( UpdateCurrentWallpaper ( wallpaper ) ) . joinBlocking ( )
appStore . dispatch ( UpdateCurrentWallpaper ( wallpaper ) )
appStore . waitUntilIdle ( )
appStore . waitUntilIdle ( )
verify { observer . showWallpaper ( wallpaper ) }
coVerify ( exactly = 1 ) { observer . loadWallpaper ( any ( ) ) }
coVerify ( exactly = 1 ) { observer . applyCurrentWallpaper ( ) }
}
}
@Test
@Test
fun `WHEN the wallpaper is updated to the current one THEN don't try showing the same wallpaper again` ( ) {
fun `GIVEN the store was observed for updates WHEN the lifecycle owner is destroyed THEN stop observing the store` ( ) {
val appStore = AppStore ( )
val observer = getObserver ( mockk ( relaxed = true ) )
val wallpapersUseCases : WallpapersUseCases = mockk {
observer . observeWallpapersStoreSubscription = mockk ( relaxed = true )
coEvery { loadBitmap ( any ( ) ) } returns null
observer . wallpapersScope = mockk {
}
every { cancel ( ) } just Runs
val observer = spyk ( WallpapersObserver ( appStore , wallpapersUseCases , mockk ( relaxed = true ) ) ) {
every { showWallpaper ( any ( ) ) } just Runs
}
}
// Ignore the call on the real instance and call again "observeWallpaperUpdates"
// on the spy to be able to verify the "showWallpaper" call in the spy.
observer . observeWallpaperUpdates ( )
val wallpaper : Wallpaper = mockk ( relaxed = true )
observer . onDestroy ( mockk ( ) )
appStore . dispatch ( UpdateCurrentWallpaper ( wallpaper ) )
appStore . waitUntilIdle ( )
verify { observer . showWallpaper ( wallpaper ) }
appStore . dispatch ( UpdateCurrentWallpaper ( wallpaper ) )
verify { observer . wallpapersScope . cancel ( ) }
appStore . waitUntilIdle ( )
verify { observer . observeWallpapersStoreSubscription !! . unsubscribe ( ) }
verify ( exactly = 1 ) { observer . showWallpaper ( wallpaper ) }
}
}
@Test
@Test
fun `GIVEN no wallpaper is provided WHEN asked to show the wallpaper THEN show the current one`( ) {
fun `GIVEN a wallpaper image is available WHEN asked to apply the current wallpaper THEN show set it to the wallpaper ImageView`( ) = runTestOnMain {
val wallpaper: Wallpaper = mockk ( )
val imageView: ImageView = mockk ( relaxed = true )
val appStore: AppStore = mockk ( relaxed = true ) {
val observer = getObserver ( wallpaperImageView = imageView )
every { state . wallpaperState . currentWallpaper } returns wallpaper
val image : Bitmap = mockk ( )
}
observer . currentWallpaperImage = image
val observer = spyk ( WallpapersObserver ( appStore , mockk ( relaxed = true ) , mockk ( relaxed = true ) ) )
observer . isWallpaperLoaded . complete ( Unit )
observer . show Wallpaper( )
observer . applyCurrent Wallpaper( )
verify { observer . showWallpaper ( wallpaper ) }
verify { imageView . setImageBitmap ( image ) }
verify { imageView . isVisible = true }
}
}
fun `G iVEN the current wallpaper is the default one WHEN showing it THEN hide the wallpaper view`( ) {
fun `G IVEN no wallpaper image is available WHEN asked to apply the current wallpaper THEN hide the wallpaper ImageView`( ) = runTestOnMain {
val wallpapersUseCases: WallpapersUseCases = mockk ( )
val imageView: ImageView = mockk ( )
val wallpaperView: ImageView = mockk ( relaxed = true )
val observer = getObserver ( wallpaperImageView = imageView )
val observer = WallpapersObserver ( mockk ( relaxed = true ) , wallpapersUseCases , wallpaperView )
observer . isWallpaperLoaded . complete ( Unit )
observer . showWallpaper( Wallpaper . Default )
observer . applyCurrentWallpaper( )
verify { wallpaper View. isVisible = false }
verify { image View. isVisible = false }
verify { wallpapersUseCases wasNot Called }
verify ( exactly = 0 ) { imageView . setImageBitmap ( any ( ) ) }
}
}
@Test
@Test
fun `GiVEN the current wallpaper is different than the default one WHEN showing it THEN load it's bitmap in the visible wallpaper view` ( ) {
fun `GIVEN the default wallpaper WHEN asked to load it THEN cache that the current image is null` ( ) = runTestOnMain {
val wallpaper : Wallpaper = mockk ( )
val observer = getObserver ( )
val bitmap : Bitmap = mockk ( )
observer . currentWallpaperImage = mockk ( )
val wallpapersUseCases : WallpapersUseCases = mockk {
coEvery { loadBitmap ( any ( ) ) } returns bitmap
}
val wallpaperView : ImageView = mockk ( relaxed = true )
val observer = WallpapersObserver ( mockk ( relaxed = true ) , wallpapersUseCases , wallpaperView )
observer . showWallpaper( wallpaper )
observer . loadWallpaper ( Wallpaper . Default )
verify { wallpaperView . isVisible = true }
assertNull ( observer . currentWallpaperImage )
verify { wallpaperView . setImageBitmap ( bitmap ) }
}
}
@Test
@Test
fun `GIVEN the observer THEN use the main thread for showing the wallpaper` ( ) {
fun `GIVEN a custom wallpaper WHEN asked to load it THEN cache it's bitmap` ( ) = runTestOnMain {
val wallpapersUseCases : WallpapersUseCases = mockk ( )
val bitmap : Bitmap = mockk ( )
val wallpaperView : ImageView = mockk ( relaxed = true )
val wallpaper : Wallpaper = mockk ( )
val usecases : WallpapersUseCases = mockk {
coEvery { loadBitmap ( wallpaper ) } returns bitmap
}
val observer = getObserver ( wallpapersUseCases = usecases )
val observer = WallpapersObserver ( mockk ( relaxed = true ) , wallpapersUseCases , wallpaperView )
observer . loadWallpaper ( wallpaper )
// Check that the context that would be used is Dispatchers.Main.immediate
assertEquals ( bitmap , observer . currentWallpaperImage )
// Unfortunately I could not also test that this is actually used when "showWallpaper" is called.
assertTrue ( observer . wallpapersScope . toString ( ) . contains ( " Dispatchers.Main.immediate " ) )
}
}
private fun getObserver (
appStore : AppStore = mockk ( relaxed = true ) ,
settings : Settings = mockk ( ) ,
wallpapersUseCases : WallpapersUseCases = mockk ( ) ,
wallpaperImageView : ImageView = mockk ( ) ,
backgroundWorkDispatcher : CoroutineDispatcher = coroutinesTestRule . testDispatcher ,
) = WallpapersObserver (
appStore = appStore ,
settings = settings ,
wallpapersUseCases = wallpapersUseCases ,
wallpaperImageView = wallpaperImageView ,
backgroundWorkDispatcher = backgroundWorkDispatcher ,
)
}
}