diff --git a/app/build.gradle b/app/build.gradle index def0c4250e..cdd245253c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,4 @@ import org.apache.tools.ant.filters.ReplaceTokens -import com.android.builder.core.DefaultManifestParser //////////// // README // @@ -22,7 +21,7 @@ repositories { flatDir { dirs 'libs' } - maven { url "https://jitpack.io" } + maven { url 'https://jitpack.io' } } configurations { @@ -42,8 +41,8 @@ dependencies { testImplementation 'androidx.work:work-testing:2.7.1' testImplementation 'androidx.test.espresso:espresso-core:3.4.0' testImplementation 'androidx.test.espresso:espresso-intents:3.5.1' - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2" - testImplementation "io.mockk:mockk:1.12.7" + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2' + testImplementation 'io.mockk:mockk:1.12.7' testImplementation 'org.json:json:20140107' testImplementation project(path: ':commcare-core', configuration: 'testsAsJar') @@ -82,7 +81,7 @@ dependencies { implementation 'com.google.android.gms:play-services-maps:17.0.0' implementation 'joda-time:joda-time:2.9.4' implementation 'net.zetetic:android-database-sqlcipher:4.5.3@aar' - implementation "androidx.sqlite:sqlite:2.2.0" + implementation 'androidx.sqlite:sqlite:2.2.0' implementation('org.apache.james:apache-mime4j:0.7.2') { exclude module: 'commons-io' } @@ -100,8 +99,8 @@ dependencies { implementation 'com.google.firebase:firebase-crashlytics:17.2.1' implementation 'androidx.legacy:legacy-support-core-ui:1.0.0' implementation 'com.duolingo.open:rtl-viewpager:2.0.0' - implementation("com.github.bumptech.glide:glide:4.9.0") { - exclude group: "com.android.support" + implementation('com.github.bumptech.glide:glide:4.9.0') { + exclude group: 'com.android.support' } implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" @@ -115,8 +114,8 @@ dependencies { implementation('io.ona.kujaku:library:0.9.0') { exclude module: 'xpp3' } - implementation "androidx.work:work-runtime:2.7.1" - implementation "androidx.work:work-runtime-ktx:2.7.1" + implementation 'androidx.work:work-runtime:2.7.1' + implementation 'androidx.work:work-runtime-ktx:2.7.1' implementation 'com.google.android.play:core:1.10.3' implementation 'android.arch.lifecycle:common-java8:1.1.1' @@ -139,28 +138,28 @@ ext { // Obtained from ~/.gradle/gradle.properties on build server (mobile agent), or your local // Obtained from ~/.gradle/gradle.properties on build server (mobile agent), or your local // ~/.gradle/gradle.properties file, or loads default empty strings if neither is present - MAPBOX_SDK_API_KEY = project.properties['MAPBOX_SDK_API_KEY'] ?: "" - ANALYTICS_TRACKING_ID_DEV = project.properties['ANALYTICS_TRACKING_ID_DEV'] ?: "" - ANALYTICS_TRACKING_ID_LIVE = project.properties['ANALYTICS_TRACKING_ID_LIVE'] ?: "" - GOOGLE_PLAY_MAPS_API_KEY = project.properties['GOOGLE_PLAY_MAPS_API_KEY'] ?: "" - RELEASE_STORE_FILE = project.properties['RELEASE_STORE_FILE'] ?: "." - RELEASE_STORE_PASSWORD = project.properties['RELEASE_STORE_PASSWORD'] ?: "" - RELEASE_KEY_ALIAS = project.properties['RELEASE_KEY_ALIAS'] ?: "" - RELEASE_KEY_PASSWORD = project.properties['RELEASE_KEY_PASSWORD'] ?: "" + MAPBOX_SDK_API_KEY = project.properties['MAPBOX_SDK_API_KEY'] ?: '' + ANALYTICS_TRACKING_ID_DEV = project.properties['ANALYTICS_TRACKING_ID_DEV'] ?: '' + ANALYTICS_TRACKING_ID_LIVE = project.properties['ANALYTICS_TRACKING_ID_LIVE'] ?: '' + GOOGLE_PLAY_MAPS_API_KEY = project.properties['GOOGLE_PLAY_MAPS_API_KEY'] ?: '' + RELEASE_STORE_FILE = project.properties['RELEASE_STORE_FILE'] ?: '.' + RELEASE_STORE_PASSWORD = project.properties['RELEASE_STORE_PASSWORD'] ?: '' + RELEASE_KEY_ALIAS = project.properties['RELEASE_KEY_ALIAS'] ?: '' + RELEASE_KEY_PASSWORD = project.properties['RELEASE_KEY_PASSWORD'] ?: '' TRUSTED_SOURCE_PUBLIC_KEY = project.properties['TRUSTED_SOURCE_PUBLIC_KEY'] ?: - "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHiuy2ULV4pobkuQN2TEjmR1tn" + - "HJ+F335hm/lVdaFQzvBmeq64MUMbumheVLDJaSUiAVzqSHDKJWH01ZQRowqBYjwo" + - "ycVSQSeO2glc6XZZ+CJudAPXe8iFWLQp3kBBnBmVcBXCOQFO7aLgQMv4nqKZsLW0" + - "HaAJkjpnc165Os+aYwIDAQAB" - GOOGLE_SERVICES_API_KEY = project.properties['GOOGLE_SERVICES_API_KEY'] ?: "" - QA_BETA_APP_ID = "" - STANDALONE_APP_ID = "" - LTS_APP_ID = "" - COMMCARE_APP_ID = "" - HQ_API_USERNAME = project.properties['HQ_API_USERNAME'] ?: "" - HQ_API_PASSWORD = project.properties['HQ_API_PASSWORD'] ?: "" - TEST_BUILD_TYPE = project.properties['TEST_BUILD_TYPE'] ?: "debug" - FIREBASE_DATABASE_URL = project.properties['FIREBASE_DATABASE_URL'] ?: "" + 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHiuy2ULV4pobkuQN2TEjmR1tn' + + 'HJ+F335hm/lVdaFQzvBmeq64MUMbumheVLDJaSUiAVzqSHDKJWH01ZQRowqBYjwo' + + 'ycVSQSeO2glc6XZZ+CJudAPXe8iFWLQp3kBBnBmVcBXCOQFO7aLgQMv4nqKZsLW0' + + 'HaAJkjpnc165Os+aYwIDAQAB' + GOOGLE_SERVICES_API_KEY = project.properties['GOOGLE_SERVICES_API_KEY'] ?: '' + QA_BETA_APP_ID = '' + STANDALONE_APP_ID = '' + LTS_APP_ID = '' + COMMCARE_APP_ID = '' + HQ_API_USERNAME = project.properties['HQ_API_USERNAME'] ?: '' + HQ_API_PASSWORD = project.properties['HQ_API_PASSWORD'] ?: '' + TEST_BUILD_TYPE = project.properties['TEST_BUILD_TYPE'] ?: 'debug' + FIREBASE_DATABASE_URL = project.properties['FIREBASE_DATABASE_URL'] ?: '' } afterEvaluate { @@ -189,11 +188,11 @@ task injectPropertiesIntoFirebaseConfigFile { } } -def ccAppId = project.hasProperty('cc_app_id') ? cc_app_id : "" -def ccDomain = project.hasProperty('cc_domain') ? cc_domain : "" -def isConsumerApp = project.hasProperty('is_consumer_app') ? is_consumer_app : "false" -def runDownloadScripts = project.hasProperty('run_download_scripts') ? run_download_scripts : "true" -def ccDomainSafe = ccDomain.replaceAll("-", "") +def ccAppId = project.hasProperty('cc_app_id') ? cc_app_id : '' +def ccDomain = project.hasProperty('cc_domain') ? cc_domain : '' +def isConsumerApp = project.hasProperty('is_consumer_app') ? is_consumer_app : 'false' +def runDownloadScripts = project.hasProperty('run_download_scripts') ? run_download_scripts : 'true' +def ccDomainSafe = ccDomain.replaceAll('-', '') def consumerAppUsername = getConsumerAppUsername(isConsumerApp) def consumerAppPassword = getConsumerAppPassword(isConsumerApp) @@ -215,13 +214,13 @@ static def getDate() { } android { - namespace "org.commcare.dalvik" - compileSdkVersion 33 + namespace 'org.commcare.dalvik' + compileSdk 33 lintOptions { abortOnError false disable 'MissingTranslation' - disable "ResourceType" + disable 'ResourceType' } // Espresso requires us to disable animations on device. @@ -245,14 +244,14 @@ android { minSdkVersion 21 targetSdkVersion 33 multiDexEnabled true - applicationId "org.commcare.dalvik" - testNamespace "org.commcare.dalvik.test" + applicationId 'org.commcare.dalvik' + testNamespace 'org.commcare.dalvik.test' project.ext.COMMCARE_APP_ID = applicationId // set the app name - def applicationName = "CommCare" - resValue "string", "application_name", applicationName - def odkProviderStr = "org.commcare.android.provider.odk" + def applicationName = 'CommCare' + resValue 'string', 'application_name', applicationName + def odkProviderStr = 'org.commcare.android.provider.odk' manifestPlaceholders = [ odkProvider : odkProviderStr, googlePlayMapsApiKey: "${project.ext.GOOGLE_PLAY_MAPS_API_KEY}" @@ -262,28 +261,28 @@ android { // when set, app won't show install screen and try to install // resources from assets folder - buildConfigField "boolean", "IS_SINGLE_APP_BUILD", "false" - buildConfigField "boolean", "IS_CONSUMER_APP", "false" - buildConfigField "String", "CONSUMER_APP_USERNAME", "\"\"" - buildConfigField "String", "CONSUMER_APP_PASSWORD", "\"\"" + buildConfigField 'boolean', 'IS_SINGLE_APP_BUILD', 'false' + buildConfigField 'boolean', 'IS_CONSUMER_APP', 'false' + buildConfigField 'String', 'CONSUMER_APP_USERNAME', "\"\"" + buildConfigField 'String', 'CONSUMER_APP_PASSWORD', "\"\"" - buildConfigField "String", "CC_AUTHORITY", "\"${applicationId}\"" - buildConfigField "String", "ODK_AUTHORITY", "\"${odkProviderStr}\"" + buildConfigField 'String', 'CC_AUTHORITY', "\"${applicationId}\"" + buildConfigField 'String', 'ODK_AUTHORITY', "\"${odkProviderStr}\"" - buildConfigField "String", "BUILD_DATE", "\"" + getDate() + "\"" - buildConfigField "String", "BUILD_NUMBER", "\"" + computeVersionCode() + "\"" - buildConfigField "String", "TRUSTED_SOURCE_PUBLIC_KEY", "\"${project.ext.TRUSTED_SOURCE_PUBLIC_KEY}\"" - buildConfigField "String", "ANALYTICS_TRACKING_ID_LIVE", "\"${project.ext.ANALYTICS_TRACKING_ID_LIVE}\"" - buildConfigField "String", "ANALYTICS_TRACKING_ID_DEV", "\"${project.ext.ANALYTICS_TRACKING_ID_DEV}\"" - buildConfigField "String", "MAPBOX_SDK_API_KEY", "\"${project.ext.MAPBOX_SDK_API_KEY}\"" + buildConfigField 'String', 'BUILD_DATE', "\"" + getDate() + "\"" + buildConfigField 'String', 'BUILD_NUMBER', "\"" + computeVersionCode() + "\"" + buildConfigField 'String', 'TRUSTED_SOURCE_PUBLIC_KEY', "\"${project.ext.TRUSTED_SOURCE_PUBLIC_KEY}\"" + buildConfigField 'String', 'ANALYTICS_TRACKING_ID_LIVE', "\"${project.ext.ANALYTICS_TRACKING_ID_LIVE}\"" + buildConfigField 'String', 'ANALYTICS_TRACKING_ID_DEV', "\"${project.ext.ANALYTICS_TRACKING_ID_DEV}\"" + buildConfigField 'String', 'MAPBOX_SDK_API_KEY', "\"${project.ext.MAPBOX_SDK_API_KEY}\"" buildConfigField "String", "HQ_API_USERNAME", "\"${project.ext.HQ_API_USERNAME}\"" buildConfigField "String", "HQ_API_PASSWORD", "\"${project.ext.HQ_API_PASSWORD}\"" buildConfigField "String", "FIREBASE_DATABASE_URL", "\"${project.ext.FIREBASE_DATABASE_URL}\"" - buildConfigField "String", "CCC_HOST", "\"connect.dimagi.com\"" + buildConfigField 'String', 'CCC_HOST', "\"connect.dimagi.com\"" - testInstrumentationRunner "org.commcare.CommCareJUnitRunner" + testInstrumentationRunner 'org.commcare.CommCareJUnitRunner' } buildFeatures { @@ -291,7 +290,7 @@ android { } applicationVariants.all { variant -> - if (variant.buildType.name == "release") { + if (variant.buildType.name == 'release') { variant.outputs.each { output -> output.versionCodeOverride = computeVersionCode() } @@ -372,7 +371,7 @@ android { } // Our flavours doesn't differ on a particular dimension yet, so just specify a dummy - flavorDimensions "dummy" + flavorDimensions 'dummy' productFlavors { commcare { // builds normal commcare @@ -381,27 +380,27 @@ android { lts { // long term support build of CommCare - applicationId "org.commcare.lts" + applicationId 'org.commcare.lts' project.ext.LTS_APP_ID = applicationId // setup content provider strings correctly to not conflict with other apps - def odkProviderStr = "org.commcare.lts.provider.odk" + def odkProviderStr = 'org.commcare.lts.provider.odk' manifestPlaceholders = [odkProvider: odkProviderStr] - buildConfigField "String", "CC_AUTHORITY", "\"${applicationId}\"" - buildConfigField "String", "ODK_AUTHORITY", "\"${odkProviderStr}\"" + buildConfigField 'String', 'CC_AUTHORITY', "\"${applicationId}\"" + buildConfigField 'String', 'ODK_AUTHORITY', "\"${odkProviderStr}\"" // set the app name - def applicationName = "CommCare LTS" - resValue "string", "application_name", applicationName + def applicationName = 'CommCare LTS' + resValue 'string', 'application_name', applicationName } cccStaging { - buildConfigField "String", "CCC_HOST", "\"connect-staging.dimagi.com\"" + buildConfigField 'String', 'CCC_HOST', '\'connect-staging.dimagi.com\'' // set the app name - def applicationName = "CommCare (CCC Staging)" - resValue "string", "application_name", applicationName + def applicationName = 'CommCare (CCC Staging)' + resValue 'string', 'application_name', applicationName } standalone { @@ -413,41 +412,41 @@ android { applicationId "org.commcare.${uniquePackageIdentifier}" project.ext.STANDALONE_APP_ID = applicationId - resValue "string", "application_name", appDisplayName + resValue 'string', 'application_name', appDisplayName def odkProviderStr = "org.commcare.${uniquePackageIdentifier}.provider.odk" manifestPlaceholders = [odkProvider: odkProviderStr] - buildConfigField "boolean", "IS_SINGLE_APP_BUILD", "true" - buildConfigField "boolean", "IS_CONSUMER_APP", isConsumerApp - buildConfigField "String", "CONSUMER_APP_USERNAME", "\"${consumerAppUsername}\"" - buildConfigField "String", "CONSUMER_APP_PASSWORD", "\"${consumerAppPassword}\"" + buildConfigField 'boolean', 'IS_SINGLE_APP_BUILD', 'true' + buildConfigField 'boolean', 'IS_CONSUMER_APP', isConsumerApp + buildConfigField 'String', 'CONSUMER_APP_USERNAME', "\"${consumerAppUsername}\"" + buildConfigField 'String', 'CONSUMER_APP_PASSWORD', "\"${consumerAppPassword}\"" // include this again so that the value get reloaded - buildConfigField "String", "CC_AUTHORITY", "\"${applicationId}\"" - buildConfigField "String", "ODK_AUTHORITY", "\"${odkProviderStr}\"" + buildConfigField 'String', 'CC_AUTHORITY', "\"${applicationId}\"" + buildConfigField 'String', 'ODK_AUTHORITY', "\"${odkProviderStr}\"" } } buildTypes { debug { - applicationIdSuffix ".debug" - def applicationId = "org.commcare.dalvik.debug" + applicationIdSuffix '.debug' + def applicationId = 'org.commcare.dalvik.debug' project.ext.COMMCARE_APP_ID = applicationId - def applicationName = "CommCare Debug" - resValue "string", "application_name", applicationName - def odkProviderStr = "org.commcare.dalvik.debug.provider.odk" + def applicationName = 'CommCare Debug' + resValue 'string', 'application_name', applicationName + def odkProviderStr = 'org.commcare.dalvik.debug.provider.odk' manifestPlaceholders = [odkProvider: odkProviderStr] minifyEnabled false debuggable true // used in test suite to build the prototype factory; otherwise unneeded - buildConfigField "String", "CC_AUTHORITY", "\"${applicationId}\"" - buildConfigField "String", "ODK_AUTHORITY", "\"${odkProviderStr}\"" - buildConfigField "String", "BUILD_DIR", fixEscaping("\"${buildDir}\"") - buildConfigField "String", "PROJECT_DIR", fixEscaping("\"${projectDir}\"") + buildConfigField 'String', 'CC_AUTHORITY', "\"${applicationId}\"" + buildConfigField 'String', 'ODK_AUTHORITY', "\"${odkProviderStr}\"" + buildConfigField 'String', 'BUILD_DIR', fixEscaping("\"${buildDir}\"") + buildConfigField 'String', 'PROJECT_DIR', fixEscaping("\"${projectDir}\"") // disable crashlytics - buildConfigField "boolean", "USE_CRASHLYTICS", "false" + buildConfigField 'boolean', 'USE_CRASHLYTICS', 'false' ext.enableCrashlytics = false } @@ -459,7 +458,7 @@ android { multiDexKeepProguard file('proguard-multidex.pro') // enable crashlytics - buildConfigField "boolean", "USE_CRASHLYTICS", "true" + buildConfigField 'boolean', 'USE_CRASHLYTICS', 'true' ext.enableCrashlytics = true } } @@ -501,7 +500,7 @@ downloadCCApp { doLast { if (execResult.exitValue != 0) { - throw new GradleException("exec failed; see output above") + throw new GradleException('exec failed; see output above') } } } @@ -509,11 +508,11 @@ downloadCCApp { // dynamic check at task execution time downloadCCApp { doLast { - if (ccAppId == "") { - throw new InvalidUserDataException("Please provide cc_app_id property (CommCare App ID) to be packaged with apk") + if (ccAppId == '') { + throw new InvalidUserDataException('Please provide cc_app_id property (CommCare App ID) to be packaged with apk') } - if (ccDomain == "") { - throw new InvalidUserDataException("Please provide cc_domain property (CommCare App Domain) to be packaged with apk") + if (ccDomain == '') { + throw new InvalidUserDataException('Please provide cc_domain property (CommCare App Domain) to be packaged with apk') } } } @@ -531,7 +530,7 @@ downloadRestoreFile { doLast { if (execResult.exitValue != 0) { - throw new GradleException("exec failed; see output above") + throw new GradleException('exec failed; see output above') } } } @@ -539,11 +538,11 @@ downloadRestoreFile { // dynamic check at task execution time downloadRestoreFile { doLast { - if (consumerAppUsername == "") { - throw new InvalidUserDataException("Please provide username for restore to be packaged with apk") + if (consumerAppUsername == '') { + throw new InvalidUserDataException('Please provide username for restore to be packaged with apk') } - if (consumerAppPassword == "") { - throw new InvalidUserDataException("Please provide password for restore to be packaged with apk") + if (consumerAppPassword == '') { + throw new InvalidUserDataException('Please provide password for restore to be packaged with apk') } } } @@ -551,7 +550,7 @@ downloadRestoreFile { // dynamically inject commcare app download into standalone build process tasks.whenTaskAdded { task -> if ((task.name == 'processStandaloneDebugResources' || - task.name == 'processStandaloneReleaseResources') && "true" == runDownloadScripts) { + task.name == 'processStandaloneReleaseResources') && 'true' == runDownloadScripts) { task.dependsOn downloadCCApp } if ((task.name == 'processStandaloneDebugResources' || @@ -570,45 +569,45 @@ static def numbersToLetters(String version) { StringBuilder words = new StringBuilder() for (String num : chars) { switch (num) { - case "1": words.append("one"); break - case "2": words.append("two"); break - case "3": words.append("three"); break - case "4": words.append("four"); break - case "5": words.append("five"); break - case "6": words.append("six"); break - case "7": words.append("seven"); break - case "8": words.append("eight"); break - case "9": words.append("nine"); break - case "0": words.append("zero"); break - default: break // skip non-numeric + case '1': words.append('one'); break + case '2': words.append('two'); break + case '3': words.append('three'); break + case '4': words.append('four'); break + case '5': words.append('five'); break + case '6': words.append('six'); break + case '7': words.append('seven'); break + case '8': words.append('eight'); break + case '9': words.append('nine'); break + case '0': words.append('zero'); break + default: break // skip non-numeric' } } return words.toString() } static def fixEscaping(String s) { - return s.replaceAll("\\\\", "\\\\\\\\") + return s.replaceAll('\\\\', '\\\\\\\\') } def getStandalonePackageIdentifier(ccDomainSafe, isConsumerApp) { if (project.hasProperty('application_name')) { - return ccDomainSafe + "." + cleanseAppNameForPackageIdentifier(application_name) - } else if ("true" == isConsumerApp) { - throw new InvalidUserDataException("An application_name property must be provided for consumer app standalone builds") + return ccDomainSafe + '.' + cleanseAppNameForPackageIdentifier(application_name) + } else if ('true' == isConsumerApp) { + throw new InvalidUserDataException('An application_name property must be provided for consumer app standalone builds') } else { return ccDomainSafe } } static def cleanseAppNameForPackageIdentifier(appName) { - return appName.replaceAll("[ '.:]", "").replaceAll("\\\\", "").toLowerCase() + return appName.replaceAll("[ '.:]", "").replaceAll('\\\\', '').toLowerCase() } def getStandaloneApplicationName(ccDomainSafe, isConsumerApp) { if (project.hasProperty('application_name')) { return application_name - } else if ("true" == isConsumerApp) { - throw new InvalidUserDataException("An application_name property must be provided for consumer app standalone builds") + } else if ('true' == isConsumerApp) { + throw new InvalidUserDataException('An application_name property must be provided for consumer app standalone builds') } else { return ccDomainSafe } @@ -617,20 +616,20 @@ def getStandaloneApplicationName(ccDomainSafe, isConsumerApp) { def getConsumerAppUsername(isConsumerApp) { if (project.hasProperty('username')) { return username - } else if ("true" == isConsumerApp) { - throw new InvalidUserDataException("A username property must be provided for consumer app standalone builds") + } else if ('true' == isConsumerApp) { + throw new InvalidUserDataException('A username property must be provided for consumer app standalone builds') } else { - return "" + return '' } } def getConsumerAppPassword(isConsumerApp) { if (project.hasProperty('password')) { return password - } else if ("true" == isConsumerApp) { - throw new InvalidUserDataException("A password property must be provided for consumer app standalone builds") + } else if ('true' == isConsumerApp) { + throw new InvalidUserDataException('A password property must be provided for consumer app standalone builds') } else { - return "" + return '' } } diff --git a/app/instrumentation-tests/src/org/commcare/androidTests/AppPreferenceSettingsTest.kt b/app/instrumentation-tests/src/org/commcare/androidTests/AppPreferenceSettingsTest.kt index ca920e9097..df5f161afb 100644 --- a/app/instrumentation-tests/src/org/commcare/androidTests/AppPreferenceSettingsTest.kt +++ b/app/instrumentation-tests/src/org/commcare/androidTests/AppPreferenceSettingsTest.kt @@ -10,7 +10,12 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions import androidx.test.espresso.intent.Intents.intended import androidx.test.espresso.intent.Intents.intending -import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.espresso.matcher.ViewMatchers.hasDescendant +import androidx.test.espresso.matcher.ViewMatchers.isChecked +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withClassName +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import org.commcare.annotations.BrowserstackTests @@ -26,7 +31,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @LargeTest @BrowserstackTests -class AppPreferenceSettingsTest: BaseTest() { +class AppPreferenceSettingsTest : BaseTest() { companion object { const val CCZ_NAME = "settings_sheet_tests.ccz" @@ -44,13 +49,13 @@ class AppPreferenceSettingsTest: BaseTest() { val settingName = "Fuzzy Search Matches" InstrumentationUtility.openOptionsMenu() onView(withText("Settings")) - .perform(click()) + .perform(click()) selectSetting(settingName) onView(withText("Enabled")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) onView(withText("Disabled")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) // Check enabled is selected by default matchSelectedPreference("Enabled") @@ -68,15 +73,15 @@ class AppPreferenceSettingsTest: BaseTest() { val settingName = "Auto Update Frequency" InstrumentationUtility.openOptionsMenu() onView(withText("Settings")) - .perform(click()) + .perform(click()) selectSetting(settingName) onView(withText("Never")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) onView(withText("Daily")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) onView(withText("Weekly")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) // Change setting and check their persistence changePreferenceAndCheckPersistence("Daily", settingName) @@ -91,7 +96,7 @@ class AppPreferenceSettingsTest: BaseTest() { InstrumentationUtility.login("settings.test.3", "123") InstrumentationUtility.openOptionsMenu() onView(withText("Settings")) - .perform(click()) + .perform(click()) selectSetting("Set Print Template") //Create a dummy file selection intent @@ -102,7 +107,7 @@ class AppPreferenceSettingsTest: BaseTest() { // Click on file fetch onView(withId(R.id.filefetch)) - .perform(click()) + .perform(click()) // Confirm that the file selection intent was called. intended(expectedIntent) @@ -110,26 +115,26 @@ class AppPreferenceSettingsTest: BaseTest() { private fun selectSetting(text: String) { onView(withText("CommCare > Settings")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) onView(withId(androidx.preference.R.id.recycler_view)) - .perform(RecyclerViewActions.scrollTo( - hasDescendant(withText(text)) + .perform(RecyclerViewActions.scrollTo( + hasDescendant(withText(text)) )) onView(withText(text)) - .perform(click()) + .perform(click()) } private fun cancelSettingChangeDialog() { onView(withText("CANCEL")) - .perform(click()) + .perform(click()) onView(withText("CommCare > Settings")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) } private fun changePreferenceAndCheckPersistence(newPref: String, setting: String) { // Change to new preference onView(withText(newPref)) - .perform(click()) + .perform(click()) //Rotate and check the selection persists. InstrumentationUtility.rotateLeft() @@ -142,8 +147,8 @@ class AppPreferenceSettingsTest: BaseTest() { private fun matchSelectedPreference(value: String) { onView(allOf( - withClassName(endsWith("AppCompatCheckedTextView")), - withText(value) + withClassName(endsWith("AppCompatCheckedTextView")), + withText(value) )).check(matches(isChecked())) } diff --git a/app/instrumentation-tests/src/org/commcare/utils/InstrumentationUtility.kt b/app/instrumentation-tests/src/org/commcare/utils/InstrumentationUtility.kt index b0a3241b46..7fc13ce7eb 100644 --- a/app/instrumentation-tests/src/org/commcare/utils/InstrumentationUtility.kt +++ b/app/instrumentation-tests/src/org/commcare/utils/InstrumentationUtility.kt @@ -15,17 +15,33 @@ import android.widget.TextView import androidx.annotation.IdRes import androidx.appcompat.app.AppCompatActivity import androidx.preference.PreferenceManager -import androidx.test.espresso.* -import androidx.test.espresso.Espresso.* -import androidx.test.espresso.action.ViewActions.* +import androidx.test.espresso.DataInteraction +import androidx.test.espresso.Espresso +import androidx.test.espresso.Espresso.onData +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu +import androidx.test.espresso.PerformException +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.ViewInteraction +import androidx.test.espresso.action.ViewActions.clearText +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.swipeUp +import androidx.test.espresso.action.ViewActions.typeText import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.Intents.intending import androidx.test.espresso.intent.matcher.IntentMatchers import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction import androidx.test.espresso.matcher.RootMatchers -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.espresso.matcher.ViewMatchers.hasSibling +import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withChild +import androidx.test.espresso.matcher.ViewMatchers.withClassName +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.util.HumanReadables import androidx.test.espresso.util.TreeIterables import androidx.test.platform.app.InstrumentationRegistry @@ -38,15 +54,15 @@ import org.commcare.CommCareInstrumentationTestApplication import org.commcare.dalvik.R import org.commcare.services.CommCareSessionService import org.commcare.utils.CustomMatchers.withChildViewCount +import org.hamcrest.CoreMatchers.anything +import org.hamcrest.CoreMatchers.startsWith import org.hamcrest.Matcher import org.hamcrest.Matchers -import org.hamcrest.Matchers.* import org.hamcrest.StringDescription import org.javarosa.core.io.StreamsUtil import org.junit.Assert.assertTrue import java.io.File import java.io.IOException -import java.util.* import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException @@ -81,26 +97,26 @@ object InstrumentationUtility { stubCcz(cczName) openOptionsMenu() onView(withText("Offline Install")) - .perform(click()) + .perform(click()) onView(withId(R.id.screen_multimedia_inflater_filefetch)) - .perform(click()) + .perform(click()) onView(withId(R.id.screen_multimedia_inflater_install)) - .perform(click()) + .perform(click()) } @JvmStatic fun uninstallCurrentApp() { openOptionsMenu() onView(withText("Go To App Manager")) - .perform(click()) + .perform(click()) clickListItem(R.id.apps_list_view, 0) onView(withText("Uninstall")) - .perform(click()) + .perform(click()) onView(withText("OK")) - .inRoot(RootMatchers.isDialog()) - .perform(click()) + .inRoot(RootMatchers.isDialog()) + .perform(click()) onView(withId(R.id.install_app_button)) - .perform(click()) + .perform(click()) } @JvmStatic @@ -109,21 +125,21 @@ object InstrumentationUtility { enterText(R.id.edit_password, password) onView(isRoot()).perform(swipeUp()) onView(withId(R.id.login_button)) - .perform(click()) + .perform(click()) } @JvmStatic fun openModule(text: String) { onView(withText("Start")) - .perform(click()) + .perform(click()) onView(withText(text)) - .perform(click()) + .perform(click()) } @JvmStatic fun openModule(module: Int) { onView(withText("Start")) - .perform(click()) + .perform(click()) clickListItem(R.id.screen_suite_menu_list, module) } @@ -145,9 +161,9 @@ object InstrumentationUtility { for (i in 0..3) { openOptionsMenu() onView(withText("About CommCare")) - .perform(click()) + .perform(click()) onView(withText("OK")) - .perform(click()) + .perform(click()) } } @@ -159,9 +175,9 @@ object InstrumentationUtility { @JvmStatic fun clickListItem(@IdRes resId: Int, position: Int) { onData(anything()) - .inAdapterView(withId(resId)) - .atPosition(position) - .perform(click()) + .inAdapterView(withId(resId)) + .atPosition(position) + .perform(click()) } /** @@ -173,9 +189,9 @@ object InstrumentationUtility { @JvmStatic fun getSubViewInListItem(@IdRes listId: Int, position: Int, @IdRes subviewId: Int): DataInteraction { return onData(anything()) - .inAdapterView(withId(listId)) - .atPosition(position) - .onChildView(withId(subviewId)) + .inAdapterView(withId(listId)) + .atPosition(position) + .onChildView(withId(subviewId)) } /** @@ -185,7 +201,7 @@ object InstrumentationUtility { @JvmStatic fun openFirstIncompleteForm() { onView(withText(startsWith("Incomplete"))) - .perform(click()) + .perform(click()) clickListItem(R.id.screen_entity_select_list, 0) } @@ -193,9 +209,9 @@ object InstrumentationUtility { fun logout() { gotoHome() onView(withId(R.id.home_gridview_buttons)) - .perform(swipeUp()) + .perform(swipeUp()) onView(withText("Log out of CommCare")) - .perform(click()) + .perform(click()) } /** @@ -207,7 +223,7 @@ object InstrumentationUtility { var intentCallback = onImageCaptureIntentSent() IntentMonitorRegistry.getInstance().addIntentCallback(intentCallback) onView(withText(R.string.capture_image)) - .perform(click()) + .perform(click()) IntentMonitorRegistry.getInstance().removeIntentCallback(intentCallback) } @@ -217,7 +233,7 @@ object InstrumentationUtility { @JvmStatic fun nextPage() { onView(withId(R.id.nav_btn_next)) - .perform(click()) + .perform(click()) } /** @@ -235,9 +251,9 @@ object InstrumentationUtility { @JvmStatic fun sleep(seconds: Int) { onView(isRoot()) - .perform(sleep( - TimeUnit.SECONDS.toMillis(seconds.toLong()) - )) + .perform(sleep( + TimeUnit.SECONDS.toMillis(seconds.toLong()) + )) } /** @@ -246,10 +262,10 @@ object InstrumentationUtility { @JvmStatic fun matchChildCount(parent: Class<*>, child: Class<*>, count: Int) { onView(withClassName(Matchers.`is`(parent.canonicalName))) - .check(matches( - withChildViewCount(count, - withClassName(Matchers.`is`(child.canonicalName))) - )) + .check(matches( + withChildViewCount(count, + withClassName(Matchers.`is`(child.canonicalName))) + )) } /** @@ -259,9 +275,9 @@ object InstrumentationUtility { @JvmStatic fun getListSize(@IdRes resId: Int): Int { val application = InstrumentationRegistry - .getInstrumentation() - .targetContext - .applicationContext as CommCareInstrumentationTestApplication + .getInstrumentation() + .targetContext + .applicationContext as CommCareInstrumentationTestApplication val activity = application.currentActivity val listView = activity.findViewById(resId) return listView.adapter.count @@ -290,21 +306,22 @@ object InstrumentationUtility { @JvmStatic fun enterText(@IdRes editTextId: Int, text: String) { onView(withId(editTextId)) - .perform(clearText()) + .perform(clearText()) onView(withId(editTextId)) - .perform(typeText(text)) + .perform(typeText(text)) Espresso.closeSoftKeyboard() } fun enterText(text: String) { onView(withClassName(Matchers.endsWith("EditText"))) - .perform(typeText((text))) + .perform(typeText((text))) Espresso.closeSoftKeyboard() } + /** * A utility to match the text present in a textfield */ - fun matchTypedText(@IdRes editTextId: Int, text: String){ + fun matchTypedText(@IdRes editTextId: Int, text: String) { onView(withId(editTextId)).check(matches(withText(text))) } @@ -353,6 +370,7 @@ object InstrumentationUtility { val mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) mDevice.pressBack() } + /** * The method does following in order: * 1. Closes keyboard. @@ -364,7 +382,7 @@ object InstrumentationUtility { Espresso.closeSoftKeyboard() Espresso.pressBack() onView(withText(backOption)) - .perform(click()) + .perform(click()) } /** @@ -376,11 +394,11 @@ object InstrumentationUtility { fun selectOptionItem(matcher: Matcher) { openOptionsMenu() onView(matcher) - .perform(click()) + .perform(click()) } @JvmStatic - fun stubIntentWithAction(action : String) { + fun stubIntentWithAction(action: String) { Intents.intending(IntentMatchers.hasAction(action)) .respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, Intent())) } @@ -394,7 +412,7 @@ object InstrumentationUtility { @JvmStatic fun assertCurrentActivity(clazz: Class) { val application = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext - as CommCareInstrumentationTestApplication + as CommCareInstrumentationTestApplication val activity = application.currentActivity assert(clazz.isInstance(activity), "Current Activity is ${activity.localClassName}") } @@ -461,14 +479,12 @@ object InstrumentationUtility { } } - - /** * A utility to verify Form Fields with their values */ fun verifyFormCellAndValue(cellData: String, value: String) { - val result : Boolean = onView( + val result: Boolean = onView( Matchers.allOf( withId(R.id.detail_type_text), withText(cellData), @@ -491,9 +507,9 @@ object InstrumentationUtility { /** * utility to search a case in Form list and select the same */ - fun searchCaseAndSelect(text: String){ + fun searchCaseAndSelect(text: String) { onView(withId(R.id.search_action_bar)).perform(click()) - enterText(androidx.appcompat.R.id.search_src_text,text) + enterText(androidx.appcompat.R.id.search_src_text, text) onView(withId(R.id.screen_entity_detail_list)).isPresent() clickListItem(R.id.screen_entity_select_list, 0) }