mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
tests: add UI tests for onboarding activity
This commit is contained in:
+136
@@ -3,6 +3,19 @@
|
||||
<component name="AndroidTestResultsUserPreferences">
|
||||
<option name="androidTestResultsTableState">
|
||||
<map>
|
||||
<entry key="-1176045050">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Realme RMX1851" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="-985524882">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
@@ -17,6 +30,33 @@
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="-568780982">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Pixel_4_XL_API_32" value="120" />
|
||||
<entry key="Realme RMX1851" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="-560165011">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Pixel_4_XL_API_32" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="-528694390">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
@@ -57,6 +97,60 @@
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="123996994">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Pixel_4_XL_API_32" value="120" />
|
||||
<entry key="Realme RMX1851" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="506209133">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Pixel_4_XL_API_32" value="120" />
|
||||
<entry key="Realme RMX1851" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="596961865">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Pixel_4_XL_API_32" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="984985251">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Realme RMX1851" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="1123912474">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
@@ -70,6 +164,35 @@
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="1418426983">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Pixel_4_XL_API_32" value="120" />
|
||||
<entry key="Realme RMX1851" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="1588084549">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Pixel_4_XL_API_32" value="120" />
|
||||
<entry key="Realme RMX1851" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
<entry key="realme RMX3312" value="120" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="1679989688">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
@@ -96,6 +219,19 @@
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="2089736396">
|
||||
<value>
|
||||
<AndroidTestResultsTableState>
|
||||
<option name="preferredColumnWidths">
|
||||
<map>
|
||||
<entry key="Duration" value="90" />
|
||||
<entry key="Pixel_4_XL_API_32" value="120" />
|
||||
<entry key="Tests" value="360" />
|
||||
</map>
|
||||
</option>
|
||||
</AndroidTestResultsTableState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide
|
||||
|
||||
import android.util.Log
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.R.string
|
||||
import com.itsaky.androidide.app.IDEApplication
|
||||
import com.itsaky.androidide.shell.executeProcessAsync
|
||||
import com.itsaky.androidide.testing.android.clickAndWaitForWindowUpdate
|
||||
import com.itsaky.androidide.testing.android.findObjectWithText
|
||||
import com.itsaky.androidide.testing.android.hasObjectWithText
|
||||
import com.itsaky.androidide.testing.android.launchAndroidIDE
|
||||
import com.itsaky.androidide.utils.Environment
|
||||
import com.termux.shared.termux.shell.command.environment.TermuxShellEnvironment
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* A special test case which is used to install the build tools on a test device. This is used to
|
||||
* setup the test device before running further tests.
|
||||
*
|
||||
* This class should not define any other tests.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class BuildToolsInstallerTest {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "BuildToolsInstallerTest"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun setupDevice() {
|
||||
if (!System.getenv("IDE_INSTRUMENTATION_SETUP_BUILDTOOLS").toBoolean()) {
|
||||
return
|
||||
}
|
||||
|
||||
val device = launchAndroidIDE()
|
||||
|
||||
// Allow permissions
|
||||
while (true) {
|
||||
val obj = device.findObjectWithText("Allow")
|
||||
obj?.clickAndWaitForWindowUpdate(device) ?: break
|
||||
}
|
||||
|
||||
// Dismiss the 'AndroidIDE statistics' dialog
|
||||
if (device.hasObjectWithText(string.title_androidide_statistics)
|
||||
&& device.hasObjectWithText(string.msg_androidide_statistics)
|
||||
) {
|
||||
device.findObjectWithText(string.btn_opt_in).clickAndWaitForWindowUpdate(device)
|
||||
}
|
||||
|
||||
// The 'Warning' dialog will be shown, no need to dismiss it.
|
||||
|
||||
// Start the installation process
|
||||
execProcAndLogOutput("pkg upd")
|
||||
execProcAndLogOutput("yes | pkg upg") // do not use -y
|
||||
execProcAndLogOutput("yes | idesetup -c")
|
||||
|
||||
// Check if the tools were installed
|
||||
assertThat(Environment.JAVA.exists()).isTrue()
|
||||
assertThat(Environment.ANDROID_HOME.exists()).isTrue()
|
||||
|
||||
Thread.sleep(5000L)
|
||||
}
|
||||
|
||||
private fun execProcAndLogOutput(cmd: String) {
|
||||
val process = executeProcessAsync {
|
||||
redirectErrorStream = true
|
||||
workingDirectory = null
|
||||
environment = TermuxShellEnvironment().getEnvironment(ApplicationProvider.getApplicationContext<IDEApplication>(), false)
|
||||
command = listOf(Environment.SHELL.absolutePath, "-c", cmd).also {
|
||||
Log.d(TAG, "setupDevice: Full command : ${it.joinToString(separator = " ")}")
|
||||
}
|
||||
}
|
||||
|
||||
logOutput(cmd, process)
|
||||
|
||||
process.waitFor()
|
||||
}
|
||||
|
||||
private fun logOutput(cmd: String, process: Process) {
|
||||
process.inputStream.bufferedReader().forEachLine {
|
||||
Log.d(TAG, "setupDevice: '${cmd}': $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.activities
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.os.Process
|
||||
import android.widget.Button
|
||||
import android.widget.GridLayout
|
||||
import android.widget.Switch
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.os.UserHandleCompat
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.Until
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.adevinta.android.barista.rule.cleardata.ClearDatabaseRule
|
||||
import com.adevinta.android.barista.rule.cleardata.ClearFilesRule
|
||||
import com.adevinta.android.barista.rule.cleardata.ClearPreferencesRule
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.buildinfo.BuildInfo
|
||||
import com.itsaky.androidide.testing.android.clickAndWaitForNewWindow
|
||||
import com.itsaky.androidide.testing.android.clickAndWaitForWindowUpdate
|
||||
import com.itsaky.androidide.testing.android.findObjectWithText
|
||||
import com.itsaky.androidide.testing.android.getApplicationContext
|
||||
import com.itsaky.androidide.testing.android.getObjectWithText
|
||||
import com.itsaky.androidide.testing.android.launchAndroidIDE
|
||||
import com.itsaky.androidide.testing.android.stringRes
|
||||
import com.itsaky.androidide.testing.android.uiAutomation
|
||||
import com.itsaky.androidide.utils.isAtLeastT
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@Suppress("TestFunctionName")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class OnboardingActivityBehaviorTest {
|
||||
|
||||
/**
|
||||
* Clear shared preferences after tests.
|
||||
*/
|
||||
@JvmField
|
||||
@Rule
|
||||
val clearPrefsRule = ClearPreferencesRule()
|
||||
|
||||
/**
|
||||
* Clear databases after tests.
|
||||
*/
|
||||
@JvmField
|
||||
@Rule
|
||||
val clearDBsRule = ClearDatabaseRule()
|
||||
|
||||
/**
|
||||
* Delete private data files after each test.
|
||||
*/
|
||||
@JvmField
|
||||
@Rule
|
||||
val clearFilesRule = ClearFilesRule()
|
||||
|
||||
@Test
|
||||
fun A_testOnboarding_permissionScreen_storagePermissionBehavior() {
|
||||
checkPermissionsBehavior(R.string.permission_desc_storage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun B_testOnboarding_permissionScreen_installPackagesPermissionBehavior() {
|
||||
checkPermissionsBehavior(R.string.permission_desc_install_packages)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun C_testOnboarding_toolsSetup_autoInstallBehavior() {
|
||||
val device = launchAndroidIDE(fromLauncher = true, clearTasks = true)
|
||||
device.grantAndroidIDEPermissions()
|
||||
|
||||
val autoInstallSwitch = device.findObject(By.checkable(true).clazz(Switch::class.java))
|
||||
assertThat(autoInstallSwitch).isNotNull()
|
||||
assertThat(autoInstallSwitch.isChecked).isTrue()
|
||||
|
||||
// verify basic switch behavior
|
||||
autoInstallSwitch.clickAndWaitForWindowUpdate(device)
|
||||
assertThat(autoInstallSwitch.isChecked).isFalse()
|
||||
assertThat(device.getObjectWithText(R.string.action_install_git).isEnabled).isFalse()
|
||||
assertThat(device.getObjectWithText(R.string.action_install_openssh).isEnabled).isFalse()
|
||||
autoInstallSwitch.clickAndWaitForWindowUpdate(device)
|
||||
assertThat(autoInstallSwitch.isChecked).isTrue()
|
||||
|
||||
// clicking on the 'done' button with 'Automatic installation' must navigate the user to
|
||||
// terminal and start the installation
|
||||
val doneButton = device.findObject(By.desc(stringRes(R.string.app_intro_done_button)))
|
||||
assertThat(doneButton).isNotNull()
|
||||
assertThat(doneButton.isClickable).isTrue()
|
||||
|
||||
if (isAtLeastT()) {
|
||||
// grant notification permission first
|
||||
device.uiAutomation.grantRuntimePermissionAsUser(
|
||||
getApplicationContext<Context>().packageName,
|
||||
Manifest.permission.POST_NOTIFICATIONS,
|
||||
UserHandleCompat.getUserHandleForUid(Process.myUid())
|
||||
)
|
||||
}
|
||||
|
||||
doneButton.clickAndWaitForNewWindow()
|
||||
|
||||
// 'Installing bootstrap packages' dialog..
|
||||
assertThat(device.findObjectWithText(R.string.bootstrap_installer_body)).isNotNull()
|
||||
|
||||
// wait for the packages to be installed
|
||||
// this should take a minute at most
|
||||
// we wait until the installation finishes and the keys GridLayout is visible
|
||||
device.wait(Until.hasObject(By.clazz(GridLayout::class.java)), 60 * 1000L)
|
||||
|
||||
// verify that the terminal keys view is visible
|
||||
assertThat(device.findObject(By.clazz(GridLayout::class.java))).isNotNull()
|
||||
assertThat(device.findObject(By.clazz(ViewPager::class.java))).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun C_testOnboarding_toolsSetup_manualInstallBehavior() {
|
||||
val device = launchAndroidIDE(fromLauncher = true, clearTasks = true)
|
||||
device.grantAndroidIDEPermissions()
|
||||
|
||||
val autoInstallSwitch = device.findObject(By.checkable(true).clazz(Switch::class.java))
|
||||
assertThat(autoInstallSwitch).isNotNull()
|
||||
assertThat(autoInstallSwitch.isChecked).isTrue()
|
||||
|
||||
// verify basic switch behavior
|
||||
autoInstallSwitch.clickAndWaitForWindowUpdate(device)
|
||||
assertThat(autoInstallSwitch.isChecked).isFalse()
|
||||
assertThat(device.getObjectWithText(R.string.action_install_git).isEnabled).isFalse()
|
||||
assertThat(device.getObjectWithText(R.string.action_install_openssh).isEnabled).isFalse()
|
||||
|
||||
// clicking on the 'done' button with 'Automatic installation' must navigate the user to
|
||||
// terminal and start the installation
|
||||
val doneButton = device.findObject(By.desc(stringRes(R.string.app_intro_done_button)))
|
||||
assertThat(doneButton).isNotNull()
|
||||
assertThat(doneButton.isClickable).isTrue()
|
||||
|
||||
if (isAtLeastT()) {
|
||||
// grant notification permission first
|
||||
device.uiAutomation.grantRuntimePermissionAsUser(
|
||||
getApplicationContext<Context>().packageName,
|
||||
Manifest.permission.POST_NOTIFICATIONS,
|
||||
UserHandleCompat.getUserHandleForUid(Process.myUid())
|
||||
)
|
||||
}
|
||||
|
||||
doneButton.clickAndWaitForNewWindow()
|
||||
|
||||
// 'Installing bootstrap packages' dialog..
|
||||
assertThat(device.findObjectWithText(R.string.bootstrap_installer_body)).isNotNull()
|
||||
|
||||
// wait for the packages to be installed
|
||||
// this should take a minute at most
|
||||
// we wait until the installation finishes and the keys GridLayout is visible
|
||||
device.wait(Until.hasObject(By.clazz(GridLayout::class.java)), 60 * 1000L)
|
||||
|
||||
// verify that the terminal keys view is visible
|
||||
assertThat(device.findObject(By.clazz(GridLayout::class.java))).isNotNull()
|
||||
assertThat(device.findObject(By.clazz(ViewPager::class.java))).isNotNull()
|
||||
}
|
||||
|
||||
private fun checkPermissionsBehavior(@StringRes permissionDesc: Int) {
|
||||
val device = launchAndroidIDE(fromLauncher = true, clearTasks = true)
|
||||
|
||||
device.nextButton().clickAndWaitForWindowUpdate(device)
|
||||
device.nextButton().clickAndWaitForWindowUpdate(device)
|
||||
|
||||
var grantButton = device.getObjectWithText(permissionDesc)
|
||||
.parent!!
|
||||
.parent!!
|
||||
.findObject(By.clazz(Button::class.java))
|
||||
|
||||
// verify that clicking the 'Grant' button next to storage navigates requests the permissions
|
||||
grantButton.clickAndWaitForNewWindow()
|
||||
|
||||
// check permission denied behavior
|
||||
device.handleAndroidRPermissions(false)
|
||||
|
||||
// verify AndroidIDE is visible
|
||||
assertThat(device.currentPackageName).isEqualTo(BuildInfo.PACKAGE_NAME)
|
||||
|
||||
grantButton = device.getObjectWithText(permissionDesc)
|
||||
.parent!!
|
||||
.parent!!
|
||||
.findObject(By.clazz(Button::class.java))
|
||||
|
||||
// verify button state after permission was not granted
|
||||
assertThat(grantButton).isNotNull()
|
||||
assertThat(grantButton.text).isNotNull()
|
||||
|
||||
// navigate to the permission page again
|
||||
grantButton.clickAndWaitForNewWindow()
|
||||
|
||||
// check permission granted behavior
|
||||
device.handleAndroidRPermissions(true)
|
||||
|
||||
grantButton = device.getObjectWithText(permissionDesc)
|
||||
.parent!!
|
||||
.parent!!
|
||||
.findObject(By.clazz(Button::class.java))
|
||||
|
||||
// verify AndroidIDE is visible
|
||||
assertThat(device.currentPackageName).isEqualTo(BuildInfo.PACKAGE_NAME)
|
||||
|
||||
// verify button state after permission was granted
|
||||
assertThat(grantButton).isNotNull()
|
||||
assertThat(grantButton.text).isNull()
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.activities
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.FlakyTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import com.adevinta.android.barista.assertion.BaristaCheckedAssertions.assertChecked
|
||||
import com.adevinta.android.barista.assertion.BaristaCheckedAssertions.assertUnchecked
|
||||
import com.adevinta.android.barista.assertion.BaristaEnabledAssertions.assertDisabled
|
||||
import com.adevinta.android.barista.assertion.BaristaEnabledAssertions.assertEnabled
|
||||
import com.adevinta.android.barista.assertion.BaristaRecyclerViewAssertions.assertRecyclerViewItemCount
|
||||
import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assertDisplayed
|
||||
import com.adevinta.android.barista.assertion.BaristaVisibilityAssertions.assertNotDisplayed
|
||||
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickBack
|
||||
import com.adevinta.android.barista.interaction.BaristaClickInteractions.clickOn
|
||||
import com.adevinta.android.barista.rule.BaristaRule
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.preferences.internal.statOptIn
|
||||
import com.itsaky.androidide.testing.android.launchAndroidIDE
|
||||
import com.itsaky.androidide.testing.android.stringRes
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@Suppress("TestFunctionName")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class OnboardingActivityStatesTest {
|
||||
|
||||
@JvmField
|
||||
@Rule
|
||||
val baristaActivityRule = BaristaRule.create(OnboardingActivity::class.java)
|
||||
|
||||
@Test
|
||||
fun A_testOnboarding_welcomeScreen() {
|
||||
baristaActivityRule.launchActivity()
|
||||
|
||||
assertDisplayed(stringRes(R.string.greeting_title))
|
||||
assertDisplayed(stringRes(R.string.greeting_subtitle))
|
||||
assertDisplayed(R.id.next)
|
||||
assertNotDisplayed(R.id.back)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun B_testOnboarding_statConsentScreen() {
|
||||
baristaActivityRule.launchActivity()
|
||||
|
||||
clickOn(R.id.next)
|
||||
|
||||
assertDisplayed(R.id.next)
|
||||
assertDisplayed(stringRes(R.string.title_androidide_statistics))
|
||||
assertDisplayed(stringRes(R.string.idepref_stats_optIn_summary))
|
||||
assertDisplayed(stringRes(R.string.msg_androidide_statistics))
|
||||
|
||||
assertDisplayed(R.id.stat_opt_in, stringRes(R.string.title_androidide_statistics))
|
||||
|
||||
assertThat(statOptIn).isTrue()
|
||||
assertChecked(R.id.stat_opt_in)
|
||||
|
||||
// disable and navigate to next screen, then verify
|
||||
clickOn(R.id.stat_opt_in)
|
||||
clickOn(R.id.next)
|
||||
assertThat(statOptIn).isFalse()
|
||||
assertUnchecked(R.id.stat_opt_in)
|
||||
|
||||
// Navigate back (statistics)
|
||||
clickBack()
|
||||
|
||||
// enable again and navigate to next screen, then verify
|
||||
clickOn(R.id.stat_opt_in)
|
||||
clickOn(R.id.next)
|
||||
assertThat(statOptIn).isTrue()
|
||||
assertChecked(R.id.stat_opt_in)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun C_testOnboarding_permissionsScreen() {
|
||||
baristaActivityRule.launchActivity()
|
||||
|
||||
clickOn(R.id.next)
|
||||
clickOn(R.id.next)
|
||||
|
||||
assertDisplayed(stringRes(R.string.onboarding_title_permissions))
|
||||
assertDisplayed(stringRes(R.string.permission_title_storage))
|
||||
assertDisplayed(stringRes(R.string.permission_desc_storage))
|
||||
assertDisplayed(stringRes(R.string.permission_title_install_packages))
|
||||
assertDisplayed(stringRes(R.string.permission_desc_install_packages))
|
||||
|
||||
assertRecyclerViewItemCount(R.id.onboarding_items, 2)
|
||||
|
||||
clickOn(R.id.next)
|
||||
assertDisplayed(stringRes(R.string.msg_grant_permissions))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun F_testOnboarding_toolsSetup() {
|
||||
|
||||
baristaActivityRule.launchActivity()
|
||||
|
||||
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||
device.grantAndroidIDEPermissions()
|
||||
|
||||
assertDisplayed(stringRes(R.string.title_install_tools))
|
||||
assertDisplayed(stringRes(R.string.subtitle_install_tools))
|
||||
assertDisplayed(R.id.auto_install_switch)
|
||||
assertDisplayed(R.id.sdk_version_layout)
|
||||
assertDisplayed(R.id.jdk_version_layout)
|
||||
assertDisplayed(R.id.install_git)
|
||||
assertDisplayed(R.id.install_openssh)
|
||||
|
||||
assertEnabled(R.id.install_git)
|
||||
assertEnabled(R.id.install_openssh)
|
||||
assertEnabled(R.id.sdk_version_layout)
|
||||
assertEnabled(R.id.jdk_version_layout)
|
||||
|
||||
assertChecked(R.id.auto_install_switch)
|
||||
assertChecked(R.id.install_git)
|
||||
assertChecked(R.id.install_openssh)
|
||||
|
||||
// switch to manual install
|
||||
clickOn(R.id.auto_install_switch)
|
||||
|
||||
// check that the auto-install related fields are disabled
|
||||
assertUnchecked(R.id.auto_install_switch)
|
||||
assertDisabled(R.id.install_git)
|
||||
assertDisabled(R.id.install_openssh)
|
||||
assertDisabled(R.id.sdk_version_layout)
|
||||
assertDisabled(R.id.jdk_version_layout)
|
||||
|
||||
// switch back to auto-install
|
||||
clickOn(R.id.auto_install_switch)
|
||||
|
||||
// check that the auto-instal related fields are enabled again
|
||||
assertChecked(R.id.auto_install_switch)
|
||||
assertEnabled(R.id.install_git)
|
||||
assertEnabled(R.id.install_openssh)
|
||||
assertEnabled(R.id.sdk_version_layout)
|
||||
assertEnabled(R.id.jdk_version_layout)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.activities
|
||||
|
||||
import android.widget.Button
|
||||
import android.widget.Switch
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.UiObject2
|
||||
import androidx.test.uiautomator.UiSelector
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.testing.android.clickAndWaitForNewWindow
|
||||
import com.itsaky.androidide.testing.android.clickAndWaitForWindowUpdate
|
||||
import com.itsaky.androidide.testing.android.getObjectWithText
|
||||
import com.itsaky.androidide.testing.android.hasObjectWithText
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
|
||||
fun UiDevice.grantAndroidIDEPermissions() {
|
||||
nextButton().clickAndWaitForWindowUpdate(this)
|
||||
|
||||
if (hasObjectWithText(R.string.title_androidide_statistics)) {
|
||||
// If we haven't opted-in for statistics yet...
|
||||
nextButton().clickAndWaitForWindowUpdate(this)
|
||||
}
|
||||
|
||||
if (hasObjectWithText(R.string.onboarding_title_permissions)) {
|
||||
// If the permissions are not granted, grant them
|
||||
for (desc in arrayOf(R.string.permission_desc_storage,
|
||||
R.string.permission_desc_install_packages)) {
|
||||
val grantButton = getObjectWithText(desc)
|
||||
.parent!!
|
||||
.parent!!
|
||||
.findObject(By.clazz(Button::class.java))
|
||||
|
||||
if (grantButton.text != null) {
|
||||
grantButton.clickAndWaitForNewWindow()
|
||||
handleAndroidRPermissions(allowPermission = true)
|
||||
}
|
||||
}
|
||||
|
||||
nextButton().clickAndWaitForWindowUpdate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun UiDevice.nextButton(): UiObject2 {
|
||||
return findObject(By.desc("NEXT"))!!
|
||||
}
|
||||
|
||||
fun UiDevice.handleAndroidRPermissions(
|
||||
allowPermission: Boolean = true,
|
||||
) {
|
||||
assertThat(currentPackageName).isEqualTo("com.android.settings")
|
||||
assertThat(hasObjectWithText(R.string.app_name)).isTrue()
|
||||
|
||||
val storageSwitch = findObject(UiSelector().className(Switch::class.java))
|
||||
assertThat(storageSwitch).isNotNull()
|
||||
|
||||
// verify initial state
|
||||
assertThat(storageSwitch.isCheckable).isTrue()
|
||||
assertThat(storageSwitch.isEnabled).isTrue()
|
||||
assertThat(storageSwitch.isChecked).isFalse()
|
||||
|
||||
if (allowPermission) {
|
||||
// enable permission
|
||||
storageSwitch.click()
|
||||
|
||||
// verify changed state
|
||||
assertThat(storageSwitch.isChecked).isTrue()
|
||||
}
|
||||
|
||||
// navigate back without enabling the permission
|
||||
pressBack()
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE 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.
|
||||
*
|
||||
* AndroidIDE 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 AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.fragments
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.*
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isChecked
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withParent
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.activities.MainActivity
|
||||
import com.itsaky.androidide.models.MainScreenAction
|
||||
import com.itsaky.androidide.templates.BooleanParameter
|
||||
import com.itsaky.androidide.templates.EnumParameter
|
||||
import com.itsaky.androidide.templates.ITemplateProvider
|
||||
import com.itsaky.androidide.templates.StringParameter
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.Matchers.allOf
|
||||
import org.hamcrest.TypeSafeMatcher
|
||||
import org.hamcrest.core.IsInstanceOf.instanceOf
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MainFragmentTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var mActivityScenarioRule = ActivityScenarioRule(MainActivity::class.java)
|
||||
|
||||
@Test
|
||||
fun testMainFragmentActionsAreShown() {
|
||||
val recyclerView = onView(allOf(
|
||||
withId(R.id.actions),
|
||||
childAtPosition(
|
||||
withId(R.id.coordinator),
|
||||
3)))
|
||||
|
||||
recyclerView.check(matches(isDisplayed()))
|
||||
|
||||
for (action in MainScreenAction.all()) {
|
||||
val button = onView(
|
||||
allOf(withText(action.text),
|
||||
withParent(allOf(withId(R.id.actions),
|
||||
withParent(withId(R.id.coordinator)))),
|
||||
isDisplayed()))
|
||||
button.check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTemplateWizardUi() {
|
||||
|
||||
onView(withText(R.string.get_started)).check(matches(isCompletelyDisplayed()))
|
||||
|
||||
val recyclerView = onView(allOf(
|
||||
withId(R.id.actions),
|
||||
childAtPosition(
|
||||
withId(R.id.coordinator),
|
||||
3)))
|
||||
|
||||
recyclerView.check(matches(isDisplayed()))
|
||||
|
||||
val action = MainScreenAction.all().find { it.id == MainScreenAction.ACTION_CREATE_PROJECT }
|
||||
assertThat(action).isNotNull()
|
||||
|
||||
action!!
|
||||
|
||||
val createProject = onView(allOf(withText(action.text), isDisplayed()))
|
||||
|
||||
createProject.check(matches(isCompletelyDisplayed()))
|
||||
createProject.perform(click())
|
||||
|
||||
val exitButton = onView(
|
||||
allOf(withId(R.id.exit_button), isCompletelyDisplayed())
|
||||
)
|
||||
|
||||
exitButton.check(matches(isCompletelyDisplayed()))
|
||||
|
||||
val templates = ITemplateProvider.getInstance().getTemplates()
|
||||
for (template in templates) {
|
||||
val templateTitle = onView(
|
||||
allOf(withId(R.id.template_name), withText(template.templateName),
|
||||
withParent(
|
||||
withParent(instanceOf(CardView::class.java))),
|
||||
isCompletelyDisplayed()))
|
||||
|
||||
templateTitle.check(matches(isCompletelyDisplayed()))
|
||||
}
|
||||
|
||||
val template = templates.first()
|
||||
val firstTemplateItem = onView(
|
||||
allOf(withId(R.id.template_name), withText(template.templateName),
|
||||
withParent(
|
||||
withParent(instanceOf(CardView::class.java))),
|
||||
isCompletelyDisplayed()))
|
||||
|
||||
firstTemplateItem.perform(click())
|
||||
|
||||
val previousButton = onView(
|
||||
allOf(withId(R.id.previous), withText(R.string.previous), isCompletelyDisplayed()))
|
||||
|
||||
val createProjectFinishButton = onView(
|
||||
allOf(withId(R.id.finish), withText(R.string.create_project),
|
||||
isCompletelyDisplayed()))
|
||||
|
||||
previousButton.check(matches(isCompletelyDisplayed()))
|
||||
createProjectFinishButton.check(matches(isCompletelyDisplayed()))
|
||||
|
||||
for (parameter in template.parameters) {
|
||||
val selector = when (parameter) {
|
||||
is StringParameter -> withText(parameter.value)
|
||||
is BooleanParameter -> allOf(withText(parameter.name),
|
||||
if (parameter.value) isChecked() else isNotChecked())
|
||||
|
||||
is EnumParameter -> withText(parameter.getDisplayName())
|
||||
else -> throw IllegalStateException("Unknown parameter type: $parameter")
|
||||
}
|
||||
val widget = onView(selector)
|
||||
widget.check(matches(isCompletelyDisplayed()))
|
||||
}
|
||||
|
||||
previousButton.perform(click())
|
||||
|
||||
Thread.sleep(1000L)
|
||||
|
||||
exitButton.check(matches(isCompletelyDisplayed()))
|
||||
exitButton.perform(click())
|
||||
|
||||
Thread.sleep(1000L)
|
||||
|
||||
onView(withText(R.string.get_started)).check(matches(isCompletelyDisplayed()))
|
||||
}
|
||||
|
||||
private fun childAtPosition(
|
||||
parentMatcher: Matcher<View>, position: Int): Matcher<View> {
|
||||
|
||||
return object : TypeSafeMatcher<View>() {
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("Child at position $position in parent ")
|
||||
parentMatcher.describeTo(description)
|
||||
}
|
||||
|
||||
public override fun matchesSafely(view: View): Boolean {
|
||||
val parent = view.parent
|
||||
return parent is ViewGroup && parentMatcher.matches(parent)
|
||||
&& view == parent.getChildAt(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
|
||||
@@ -42,12 +42,6 @@ import com.termux.shared.termux.TermuxConstants
|
||||
|
||||
class OnboardingActivity : AppIntro2() {
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
finishAffinity()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val KEY_ARCHCONFIG_WARNING_IS_SHOWN = "ide.archConfig.experimentalWarning.isShown"
|
||||
@@ -63,8 +57,6 @@ class OnboardingActivity : AppIntro2() {
|
||||
finish()
|
||||
}
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
|
||||
setSwipeLock(true)
|
||||
setTransformer(AppIntroPageTransformerType.Fade)
|
||||
setProgressIndicator()
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.greenrobot.eventbus.EventBus;
|
||||
public class PreferenceManager {
|
||||
|
||||
private final SharedPreferences prefs;
|
||||
private boolean isReadOnly = false;
|
||||
|
||||
public PreferenceManager(Context ctx) {
|
||||
this(ctx, null);
|
||||
@@ -46,7 +47,9 @@ public class PreferenceManager {
|
||||
}
|
||||
|
||||
public PreferenceManager remove(String key) {
|
||||
prefs.edit().remove(key).apply();
|
||||
final var edit = prefs.edit();
|
||||
edit.remove(key);
|
||||
applyChanges(edit);
|
||||
dispatchRemoveEvent(key);
|
||||
return this;
|
||||
}
|
||||
@@ -55,8 +58,27 @@ public class PreferenceManager {
|
||||
EventBus.getDefault().post(new PreferenceRemoveEvent(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether this preference manager is read-only.
|
||||
*/
|
||||
public boolean isReadOnly() {
|
||||
return isReadOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this preference manager is read-only.
|
||||
*
|
||||
* @param readOnly <code>true</code>, if the preference manager is read-only, <code>false</code>
|
||||
* otherwise.
|
||||
*/
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
isReadOnly = readOnly;
|
||||
}
|
||||
|
||||
public PreferenceManager putInt(String key, int val) {
|
||||
prefs.edit().putInt(key, val).apply();
|
||||
final var edit = prefs.edit();
|
||||
edit.putInt(key, val);
|
||||
applyChanges(edit);
|
||||
dispatchChangeEvent(key, val);
|
||||
return this;
|
||||
}
|
||||
@@ -70,7 +92,9 @@ public class PreferenceManager {
|
||||
}
|
||||
|
||||
public void putFloat(String key, float val) {
|
||||
prefs.edit().putFloat(key, val).apply();
|
||||
final var edit = prefs.edit();
|
||||
edit.putFloat(key, val);
|
||||
applyChanges(edit);
|
||||
dispatchChangeEvent(key, val);
|
||||
}
|
||||
|
||||
@@ -91,7 +115,9 @@ public class PreferenceManager {
|
||||
}
|
||||
|
||||
public PreferenceManager putString(String key, String value) {
|
||||
prefs.edit().putString(key, value).apply();
|
||||
final var edit = prefs.edit();
|
||||
edit.putString(key, value);
|
||||
applyChanges(edit);
|
||||
dispatchChangeEvent(key, value);
|
||||
return this;
|
||||
}
|
||||
@@ -105,7 +131,9 @@ public class PreferenceManager {
|
||||
}
|
||||
|
||||
public void putBoolean(String key, boolean value) {
|
||||
prefs.edit().putBoolean(key, value).apply();
|
||||
final var edit = prefs.edit();
|
||||
edit.putBoolean(key, value);
|
||||
applyChanges(edit);
|
||||
dispatchChangeEvent(key, value);
|
||||
}
|
||||
|
||||
@@ -114,7 +142,9 @@ public class PreferenceManager {
|
||||
}
|
||||
|
||||
public void putLong(String key, long value) {
|
||||
prefs.edit().putLong(key, value).apply();
|
||||
final var edit = prefs.edit();
|
||||
edit.putLong(key, value);
|
||||
applyChanges(edit);
|
||||
dispatchChangeEvent(key, value);
|
||||
}
|
||||
|
||||
@@ -122,4 +152,7 @@ public class PreferenceManager {
|
||||
return prefs.getInt(key, def);
|
||||
}
|
||||
|
||||
protected void applyChanges(SharedPreferences.Editor editor) {
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ tests-androidx-espresso-contrib = { module = "androidx.test.espresso:espresso-co
|
||||
tests-androidx-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.2.0" }
|
||||
tests-mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version = "5.2.1" }
|
||||
tests-mockk = { module = "io.mockk:mockk", version = "1.13.8" }
|
||||
tests-barista = { module = "com.adevinta.android:barista", version = "4.2.0" }
|
||||
|
||||
# Tooling
|
||||
tooling-builderModel = { module = "com.android.tools.build:builder-model", version.ref = "agp-tooling" }
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ var statConsentDialogShown: Boolean
|
||||
}
|
||||
|
||||
var statOptIn: Boolean
|
||||
get() = prefManager.getBoolean(STAT_OPT_IN, false)
|
||||
get() = prefManager.getBoolean(STAT_OPT_IN, true)
|
||||
set(value) {
|
||||
prefManager.putBoolean(STAT_OPT_IN, value)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ dependencies {
|
||||
api(libs.tests.androidx.uiautomator)
|
||||
api(libs.tests.junit)
|
||||
api(libs.tests.google.truth)
|
||||
api(libs.tests.barista) {
|
||||
exclude("org.jetbrains.kotlin")
|
||||
}
|
||||
|
||||
api(projects.buildInfo)
|
||||
api(projects.common)
|
||||
|
||||
@@ -20,6 +20,18 @@ package com.itsaky.androidide.testing.android
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.itsaky.androidide.app.BaseApplication
|
||||
|
||||
/**
|
||||
* Get the application context.
|
||||
*/
|
||||
fun <AppContext : Context> getApplicationContext(): AppContext =
|
||||
ApplicationProvider.getApplicationContext()
|
||||
|
||||
/**
|
||||
* Get the [BaseApplication] instance.
|
||||
*/
|
||||
fun getBaseApplication() = getApplicationContext<BaseApplication>()
|
||||
|
||||
/**
|
||||
* Get the value of the string resource.
|
||||
|
||||
+12
-4
@@ -18,11 +18,11 @@
|
||||
package com.itsaky.androidide.testing.android
|
||||
|
||||
import android.view.KeyCharacterMap
|
||||
import android.view.KeyEvent
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.UiObject2
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.buildinfo.BuildInfo
|
||||
|
||||
private val keyCharMap by lazy {
|
||||
@@ -45,7 +45,7 @@ fun UiDevice.hasObjectWithText(text: String) =
|
||||
* @see UiDevice.hasObject
|
||||
*/
|
||||
fun UiDevice.hasObjectWithText(@StringRes text: Int) =
|
||||
hasObject(By.text(stringRes(text)))
|
||||
hasObjectWithText(stringRes(text))
|
||||
|
||||
/**
|
||||
* @see UiDevice.findObject
|
||||
@@ -57,7 +57,16 @@ fun UiDevice.findObjectWithText(text: String): UiObject2? =
|
||||
* @see UiDevice.findObjectWithText
|
||||
*/
|
||||
fun UiDevice.findObjectWithText(@StringRes text: Int): UiObject2? =
|
||||
findObject(By.text(stringRes(text)))
|
||||
findObjectWithText(stringRes(text))
|
||||
|
||||
|
||||
fun UiDevice.getObjectWithText(text: String): UiObject2 {
|
||||
val obj = findObjectWithText(text)
|
||||
assertThat(obj).isNotNull()
|
||||
return obj!!
|
||||
}
|
||||
|
||||
fun UiDevice.getObjectWithText(@StringRes text: Int): UiObject2 = getObjectWithText(stringRes(text))
|
||||
|
||||
/**
|
||||
* Sends key events to this [UiDevice] to type the given [text].
|
||||
@@ -67,7 +76,6 @@ fun UiDevice.sendKeyEvents(text: String) {
|
||||
val keyEvents = keyCharMap.getEvents(text.toCharArray())
|
||||
for (keyEvent in keyEvents) {
|
||||
uiAutomation.injectInputEvent(keyEvent, true)
|
||||
// pressKeyCode(keyEvent.keyCode, keyEvent.metaState)
|
||||
waitForIdle()
|
||||
}
|
||||
}
|
||||
+36
-1
@@ -17,6 +17,8 @@
|
||||
|
||||
package com.itsaky.androidide.testing.android
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.UiObject2
|
||||
import androidx.test.uiautomator.Until
|
||||
@@ -43,4 +45,37 @@ fun UiObject2?.clickAndWaitForNewWindow(timeout: Long = LAUNCH_TIMEOUT) =
|
||||
run {
|
||||
assertThat(this).isNotNull()
|
||||
this!!.clickAndWait(Until.newWindow(), timeout)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UiDevice.hasObject
|
||||
*/
|
||||
fun UiObject2.hasObjectWithText(text: String) =
|
||||
hasObject(By.text(text))
|
||||
|
||||
/**
|
||||
* @see UiObject2.hasObject
|
||||
*/
|
||||
fun UiObject2.hasObjectWithText(@StringRes text: Int) =
|
||||
hasObjectWithText(stringRes(text))
|
||||
|
||||
/**
|
||||
* @see UiObject2.findObject
|
||||
*/
|
||||
fun UiObject2.findObjectWithText(text: String): UiObject2? =
|
||||
findObject(By.text(text))
|
||||
|
||||
/**
|
||||
* @see UiObject2.findObjectWithText
|
||||
*/
|
||||
fun UiObject2.findObjectWithText(@StringRes text: Int): UiObject2? =
|
||||
findObjectWithText(stringRes(text))
|
||||
|
||||
|
||||
fun UiObject2.getObjectWithText(text: String): UiObject2 {
|
||||
val obj = findObjectWithText(text)
|
||||
assertThat(obj).isNotNull()
|
||||
return obj!!
|
||||
}
|
||||
|
||||
fun UiObject2.getObjectWithText(@StringRes text: Int): UiObject2 = getObjectWithText(stringRes(text))
|
||||
Reference in New Issue
Block a user