From b00e33522181f4f0608969606e84c4cb08c37bd5 Mon Sep 17 00:00:00 2001 From: MohitMali Date: Mon, 16 Oct 2023 12:01:55 +0530 Subject: [PATCH 1/9] Fixed Hosted Books don't update on Application --- .../kiwixmobile/core/webserver/ZimHostFragment.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt index 8a48b889e..aa325fc86 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt @@ -94,6 +94,7 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { private lateinit var serviceConnection: ServiceConnection private var dialog: Dialog? = null private var activityZimHostBinding: ActivityZimHostBinding? = null + private var allBooks: List? = null override val fragmentTitle: String? by lazy { getString(R.string.menu_wifi_hotspot) } @@ -118,6 +119,16 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { as ArrayList } + private val selectedBooks: List + get() { + return booksAdapter.items + .filter(BooksOnDiskListItem::isSelected) + .filterIsInstance() + .map { + it + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -370,6 +381,7 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { activityZimHostBinding?.startServerButton?.setBackgroundColor( ContextCompat.getColor(requireActivity(), R.color.stopServerRed) ) + booksAdapter.items = selectedBooks bookDelegate.selectionMode = SelectionMode.NORMAL booksAdapter.notifyDataSetChanged() } @@ -396,6 +408,7 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { activityZimHostBinding?.startServerButton?.setBackgroundColor( ContextCompat.getColor(requireActivity(), R.color.startServerGreen) ) + allBooks?.let { booksAdapter.items = it } bookDelegate.selectionMode = SelectionMode.MULTI booksAdapter.notifyDataSetChanged() } @@ -484,6 +497,7 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { } } booksAdapter.items = updatedBooksList + allBooks = books } } From 59e474ed649991ee1d2a897c8338dad5f96b7058 Mon Sep 17 00:00:00 2001 From: MohitMali Date: Mon, 16 Oct 2023 12:24:00 +0530 Subject: [PATCH 2/9] Checkbox always visible to user in Wifi Hotspot and user can select and unselect zim files when server already started --- .../core/webserver/WebServerHelper.kt | 37 +++++++++++++------ .../core/webserver/ZimHostFragment.kt | 34 +++++++---------- .../webserver/wifi_hotspot/HotspotService.kt | 18 ++++++--- 3 files changed, 52 insertions(+), 37 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/WebServerHelper.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/WebServerHelper.kt index 34086f196..e399e2bea 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/WebServerHelper.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/WebServerHelper.kt @@ -45,12 +45,15 @@ class WebServerHelper @Inject constructor( private var isServerStarted = false private var validIpAddressDisposable: Disposable? = null - fun startServerHelper(selectedBooksPath: ArrayList): ServerStatus { + fun startServerHelper( + selectedBooksPath: ArrayList, + restartServer: Boolean + ): ServerStatus? { val ip = getIpAddress() return if (ip.isNullOrEmpty()) { ServerStatus(false, R.string.error_ip_address_not_found) } else { - startAndroidWebServer(selectedBooksPath) + startAndroidWebServer(selectedBooksPath, restartServer) } } @@ -61,16 +64,28 @@ class WebServerHelper @Inject constructor( } } - private fun startAndroidWebServer(selectedBooksPath: ArrayList): ServerStatus { - var errorMessage: Int? = null + private fun startAndroidWebServer( + selectedBooksPath: ArrayList, + restartServer: Boolean + ): ServerStatus? { + var serverStatus: ServerStatus? = null if (!isServerStarted) { - ServerUtils.port = DEFAULT_PORT - kiwixServer = kiwixServerFactory.createKiwixServer(selectedBooksPath).also { - updateServerState(it.startServer(ServerUtils.port)) - Log.d(TAG, "Server status$isServerStarted").also { - if (!isServerStarted) { - errorMessage = R.string.error_server_already_running - } + serverStatus = startKiwixServer(selectedBooksPath) + } else if (restartServer) { + kiwixServer?.stopServer() + serverStatus = startKiwixServer(selectedBooksPath) + } + return serverStatus + } + + private fun startKiwixServer(selectedBooksPath: ArrayList): ServerStatus { + var errorMessage: Int? = null + ServerUtils.port = DEFAULT_PORT + kiwixServer = kiwixServerFactory.createKiwixServer(selectedBooksPath).also { + updateServerState(it.startServer(ServerUtils.port)) + Log.d(TAG, "Server status$isServerStarted").also { + if (!isServerStarted) { + errorMessage = R.string.error_server_already_running } } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt index aa325fc86..58bb5b0ed 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt @@ -94,7 +94,6 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { private lateinit var serviceConnection: ServiceConnection private var dialog: Dialog? = null private var activityZimHostBinding: ActivityZimHostBinding? = null - private var allBooks: List? = null override val fragmentTitle: String? by lazy { getString(R.string.menu_wifi_hotspot) } @@ -119,16 +118,6 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { as ArrayList } - private val selectedBooks: List - get() { - return booksAdapter.items - .filter(BooksOnDiskListItem::isSelected) - .filterIsInstance() - .map { - it - } - } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -327,6 +316,9 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { } booksAdapter.items = booksList saveHostedBooks(booksList) + if (ServerUtils.isServerStarted) { + startWifiHotspot(true) + } } override fun onStart() { @@ -381,8 +373,7 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { activityZimHostBinding?.startServerButton?.setBackgroundColor( ContextCompat.getColor(requireActivity(), R.color.stopServerRed) ) - booksAdapter.items = selectedBooks - bookDelegate.selectionMode = SelectionMode.NORMAL + bookDelegate.selectionMode = SelectionMode.MULTI booksAdapter.notifyDataSetChanged() } @@ -408,7 +399,6 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { activityZimHostBinding?.startServerButton?.setBackgroundColor( ContextCompat.getColor(requireActivity(), R.color.startServerGreen) ) - allBooks?.let { booksAdapter.items = it } bookDelegate.selectionMode = SelectionMode.MULTI booksAdapter.notifyDataSetChanged() } @@ -497,17 +487,20 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { } } booksAdapter.items = updatedBooksList - allBooks = books } } + private fun startWifiHotspot(restartServer: Boolean) { + requireActivity().startService( + createHotspotIntent(ACTION_START_SERVER).putStringArrayListExtra( + SELECTED_ZIM_PATHS_KEY, selectedBooksPath + ).putExtra(RESTART_SERVER, restartServer) + ) + } + override fun onIpAddressValid() { dialog?.dismiss() - requireActivity().startService( - createHotspotIntent(ACTION_START_SERVER).putStringArrayListExtra( - SELECTED_ZIM_PATHS_KEY, selectedBooksPath - ) - ) + startWifiHotspot(false) } override fun onIpAddressInvalid() { @@ -517,6 +510,7 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { companion object { const val SELECTED_ZIM_PATHS_KEY = "selected_zim_paths" + const val RESTART_SERVER = "restart_server" const val PERMISSION_REQUEST_CODE_COARSE_LOCATION = 10 } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/wifi_hotspot/HotspotService.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/wifi_hotspot/HotspotService.kt index ac02b404f..ec27f1afc 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/wifi_hotspot/HotspotService.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/wifi_hotspot/HotspotService.kt @@ -29,6 +29,7 @@ import org.kiwix.kiwixmobile.core.utils.ServerUtils.getSocketAddress import org.kiwix.kiwixmobile.core.webserver.WebServerHelper import org.kiwix.kiwixmobile.core.webserver.ZimHostCallbacks import org.kiwix.kiwixmobile.core.webserver.ZimHostFragment +import org.kiwix.kiwixmobile.core.webserver.ZimHostFragment.Companion.RESTART_SERVER import java.lang.ref.WeakReference import javax.inject.Inject @@ -68,22 +69,27 @@ class HotspotService : super.onDestroy() } + @Suppress("NestedBlockDepth") override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { when (intent.action) { - ACTION_START_SERVER -> + ACTION_START_SERVER -> { + val restartServer = intent.getBooleanExtra(RESTART_SERVER, false) intent.getStringArrayListExtra(ZimHostFragment.SELECTED_ZIM_PATHS_KEY)?.let { - val serverStatus = webServerHelper?.startServerHelper(it) + val serverStatus = webServerHelper?.startServerHelper(it, restartServer) if (serverStatus?.isServerStarted == true) { zimHostCallbacks?.onServerStarted(getSocketAddress()) startForegroundNotificationHelper() - Toast.makeText( - this, R.string.server_started_successfully_toast_message, - Toast.LENGTH_SHORT - ).show() + if (!restartServer) { + Toast.makeText( + this, R.string.server_started_successfully_toast_message, + Toast.LENGTH_SHORT + ).show() + } } else { onServerFailedToStart(serverStatus?.errorMessage) } } ?: kotlin.run { onServerFailedToStart(R.string.no_books_selected_toast_message) } + } ACTION_STOP_SERVER -> { Toast.makeText( From d660d806a2fd7d3bf21cda3fee74f90179ff5d70 Mon Sep 17 00:00:00 2001 From: MohitMali Date: Mon, 16 Oct 2023 12:30:40 +0530 Subject: [PATCH 3/9] Added test cases for ZimHostFragment --- .../webserver/ZimHostFragmentTest.kt | 114 ++++++++++++++++++ .../kiwixmobile/webserver/ZimHostRobot.kt | 10 ++ app/src/androidTest/resources/small.zim | Bin 0 -> 4070 bytes 3 files changed, 124 insertions(+) create mode 100644 app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt create mode 100644 app/src/androidTest/resources/small.zim diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt new file mode 100644 index 000000000..b9f2a995d --- /dev/null +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt @@ -0,0 +1,114 @@ +/* + * Kiwix Android + * Copyright (c) 2023 Kiwix + * 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 . + * + */ + +package org.kiwix.kiwixmobile.webserver + +import androidx.core.content.edit +import androidx.lifecycle.Lifecycle +import androidx.preference.PreferenceManager +import androidx.test.core.app.ActivityScenario +import androidx.test.internal.runner.junit4.statement.UiThreadStatement +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import leakcanary.LeakAssertions +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.SharedPreferenceUtil +import org.kiwix.kiwixmobile.main.KiwixMainActivity +import org.kiwix.kiwixmobile.testutils.RetryRule +import org.kiwix.kiwixmobile.testutils.TestUtils +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStream + +class ZimHostFragmentTest : BaseActivityTest() { + @Rule + @JvmField + var retryRule = RetryRule() + + private lateinit var kiwixMainActivity: KiwixMainActivity + private lateinit var sharedPreferenceUtil: SharedPreferenceUtil + + @Before + override fun waitForIdle() { + UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply { + if (TestUtils.isSystemUINotRespondingDialogVisible(this)) { + TestUtils.closeSystemDialogs(context) + } + waitForIdle() + } + sharedPreferenceUtil = SharedPreferenceUtil(context) + PreferenceManager.getDefaultSharedPreferences(context).edit { + putBoolean(SharedPreferenceUtil.PREF_SHOW_INTRO, false) + putBoolean(SharedPreferenceUtil.PREF_WIFI_ONLY, false) + putBoolean(SharedPreferenceUtil.IS_PLAY_STORE_BUILD, true) + putBoolean(SharedPreferenceUtil.PREF_IS_TEST, true) + } + activityScenario = ActivityScenario.launch(KiwixMainActivity::class.java).apply { + moveToState(Lifecycle.State.RESUMED) + } + } + + @Test + fun testZimHostFragment() { + activityScenario.onActivity { + kiwixMainActivity = it + kiwixMainActivity.navigate(R.id.libraryFragment) + } + loadZimFileInApplication("testzim.zim") + loadZimFileInApplication("small.zim") + zimHost { + refreshLibraryList() + assertZimFilesLoaded() + } + UiThreadStatement.runOnUiThread { + kiwixMainActivity.navigate(R.id.zimHostFragment) + } + LeakAssertions.assertNoLeaks() + } + + private fun loadZimFileInApplication(zimFileName: String) { + val loadFileStream = + ZimHostFragmentTest::class.java.classLoader.getResourceAsStream(zimFileName) + val zimFile = File(sharedPreferenceUtil.prefStorage, zimFileName) + 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) + } + } + } + } + + @After + fun setIsTestPreference() { + PreferenceManager.getDefaultSharedPreferences(context).edit { + putBoolean(SharedPreferenceUtil.PREF_IS_TEST, false) + putBoolean(SharedPreferenceUtil.IS_PLAY_STORE_BUILD, false) + } + } +} diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt index 26db0ca50..f3d672f80 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt @@ -19,7 +19,9 @@ package org.kiwix.kiwixmobile.webserver import applyWithViewHierarchyPrinting +import com.adevinta.android.barista.interaction.BaristaSwipeRefreshInteractions.refresh import org.kiwix.kiwixmobile.BaseRobot +import org.kiwix.kiwixmobile.Findable.Text import org.kiwix.kiwixmobile.Findable.StringId.TextId import org.kiwix.kiwixmobile.R @@ -30,4 +32,12 @@ class ZimHostRobot : BaseRobot() { fun assertMenuWifiHotspotDiplayed() { isVisible(TextId(R.string.menu_wifi_hotspot)) } + + fun refreshLibraryList() { + refresh(R.id.zim_swiperefresh) + } + + fun assertZimFilesLoaded() { + isVisible(Text("Test_Zim")) + } } diff --git a/app/src/androidTest/resources/small.zim b/app/src/androidTest/resources/small.zim new file mode 100644 index 0000000000000000000000000000000000000000..cb7072da6ec68a73f35bfe73c81c745b087ae8d5 GIT binary patch literal 4070 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 AGynhq literal 0 HcmV?d00001 From 3432b32431e8ca393c39c6becb2f599c00199a50 Mon Sep 17 00:00:00 2001 From: MohitMali Date: Mon, 16 Oct 2023 12:39:50 +0530 Subject: [PATCH 4/9] Improved ZimHostFragmentTest to properly test the server functionality. * Created a new class to match the how many checkbox is checked in the recyclerview, it will also help to test this type of functionality in future. --- ...yclerViewSelectedCheckBoxCountAssertion.kt | 75 ++++++++++++ .../webserver/ZimHostFragmentTest.kt | 112 +++++++++++++----- .../kiwixmobile/webserver/ZimHostRobot.kt | 54 ++++++++- .../core/utils/SharedPreferenceUtil.kt | 5 +- 4 files changed, 215 insertions(+), 31 deletions(-) create mode 100644 app/src/androidTest/java/org/kiwix/kiwixmobile/utils/RecyclerViewSelectedCheckBoxCountAssertion.kt diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/utils/RecyclerViewSelectedCheckBoxCountAssertion.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/utils/RecyclerViewSelectedCheckBoxCountAssertion.kt new file mode 100644 index 000000000..631c04fdd --- /dev/null +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/utils/RecyclerViewSelectedCheckBoxCountAssertion.kt @@ -0,0 +1,75 @@ +/* + * Kiwix Android + * Copyright (c) 2023 Kiwix + * 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 . + * + */ + +package org.kiwix.kiwixmobile.utils + +import android.view.View +import android.widget.CheckBox +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.TypeSafeMatcher +import org.hamcrest.core.AllOf.allOf + +class RecyclerViewSelectedCheckBoxCountAssertion( + private val recyclerViewId: Int, + private val checkBoxId: Int +) { + fun countCheckedCheckboxes(): Int { + var checkedCount = 0 + + // Find the RecyclerView + val recyclerViewMatcher: Matcher = allOf( + isAssignableFrom(RecyclerView::class.java), + isDisplayed(), + withId(recyclerViewId) + ) + + // Use a custom ViewMatcher to find checkboxes that are checked + val checkBoxMatcher: Matcher = object : TypeSafeMatcher() { + override fun matchesSafely(view: View): Boolean = + view is CheckBox && view.isChecked + + override fun describeTo(description: Description) { + description.appendText("is checked") + } + } + + // Count the checked checkboxes + onView(recyclerViewMatcher).check { view, noViewFoundException -> + if (noViewFoundException != null) { + throw noViewFoundException + } + val recyclerView = view as RecyclerView + (0 until recyclerView.childCount) + .asSequence() + .map { + // Check the checkbox directly without using inRoot + recyclerView.getChildAt(it).findViewById(checkBoxId) + } + .filter { it != null && checkBoxMatcher.matches(it) } + .forEach { _ -> checkedCount++ } + } + + return checkedCount + } +} diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt index b9f2a995d..251be3edc 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt @@ -18,19 +18,19 @@ package org.kiwix.kiwixmobile.webserver -import androidx.core.content.edit +import android.Manifest +import android.content.Context +import android.os.Build import androidx.lifecycle.Lifecycle -import androidx.preference.PreferenceManager import androidx.test.core.app.ActivityScenario -import androidx.test.internal.runner.junit4.statement.UiThreadStatement import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.rule.GrantPermissionRule import androidx.test.uiautomator.UiDevice import leakcanary.LeakAssertions 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.SharedPreferenceUtil import org.kiwix.kiwixmobile.main.KiwixMainActivity @@ -40,28 +40,52 @@ import java.io.File import java.io.FileOutputStream import java.io.OutputStream -class ZimHostFragmentTest : BaseActivityTest() { +class ZimHostFragmentTest { @Rule @JvmField var retryRule = RetryRule() - private lateinit var kiwixMainActivity: KiwixMainActivity private lateinit var sharedPreferenceUtil: SharedPreferenceUtil + private lateinit var activityScenario: ActivityScenario + + private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { + arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.SYSTEM_ALERT_WINDOW, + Manifest.permission.NEARBY_WIFI_DEVICES + ) + } else { + arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + } + + @Rule + @JvmField + var permissionRules: GrantPermissionRule = + GrantPermissionRule.grant(*permissions) + private var context: Context? = null + @Before - override fun waitForIdle() { + fun waitForIdle() { + context = InstrumentationRegistry.getInstrumentation().targetContext UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply { if (TestUtils.isSystemUINotRespondingDialogVisible(this)) { TestUtils.closeSystemDialogs(context) } waitForIdle() } - sharedPreferenceUtil = SharedPreferenceUtil(context) - PreferenceManager.getDefaultSharedPreferences(context).edit { - putBoolean(SharedPreferenceUtil.PREF_SHOW_INTRO, false) - putBoolean(SharedPreferenceUtil.PREF_WIFI_ONLY, false) - putBoolean(SharedPreferenceUtil.IS_PLAY_STORE_BUILD, true) - putBoolean(SharedPreferenceUtil.PREF_IS_TEST, true) + context?.let { + sharedPreferenceUtil = SharedPreferenceUtil(it).apply { + setIntroShown() + putPrefWifiOnly(false) + setIsPlayStoreBuildType(true) + prefIsTest = true + } } activityScenario = ActivityScenario.launch(KiwixMainActivity::class.java).apply { moveToState(Lifecycle.State.RESUMED) @@ -70,20 +94,50 @@ class ZimHostFragmentTest : BaseActivityTest() { @Test fun testZimHostFragment() { - activityScenario.onActivity { - kiwixMainActivity = it - kiwixMainActivity.navigate(R.id.libraryFragment) + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { + activityScenario.onActivity { + it.navigate(R.id.libraryFragment) + } + loadZimFileInApplication("testzim.zim") + loadZimFileInApplication("small.zim") + zimHost { + refreshLibraryList() + assertZimFilesLoaded() + openZimHostFragment() + clickOnTestZim() + + // Start the server with one ZIM file + startServer() + assertServerStarted() + + // Check that only one ZIM file is hosted on the server + assertItemHostedOnServer(1) + + // Stop the server + stopServer() + assertServerStopped() + + // Select the test ZIM file to host on the server + clickOnTestZim() + + // Start the server with two ZIM files + startServer() + assertServerStarted() + + // Check that both ZIM files are hosted on the server + assertItemHostedOnServer(2) + + // Unselect the test ZIM to test restarting server functionality + clickOnTestZim() + + // Check if the server is running + assertServerStarted() + + // Check that only one ZIM file is hosted on the server after unselecting + assertItemHostedOnServer(1) + } + LeakAssertions.assertNoLeaks() } - loadZimFileInApplication("testzim.zim") - loadZimFileInApplication("small.zim") - zimHost { - refreshLibraryList() - assertZimFilesLoaded() - } - UiThreadStatement.runOnUiThread { - kiwixMainActivity.navigate(R.id.zimHostFragment) - } - LeakAssertions.assertNoLeaks() } private fun loadZimFileInApplication(zimFileName: String) { @@ -106,9 +160,9 @@ class ZimHostFragmentTest : BaseActivityTest() { @After fun setIsTestPreference() { - PreferenceManager.getDefaultSharedPreferences(context).edit { - putBoolean(SharedPreferenceUtil.PREF_IS_TEST, false) - putBoolean(SharedPreferenceUtil.IS_PLAY_STORE_BUILD, false) + sharedPreferenceUtil.apply { + setIsPlayStoreBuildType(false) + prefIsTest = false } } } diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt index f3d672f80..47d9ff2f5 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt @@ -18,12 +18,19 @@ package org.kiwix.kiwixmobile.webserver +import androidx.test.espresso.matcher.ViewMatchers.assertThat import applyWithViewHierarchyPrinting +import com.adevinta.android.barista.interaction.BaristaSleepInteractions import com.adevinta.android.barista.interaction.BaristaSwipeRefreshInteractions.refresh +import org.hamcrest.CoreMatchers import org.kiwix.kiwixmobile.BaseRobot -import org.kiwix.kiwixmobile.Findable.Text import org.kiwix.kiwixmobile.Findable.StringId.TextId +import org.kiwix.kiwixmobile.Findable.Text +import org.kiwix.kiwixmobile.Findable.ViewId import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.testutils.TestUtils +import org.kiwix.kiwixmobile.utils.RecyclerViewSelectedCheckBoxCountAssertion +import org.kiwix.kiwixmobile.utils.StandardActions.openDrawer fun zimHost(func: ZimHostRobot.() -> Unit) = ZimHostRobot().applyWithViewHierarchyPrinting(func) @@ -34,10 +41,55 @@ class ZimHostRobot : BaseRobot() { } fun refreshLibraryList() { + pauseForBetterTestPerformance() refresh(R.id.zim_swiperefresh) } fun assertZimFilesLoaded() { + pauseForBetterTestPerformance() isVisible(Text("Test_Zim")) } + + fun openZimHostFragment() { + openDrawer() + clickOn(TextId(R.string.menu_wifi_hotspot)) + } + + fun clickOnTestZim() { + clickOn(Text("Test_Zim")) + } + + fun startServer() { + clickOn(ViewId(R.id.startServerButton)) + pauseForBetterTestPerformance() + isVisible(TextId(R.string.wifi_dialog_title)) + clickOn(TextId(R.string.hotspot_dialog_neutral_button)) + } + + fun assertServerStarted() { + pauseForBetterTestPerformance() + isVisible(Text("STOP SERVER")) + } + + fun assertItemHostedOnServer(itemCount: Int) { + val checkedCheckboxCount = + RecyclerViewSelectedCheckBoxCountAssertion( + R.id.recyclerViewZimHost, + R.id.itemBookCheckbox + ).countCheckedCheckboxes() + assertThat(checkedCheckboxCount, CoreMatchers.`is`(itemCount)) + } + + fun stopServer() { + clickOn(ViewId(R.id.startServerButton)) + } + + fun assertServerStopped() { + pauseForBetterTestPerformance() + isVisible(Text("START SERVER")) + } + + private fun pauseForBetterTestPerformance() { + BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS_FOR_SEARCH_TEST.toLong()) + } } diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt index 06d239224..d2025385c 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt @@ -60,8 +60,11 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) { val prefIsFirstRun: Boolean get() = sharedPreferences.getBoolean(PREF_IS_FIRST_RUN, true) - val prefIsTest: Boolean + var prefIsTest: Boolean get() = sharedPreferences.getBoolean(PREF_IS_TEST, false) + set(prefIsTest) { + sharedPreferences.edit { putBoolean(PREF_IS_TEST, prefIsTest) } + } val prefShowShowCaseToUser: Boolean get() = sharedPreferences.getBoolean(PREF_SHOW_SHOWCASE, true) From c3049a6a597919368f5364e5983c84e3dae1fbd1 Mon Sep 17 00:00:00 2001 From: MohitMali Date: Mon, 16 Oct 2023 12:48:56 +0530 Subject: [PATCH 5/9] Fixed memory leak on API level 24. * When the application goes in the background from `ZimHostFragment`, `KiwixReaderFragment/ZimHostFragment` without starting the Service then the `ReadAloudService`, and `HotspotService` variables are not used and GC try to clear those objects but we are not clearing those objects, that's why memory leak is happening. So now we have free those objects if they are not in use. --- .../core/main/CoreReaderFragment.kt | 18 +++++++++++++--- .../core/webserver/ZimHostFragment.kt | 21 ++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt index bb4d7774e..a579544d0 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt @@ -333,6 +333,7 @@ abstract class CoreReaderFragment : private var readAloudService: ReadAloudService? = null private var navigationHistoryList: MutableList = ArrayList() private var isReadSelection = false + private var isReadAloudServiceRunning = false private var storagePermissionForNotesLauncher: ActivityResultLauncher? = registerForActivityResult( @@ -1047,8 +1048,7 @@ abstract class CoreReaderFragment : } catch (ignore: IllegalArgumentException) { // to handle if service is already unbounded } - readAloudService?.registerCallBack(null) - readAloudService = null + unRegisterReadAloudService() storagePermissionForNotesLauncher?.unregister() storagePermissionForNotesLauncher = null } @@ -2074,9 +2074,17 @@ abstract class CoreReaderFragment : private fun unbindService() { readAloudService?.let { requireActivity().unbindService(serviceConnection) + if (!isReadAloudServiceRunning) { + unRegisterReadAloudService() + } } } + private fun unRegisterReadAloudService() { + readAloudService?.registerCallBack(null) + readAloudService = null + } + private fun createReadAloudIntent(action: String, isPauseTTS: Boolean): Intent = Intent(requireActivity(), ReadAloudService::class.java).apply { setAction(action) @@ -2086,7 +2094,11 @@ abstract class CoreReaderFragment : } private fun setActionAndStartTTSService(action: String, isPauseTTS: Boolean = false) { - requireActivity().startService(createReadAloudIntent(action, isPauseTTS)) + requireActivity().startService( + createReadAloudIntent(action, isPauseTTS) + ).also { + isReadAloudServiceRunning = action == ACTION_PAUSE_OR_RESUME_TTS + } } protected abstract fun restoreViewStateOnValidJSON( diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt index 58bb5b0ed..d9f0052cc 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/webserver/ZimHostFragment.kt @@ -94,6 +94,7 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { private lateinit var serviceConnection: ServiceConnection private var dialog: Dialog? = null private var activityZimHostBinding: ActivityZimHostBinding? = null + private var isHotspotServiceRunning = false override val fragmentTitle: String? by lazy { getString(R.string.menu_wifi_hotspot) } @@ -304,7 +305,11 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { } private fun stopServer() { - requireActivity().startService(createHotspotIntent(ACTION_STOP_SERVER)) + requireActivity().startService( + createHotspotIntent(ACTION_STOP_SERVER) + ).also { + isHotspotServiceRunning = false + } } private fun select(bookOnDisk: BooksOnDiskListItem.BookOnDisk) { @@ -341,6 +346,9 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { private fun unbindService() { hotspotService?.let { requireActivity().unbindService(serviceConnection) + if (!isHotspotServiceRunning) { + unRegisterHotspotService() + } } } @@ -406,11 +414,16 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { override fun onDestroyView() { super.onDestroyView() activityZimHostBinding?.recyclerViewZimHost?.adapter = null - hotspotService?.registerCallBack(null) + unRegisterHotspotService() presenter.detachView() activityZimHostBinding = null } + private fun unRegisterHotspotService() { + hotspotService?.registerCallBack(null) + hotspotService = null + } + // Advice user to turn on hotspot manually for API<26 private fun startHotspotManuallyDialog() { @@ -495,7 +508,9 @@ class ZimHostFragment : BaseFragment(), ZimHostCallbacks, ZimHostContract.View { createHotspotIntent(ACTION_START_SERVER).putStringArrayListExtra( SELECTED_ZIM_PATHS_KEY, selectedBooksPath ).putExtra(RESTART_SERVER, restartServer) - ) + ).also { + isHotspotServiceRunning = true + } } override fun onIpAddressValid() { From 8a4056592e4328e1a3100d8b42ac27bd0ef16a9d Mon Sep 17 00:00:00 2001 From: MohitMali Date: Mon, 16 Oct 2023 12:52:48 +0530 Subject: [PATCH 6/9] Improved ZimHostFragmentTest. * We have made improvements to `ZimHostFragmentTest`. We added the `ACCESS_FINE_LOCATION` permission, which is required for running this test case on real devices. Additionally, we have enhanced the permission array and removed unnecessary permissions from the test case. --- .../org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt index 251be3edc..af9f008e0 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt @@ -49,7 +49,7 @@ class ZimHostFragmentTest { private lateinit var activityScenario: ActivityScenario - private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { + private val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, @@ -60,7 +60,8 @@ class ZimHostFragmentTest { arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.ACCESS_COARSE_LOCATION + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION ) } From 96b1b195d5c165f4a2f00063752fd4efc08cbd6a Mon Sep 17 00:00:00 2001 From: MohitMali Date: Thu, 26 Oct 2023 17:30:40 +0530 Subject: [PATCH 7/9] Fixed `ZimHostFragment` failing on API level 33 (Android 13) because it has the `SYSTEM_ALERT_WINDOW` permission which is not grantable through code on Android 13, so we have removed this permission from our `ZimHostFragment` test as well as from our other test cases since we had added this to fix the test cases because without this permission test cases were not launching on API level 21, and now our minimum SDK version is 24 so this permission is no longer needed. --- .../org/kiwix/kiwixmobile/BaseActivityTest.kt | 17 ++++------------- .../java/org/kiwix/kiwixmobile/NetworkTest.kt | 19 +++++-------------- .../language/LanguageFragmentTest.kt | 17 ++++------------- .../settings/KiwixSettingsFragmentTest.kt | 17 ++++------------- .../splash/KiwixSplashActivityTest.kt | 17 ++++------------- .../webserver/ZimHostFragmentTest.kt | 1 - 6 files changed, 21 insertions(+), 67 deletions(-) diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/BaseActivityTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/BaseActivityTest.kt index 010bddc0e..c2f2b67e2 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/BaseActivityTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/BaseActivityTest.kt @@ -20,7 +20,6 @@ package org.kiwix.kiwixmobile import android.Manifest.permission import android.content.Context -import android.os.Build import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation @@ -35,18 +34,10 @@ import org.kiwix.kiwixmobile.main.KiwixMainActivity abstract class BaseActivityTest { open lateinit var activityScenario: ActivityScenario - private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { - arrayOf( - permission.READ_EXTERNAL_STORAGE, - permission.WRITE_EXTERNAL_STORAGE, - permission.SYSTEM_ALERT_WINDOW - ) - } else { - arrayOf( - permission.READ_EXTERNAL_STORAGE, - permission.WRITE_EXTERNAL_STORAGE - ) - } + private val permissions = arrayOf( + permission.READ_EXTERNAL_STORAGE, + permission.WRITE_EXTERNAL_STORAGE + ) @get:Rule var permissionRules: GrantPermissionRule = diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/NetworkTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/NetworkTest.kt index 908e9e9e1..4be412547 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/NetworkTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/NetworkTest.kt @@ -18,16 +18,15 @@ package org.kiwix.kiwixmobile import android.Manifest -import android.os.Build import android.util.Log import androidx.test.core.app.ActivityScenario -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.espresso.Espresso import androidx.test.espresso.IdlingPolicies import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.action.ViewActions import androidx.test.espresso.matcher.ViewMatchers import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn import com.adevinta.android.barista.interaction.BaristaDialogInteractions @@ -58,18 +57,10 @@ class NetworkTest { // @Inject // MockWebServer mockWebServer - private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { - arrayOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.SYSTEM_ALERT_WINDOW - ) - } else { - arrayOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - } + private val permissions = arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) @Rule @JvmField diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageFragmentTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageFragmentTest.kt index d0e1a99f5..e49692736 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageFragmentTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/language/LanguageFragmentTest.kt @@ -19,7 +19,6 @@ package org.kiwix.kiwixmobile.language import android.Manifest import android.app.Instrumentation -import android.os.Build import androidx.core.content.edit import androidx.preference.PreferenceManager import androidx.test.ext.junit.rules.ActivityScenarioRule @@ -50,18 +49,10 @@ class LanguageFragmentTest { @get:Rule var activityScenarioRule = ActivityScenarioRule(KiwixMainActivity::class.java) - private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { - arrayOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.SYSTEM_ALERT_WINDOW - ) - } else { - arrayOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - } + private val permissions = arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) @Rule @JvmField diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/settings/KiwixSettingsFragmentTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/settings/KiwixSettingsFragmentTest.kt index 98089dcea..0fa0e8144 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/settings/KiwixSettingsFragmentTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/settings/KiwixSettingsFragmentTest.kt @@ -18,7 +18,6 @@ package org.kiwix.kiwixmobile.settings import android.Manifest -import android.os.Build import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.internal.runner.junit4.statement.UiThreadStatement import androidx.test.platform.app.InstrumentationRegistry @@ -46,18 +45,10 @@ class KiwixSettingsFragmentTest { @get:Rule var activityScenarioRule = ActivityScenarioRule(KiwixMainActivity::class.java) - private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { - arrayOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.SYSTEM_ALERT_WINDOW - ) - } else { - arrayOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - } + private val permissions = arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) @Rule @JvmField diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/splash/KiwixSplashActivityTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/splash/KiwixSplashActivityTest.kt index b51cfa5cd..e2c0bca15 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/splash/KiwixSplashActivityTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/splash/KiwixSplashActivityTest.kt @@ -19,7 +19,6 @@ package org.kiwix.kiwixmobile.splash import android.Manifest import android.content.Context -import android.os.Build import androidx.preference.PreferenceManager import androidx.test.core.app.ActivityScenario import androidx.test.espresso.Espresso @@ -59,18 +58,10 @@ class KiwixSplashActivityTest { private val activityScenario: ActivityScenario = ActivityScenario.launch(KiwixMainActivity::class.java) - private val permissions = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { - arrayOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.SYSTEM_ALERT_WINDOW - ) - } else { - arrayOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - } + private val permissions = arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) @Rule @JvmField diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt index af9f008e0..c12560202 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt @@ -53,7 +53,6 @@ class ZimHostFragmentTest { arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.SYSTEM_ALERT_WINDOW, Manifest.permission.NEARBY_WIFI_DEVICES ) } else { From a7b5a7730f545d1f82e9b0aaa79b19a7136c42a2 Mon Sep 17 00:00:00 2001 From: MohitMali Date: Fri, 27 Oct 2023 15:45:00 +0530 Subject: [PATCH 8/9] Fixed test failure on API level 33. * In the `ZimHostFragment`, there were occasional test failures due to specific conditions. When reattempting the test, it failed to detect the 'WiFi connection detected' dialog because the server was already running. To resolve this issue, we have improved our test case. Now, we first check if the server is already running. If it is, we close the server before running the test case. * In previous test failures within the `ZimHostFragment`, there were instances where the zim file was unselected, causing our test case to fail to locate the required views. To mitigate this, we now check whether the zim file is selected. If it's not selected, we first select the zim file before running the test case. * n the `LocalLibraryFragment` test, there were cases where it was unable to locate the 'file_management_no_files' view due to variations in the order of test cases. This occurred because a zim file was sometimes present in the `LocalLibrary`. To address this, we now check for the presence of any zim files in the `LocalLibrary` and delete them before running our test case. --- .../nav/destination/library/LibraryRobot.kt | 26 ++++++--- .../utils/RecyclerViewItemCount.kt | 41 ++++++++++++++ .../webserver/ZimHostFragmentTest.kt | 7 +++ .../kiwixmobile/webserver/ZimHostRobot.kt | 56 +++++++++++++++++++ 4 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 app/src/androidTest/java/org/kiwix/kiwixmobile/utils/RecyclerViewItemCount.kt diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt index c1691b8ee..1a4667816 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/nav/destination/library/LibraryRobot.kt @@ -19,20 +19,25 @@ package org.kiwix.kiwixmobile.nav.destination.library import android.util.Log +import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.longClick import androidx.test.espresso.assertion.ViewAssertions -import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition +import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition +import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText import applyWithViewHierarchyPrinting import com.adevinta.android.barista.interaction.BaristaSleepInteractions import org.kiwix.kiwixmobile.BaseRobot -import org.kiwix.kiwixmobile.Findable.Text import org.kiwix.kiwixmobile.Findable.ViewId import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferRobot import org.kiwix.kiwixmobile.localFileTransfer.localFileTransfer import org.kiwix.kiwixmobile.testutils.TestUtils +import org.kiwix.kiwixmobile.utils.RecyclerViewItemCount fun library(func: LibraryRobot.() -> Unit) = LibraryRobot().applyWithViewHierarchyPrinting(func) @@ -60,7 +65,17 @@ class LibraryRobot : BaseRobot() { fun deleteZimIfExists() { try { - longClickOnZimFile() + val recyclerViewId: Int = R.id.zimfilelist + val recyclerViewItemsCount = RecyclerViewItemCount(recyclerViewId).checkRecyclerViewCount() + // Scroll to the end of the RecyclerView to ensure all items are visible + onView(withId(recyclerViewId)) + .perform(scrollToPosition(recyclerViewItemsCount - 1)) + + for (position in 0 until recyclerViewItemsCount) { + // Long-click the item to select it + onView(withId(recyclerViewId)) + .perform(actionOnItemAtPosition(position, longClick())) + } clickOnFileDeleteIcon() assertDeleteDialogDisplayed() clickOnDeleteZimFile() @@ -75,6 +90,7 @@ class LibraryRobot : BaseRobot() { } private fun clickOnFileDeleteIcon() { + pauseForBetterTestPerformance() clickOn(ViewId(R.id.zim_file_delete_item)) } @@ -84,10 +100,6 @@ class LibraryRobot : BaseRobot() { .check(ViewAssertions.matches(isDisplayed())) } - private fun longClickOnZimFile() { - longClickOn(Text(zimFileTitle)) - } - private fun clickOnDeleteZimFile() { pauseForBetterTestPerformance() onView(withText("DELETE")).perform(click()) diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/utils/RecyclerViewItemCount.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/utils/RecyclerViewItemCount.kt new file mode 100644 index 000000000..8e7cddf30 --- /dev/null +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/utils/RecyclerViewItemCount.kt @@ -0,0 +1,41 @@ +/* + * Kiwix Android + * Copyright (c) 2023 Kiwix + * 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 . + * + */ + +package org.kiwix.kiwixmobile.utils + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.NoMatchingViewException +import androidx.test.espresso.matcher.ViewMatchers.withId + +class RecyclerViewItemCount(private val recyclerViewId: Int) { + fun checkRecyclerViewCount(): Int { + var recyclerViewItemCount = 0 + onView(withId(recyclerViewId)) + .check { view: View, noViewFoundException: NoMatchingViewException? -> + if (noViewFoundException != null) { + throw noViewFoundException + } + val recyclerView = view as RecyclerView + // Get the item count from the RecyclerView + recyclerViewItemCount = recyclerView.adapter?.itemCount ?: 0 + } + return recyclerViewItemCount + } +} diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt index c12560202..a359a4431 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostFragmentTest.kt @@ -104,6 +104,13 @@ class ZimHostFragmentTest { refreshLibraryList() assertZimFilesLoaded() openZimHostFragment() + + // Check if server is already started + stopServerIfAlreadyStarted() + + // Check if both zim file are selected or not to properly run our test case + selectZimFileIfNotAlreadySelected() + clickOnTestZim() // Start the server with one ZIM file diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt index 47d9ff2f5..700003d25 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt @@ -18,10 +18,16 @@ package org.kiwix.kiwixmobile.webserver +import android.util.Log +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.assertThat import applyWithViewHierarchyPrinting import com.adevinta.android.barista.interaction.BaristaSleepInteractions import com.adevinta.android.barista.interaction.BaristaSwipeRefreshInteractions.refresh +import junit.framework.AssertionFailedError import org.hamcrest.CoreMatchers import org.kiwix.kiwixmobile.BaseRobot import org.kiwix.kiwixmobile.Findable.StringId.TextId @@ -29,6 +35,8 @@ import org.kiwix.kiwixmobile.Findable.Text import org.kiwix.kiwixmobile.Findable.ViewId import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.testutils.TestUtils +import org.kiwix.kiwixmobile.utils.RecyclerViewItemCount +import org.kiwix.kiwixmobile.utils.RecyclerViewMatcher import org.kiwix.kiwixmobile.utils.RecyclerViewSelectedCheckBoxCountAssertion import org.kiwix.kiwixmobile.utils.StandardActions.openDrawer @@ -71,6 +79,54 @@ class ZimHostRobot : BaseRobot() { isVisible(Text("STOP SERVER")) } + fun stopServerIfAlreadyStarted() { + try { + assertServerStarted() + stopServer() + } catch (exception: Exception) { + Log.i( + "ZIM_HOST_FRAGMENT", + "Failed to stop the server, Probably because server is not running" + ) + } + } + + fun selectZimFileIfNotAlreadySelected() { + try { + // check both files are selected. + assertItemHostedOnServer(2) + } catch (assertionFailedError: AssertionFailedError) { + try { + val recyclerViewItemsCount = + RecyclerViewItemCount(R.id.recyclerViewZimHost).checkRecyclerViewCount() + (0 until recyclerViewItemsCount) + .asSequence() + .filter { it != 0 } + .forEach(::selectZimFile) + } catch (assertionFailedError: AssertionFailedError) { + Log.i("ZIM_HOST_FRAGMENT", "Failed to select the zim file, probably it is already selected") + } + } + } + + private fun selectZimFile(position: Int) { + try { + onView( + RecyclerViewMatcher(R.id.recyclerViewZimHost).atPositionOnView( + position, + R.id.itemBookCheckbox + ) + ).check(matches(ViewMatchers.isChecked())) + } catch (assertionError: AssertionFailedError) { + onView( + RecyclerViewMatcher(R.id.recyclerViewZimHost).atPositionOnView( + position, + R.id.itemBookCheckbox + ) + ).perform(click()) + } + } + fun assertItemHostedOnServer(itemCount: Int) { val checkedCheckboxCount = RecyclerViewSelectedCheckBoxCountAssertion( From 969ad0d4b1bb8a16cea1e6d33c4a57e1674a08c6 Mon Sep 17 00:00:00 2001 From: MohitMali Date: Fri, 27 Oct 2023 18:06:47 +0530 Subject: [PATCH 9/9] Increase timeout to check views are displayed or not. --- .../java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt index 700003d25..19cc01724 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/webserver/ZimHostRobot.kt @@ -110,6 +110,7 @@ class ZimHostRobot : BaseRobot() { } private fun selectZimFile(position: Int) { + pauseForBetterTestPerformance() try { onView( RecyclerViewMatcher(R.id.recyclerViewZimHost).atPositionOnView( @@ -118,6 +119,7 @@ class ZimHostRobot : BaseRobot() { ) ).check(matches(ViewMatchers.isChecked())) } catch (assertionError: AssertionFailedError) { + pauseForBetterTestPerformance() onView( RecyclerViewMatcher(R.id.recyclerViewZimHost).atPositionOnView( position, @@ -146,6 +148,6 @@ class ZimHostRobot : BaseRobot() { } private fun pauseForBetterTestPerformance() { - BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS_FOR_SEARCH_TEST.toLong()) + BaristaSleepInteractions.sleep(TestUtils.TEST_PAUSE_MS.toLong()) } }