Fixed: Memory leak in CoreReaderFragment.

* Upgraded the leakCanary to `2.14`.
* Refactored the remaining test cases according to compose UI.
This commit is contained in:
MohitMaliFtechiz 2025-03-24 15:55:38 +05:30
parent 080e518d2b
commit 6aa0980e4d
17 changed files with 107 additions and 138 deletions

View File

@ -42,7 +42,6 @@ import junit.framework.AssertionFailedError
import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertEquals
import org.hamcrest.CoreMatchers.containsString import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.junit.Assert
import org.kiwix.kiwixmobile.BaseRobot import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.Findable.StringId.TextId import org.kiwix.kiwixmobile.Findable.StringId.TextId
import org.kiwix.kiwixmobile.Findable.ViewId import org.kiwix.kiwixmobile.Findable.ViewId
@ -100,16 +99,14 @@ class DownloadRobot : BaseRobot() {
} }
fun checkIfZimFileDownloaded(composeTestRule: ComposeContentTestRule) { fun checkIfZimFileDownloaded(composeTestRule: ComposeContentTestRule) {
pauseForBetterTestPerformance()
try { try {
testFlakyView({ testFlakyView({
composeTestRule.runOnIdle { composeTestRule.waitForIdle()
composeTestRule.onNodeWithTag(NO_FILE_TEXT_TESTING_TAG).assertIsDisplayed() composeTestRule.onNodeWithTag(NO_FILE_TEXT_TESTING_TAG).assertIsDisplayed()
}
}) })
// if the "No files here" text found that means it failed to download the ZIM file. // if the "No files here" text found that means it failed to download the ZIM file.
Assert.fail("Couldn't download the zim file. The [No files here] text is visible on screen") throw RuntimeException("Couldn't download the zim file. The [No files here] text is visible on screen")
} catch (_: AssertionFailedError) { } catch (_: AssertionError) {
// check if "No files here" text is not visible on // check if "No files here" text is not visible on
// screen that means zim file is downloaded successfully. // screen that means zim file is downloaded successfully.
} }

View File

@ -27,17 +27,12 @@ import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.IdlingPolicies import androidx.test.espresso.IdlingPolicies
import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.accessibility.AccessibilityChecks import androidx.test.espresso.accessibility.AccessibilityChecks
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import com.adevinta.android.barista.interaction.BaristaSleepInteractions import com.adevinta.android.barista.interaction.BaristaSleepInteractions
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.DuplicateClickableBoundsCheck
import leakcanary.LeakAssertions import leakcanary.LeakAssertions
import org.hamcrest.Matchers.allOf
import org.junit.After import org.junit.After
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
@ -76,12 +71,6 @@ class DownloadTest : BaseActivityTest() {
init { init {
AccessibilityChecks.enable().apply { AccessibilityChecks.enable().apply {
setRunChecksFromRootView(true) setRunChecksFromRootView(true)
setSuppressingResultMatcher(
allOf(
matchesCheck(DuplicateClickableBoundsCheck::class.java),
matchesViews(ViewMatchers.withId(R.id.get_zim_nearby_device))
)
)
} }
} }

View File

@ -24,17 +24,12 @@ import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.accessibility.AccessibilityChecks import androidx.test.espresso.accessibility.AccessibilityChecks
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import com.adevinta.android.barista.interaction.BaristaSleepInteractions import com.adevinta.android.barista.interaction.BaristaSleepInteractions
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.DuplicateClickableBoundsCheck
import leakcanary.LeakAssertions import leakcanary.LeakAssertions
import org.hamcrest.Matchers.allOf
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@ -64,12 +59,6 @@ class InitialDownloadTest : BaseActivityTest() {
init { init {
AccessibilityChecks.enable().apply { AccessibilityChecks.enable().apply {
setRunChecksFromRootView(true) setRunChecksFromRootView(true)
setSuppressingResultMatcher(
allOf(
matchesCheck(DuplicateClickableBoundsCheck::class.java),
matchesViews(ViewMatchers.withId(R.id.get_zim_nearby_device))
)
)
} }
} }

View File

@ -22,6 +22,7 @@ import android.Manifest
import android.app.Instrumentation import android.app.Instrumentation
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.core.content.edit import androidx.core.content.edit
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -55,6 +56,9 @@ class LocalFileTransferTest {
@JvmField @JvmField
var retryRule = RetryRule() var retryRule = RetryRule()
@get:Rule
val composeTestRule = createComposeRule()
private lateinit var context: Context private lateinit var context: Context
private lateinit var activityScenario: ActivityScenario<KiwixMainActivity> private lateinit var activityScenario: ActivityScenario<KiwixMainActivity>
@ -133,8 +137,8 @@ class LocalFileTransferTest {
it.navigate(R.id.libraryFragment) it.navigate(R.id.libraryFragment)
} }
library { library {
assertGetZimNearbyDeviceDisplayed() assertGetZimNearbyDeviceDisplayed(composeTestRule)
clickFileTransferIcon { clickFileTransferIcon(composeTestRule) {
assertReceiveFileTitleVisible() assertReceiveFileTitleVisible()
assertSearchDeviceMenuItemVisible() assertSearchDeviceMenuItemVisible()
clickOnSearchDeviceMenuItem() clickOnSearchDeviceMenuItem()
@ -164,8 +168,8 @@ class LocalFileTransferTest {
} }
StandardActions.closeDrawer() StandardActions.closeDrawer()
library { library {
assertGetZimNearbyDeviceDisplayed() assertGetZimNearbyDeviceDisplayed(composeTestRule)
clickFileTransferIcon { clickFileTransferIcon(composeTestRule) {
assertClickNearbyDeviceMessageVisible() assertClickNearbyDeviceMessageVisible()
clickOnGotItButton() clickOnGotItButton()
assertDeviceNameMessageVisible() assertDeviceNameMessageVisible()
@ -175,7 +179,7 @@ class LocalFileTransferTest {
assertTransferZimFilesListMessageVisible() assertTransferZimFilesListMessageVisible()
clickOnGotItButton() clickOnGotItButton()
pressBack() pressBack()
assertGetZimNearbyDeviceDisplayed() assertGetZimNearbyDeviceDisplayed(composeTestRule)
} }
} }
LeakAssertions.assertNoLeaks() LeakAssertions.assertNoLeaks()
@ -194,7 +198,9 @@ class LocalFileTransferTest {
StandardActions.closeDrawer() StandardActions.closeDrawer()
library { library {
// test show case view show once. // test show case view show once.
clickFileTransferIcon(LocalFileTransferRobot::assertClickNearbyDeviceMessageNotVisible) clickFileTransferIcon(composeTestRule) {
LocalFileTransferRobot::assertClickNearbyDeviceMessageNotVisible
}
} }
} }

View File

@ -30,7 +30,6 @@ import androidx.test.espresso.web.webdriver.DriverAtoms
import androidx.test.espresso.web.webdriver.Locator import androidx.test.espresso.web.webdriver.Locator
import applyWithViewHierarchyPrinting import applyWithViewHierarchyPrinting
import com.adevinta.android.barista.interaction.BaristaSleepInteractions import com.adevinta.android.barista.interaction.BaristaSleepInteractions
import junit.framework.AssertionFailedError
import org.kiwix.kiwixmobile.BaseRobot import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.Findable import org.kiwix.kiwixmobile.Findable
import org.kiwix.kiwixmobile.Findable.StringId.TextId import org.kiwix.kiwixmobile.Findable.StringId.TextId
@ -108,7 +107,7 @@ class CopyMoveFileHandlerRobot : BaseRobot() {
try { try {
composeTestRule.onNodeWithTag(NO_FILE_TEXT_TESTING_TAG).assertIsDisplayed() composeTestRule.onNodeWithTag(NO_FILE_TEXT_TESTING_TAG).assertIsDisplayed()
throw RuntimeException("ZimFile not added in the local library") throw RuntimeException("ZimFile not added in the local library")
} catch (_: AssertionFailedError) { } catch (_: AssertionError) {
// do nothing zim file is added in the local library // do nothing zim file is added in the local library
} }
} }

View File

@ -17,6 +17,7 @@
*/ */
package org.kiwix.kiwixmobile.main package org.kiwix.kiwixmobile.main
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.core.content.edit import androidx.core.content.edit
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -50,6 +51,9 @@ class TopLevelDestinationTest : BaseActivityTest() {
@JvmField @JvmField
var retryRule = RetryRule() var retryRule = RetryRule()
@get:Rule
val composeTestRule = createComposeRule()
@Before @Before
override fun waitForIdle() { override fun waitForIdle() {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply { UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply {
@ -106,8 +110,10 @@ class TopLevelDestinationTest : BaseActivityTest() {
} }
clickDownloadOnBottomNav(OnlineLibraryRobot::assertLibraryListDisplayed) clickDownloadOnBottomNav(OnlineLibraryRobot::assertLibraryListDisplayed)
clickLibraryOnBottomNav { clickLibraryOnBottomNav {
assertGetZimNearbyDeviceDisplayed() assertGetZimNearbyDeviceDisplayed(composeTestRule)
clickFileTransferIcon(LocalFileTransferRobot::assertReceiveFileTitleVisible) clickFileTransferIcon(composeTestRule) {
LocalFileTransferRobot::assertReceiveFileTitleVisible
}
} }
clickBookmarksOnNavDrawer { clickBookmarksOnNavDrawer {
assertBookMarksDisplayed() assertBookMarksDisplayed()

View File

@ -21,19 +21,17 @@ package org.kiwix.kiwixmobile.nav.destination.library
import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.longClick
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithTag
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.longClick
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition
import androidx.test.espresso.matcher.RootMatchers.isDialog import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import applyWithViewHierarchyPrinting import applyWithViewHierarchyPrinting
import com.adevinta.android.barista.interaction.BaristaSleepInteractions import com.adevinta.android.barista.interaction.BaristaSleepInteractions
import junit.framework.AssertionFailedError
import org.kiwix.kiwixmobile.BaseRobot import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.Findable.ViewId import org.kiwix.kiwixmobile.Findable.ViewId
import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.R
@ -42,52 +40,61 @@ import org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferRobot
import org.kiwix.kiwixmobile.localFileTransfer.localFileTransfer import org.kiwix.kiwixmobile.localFileTransfer.localFileTransfer
import org.kiwix.kiwixmobile.nav.destination.library.local.BOOK_LIST_TESTING_TAG import org.kiwix.kiwixmobile.nav.destination.library.local.BOOK_LIST_TESTING_TAG
import org.kiwix.kiwixmobile.nav.destination.library.local.CONTENT_LOADING_PROGRESSBAR_TESTING_TAG import org.kiwix.kiwixmobile.nav.destination.library.local.CONTENT_LOADING_PROGRESSBAR_TESTING_TAG
import org.kiwix.kiwixmobile.nav.destination.library.local.LOCAL_FILE_TRANSFER_MENU_BUTTON_TESTING_TAG
import org.kiwix.kiwixmobile.nav.destination.library.local.NO_FILE_TEXT_TESTING_TAG import org.kiwix.kiwixmobile.nav.destination.library.local.NO_FILE_TEXT_TESTING_TAG
import org.kiwix.kiwixmobile.testutils.TestUtils import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.kiwixmobile.testutils.TestUtils.refresh import org.kiwix.kiwixmobile.testutils.TestUtils.refresh
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
import org.kiwix.kiwixmobile.utils.RecyclerViewItemCount import org.kiwix.kiwixmobile.ui.BOOK_ITEM_TESTING_TAG
import java.lang.AssertionError
fun library(func: LibraryRobot.() -> Unit) = LibraryRobot().applyWithViewHierarchyPrinting(func) fun library(func: LibraryRobot.() -> Unit) = LibraryRobot().applyWithViewHierarchyPrinting(func)
class LibraryRobot : BaseRobot() { class LibraryRobot : BaseRobot() {
private val zimFileTitle = "Test_Zim" private val zimFileTitle = "Test_Zim"
fun assertGetZimNearbyDeviceDisplayed() { fun assertGetZimNearbyDeviceDisplayed(composeTestRule: ComposeContentTestRule) {
isVisible(ViewId(R.id.get_zim_nearby_device)) composeTestRule.apply {
waitForIdle()
onNodeWithTag(LOCAL_FILE_TRANSFER_MENU_BUTTON_TESTING_TAG).assertIsDisplayed()
}
} }
fun clickFileTransferIcon(func: LocalFileTransferRobot.() -> Unit) { fun clickFileTransferIcon(
clickOn(ViewId(R.id.get_zim_nearby_device)) composeTestRule: ComposeContentTestRule,
func: LocalFileTransferRobot.() -> Unit
) {
composeTestRule.apply {
waitForIdle()
onNodeWithTag(LOCAL_FILE_TRANSFER_MENU_BUTTON_TESTING_TAG).performClick()
}
localFileTransfer(func) localFileTransfer(func)
} }
fun assertLibraryListDisplayed(composeTestRule: ComposeContentTestRule) { fun assertLibraryListDisplayed(composeTestRule: ComposeContentTestRule) {
testFlakyView({ testFlakyView({
composeTestRule.runOnIdle { composeTestRule.waitForIdle()
composeTestRule.onNodeWithTag(BOOK_LIST_TESTING_TAG).assertIsDisplayed() composeTestRule.onNodeWithTag(BOOK_LIST_TESTING_TAG).assertIsDisplayed()
}
}) })
} }
private fun assertNoFilesTextDisplayed(composeTestRule: ComposeContentTestRule) { private fun assertNoFilesTextDisplayed(composeTestRule: ComposeContentTestRule) {
testFlakyView({ testFlakyView({
composeTestRule.runOnIdle { composeTestRule.waitForIdle()
composeTestRule.onNodeWithTag(NO_FILE_TEXT_TESTING_TAG).assertIsDisplayed() composeTestRule.onNodeWithTag(NO_FILE_TEXT_TESTING_TAG).assertIsDisplayed()
}
}) })
} }
fun refreshList(composeTestRule: ComposeContentTestRule) { fun refreshList(composeTestRule: ComposeContentTestRule) {
composeTestRule.runOnIdle {
try { try {
composeTestRule.waitForIdle()
composeTestRule.onNodeWithTag(NO_FILE_TEXT_TESTING_TAG).assertIsDisplayed() composeTestRule.onNodeWithTag(NO_FILE_TEXT_TESTING_TAG).assertIsDisplayed()
composeTestRule.refresh() composeTestRule.refresh()
} catch (_: AssertionFailedError) { } catch (_: AssertionError) {
try { try {
composeTestRule.onNodeWithTag(BOOK_LIST_TESTING_TAG).assertIsDisplayed() composeTestRule.onNodeWithTag(BOOK_LIST_TESTING_TAG).assertIsDisplayed()
composeTestRule.refresh() composeTestRule.refresh()
} catch (_: AssertionFailedError) { } catch (_: AssertionError) {
Log.i( Log.i(
"LOCAL_LIBRARY", "LOCAL_LIBRARY",
"No need to refresh the data, since there is no files found" "No need to refresh the data, since there is no files found"
@ -95,9 +102,9 @@ class LibraryRobot : BaseRobot() {
} }
} }
} }
}
fun waitUntilZimFilesRefreshing(composeTestRule: ComposeContentTestRule) { fun waitUntilZimFilesRefreshing(composeTestRule: ComposeContentTestRule) {
pauseForBetterTestPerformance()
testFlakyView({ testFlakyView({
composeTestRule.onNodeWithTag(CONTENT_LOADING_PROGRESSBAR_TESTING_TAG) composeTestRule.onNodeWithTag(CONTENT_LOADING_PROGRESSBAR_TESTING_TAG)
.assertIsNotDisplayed() .assertIsNotDisplayed()
@ -110,19 +117,13 @@ class LibraryRobot : BaseRobot() {
composeTestRule.onNodeWithTag(NO_FILE_TEXT_TESTING_TAG).assertIsDisplayed() composeTestRule.onNodeWithTag(NO_FILE_TEXT_TESTING_TAG).assertIsDisplayed()
// if this view is displaying then we do not need to run the further code. // if this view is displaying then we do not need to run the further code.
return return
} catch (_: AssertionFailedError) { } catch (_: AssertionError) {
Log.e("DELETE_ZIM_FILE", "Zim files found in local library so we are deleting them") Log.e("DELETE_ZIM_FILE", "Zim files found in local library so we are deleting them")
} }
val recyclerViewId: Int = R.id.zimfilelist val zimFileNodes = composeTestRule.onAllNodesWithTag(BOOK_ITEM_TESTING_TAG)
val recyclerViewItemsCount = RecyclerViewItemCount(recyclerViewId).checkRecyclerViewCount() val itemCount = zimFileNodes.fetchSemanticsNodes().size
// Scroll to the end of the RecyclerView to ensure all items are visible repeat(itemCount) { index ->
onView(withId(recyclerViewId)) zimFileNodes[index].performTouchInput { longClick() }
.perform(scrollToPosition<ViewHolder>(recyclerViewItemsCount - 1))
for (position in 0 until recyclerViewItemsCount) {
// Long-click the item to select it
onView(withId(recyclerViewId))
.perform(actionOnItemAtPosition<ViewHolder>(position, longClick()))
} }
clickOnFileDeleteIcon() clickOnFileDeleteIcon()
clickOnDeleteZimFile() clickOnDeleteZimFile()

View File

@ -24,14 +24,9 @@ import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.accessibility.AccessibilityChecks import androidx.test.espresso.accessibility.AccessibilityChecks
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice 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.DuplicateClickableBoundsCheck
import leakcanary.LeakAssertions import leakcanary.LeakAssertions
import org.hamcrest.Matchers.allOf
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@ -60,12 +55,6 @@ class LocalLibraryTest : BaseActivityTest() {
init { init {
AccessibilityChecks.enable().apply { AccessibilityChecks.enable().apply {
setRunChecksFromRootView(true) setRunChecksFromRootView(true)
setSuppressingResultMatcher(
allOf(
matchesCheck(DuplicateClickableBoundsCheck::class.java),
matchesViews(ViewMatchers.withId(R.id.get_zim_nearby_device))
)
)
} }
} }
@ -142,6 +131,7 @@ class LocalLibraryTest : BaseActivityTest() {
} }
library { library {
refreshList(composeTestRule) refreshList(composeTestRule)
waitUntilZimFilesRefreshing(composeTestRule)
assertLibraryListDisplayed(composeTestRule) assertLibraryListDisplayed(composeTestRule)
} }
LeakAssertions.assertNoLeaks() LeakAssertions.assertNoLeaks()

View File

@ -23,14 +23,9 @@ import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.accessibility.AccessibilityChecks import androidx.test.espresso.accessibility.AccessibilityChecks
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import com.adevinta.android.barista.interaction.BaristaSleepInteractions import com.adevinta.android.barista.interaction.BaristaSleepInteractions
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.DuplicateClickableBoundsCheck
import org.hamcrest.Matchers.allOf
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -55,12 +50,6 @@ class OnlineLibraryFragmentTest : BaseActivityTest() {
init { init {
AccessibilityChecks.enable().apply { AccessibilityChecks.enable().apply {
setRunChecksFromRootView(true) setRunChecksFromRootView(true)
setSuppressingResultMatcher(
allOf(
matchesCheck(DuplicateClickableBoundsCheck::class.java),
matchesViews(ViewMatchers.withId(R.id.get_zim_nearby_device))
)
)
} }
} }

View File

@ -26,14 +26,12 @@ import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.accessibility.AccessibilityChecks import androidx.test.espresso.accessibility.AccessibilityChecks
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.internal.runner.junit4.statement.UiThreadStatement import androidx.test.internal.runner.junit4.statement.UiThreadStatement
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice 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.matchesCheck
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesViews import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesViews
import com.google.android.apps.common.testing.accessibility.framework.checks.DuplicateClickableBoundsCheck
import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck
import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck
import leakcanary.LeakAssertions import leakcanary.LeakAssertions
@ -48,8 +46,8 @@ import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange import org.kiwix.kiwixmobile.core.utils.LanguageUtils.Companion.handleLocaleChange
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.main.KiwixMainActivity import org.kiwix.kiwixmobile.main.KiwixMainActivity
import org.kiwix.kiwixmobile.nav.destination.library.local.LocalLibraryFragmentDirections
import org.kiwix.kiwixmobile.nav.destination.library.library import org.kiwix.kiwixmobile.nav.destination.library.library
import org.kiwix.kiwixmobile.nav.destination.library.local.LocalLibraryFragmentDirections
import org.kiwix.kiwixmobile.testutils.RetryRule import org.kiwix.kiwixmobile.testutils.RetryRule
import org.kiwix.kiwixmobile.testutils.TestUtils import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.kiwixmobile.testutils.TestUtils.closeSystemDialogs import org.kiwix.kiwixmobile.testutils.TestUtils.closeSystemDialogs
@ -105,10 +103,6 @@ class NoteFragmentTest : BaseActivityTest() {
setRunChecksFromRootView(true) setRunChecksFromRootView(true)
setSuppressingResultMatcher( setSuppressingResultMatcher(
anyOf( anyOf(
allOf(
matchesCheck(DuplicateClickableBoundsCheck::class.java),
matchesViews(ViewMatchers.withId(R.id.get_zim_nearby_device))
),
allOf( allOf(
matchesCheck(TouchTargetSizeCheck::class.java), matchesCheck(TouchTargetSizeCheck::class.java),
matchesViews(withContentDescription("More options")) matchesViews(withContentDescription("More options"))

View File

@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.shortcuts
import android.app.Instrumentation import android.app.Instrumentation
import android.content.Intent import android.content.Intent
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.core.content.edit import androidx.core.content.edit
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -55,6 +56,9 @@ class GetContentShortcutTest {
@JvmField @JvmField
val retryRule = RetryRule() val retryRule = RetryRule()
@get:Rule
val composeTestRule = createComposeRule()
init { init {
AccessibilityChecks.enable().setRunChecksFromRootView(true) AccessibilityChecks.enable().setRunChecksFromRootView(true)
} }
@ -111,8 +115,10 @@ class GetContentShortcutTest {
} }
clickDownloadOnBottomNav(OnlineLibraryRobot::assertLibraryListDisplayed) clickDownloadOnBottomNav(OnlineLibraryRobot::assertLibraryListDisplayed)
clickLibraryOnBottomNav { clickLibraryOnBottomNav {
assertGetZimNearbyDeviceDisplayed() assertGetZimNearbyDeviceDisplayed(composeTestRule)
clickFileTransferIcon(LocalFileTransferRobot::assertReceiveFileTitleVisible) clickFileTransferIcon(composeTestRule) {
LocalFileTransferRobot::assertReceiveFileTitleVisible
}
} }
clickBookmarksOnNavDrawer { clickBookmarksOnNavDrawer {
assertBookMarksDisplayed() assertBookMarksDisplayed()

View File

@ -25,14 +25,10 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.accessibility.AccessibilityChecks import androidx.test.espresso.accessibility.AccessibilityChecks
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule import androidx.test.rule.GrantPermissionRule
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesCheck import leakcanary.LeakAssertions
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesViews
import com.google.android.apps.common.testing.accessibility.framework.checks.DuplicateClickableBoundsCheck
import org.hamcrest.Matchers.allOf
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@ -87,12 +83,6 @@ class ZimHostFragmentTest {
init { init {
AccessibilityChecks.enable().apply { AccessibilityChecks.enable().apply {
setRunChecksFromRootView(true) setRunChecksFromRootView(true)
setSuppressingResultMatcher(
allOf(
matchesCheck(DuplicateClickableBoundsCheck::class.java),
matchesViews(withId(R.id.get_zim_nearby_device))
)
)
} }
} }
@ -149,7 +139,7 @@ class ZimHostFragmentTest {
loadZimFileInApplication("small.zim") loadZimFileInApplication("small.zim")
zimHost { zimHost {
refreshLibraryList(composeTestRule) refreshLibraryList(composeTestRule)
assertZimFilesLoaded() assertZimFilesLoaded(composeTestRule)
openZimHostFragment() openZimHostFragment()
// Check if server is already started // Check if server is already started
@ -200,6 +190,7 @@ class ZimHostFragmentTest {
stopServer(composeTestRule) stopServer(composeTestRule)
} }
} }
LeakAssertions.assertNoLeaks()
} }
private fun loadZimFileInApplication(zimFileName: String) { private fun loadZimFileInApplication(zimFileName: String) {

View File

@ -18,11 +18,13 @@
package org.kiwix.kiwixmobile.webserver package org.kiwix.kiwixmobile.webserver
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsOn import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
@ -40,6 +42,7 @@ import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.kiwixmobile.testutils.TestUtils.refresh import org.kiwix.kiwixmobile.testutils.TestUtils.refresh
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
import org.kiwix.kiwixmobile.ui.BOOK_ITEM_CHECKBOX_TESTING_TAG import org.kiwix.kiwixmobile.ui.BOOK_ITEM_CHECKBOX_TESTING_TAG
import org.kiwix.kiwixmobile.ui.BOOK_ITEM_TESTING_TAG
import org.kiwix.kiwixmobile.utils.StandardActions.openDrawer import org.kiwix.kiwixmobile.utils.StandardActions.openDrawer
fun zimHost(func: ZimHostRobot.() -> Unit) = ZimHostRobot().applyWithViewHierarchyPrinting(func) fun zimHost(func: ZimHostRobot.() -> Unit) = ZimHostRobot().applyWithViewHierarchyPrinting(func)
@ -50,15 +53,16 @@ class ZimHostRobot : BaseRobot() {
} }
fun refreshLibraryList(composeTestRule: ComposeContentTestRule) { fun refreshLibraryList(composeTestRule: ComposeContentTestRule) {
pauseForBetterTestPerformance() composeTestRule.apply {
composeTestRule.runOnIdle { waitForIdle()
composeTestRule.refresh() refresh()
} }
} }
fun assertZimFilesLoaded() { fun assertZimFilesLoaded(composeTestRule: ComposeContentTestRule) {
pauseForBetterTestPerformance() pauseForBetterTestPerformance()
isVisible(Text("Test_Zim")) val zimFileNodes = composeTestRule.onAllNodesWithTag(BOOK_ITEM_TESTING_TAG)
zimFileNodes.assertCountEquals(2)
} }
fun openZimHostFragment() { fun openZimHostFragment() {
@ -137,7 +141,7 @@ class ZimHostRobot : BaseRobot() {
try { try {
composeTestRule.onNodeWithTag("$BOOK_ITEM_CHECKBOX_TESTING_TAG$position") composeTestRule.onNodeWithTag("$BOOK_ITEM_CHECKBOX_TESTING_TAG$position")
.assertIsOn() .assertIsOn()
} catch (_: AssertionFailedError) { } catch (_: AssertionError) {
composeTestRule.onNodeWithTag("$BOOK_ITEM_CHECKBOX_TESTING_TAG$position") composeTestRule.onNodeWithTag("$BOOK_ITEM_CHECKBOX_TESTING_TAG$position")
.performClick() .performClick()
} }

View File

@ -183,10 +183,12 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
LaunchedEffect(isBottomNavVisible) { LaunchedEffect(isBottomNavVisible) {
(requireActivity() as KiwixMainActivity).toggleBottomNavigation(isBottomNavVisible) (requireActivity() as KiwixMainActivity).toggleBottomNavigation(isBottomNavVisible)
} }
LaunchedEffect(Unit) {
updateLibraryScreenState( updateLibraryScreenState(
bottomNavigationHeight = getBottomNavigationHeight(), bottomNavigationHeight = getBottomNavigationHeight(),
actionMenuItems = actionMenuItems() actionMenuItems = actionMenuItems()
) )
}
LocalLibraryScreen( LocalLibraryScreen(
listState = lazyListState, listState = lazyListState,
state = libraryScreenState.value, state = libraryScreenState.value,

View File

@ -58,6 +58,7 @@ import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
const val BOOK_ITEM_CHECKBOX_TESTING_TAG = "bookItemCheckboxTestingTag" const val BOOK_ITEM_CHECKBOX_TESTING_TAG = "bookItemCheckboxTestingTag"
const val BOOK_ITEM_TESTING_TAG = "bookItemTestingTag"
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
@ -86,7 +87,8 @@ fun BookItem(
onLongClick?.invoke(bookOnDisk) onLongClick?.invoke(bookOnDisk)
} }
} }
), )
.testTag(BOOK_ITEM_TESTING_TAG),
shape = MaterialTheme.shapes.extraSmall, shape = MaterialTheme.shapes.extraSmall,
elevation = CardDefaults.elevatedCardElevation(), elevation = CardDefaults.elevatedCardElevation(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer) colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer)

View File

@ -56,7 +56,7 @@ object Versions {
const val javax_annotation_api: String = "1.3.2" const val javax_annotation_api: String = "1.3.2"
const val leakcanary_android: String = "2.13" const val leakcanary_android: String = "2.14"
const val constraintlayout: String = "2.1.4" const val constraintlayout: String = "2.1.4"

View File

@ -1253,8 +1253,7 @@ abstract class CoreReaderFragment :
ignore.printStackTrace() ignore.printStackTrace()
} }
if (sharedPreferenceUtil?.showIntro() == true) { if (sharedPreferenceUtil?.showIntro() == true) {
val activity = requireActivity() as AppCompatActivity? (requireActivity() as? AppCompatActivity)?.setSupportActionBar(null)
activity?.setSupportActionBar(null)
} }
repositoryActions?.dispose() repositoryActions?.dispose()
safeDispose() safeDispose()
@ -1281,7 +1280,7 @@ abstract class CoreReaderFragment :
// to fix IntroFragmentTest see https://github.com/kiwix/kiwix-android/pull/3217 // to fix IntroFragmentTest see https://github.com/kiwix/kiwix-android/pull/3217
try { try {
requireActivity().unbindService(serviceConnection) requireActivity().unbindService(serviceConnection)
} catch (ignore: IllegalArgumentException) { } catch (_: IllegalArgumentException) {
// to handle if service is already unbounded // to handle if service is already unbounded
} }
unRegisterReadAloudService() unRegisterReadAloudService()
@ -1291,6 +1290,7 @@ abstract class CoreReaderFragment :
donationDialogHandler = null donationDialogHandler = null
} }
@SuppressLint("ClickableViewAccessibility")
private fun unBindViewsAndBinding() { private fun unBindViewsAndBinding() {
activityMainRoot = null activityMainRoot = null
noOpenBookButton = null noOpenBookButton = null
@ -1313,11 +1313,15 @@ abstract class CoreReaderFragment :
videoView = null videoView = null
contentFrame = null contentFrame = null
toolbarContainer = null toolbarContainer = null
compatCallback?.finish()
compatCallback = null
toolbar?.setOnTouchListener(null)
toolbar = null toolbar = null
progressBar = null progressBar = null
drawerLayout = null drawerLayout = null
closeAllTabsButton = null closeAllTabsButton = null
tableDrawerRightContainer = null tableDrawerRightContainer = null
fragmentReaderBinding?.root?.removeAllViews()
fragmentReaderBinding = null fragmentReaderBinding = null
donationLayout?.removeAllViews() donationLayout?.removeAllViews()
donationLayout = null donationLayout = null
@ -2508,7 +2512,7 @@ abstract class CoreReaderFragment :
repositoryActions?.clearWebViewPageHistory() repositoryActions?.clearWebViewPageHistory()
} }
val coreApp = sharedPreferenceUtil?.context as CoreApp val coreApp = sharedPreferenceUtil?.context as CoreApp
val settings = coreApp.getMainActivity().getSharedPreferences( val settings = coreApp.getSharedPreferences(
SharedPreferenceUtil.PREF_KIWIX_MOBILE, SharedPreferenceUtil.PREF_KIWIX_MOBILE,
0 0
) )