From e4dc56da1fa5f4f9debed444b4b891ed5665b6af Mon Sep 17 00:00:00 2001 From: Kaung Khant Soe Date: Mon, 6 Feb 2023 17:29:24 +0700 Subject: [PATCH 1/9] [#83] Add ui test for DetailScreen --- .../nimblehq/compose/crypto/test/MockUtil.kt | 8 +++- .../crypto/ui/screens/detail/DetailScreen.kt | 40 ++++++------------- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/test/MockUtil.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/test/MockUtil.kt index dafdca8d..300dfe42 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/test/MockUtil.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/test/MockUtil.kt @@ -1,7 +1,6 @@ package co.nimblehq.compose.crypto.test -import co.nimblehq.compose.crypto.domain.model.CoinDetail -import co.nimblehq.compose.crypto.domain.model.CoinItem +import co.nimblehq.compose.crypto.domain.model.* import java.math.BigDecimal object MockUtil { @@ -97,4 +96,9 @@ object MockUtil { lastUpdated = "lastUpdated" ) ) + + val coinPrices = listOf( + CoinPrice(1000,BigDecimal.ZERO), + CoinPrice(2000, BigDecimal.ONE) + ) } diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailScreen.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailScreen.kt index 51fdd8f7..c9628180 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailScreen.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailScreen.kt @@ -9,9 +9,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.testTag +import androidx.compose.ui.platform.* import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -42,12 +40,8 @@ import me.bytebeats.views.charts.line.render.line.SolidLineDrawer import me.bytebeats.views.charts.line.render.point.EmptyPointDrawer import me.bytebeats.views.charts.simpleChartAnimation -const val TestTagDetailLogo = "DetailLogo" -const val TestTagDetailCircularProgress = "DetailCircularProgress" -const val TestTagDetailLineChart = "DetailLineChart" -const val TestTagDetailChartInterval = "DetailChartInterval" -const val TestTagDetailCoinInfo = "DetailCoinInfo" -const val TestTagDetailSellBuyGroup = "DetailSellBuyGroup" +const val TestTagDetailLoading = "TestTagDetailLoading" +const val TestTagDetailCoinPriceChart = "TestTagDetailCoinPriceChart" @Composable fun DetailScreen( @@ -135,8 +129,7 @@ private fun DetailScreenContent( top.linkTo(appBar.bottom) linkTo(start = parent.start, end = parent.end) } - .padding(top = Dp8) - .testTag(tag = TestTagDetailLogo), + .padding(top = Dp8), painter = rememberAsyncImagePainter(coinDetailUiModel.image), contentDescription = null ) @@ -174,7 +167,7 @@ private fun DetailScreenContent( start.linkTo(parent.start) end.linkTo(parent.end) } - .testTag(tag = TestTagDetailLineChart), + .testTag(TestTagDetailCoinPriceChart), lineChartData = LineChartData( points = coinPrices.map { coinPrice -> val price = stringResource( @@ -199,13 +192,11 @@ private fun DetailScreenContent( // Chart intervals ChartIntervalsButtonGroup( - modifier = Modifier - .constrainAs(intervals) { - top.linkTo(graph.bottom, margin = Dp24) - start.linkTo(parent.start) - end.linkTo(parent.end) - } - .testTag(tag = TestTagDetailChartInterval), + modifier = Modifier.constrainAs(intervals) { + top.linkTo(graph.bottom, margin = Dp24) + start.linkTo(parent.start) + end.linkTo(parent.end) + }, onIntervalChanged = onTimeIntervalsChanged::invoke ) @@ -229,7 +220,7 @@ private fun DetailScreenContent( bottom = parent.bottom ) } - .testTag(tag = TestTagDetailCircularProgress), + .testTag(TestTagDetailLoading), ) } } @@ -238,8 +229,7 @@ private fun DetailScreenContent( Box( modifier = Modifier .fillMaxSize() - .navigationBarsPadding() - .testTag(tag = TestTagDetailSellBuyGroup), + .navigationBarsPadding(), contentAlignment = Alignment.BottomEnd ) { SellBuyGroup( @@ -258,11 +248,7 @@ private fun CoinInfo( sellBuyLayoutHeight: Dp, coinDetailUiModel: CoinDetailUiModel ) { - Column( - modifier = modifier - .padding(start = Dp16, end = Dp16, bottom = sellBuyLayoutHeight) - .testTag(tag = TestTagDetailCoinInfo) - ) { + Column(modifier = modifier.padding(start = Dp16, end = Dp16, bottom = sellBuyLayoutHeight)) { DetailItem( modifier = Modifier, title = stringResource(id = R.string.detail_market_cap_title), From eff7052fa1afb0de61753d50a808f2f106851aec Mon Sep 17 00:00:00 2001 From: Kaung Khant Soe Date: Mon, 6 Feb 2023 17:35:50 +0700 Subject: [PATCH 2/9] [#83] Add ui test for DetailScreen --- .../ui/screen/detail/DetailScreenTest.kt | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenTest.kt diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenTest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenTest.kt new file mode 100644 index 00000000..deebb363 --- /dev/null +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenTest.kt @@ -0,0 +1,170 @@ +package co.nimblehq.compose.crypto.ui.screen.detail + +import androidx.activity.compose.setContent +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import co.nimblehq.compose.crypto.R +import co.nimblehq.compose.crypto.domain.usecase.GetCoinDetailUseCase +import co.nimblehq.compose.crypto.domain.usecase.GetCoinPricesUseCase +import co.nimblehq.compose.crypto.extension.toFormattedString +import co.nimblehq.compose.crypto.test.MockUtil.coinDetail +import co.nimblehq.compose.crypto.test.MockUtil.coinPrices +import co.nimblehq.compose.crypto.ui.BaseScreenTest +import co.nimblehq.compose.crypto.ui.components.chartintervals.TimeIntervals +import co.nimblehq.compose.crypto.ui.navigation.AppDestination +import co.nimblehq.compose.crypto.ui.screens.MainActivity +import co.nimblehq.compose.crypto.ui.screens.detail.* +import co.nimblehq.compose.crypto.ui.screens.home.FIAT_CURRENCY +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import kotlin.math.abs + +@ExperimentalCoroutinesApi +class DetailScreenTest : BaseScreenTest() { + + @get:Rule + val composeAndroidTestRule = createAndroidComposeRule() + + private val errorGeneric: String + get() = composeAndroidTestRule.activity.getString(R.string.error_generic) + + private val mockGetCoinDetailUseCase: GetCoinDetailUseCase = mockk() + private val mockGetCoinPricesUseCase: GetCoinPricesUseCase = mockk() + + private lateinit var detailViewModel: DetailViewModel + + private var appDestination: AppDestination? = null + + @Before + fun setUp() { + composeAndroidTestRule.activity.setContent { + DetailScreen( + coinId = "", + viewModel = detailViewModel, + navigator = { destination -> + appDestination = destination + } + ) + } + } + + @Test + fun when_navigate_to_detail_screen_it_show_loading() { + composeAndroidTestRule.activity.setContent { + DetailScreen( + coinId = "", + navigator = { destination -> + appDestination = destination + } + ) + } + + initDetailViewModel() + + with(composeAndroidTestRule) { + onNodeWithTag(TestTagDetailLoading).assertIsDisplayed() + } + } + + @Test + fun when_navigate_to_detail_screen_it_render_chart_interval_buttons_properly() { + every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) + every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) + + initDetailViewModel() + + with(composeAndroidTestRule) { + onNodeWithText(TimeIntervals.ONE_DAY.text).assertIsDisplayed() + onNodeWithText(TimeIntervals.ONE_WEEK.text).assertIsDisplayed() + onNodeWithText(TimeIntervals.ONE_MONTH.text).assertIsDisplayed() + onNodeWithText(TimeIntervals.ONE_YEAR.text).assertIsDisplayed() + onNodeWithText(TimeIntervals.FIVE_YEAR.text).assertIsDisplayed() + } + } + + @Test + fun when_navigate_to_detail_screen_it_render_currentPrice_and_priceChangePercentage24hInCurrency_properly() { + every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) + every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) + + initDetailViewModel() + + with(composeAndroidTestRule) { + coinDetail.marketData?.let { marketData -> + val currentPrice = "$${marketData.currentPrice[FIAT_CURRENCY]?.toFormattedString()}" + onNodeWithText(currentPrice).assertIsDisplayed() + + val priceChangePercentage24hInCurrency = this.activity.getString( + R.string.coin_profit_percent, + abs(marketData.priceChangePercentage24hInCurrency[FIAT_CURRENCY] ?: 0.0).toFormattedString() + ) + onNodeWithText(priceChangePercentage24hInCurrency).assertIsDisplayed() + } + } + } + + @Test + fun when_navigate_to_detail_screen_it_render_coin_info_properly() { + every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) + every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) + + initDetailViewModel() + + with(composeAndroidTestRule) { + coinDetail.marketData?.let { marketData -> + val marketCap = "$${marketData.marketCap[FIAT_CURRENCY]?.toFormattedString()}" + onNodeWithText(marketCap).assertIsDisplayed() + + val allTimeHigh = "$${marketData.ath[FIAT_CURRENCY]?.toFormattedString()}" + onNodeWithText(allTimeHigh).assertIsDisplayed() + + val allTimeLow = "$${marketData.atl[FIAT_CURRENCY]?.toFormattedString()}" + onNodeWithText(allTimeLow).assertIsDisplayed() + } + } + } + + @Test + fun when_navigate_to_detail_screen_and_has_api_error_coin_price_chart_is_not_displayed() { + every { mockGetCoinDetailUseCase.execute(any()) } returns flow { + throw Throwable(errorGeneric) + } + every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) + + initDetailViewModel() + + with(composeAndroidTestRule) { + onNodeWithText(TestTagDetailCoinPriceChart).assertDoesNotExist() + } + } + + @Test + fun when_navigate_to_detail_screen_chart_interval_buttons_are_clickable() { + every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) + every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) + + initDetailViewModel() + + with(composeAndroidTestRule) { + onNodeWithText(TimeIntervals.ONE_DAY.text).assertHasClickAction() + onNodeWithText(TimeIntervals.ONE_WEEK.text).assertHasClickAction() + onNodeWithText(TimeIntervals.ONE_MONTH.text).assertHasClickAction() + onNodeWithText(TimeIntervals.ONE_YEAR.text).assertHasClickAction() + onNodeWithText(TimeIntervals.FIVE_YEAR.text).assertHasClickAction() + } + } + + private fun initDetailViewModel() { + detailViewModel = DetailViewModel( + dispatchers = testDispatcherProvider, + getCoinDetailUseCase = mockGetCoinDetailUseCase, + getCoinPricesUseCase = mockGetCoinPricesUseCase + ) + } +} From 4c93714b150842ca6a1ba7f2ce5312854b9435c0 Mon Sep 17 00:00:00 2001 From: Kaung Khant Soe Date: Fri, 10 Feb 2023 13:40:31 +0700 Subject: [PATCH 3/9] [#83] Minor function name changes and refactors --- ...ailScreenTest.kt => DetailScreenUiTest.kt} | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) rename app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/{DetailScreenTest.kt => DetailScreenUiTest.kt} (89%) diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenTest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt similarity index 89% rename from app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenTest.kt rename to app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt index deebb363..debf0509 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenTest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt @@ -26,7 +26,7 @@ import org.junit.Test import kotlin.math.abs @ExperimentalCoroutinesApi -class DetailScreenTest : BaseScreenTest() { +class DetailScreenUiTest : BaseScreenTest() { @get:Rule val composeAndroidTestRule = createAndroidComposeRule() @@ -43,6 +43,7 @@ class DetailScreenTest : BaseScreenTest() { @Before fun setUp() { + initDetailViewModel() composeAndroidTestRule.activity.setContent { DetailScreen( coinId = "", @@ -55,7 +56,7 @@ class DetailScreenTest : BaseScreenTest() { } @Test - fun when_navigate_to_detail_screen_it_show_loading() { + fun when_navigating_to_detail_screen_it_shows_loading() { composeAndroidTestRule.activity.setContent { DetailScreen( coinId = "", @@ -65,20 +66,16 @@ class DetailScreenTest : BaseScreenTest() { ) } - initDetailViewModel() - with(composeAndroidTestRule) { onNodeWithTag(TestTagDetailLoading).assertIsDisplayed() } } @Test - fun when_navigate_to_detail_screen_it_render_chart_interval_buttons_properly() { + fun when_navigating_to_detail_screen_it_renders_chart_interval_buttons_properly() { every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) - initDetailViewModel() - with(composeAndroidTestRule) { onNodeWithText(TimeIntervals.ONE_DAY.text).assertIsDisplayed() onNodeWithText(TimeIntervals.ONE_WEEK.text).assertIsDisplayed() @@ -89,12 +86,10 @@ class DetailScreenTest : BaseScreenTest() { } @Test - fun when_navigate_to_detail_screen_it_render_currentPrice_and_priceChangePercentage24hInCurrency_properly() { + fun when_navigating_to_detail_screen_it_render_currentPrice_and_priceChangePercentage24hInCurrency_properly() { every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) - initDetailViewModel() - with(composeAndroidTestRule) { coinDetail.marketData?.let { marketData -> val currentPrice = "$${marketData.currentPrice[FIAT_CURRENCY]?.toFormattedString()}" @@ -110,12 +105,10 @@ class DetailScreenTest : BaseScreenTest() { } @Test - fun when_navigate_to_detail_screen_it_render_coin_info_properly() { + fun when_navigating_to_detail_screen_it_render_coin_info_properly() { every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) - initDetailViewModel() - with(composeAndroidTestRule) { coinDetail.marketData?.let { marketData -> val marketCap = "$${marketData.marketCap[FIAT_CURRENCY]?.toFormattedString()}" @@ -131,26 +124,22 @@ class DetailScreenTest : BaseScreenTest() { } @Test - fun when_navigate_to_detail_screen_and_has_api_error_coin_price_chart_is_not_displayed() { + fun when_navigating_to_detail_screen_and_has_api_error_coin_price_chart_is_not_displayed() { every { mockGetCoinDetailUseCase.execute(any()) } returns flow { throw Throwable(errorGeneric) } every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) - initDetailViewModel() - with(composeAndroidTestRule) { onNodeWithText(TestTagDetailCoinPriceChart).assertDoesNotExist() } } @Test - fun when_navigate_to_detail_screen_chart_interval_buttons_are_clickable() { + fun when_navigating_to_detail_screen_chart_interval_buttons_are_clickable() { every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) - initDetailViewModel() - with(composeAndroidTestRule) { onNodeWithText(TimeIntervals.ONE_DAY.text).assertHasClickAction() onNodeWithText(TimeIntervals.ONE_WEEK.text).assertHasClickAction() From e6763b76d5886937547c6632ca19d782f7802fde Mon Sep 17 00:00:00 2001 From: Kaung Khant Soe Date: Thu, 16 Feb 2023 17:00:08 +0700 Subject: [PATCH 4/9] [#83] Update loading test case --- .../crypto/ui/screen/detail/DetailScreenUiTest.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt index debf0509..1658e3e5 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt @@ -18,6 +18,7 @@ import co.nimblehq.compose.crypto.ui.screens.home.FIAT_CURRENCY import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import org.junit.Before @@ -57,13 +58,13 @@ class DetailScreenUiTest : BaseScreenTest() { @Test fun when_navigating_to_detail_screen_it_shows_loading() { - composeAndroidTestRule.activity.setContent { - DetailScreen( - coinId = "", - navigator = { destination -> - appDestination = destination - } - ) + every { mockGetCoinDetailUseCase.execute(any()) } returns flow { + delay(100L) + emit(coinDetail) + } + every { mockGetCoinPricesUseCase.execute(any()) } returns flow { + delay(100L) + emit(coinPrices) } with(composeAndroidTestRule) { From b1aa460b0eb91bf984cb23e7ef8a1a6565a7f8e9 Mon Sep 17 00:00:00 2001 From: Kaung Khant Soe Date: Fri, 17 Feb 2023 17:34:16 +0700 Subject: [PATCH 5/9] [#83] Move data mocking to setup. --- .../ui/screen/detail/DetailScreenUiTest.kt | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt index 1658e3e5..84554e71 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt @@ -44,7 +44,11 @@ class DetailScreenUiTest : BaseScreenTest() { @Before fun setUp() { - initDetailViewModel() + + every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) + every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) + + initViewModel() composeAndroidTestRule.activity.setContent { DetailScreen( coinId = "", @@ -74,8 +78,6 @@ class DetailScreenUiTest : BaseScreenTest() { @Test fun when_navigating_to_detail_screen_it_renders_chart_interval_buttons_properly() { - every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) - every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) with(composeAndroidTestRule) { onNodeWithText(TimeIntervals.ONE_DAY.text).assertIsDisplayed() @@ -88,8 +90,6 @@ class DetailScreenUiTest : BaseScreenTest() { @Test fun when_navigating_to_detail_screen_it_render_currentPrice_and_priceChangePercentage24hInCurrency_properly() { - every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) - every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) with(composeAndroidTestRule) { coinDetail.marketData?.let { marketData -> @@ -107,8 +107,6 @@ class DetailScreenUiTest : BaseScreenTest() { @Test fun when_navigating_to_detail_screen_it_render_coin_info_properly() { - every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) - every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) with(composeAndroidTestRule) { coinDetail.marketData?.let { marketData -> @@ -129,7 +127,6 @@ class DetailScreenUiTest : BaseScreenTest() { every { mockGetCoinDetailUseCase.execute(any()) } returns flow { throw Throwable(errorGeneric) } - every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) with(composeAndroidTestRule) { onNodeWithText(TestTagDetailCoinPriceChart).assertDoesNotExist() @@ -138,8 +135,6 @@ class DetailScreenUiTest : BaseScreenTest() { @Test fun when_navigating_to_detail_screen_chart_interval_buttons_are_clickable() { - every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) - every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) with(composeAndroidTestRule) { onNodeWithText(TimeIntervals.ONE_DAY.text).assertHasClickAction() @@ -150,7 +145,7 @@ class DetailScreenUiTest : BaseScreenTest() { } } - private fun initDetailViewModel() { + private fun initViewModel() { detailViewModel = DetailViewModel( dispatchers = testDispatcherProvider, getCoinDetailUseCase = mockGetCoinDetailUseCase, From 821ed83933cee0be69bf576f8bee8c45a34deb88 Mon Sep 17 00:00:00 2001 From: Kaung Khant Soe Date: Mon, 20 Feb 2023 14:03:23 +0700 Subject: [PATCH 6/9] [#83] Minor refactors from PR. --- .../ui/screen/detail/DetailScreenUiTest.kt | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt index 84554e71..e8390942 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt @@ -9,9 +9,8 @@ import co.nimblehq.compose.crypto.domain.usecase.GetCoinPricesUseCase import co.nimblehq.compose.crypto.extension.toFormattedString import co.nimblehq.compose.crypto.test.MockUtil.coinDetail import co.nimblehq.compose.crypto.test.MockUtil.coinPrices -import co.nimblehq.compose.crypto.ui.BaseScreenTest +import co.nimblehq.compose.crypto.test.TestDispatchersProvider import co.nimblehq.compose.crypto.ui.components.chartintervals.TimeIntervals -import co.nimblehq.compose.crypto.ui.navigation.AppDestination import co.nimblehq.compose.crypto.ui.screens.MainActivity import co.nimblehq.compose.crypto.ui.screens.detail.* import co.nimblehq.compose.crypto.ui.screens.home.FIAT_CURRENCY @@ -27,7 +26,7 @@ import org.junit.Test import kotlin.math.abs @ExperimentalCoroutinesApi -class DetailScreenUiTest : BaseScreenTest() { +class DetailScreenUiTest { @get:Rule val composeAndroidTestRule = createAndroidComposeRule() @@ -40,11 +39,9 @@ class DetailScreenUiTest : BaseScreenTest() { private lateinit var detailViewModel: DetailViewModel - private var appDestination: AppDestination? = null @Before fun setUp() { - every { mockGetCoinDetailUseCase.execute(any()) } returns flowOf(coinDetail) every { mockGetCoinPricesUseCase.execute(any()) } returns flowOf(coinPrices) @@ -53,9 +50,7 @@ class DetailScreenUiTest : BaseScreenTest() { DetailScreen( coinId = "", viewModel = detailViewModel, - navigator = { destination -> - appDestination = destination - } + navigator = { } ) } } @@ -78,7 +73,6 @@ class DetailScreenUiTest : BaseScreenTest() { @Test fun when_navigating_to_detail_screen_it_renders_chart_interval_buttons_properly() { - with(composeAndroidTestRule) { onNodeWithText(TimeIntervals.ONE_DAY.text).assertIsDisplayed() onNodeWithText(TimeIntervals.ONE_WEEK.text).assertIsDisplayed() @@ -90,7 +84,6 @@ class DetailScreenUiTest : BaseScreenTest() { @Test fun when_navigating_to_detail_screen_it_render_currentPrice_and_priceChangePercentage24hInCurrency_properly() { - with(composeAndroidTestRule) { coinDetail.marketData?.let { marketData -> val currentPrice = "$${marketData.currentPrice[FIAT_CURRENCY]?.toFormattedString()}" @@ -107,7 +100,6 @@ class DetailScreenUiTest : BaseScreenTest() { @Test fun when_navigating_to_detail_screen_it_render_coin_info_properly() { - with(composeAndroidTestRule) { coinDetail.marketData?.let { marketData -> val marketCap = "$${marketData.marketCap[FIAT_CURRENCY]?.toFormattedString()}" @@ -135,7 +127,6 @@ class DetailScreenUiTest : BaseScreenTest() { @Test fun when_navigating_to_detail_screen_chart_interval_buttons_are_clickable() { - with(composeAndroidTestRule) { onNodeWithText(TimeIntervals.ONE_DAY.text).assertHasClickAction() onNodeWithText(TimeIntervals.ONE_WEEK.text).assertHasClickAction() @@ -147,7 +138,7 @@ class DetailScreenUiTest : BaseScreenTest() { private fun initViewModel() { detailViewModel = DetailViewModel( - dispatchers = testDispatcherProvider, + dispatchers = TestDispatchersProvider, getCoinDetailUseCase = mockGetCoinDetailUseCase, getCoinPricesUseCase = mockGetCoinPricesUseCase ) From f3ce8b5983832e4f04ae5f586c7369b12091d917 Mon Sep 17 00:00:00 2001 From: Kaung Khant Soe Date: Mon, 20 Feb 2023 14:04:33 +0700 Subject: [PATCH 7/9] [#83] Remove redundant space --- .../compose/crypto/ui/screen/detail/DetailScreenUiTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt index e8390942..488625d8 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt @@ -50,7 +50,7 @@ class DetailScreenUiTest { DetailScreen( coinId = "", viewModel = detailViewModel, - navigator = { } + navigator = {} ) } } From 8ae5de761f47ab843f41389f8c032f15fb5d3569 Mon Sep 17 00:00:00 2001 From: Kaung Khant Soe Date: Tue, 21 Feb 2023 13:46:58 +0700 Subject: [PATCH 8/9] [#83] - Use coinUiModel instead of coinDetail - Move HomeScreenUITest to home folder - Rename DetailScreenUiTest to DetailScreenUITest --- ...lScreenUiTest.kt => DetailScreenUITest.kt} | 46 ++++++++----------- .../ui/screen/{ => home}/HomeScreenUITest.kt | 2 +- 2 files changed, 20 insertions(+), 28 deletions(-) rename app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/{DetailScreenUiTest.kt => DetailScreenUITest.kt} (70%) rename app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/{ => home}/HomeScreenUITest.kt (99%) diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUITest.kt similarity index 70% rename from app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt rename to app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUITest.kt index 488625d8..2edb20b9 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUiTest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/detail/DetailScreenUITest.kt @@ -13,7 +13,7 @@ import co.nimblehq.compose.crypto.test.TestDispatchersProvider import co.nimblehq.compose.crypto.ui.components.chartintervals.TimeIntervals import co.nimblehq.compose.crypto.ui.screens.MainActivity import co.nimblehq.compose.crypto.ui.screens.detail.* -import co.nimblehq.compose.crypto.ui.screens.home.FIAT_CURRENCY +import co.nimblehq.compose.crypto.ui.uimodel.toUiModel import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -26,7 +26,7 @@ import org.junit.Test import kotlin.math.abs @ExperimentalCoroutinesApi -class DetailScreenUiTest { +class DetailScreenUITest { @get:Rule val composeAndroidTestRule = createAndroidComposeRule() @@ -56,14 +56,9 @@ class DetailScreenUiTest { } @Test - fun when_navigating_to_detail_screen_it_shows_loading() { + fun when_navigating_to_detail_screen__it_shows_loading() { every { mockGetCoinDetailUseCase.execute(any()) } returns flow { delay(100L) - emit(coinDetail) - } - every { mockGetCoinPricesUseCase.execute(any()) } returns flow { - delay(100L) - emit(coinPrices) } with(composeAndroidTestRule) { @@ -72,7 +67,7 @@ class DetailScreenUiTest { } @Test - fun when_navigating_to_detail_screen_it_renders_chart_interval_buttons_properly() { + fun when_navigating_to_detail_screen__it_renders_chart_interval_buttons_properly() { with(composeAndroidTestRule) { onNodeWithText(TimeIntervals.ONE_DAY.text).assertIsDisplayed() onNodeWithText(TimeIntervals.ONE_WEEK.text).assertIsDisplayed() @@ -83,39 +78,36 @@ class DetailScreenUiTest { } @Test - fun when_navigating_to_detail_screen_it_render_currentPrice_and_priceChangePercentage24hInCurrency_properly() { + fun when_navigating_to_detail_screen__it_render_currentPrice_and_priceChangePercentage24hInCurrency_properly() { with(composeAndroidTestRule) { - coinDetail.marketData?.let { marketData -> - val currentPrice = "$${marketData.currentPrice[FIAT_CURRENCY]?.toFormattedString()}" - onNodeWithText(currentPrice).assertIsDisplayed() - - val priceChangePercentage24hInCurrency = this.activity.getString( - R.string.coin_profit_percent, - abs(marketData.priceChangePercentage24hInCurrency[FIAT_CURRENCY] ?: 0.0).toFormattedString() - ) - onNodeWithText(priceChangePercentage24hInCurrency).assertIsDisplayed() - } + val coinUiModel = coinDetail.toUiModel() + val currentPrice = "$${coinUiModel.currentPrice.toFormattedString()}" + onNodeWithText(currentPrice).assertIsDisplayed() + + val priceChangePercentage24hInCurrency = "${abs(coinUiModel.priceChangePercentage24hInCurrency).toFormattedString()}%" + onNodeWithText(priceChangePercentage24hInCurrency).assertIsDisplayed() } } @Test - fun when_navigating_to_detail_screen_it_render_coin_info_properly() { + fun when_navigating_to_detail_screen__it_render_coin_info_properly() { with(composeAndroidTestRule) { coinDetail.marketData?.let { marketData -> - val marketCap = "$${marketData.marketCap[FIAT_CURRENCY]?.toFormattedString()}" + val coinUiModel = coinDetail.toUiModel() + val marketCap = "$${coinUiModel.marketCap.toFormattedString()}" onNodeWithText(marketCap).assertIsDisplayed() - val allTimeHigh = "$${marketData.ath[FIAT_CURRENCY]?.toFormattedString()}" - onNodeWithText(allTimeHigh).assertIsDisplayed() + val athTimeHigh = "$${coinUiModel.ath.toFormattedString()}" + onNodeWithText(athTimeHigh).assertIsDisplayed() - val allTimeLow = "$${marketData.atl[FIAT_CURRENCY]?.toFormattedString()}" + val allTimeLow = "$${coinUiModel.atl.toFormattedString()}" onNodeWithText(allTimeLow).assertIsDisplayed() } } } @Test - fun when_navigating_to_detail_screen_and_has_api_error_coin_price_chart_is_not_displayed() { + fun when_navigating_to_detail_screen_and_has_api_error__coin_price_chart_is_not_displayed() { every { mockGetCoinDetailUseCase.execute(any()) } returns flow { throw Throwable(errorGeneric) } @@ -126,7 +118,7 @@ class DetailScreenUiTest { } @Test - fun when_navigating_to_detail_screen_chart_interval_buttons_are_clickable() { + fun when_navigating_to_detail_screen__chart_interval_buttons_are_clickable() { with(composeAndroidTestRule) { onNodeWithText(TimeIntervals.ONE_DAY.text).assertHasClickAction() onNodeWithText(TimeIntervals.ONE_WEEK.text).assertHasClickAction() diff --git a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/home/HomeScreenUITest.kt similarity index 99% rename from app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt rename to app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/home/HomeScreenUITest.kt index 5db5bb08..88c9259c 100644 --- a/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/HomeScreenUITest.kt +++ b/app/src/androidTest/java/co/nimblehq/compose/crypto/ui/screen/home/HomeScreenUITest.kt @@ -1,4 +1,4 @@ -package co.nimblehq.compose.crypto.ui.screen +package co.nimblehq.compose.crypto.ui.screen.home import androidx.activity.compose.setContent import androidx.compose.ui.test.* From 6aff1cd2f0c11c9e740c1c63a2dc5a642d9da4ca Mon Sep 17 00:00:00 2001 From: Kaung Khant Soe Date: Tue, 21 Feb 2023 16:14:41 +0700 Subject: [PATCH 9/9] [#83] Fix test fail --- .../crypto/ui/screens/detail/DetailScreen.kt | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailScreen.kt b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailScreen.kt index c9628180..51fdd8f7 100644 --- a/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailScreen.kt +++ b/app/src/main/java/co/nimblehq/compose/crypto/ui/screens/detail/DetailScreen.kt @@ -9,7 +9,9 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.* +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -40,8 +42,12 @@ import me.bytebeats.views.charts.line.render.line.SolidLineDrawer import me.bytebeats.views.charts.line.render.point.EmptyPointDrawer import me.bytebeats.views.charts.simpleChartAnimation -const val TestTagDetailLoading = "TestTagDetailLoading" -const val TestTagDetailCoinPriceChart = "TestTagDetailCoinPriceChart" +const val TestTagDetailLogo = "DetailLogo" +const val TestTagDetailCircularProgress = "DetailCircularProgress" +const val TestTagDetailLineChart = "DetailLineChart" +const val TestTagDetailChartInterval = "DetailChartInterval" +const val TestTagDetailCoinInfo = "DetailCoinInfo" +const val TestTagDetailSellBuyGroup = "DetailSellBuyGroup" @Composable fun DetailScreen( @@ -129,7 +135,8 @@ private fun DetailScreenContent( top.linkTo(appBar.bottom) linkTo(start = parent.start, end = parent.end) } - .padding(top = Dp8), + .padding(top = Dp8) + .testTag(tag = TestTagDetailLogo), painter = rememberAsyncImagePainter(coinDetailUiModel.image), contentDescription = null ) @@ -167,7 +174,7 @@ private fun DetailScreenContent( start.linkTo(parent.start) end.linkTo(parent.end) } - .testTag(TestTagDetailCoinPriceChart), + .testTag(tag = TestTagDetailLineChart), lineChartData = LineChartData( points = coinPrices.map { coinPrice -> val price = stringResource( @@ -192,11 +199,13 @@ private fun DetailScreenContent( // Chart intervals ChartIntervalsButtonGroup( - modifier = Modifier.constrainAs(intervals) { - top.linkTo(graph.bottom, margin = Dp24) - start.linkTo(parent.start) - end.linkTo(parent.end) - }, + modifier = Modifier + .constrainAs(intervals) { + top.linkTo(graph.bottom, margin = Dp24) + start.linkTo(parent.start) + end.linkTo(parent.end) + } + .testTag(tag = TestTagDetailChartInterval), onIntervalChanged = onTimeIntervalsChanged::invoke ) @@ -220,7 +229,7 @@ private fun DetailScreenContent( bottom = parent.bottom ) } - .testTag(TestTagDetailLoading), + .testTag(tag = TestTagDetailCircularProgress), ) } } @@ -229,7 +238,8 @@ private fun DetailScreenContent( Box( modifier = Modifier .fillMaxSize() - .navigationBarsPadding(), + .navigationBarsPadding() + .testTag(tag = TestTagDetailSellBuyGroup), contentAlignment = Alignment.BottomEnd ) { SellBuyGroup( @@ -248,7 +258,11 @@ private fun CoinInfo( sellBuyLayoutHeight: Dp, coinDetailUiModel: CoinDetailUiModel ) { - Column(modifier = modifier.padding(start = Dp16, end = Dp16, bottom = sellBuyLayoutHeight)) { + Column( + modifier = modifier + .padding(start = Dp16, end = Dp16, bottom = sellBuyLayoutHeight) + .testTag(tag = TestTagDetailCoinInfo) + ) { DetailItem( modifier = Modifier, title = stringResource(id = R.string.detail_market_cap_title),