mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-15 02:18:04 -04:00
Fixed the application was crashing when we frequently searching and clicking on any article.
* Added test cases for testing this scenario.
This commit is contained in:
parent
e914b25957
commit
1df7a0b27a
@ -166,6 +166,23 @@ class SearchFragmentTest : BaseActivityTest() {
|
|||||||
// go to reader screen
|
// go to reader screen
|
||||||
pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Added test for checking the crash scenario where the application was crashing when we
|
||||||
|
// frequently searched for article, and clicked on the searched item.
|
||||||
|
search {
|
||||||
|
// test by searching 10 article and clicking on them
|
||||||
|
searchAndClickOnArticle(searchQueryForDownloadedZimFile)
|
||||||
|
searchAndClickOnArticle("A Song")
|
||||||
|
searchAndClickOnArticle("The Ra")
|
||||||
|
searchAndClickOnArticle("The Ge")
|
||||||
|
searchAndClickOnArticle("Wish")
|
||||||
|
searchAndClickOnArticle("WIFI")
|
||||||
|
searchAndClickOnArticle("Woman")
|
||||||
|
searchAndClickOnArticle("Big Ba")
|
||||||
|
searchAndClickOnArticle("My Wor")
|
||||||
|
searchAndClickOnArticle("100")
|
||||||
|
assertArticleLoaded()
|
||||||
|
}
|
||||||
removeTemporaryZimFilesToFreeUpDeviceStorage()
|
removeTemporaryZimFilesToFreeUpDeviceStorage()
|
||||||
LeakAssertions.assertNoLeaks()
|
LeakAssertions.assertNoLeaks()
|
||||||
}
|
}
|
||||||
|
@ -20,26 +20,27 @@ package org.kiwix.kiwixmobile.search
|
|||||||
|
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.action.ViewActions.clearText
|
import androidx.test.espresso.action.ViewActions.clearText
|
||||||
import androidx.test.espresso.action.ViewActions.click
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
import androidx.test.espresso.action.ViewActions.typeText
|
import androidx.test.espresso.action.ViewActions.typeText
|
||||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
|
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
|
||||||
import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition
|
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA
|
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import androidx.test.espresso.web.sugar.Web.onWebView
|
||||||
|
import androidx.test.espresso.web.webdriver.DriverAtoms.findElement
|
||||||
|
import androidx.test.espresso.web.webdriver.Locator
|
||||||
import androidx.test.uiautomator.UiDevice
|
import androidx.test.uiautomator.UiDevice
|
||||||
import applyWithViewHierarchyPrinting
|
import applyWithViewHierarchyPrinting
|
||||||
import com.adevinta.android.barista.interaction.BaristaSleepInteractions
|
import com.adevinta.android.barista.interaction.BaristaSleepInteractions
|
||||||
import org.hamcrest.Matchers.allOf
|
import com.adevinta.android.barista.internal.matcher.HelperMatchers.atPosition
|
||||||
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.core.R
|
import org.kiwix.kiwixmobile.core.R
|
||||||
import org.kiwix.kiwixmobile.testutils.TestUtils
|
import org.kiwix.kiwixmobile.testutils.TestUtils
|
||||||
|
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
|
||||||
|
|
||||||
fun search(func: SearchRobot.() -> Unit) = SearchRobot().applyWithViewHierarchyPrinting(func)
|
fun search(func: SearchRobot.() -> Unit) = SearchRobot().applyWithViewHierarchyPrinting(func)
|
||||||
|
|
||||||
@ -67,29 +68,24 @@ class SearchRobot : BaseRobot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun searchWithFrequentlyTypedWords(query: String, wait: Long = 0L) {
|
fun searchWithFrequentlyTypedWords(query: String, wait: Long = 0L) {
|
||||||
val searchView = onView(withId(R.id.search_src_text))
|
testFlakyView({
|
||||||
for (char in query) {
|
val searchView = onView(withId(R.id.search_src_text))
|
||||||
searchView.perform(typeText(char.toString()))
|
for (char in query) {
|
||||||
if (wait != 0L) {
|
searchView.perform(typeText(char.toString()))
|
||||||
BaristaSleepInteractions.sleep(wait)
|
if (wait != 0L) {
|
||||||
|
BaristaSleepInteractions.sleep(wait)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertSearchSuccessful(searchResult: String) {
|
fun assertSearchSuccessful(searchResult: String) {
|
||||||
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS_FOR_SEARCH_TEST.toLong())
|
BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS_FOR_SEARCH_TEST.toLong())
|
||||||
val recyclerViewId = R.id.search_list
|
val recyclerViewId = R.id.search_list
|
||||||
|
|
||||||
// Scroll to the first position in the RecyclerView
|
onView(withId(recyclerViewId)).check(
|
||||||
onView(withId(recyclerViewId)).perform(scrollToPosition<ViewHolder>(0))
|
|
||||||
|
|
||||||
// Match the view at the first position in the RecyclerView
|
|
||||||
onView(withText(searchResult)).check(
|
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
atPosition(0, hasDescendant(withText(searchResult)))
|
||||||
isDisplayed(),
|
|
||||||
isDescendantOfA(withId(recyclerViewId))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -106,4 +102,26 @@ class SearchRobot : BaseRobot() {
|
|||||||
val searchView = onView(withId(R.id.search_src_text))
|
val searchView = onView(withId(R.id.search_src_text))
|
||||||
searchView.perform(clearText())
|
searchView.perform(clearText())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun openSearchScreen() {
|
||||||
|
testFlakyView({ onView(withId(R.id.menu_search)).perform(click()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchAndClickOnArticle(searchString: String) {
|
||||||
|
openSearchScreen()
|
||||||
|
searchWithFrequentlyTypedWords(searchString)
|
||||||
|
clickOnSearchItemInSearchList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertArticleLoaded() {
|
||||||
|
testFlakyView({
|
||||||
|
onWebView()
|
||||||
|
.withElement(
|
||||||
|
findElement(
|
||||||
|
Locator.XPATH,
|
||||||
|
"//*[contains(text(), 'Big Baby DRAM')]"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,6 +285,15 @@ class SearchFragment : BaseFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun render(state: SearchState) {
|
private suspend fun render(state: SearchState) {
|
||||||
|
// Check if the fragment is visible on the screen. This method called multiple times
|
||||||
|
// (7-14 times) when an item in the search list is clicked, which leads to unnecessary
|
||||||
|
// data loading and also causes a crash.
|
||||||
|
// The issue arises because the searchViewModel takes a moment to detach from the window,
|
||||||
|
// and during this time, this method is called multiple times due to the rendering process.
|
||||||
|
// To avoid unnecessary data loading and prevent crashes, we check if the search screen is
|
||||||
|
// visible to the user before proceeding. If the screen is not visible,
|
||||||
|
// we skip the data loading process.
|
||||||
|
// if (!isVisible) return
|
||||||
searchMutex.withLock {
|
searchMutex.withLock {
|
||||||
// `cancelAndJoin` cancels the previous running job and waits for it to completely cancel.
|
// `cancelAndJoin` cancels the previous running job and waits for it to completely cancel.
|
||||||
renderingJob?.cancelAndJoin()
|
renderingJob?.cancelAndJoin()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user