Added DonationDialogTest UI test cases to thoroughly verify the functionality using Compose UI.

* Minor improvements to dark mode styling for the donation layout buttons.
This commit is contained in:
MohitMaliFtechiz 2025-06-28 20:11:13 +05:30
parent bea28a16a3
commit 1e9e634fca
4 changed files with 284 additions and 9 deletions

View File

@ -0,0 +1,229 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.reader
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.core.content.edit
import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.accessibility.AccessibilityChecks
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesCheck
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesViews
import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck
import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.anyOf
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.kiwix.kiwixmobile.BaseActivityTest
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.THREE_MONTHS_IN_MILLISECONDS
import org.kiwix.kiwixmobile.core.utils.TestingUtils.COMPOSE_TEST_RULE_ORDER
import org.kiwix.kiwixmobile.core.utils.TestingUtils.RETRY_RULE_ORDER
import org.kiwix.kiwixmobile.main.KiwixMainActivity
import org.kiwix.kiwixmobile.nav.destination.library.library
import org.kiwix.kiwixmobile.testutils.RetryRule
import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.kiwixmobile.testutils.TestUtils.closeSystemDialogs
import org.kiwix.kiwixmobile.testutils.TestUtils.isSystemUINotRespondingDialogVisible
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
class DonationDialogTest : BaseActivityTest() {
@Rule(order = RETRY_RULE_ORDER)
@JvmField
val retryRule = RetryRule()
@get:Rule(order = COMPOSE_TEST_RULE_ORDER)
val composeTestRule = createComposeRule()
private lateinit var kiwixMainActivity: KiwixMainActivity
private lateinit var sharedPreferenceUtil: SharedPreferenceUtil
@Before
override fun waitForIdle() {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply {
if (isSystemUINotRespondingDialogVisible(this)) {
closeSystemDialogs(context, this)
}
waitForIdle()
}
PreferenceManager.getDefaultSharedPreferences(context).edit {
putBoolean(SharedPreferenceUtil.PREF_SHOW_INTRO, false)
putBoolean(SharedPreferenceUtil.PREF_WIFI_ONLY, false)
putBoolean(SharedPreferenceUtil.PREF_IS_TEST, true)
putString(SharedPreferenceUtil.PREF_LANG, "en")
}
sharedPreferenceUtil = SharedPreferenceUtil(context)
activityScenario =
ActivityScenario.launch(KiwixMainActivity::class.java).apply {
moveToState(Lifecycle.State.RESUMED)
onActivity {
handleLocaleChange(
it,
"en",
sharedPreferenceUtil
)
}
}
}
init {
AccessibilityChecks.enable().apply {
setRunChecksFromRootView(true)
setSuppressingResultMatcher(
anyOf(
allOf(
matchesCheck(TouchTargetSizeCheck::class.java),
matchesViews(withContentDescription("More options"))
),
matchesCheck(SpeakableTextPresentCheck::class.java)
)
)
}
}
@Test
fun showDonationPopupWhenApplicationIsThreeMonthOldAndHaveAtleastOneZIMFile() {
loadZIMFileInApplication()
sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds = 0L
sharedPreferenceUtil.laterClickedMilliSeconds = 0L
openReaderFragment()
donation { assertDonationDialogDisplayed(composeTestRule) }
}
@Test
fun shouldNotShowDonationPopupWhenApplicationIsThreeMonthOldAndDoNotHaveAnyZIMFile() {
sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds = 0L
activityScenario.onActivity {
kiwixMainActivity = it
kiwixMainActivity.navigate(R.id.libraryFragment)
}
deleteAllZIMFilesFromApplication()
openReaderFragment()
donation { assertDonationDialogIsNotDisplayed(composeTestRule) }
}
@Test
fun shouldNotShowPopupIfTimeSinceLastPopupIsLessThanThreeMonth() {
sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds =
System.currentTimeMillis() - (THREE_MONTHS_IN_MILLISECONDS / 2)
loadZIMFileInApplication()
openReaderFragment()
donation { assertDonationDialogIsNotDisplayed(composeTestRule) }
}
@Test
fun shouldShowDonationPopupIfTimeSinceLastPopupExceedsThreeMonths() {
sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds =
System.currentTimeMillis() - (THREE_MONTHS_IN_MILLISECONDS + 1000)
loadZIMFileInApplication()
openReaderFragment()
donation { assertDonationDialogDisplayed(composeTestRule) }
}
@Test
fun testShouldShowDonationPopupWhenLaterClickedTimeExceedsThreeMonths() {
sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds = 0L
sharedPreferenceUtil.laterClickedMilliSeconds =
System.currentTimeMillis() - (THREE_MONTHS_IN_MILLISECONDS + 1000)
loadZIMFileInApplication()
openReaderFragment()
donation { assertDonationDialogDisplayed(composeTestRule) }
}
@Test
fun testShouldNotShowPopupIfLaterClickedTimeIsLessThanThreeMonths() {
sharedPreferenceUtil.lastDonationPopupShownInMilliSeconds = 0L
sharedPreferenceUtil.laterClickedMilliSeconds =
System.currentTimeMillis() - 10000L
loadZIMFileInApplication()
openReaderFragment()
donation { assertDonationDialogIsNotDisplayed(composeTestRule) }
}
private fun openReaderFragment() {
UiThreadStatement.runOnUiThread {
kiwixMainActivity.navigate(kiwixMainActivity.readerFragmentResId)
}
}
private fun loadZIMFileInApplication() {
openLocalLibraryScreen()
deleteAllZIMFilesFromApplication()
val loadFileStream =
DonationDialogTest::class.java.classLoader.getResourceAsStream("testzim.zim")
val zimFile =
File(
context.getExternalFilesDirs(null)[0],
"testzim.zim"
)
if (zimFile.exists()) zimFile.delete()
zimFile.createNewFile()
loadFileStream.use { inputStream ->
val outputStream: OutputStream = FileOutputStream(zimFile)
outputStream.use { it ->
val buffer = ByteArray(inputStream.available())
var length: Int
while (inputStream.read(buffer).also { length = it } > 0) {
it.write(buffer, 0, length)
}
}
}
refreshZIMFilesList()
}
private fun openLocalLibraryScreen() {
activityScenario.onActivity {
kiwixMainActivity = it
kiwixMainActivity.navigate(R.id.libraryFragment)
}
}
private fun refreshZIMFilesList() {
library {
refreshList(composeTestRule)
waitUntilZimFilesRefreshing(composeTestRule)
}
}
private fun deleteAllZIMFilesFromApplication() {
refreshZIMFilesList()
library {
// delete all the ZIM files showing in the LocalLibrary
// screen to properly test the scenario.
deleteZimIfExists(composeTestRule)
}
}
@After
fun finish() {
TestUtils.deleteTemporaryFilesOfTestCases(context)
}
}

View File

@ -0,0 +1,45 @@
/*
* Kiwix Android
* Copyright (c) 2025 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.reader
import androidx.compose.ui.test.isDisplayed
import androidx.compose.ui.test.isNotDisplayed
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.onNodeWithTag
import applyWithViewHierarchyPrinting
import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.core.main.reader.DONATION_LAYOUT_TESTING_TAG
import org.kiwix.kiwixmobile.testutils.TestUtils.waitUntilTimeout
fun donation(func: DonationRobot.() -> Unit) = DonationRobot().applyWithViewHierarchyPrinting(func)
class DonationRobot : BaseRobot() {
fun assertDonationDialogDisplayed(composeTestRule: ComposeContentTestRule) {
composeTestRule.apply {
waitUntilTimeout()
onNodeWithTag(DONATION_LAYOUT_TESTING_TAG).isDisplayed()
}
}
fun assertDonationDialogIsNotDisplayed(composeTestRule: ComposeContentTestRule) {
composeTestRule.apply {
waitUntilTimeout()
onNodeWithTag(DONATION_LAYOUT_TESTING_TAG).isNotDisplayed()
}
}
}

View File

@ -40,16 +40,18 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.ui.theme.DenimBlue800
import org.kiwix.kiwixmobile.core.utils.ComposeDimens import org.kiwix.kiwixmobile.core.utils.ComposeDimens
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.DONATION_LAYOUT_MAXIMUM_WIDTH import org.kiwix.kiwixmobile.core.utils.ComposeDimens.DONATION_LAYOUT_MAXIMUM_WIDTH
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
const val DONATION_LAYOUT_TESTING_TAG = "donationLayoutTestingTag"
@Composable @Composable
fun DonationLayout( fun DonationLayout(
appName: String, appName: String,
@ -70,7 +72,7 @@ fun DonationLayout(
) )
.padding(horizontal = SIXTEEN_DP), .padding(horizontal = SIXTEEN_DP),
) { ) {
DonationDialogCard( DonationLayoutCard(
appName, appName,
onDonateButtonClick, onDonateButtonClick,
onLaterButtonClick onLaterButtonClick
@ -79,7 +81,7 @@ fun DonationLayout(
} }
@Composable @Composable
fun DonationDialogCard( fun DonationLayoutCard(
appName: String, appName: String,
onDonateButtonClick: () -> Unit, onDonateButtonClick: () -> Unit,
onLaterButtonClick: () -> Unit onLaterButtonClick: () -> Unit
@ -88,7 +90,8 @@ fun DonationDialogCard(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .wrapContentHeight()
.padding(ComposeDimens.SIXTEEN_DP), .padding(ComposeDimens.SIXTEEN_DP)
.testTag(DONATION_LAYOUT_TESTING_TAG),
shape = MaterialTheme.shapes.medium, shape = MaterialTheme.shapes.medium,
elevation = CardDefaults.cardElevation(defaultElevation = ComposeDimens.SIX_DP), elevation = CardDefaults.cardElevation(defaultElevation = ComposeDimens.SIX_DP),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer) colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer)
@ -152,12 +155,10 @@ fun DonationDialogButton(
onButtonClick: () -> Unit, onButtonClick: () -> Unit,
@StringRes buttonText: Int @StringRes buttonText: Int
) { ) {
TextButton( TextButton(onClick = onButtonClick) {
onClick = onButtonClick
) {
Text( Text(
text = stringResource(buttonText), text = stringResource(buttonText),
color = DenimBlue800 color = MaterialTheme.colorScheme.primary
) )
} }
} }

View File

@ -154,7 +154,7 @@ class DonationDialogHandlerTest {
} }
@Test @Test
fun `test should show popup if later clicked time is less than three months`() = fun `test should not show popup if later clicked time is less than three months`() =
runTest { runTest {
donationDialogHandler = spyk(donationDialogHandler) donationDialogHandler = spyk(donationDialogHandler)
val currentMilliSeconds = System.currentTimeMillis() val currentMilliSeconds = System.currentTimeMillis()