diff --git a/androidshared/src/main/java/org/odk/collect/androidshared/system/SavedInstanceStateProvider.kt b/androidshared/src/main/java/org/odk/collect/androidshared/system/SavedInstanceStateProvider.kt deleted file mode 100644 index 660997fe230..00000000000 --- a/androidshared/src/main/java/org/odk/collect/androidshared/system/SavedInstanceStateProvider.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.odk.collect.androidshared.system - -import android.os.Bundle - -interface SavedInstanceStateProvider { - fun getState(savedInstanceState: Bundle?): Bundle? -} diff --git a/async/src/main/java/org/odk/collect/async/SchedulerAsyncTaskMimic.kt b/async/src/main/java/org/odk/collect/async/SchedulerAsyncTaskMimic.kt new file mode 100644 index 00000000000..8f1f2d680f3 --- /dev/null +++ b/async/src/main/java/org/odk/collect/async/SchedulerAsyncTaskMimic.kt @@ -0,0 +1,76 @@ +package org.odk.collect.async + +import android.os.AsyncTask + +/** + * Basic reimplementation of the [AsyncTask] API that allows an [AsyncTask] implementation to + * use [Scheduler] with minimal internal and external changes. + */ +abstract class SchedulerAsyncTaskMimic(private val scheduler: Scheduler) { + + @Volatile + private var status: AsyncTask.Status = AsyncTask.Status.PENDING + + @Volatile + private var cancelled = false + + protected abstract fun onPreExecute() + protected abstract fun doInBackground(vararg params: Params): Result + protected abstract fun onProgressUpdate(vararg values: Progress) + protected abstract fun onPostExecute(result: Result) + protected abstract fun onCancelled() + + /** + * Execute [doInBackground] on calling thread and return the [Result] value. Should probably + * not be used as a replacement for [AsyncTask.get] (unless it's for testing purposes). + */ + fun executeSynchronously(vararg params: Params): Result { + return doInBackground(*params) + } + + fun execute(vararg params: Params): SchedulerAsyncTaskMimic { + status = AsyncTask.Status.RUNNING + onPreExecute() + + scheduler.immediate( + background = { + doInBackground(*params) + }, + foreground = { result -> + if (cancelled) { + onCancelled() + } else { + onPostExecute(result) + } + + status = AsyncTask.Status.FINISHED + } + ) + + return this + } + + fun getStatus(): AsyncTask.Status { + return status + } + + /** + * Unlike [AsyncTask.cancel], this does not offer the option to attempt to interrupt the + * background thread running [doInBackground]. Calling [cancel] will allow [doInBackground] + * to finish, but will prevent [onPostExecute] from running ([onCancelled] will be run + * instead). + */ + fun cancel() { + cancelled = true + } + + fun isCancelled(): Boolean { + return cancelled + } + + protected fun publishProgress(vararg values: Progress) { + scheduler.immediate( + foreground = { onProgressUpdate(*values) } + ) + } +} diff --git a/collect_app/build.gradle b/collect_app/build.gradle index 98e781c2c00..34cc142d402 100644 --- a/collect_app/build.gradle +++ b/collect_app/build.gradle @@ -200,15 +200,6 @@ android { } } - - sourceSets { - androidTest { - java.srcDirs += "src/commonTest/java" - } - test { - java.srcDirs += "src/commonTest/java" - } - } lint { abortOnError true checkDependencies true @@ -373,6 +364,7 @@ dependencies { exclude group: 'org.robolectric' // Some tests in `collect_app` don't work with newer Robolectric } testImplementation(project(":shadows")) + testImplementation(project(":test-forms")) testImplementation Dependencies.robolectric @@ -389,6 +381,7 @@ dependencies { testImplementation Dependencies.androidx_test_core_ktx androidTestImplementation project(':androidtest') + androidTestImplementation project(':test-forms') androidTestImplementation Dependencies.mockito_android androidTestImplementation Dependencies.androidx_test_ext_junit diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/AudioRecordingTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/AudioRecordingTest.java index da276a15bac..4b3d2550ada 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/AudioRecordingTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/AudioRecordingTest.java @@ -1,6 +1,6 @@ package org.odk.collect.android.feature.formentry; -import static org.odk.collect.android.support.FileUtils.copyFileFromAssets; +import static org.odk.collect.android.utilities.FileUtils.copyFileFromResources; import android.app.Application; @@ -10,12 +10,12 @@ import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.runner.RunWith; -import org.odk.collect.android.support.rules.CollectTestRule; import org.odk.collect.android.support.TestDependencies; -import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.android.support.pages.FormEntryPage; import org.odk.collect.android.support.pages.MainMenuPage; import org.odk.collect.android.support.pages.OkDialog; +import org.odk.collect.android.support.rules.CollectTestRule; +import org.odk.collect.android.support.rules.TestRuleChain; import org.odk.collect.audiorecorder.recording.AudioRecorder; import org.odk.collect.audiorecorder.testsupport.StubAudioRecorder; @@ -35,7 +35,7 @@ public AudioRecorder providesAudioRecorder(Application application) { File stubRecording = File.createTempFile("test", ".m4a"); stubRecording.deleteOnExit(); - copyFileFromAssets("media/test.m4a", stubRecording.getAbsolutePath()); + copyFileFromResources("media/test.m4a", stubRecording.getAbsolutePath()); stubAudioRecorderViewModel = new StubAudioRecorder(stubRecording.getAbsolutePath()); } catch (IOException e) { throw new RuntimeException(e); diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/BackgroundAudioRecordingTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/BackgroundAudioRecordingTest.java index 05b1dc84ef7..7d651158843 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/BackgroundAudioRecordingTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/BackgroundAudioRecordingTest.java @@ -4,7 +4,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.odk.collect.android.support.FileUtils.copyFileFromAssets; +import static org.odk.collect.android.utilities.FileUtils.copyFileFromResources; import android.Manifest; import android.app.Activity; @@ -56,7 +56,7 @@ public AudioRecorder providesAudioRecorder(Application application) { File stubRecording = File.createTempFile("test", ".m4a"); stubRecording.deleteOnExit(); - copyFileFromAssets("media/test.m4a", stubRecording.getAbsolutePath()); + copyFileFromResources("media/test.m4a", stubRecording.getAbsolutePath()); stubAudioRecorderViewModel = new StubAudioRecorder(stubRecording.getAbsolutePath()); } catch (IOException e) { throw new RuntimeException(e); diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ExternalAudioRecordingTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ExternalAudioRecordingTest.java index 0171286395f..a4fab7ad98f 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ExternalAudioRecordingTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ExternalAudioRecordingTest.java @@ -2,7 +2,7 @@ import static androidx.test.espresso.intent.Intents.intending; import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction; -import static org.odk.collect.android.support.FileUtils.copyFileFromAssets; +import static org.odk.collect.android.utilities.FileUtils.copyFileFromResources; import android.app.Activity; import android.app.Instrumentation; @@ -39,7 +39,7 @@ public class ExternalAudioRecordingTest { try { File stubRecording = File.createTempFile("test", ".m4a"); stubRecording.deleteOnExit(); - copyFileFromAssets("media/test.m4a", stubRecording.getAbsolutePath()); + copyFileFromResources("media/test.m4a", stubRecording.getAbsolutePath()); Intent intent = new Intent(); intent.setData(Uri.fromFile(stubRecording)); diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FieldListUpdateTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FieldListUpdateTest.java index 63c4d150771..d45faf77daa 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FieldListUpdateTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/FieldListUpdateTest.java @@ -43,6 +43,7 @@ import static org.odk.collect.android.support.matchers.CustomMatchers.withIndex; import android.app.Activity; +import android.app.Application; import android.app.Instrumentation; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -60,7 +61,7 @@ import org.junit.Test; import org.junit.rules.RuleChain; import org.odk.collect.android.R; -import org.odk.collect.android.TestSettingsProvider; +import org.odk.collect.android.injection.DaggerUtils; import org.odk.collect.android.preferences.GuidanceHint; import org.odk.collect.android.storage.StoragePathProvider; import org.odk.collect.android.support.pages.FormEntryPage; @@ -323,7 +324,11 @@ public void questionsAppearingBeforeCurrentBinaryQuestion_ShouldNotChangeFocus() @Test public void changeInValueUsedInGuidanceHint_ShouldChangeGuidanceHintText() { - TestSettingsProvider.getUnprotectedSettings().save(ProjectKeys.KEY_GUIDANCE_HINT, GuidanceHint.YES.toString()); + DaggerUtils.getComponent(ApplicationProvider.getApplicationContext()) + .settingsProvider() + .getUnprotectedSettings() + .save(ProjectKeys.KEY_GUIDANCE_HINT, GuidanceHint.YES.toString()); + jumpToGroupWithText("Guidance hint"); onView(withText(startsWith("Source11"))).perform(click()); diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/GuidanceHintFormTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/GuidanceHintFormTest.java index ae3ec0b5820..2efdb942eb1 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/GuidanceHintFormTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/GuidanceHintFormTest.java @@ -8,13 +8,16 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.CoreMatchers.not; +import android.app.Application; + +import androidx.test.core.app.ApplicationProvider; import androidx.test.espresso.matcher.ViewMatchers; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; import org.odk.collect.android.R; -import org.odk.collect.android.TestSettingsProvider; +import org.odk.collect.android.injection.DaggerUtils; import org.odk.collect.android.preferences.GuidanceHint; import org.odk.collect.android.support.rules.BlankFormTestRule; import org.odk.collect.android.support.rules.TestRuleChain; @@ -36,7 +39,11 @@ public void guidanceHint_ShouldBeHiddenByDefault() { @Test public void guidanceHint_ShouldBeDisplayedWhenSettingSetToYes() { - TestSettingsProvider.getUnprotectedSettings().save(ProjectKeys.KEY_GUIDANCE_HINT, GuidanceHint.YES.toString()); + DaggerUtils.getComponent(ApplicationProvider.getApplicationContext()) + .settingsProvider() + .getUnprotectedSettings() + .save(ProjectKeys.KEY_GUIDANCE_HINT, GuidanceHint.YES.toString()); + // jump to force recreation of the view after the settings change onView(withId(R.id.menu_goto)).perform(click()); onView(withId(R.id.jumpBeginningButton)).perform(click()); @@ -46,7 +53,11 @@ public void guidanceHint_ShouldBeDisplayedWhenSettingSetToYes() { @Test public void guidanceHint_ShouldBeDisplayedAfterClickWhenSettingSetToYesCollapsed() { - TestSettingsProvider.getUnprotectedSettings().save(ProjectKeys.KEY_GUIDANCE_HINT, GuidanceHint.YES_COLLAPSED.toString()); + DaggerUtils.getComponent(ApplicationProvider.getApplicationContext()) + .settingsProvider() + .getUnprotectedSettings() + .save(ProjectKeys.KEY_GUIDANCE_HINT, GuidanceHint.YES_COLLAPSED.toString()); + // jump to force recreation of the view after the settings change onView(withId(R.id.menu_goto)).perform(click()); onView(withId(R.id.jumpBeginningButton)).perform(click()); diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/IntentGroupTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/IntentGroupTest.java index adcc85adc09..5147b0655af 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/IntentGroupTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/IntentGroupTest.java @@ -37,7 +37,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.odk.collect.android.support.matchers.CustomMatchers.withIndex; -import static org.odk.collect.android.support.FileUtils.copyFileFromAssets; +import static org.odk.collect.android.utilities.FileUtils.copyFileFromResources; import android.app.Activity; import android.app.Instrumentation; @@ -251,7 +251,7 @@ private Uri createTempFile(String name, String extension) throws IOException { .getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); File file = File.createTempFile(name, extension, downloadsDir); - copyFileFromAssets("media" + File.separator + name + "." + extension, file.getPath()); + copyFileFromResources("media" + File.separator + name + "." + extension, file.getPath()); return getUriForFile(file); } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ProcessRestoreTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ProcessRestoreTest.kt deleted file mode 100644 index a5a9df2b2f1..00000000000 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/ProcessRestoreTest.kt +++ /dev/null @@ -1,64 +0,0 @@ -package org.odk.collect.android.feature.formentry - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Rule -import org.junit.Test -import org.junit.rules.RuleChain -import org.junit.runner.RunWith -import org.odk.collect.android.R -import org.odk.collect.android.support.CollectHelpers -import org.odk.collect.android.support.pages.FormEntryPage -import org.odk.collect.android.support.pages.FormHierarchyPage -import org.odk.collect.android.support.pages.Page -import org.odk.collect.android.support.rules.FormEntryActivityTestRule -import org.odk.collect.android.support.rules.TestRuleChain - -@RunWith(AndroidJUnit4::class) -class ProcessRestoreTest { - - private val rule = FormEntryActivityTestRule() - - @get:Rule - val ruleChain: RuleChain = TestRuleChain.chain().around(rule) - - @Test - fun whenProcessIsKilledAndRestoredDuringFormEntry_returnsToHierarchy() { - rule.setUpProjectAndCopyForm("one-question.xml") - .fillNewForm("one-question.xml", "One Question") - .answerQuestion("what is your age", "123") - .let { simulateProcessRestore(FormHierarchyPage("One Question")) } - - .assertText("123") - .pressBack(FormEntryPage("One Question")) - .assertQuestion("what is your age") - } - - @Test - fun whenProcessIsKilledAndRestoredDuringFormEntry_andThereADialogFragmentOpen_returnsToHierarchy() { - rule.setUpProjectAndCopyForm("all-widgets.xml") - .fillNewForm("all-widgets.xml", "All widgets") - .clickGoToArrow() - .clickOnGroup("Select one widgets") - .clickOnQuestion("Select one from map widget") - .clickOnString(org.odk.collect.strings.R.string.select_place) - .let { simulateProcessRestore(FormHierarchyPage("All widgets")) } - - .pressBack(FormEntryPage("All widgets")) - .assertQuestion("Welcome to ODK Collect! This form showcases the different available question types (widgets).") - } - - /** - * Simulate a "process restore" case where an app in the background is killed by Android - * to reclaim memory, change permissions etc and then the process is recreated (backstack etc) - * when navigated back to - */ - private fun > simulateProcessRestore(destination: Page): Page { - rule.navigateAwayFromActivity() - rule.destroyActivity() - - CollectHelpers.simulateProcessRestart() - rule.restoreActivity() - - return destination.assertOnPage() - } -} diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SavePointTest.kt b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SavePointTest.kt index acc73012fab..40179c9a395 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SavePointTest.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/SavePointTest.kt @@ -7,7 +7,6 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.RuleChain import org.junit.runner.RunWith -import org.odk.collect.android.support.CollectHelpers import org.odk.collect.android.support.StorageUtils import org.odk.collect.android.support.pages.AppClosedPage import org.odk.collect.android.support.pages.FormEntryPage @@ -223,18 +222,15 @@ class SavePointTest { * being battery dying). */ private fun simulateBatteryDeath(): FormEntryActivityTestRule { - CollectHelpers.simulateProcessRestart() - return rule + return rule.simulateProcessRestart() } /** * Simulate a "process death" case where an app in the background is killed */ private fun simulateProcessDeath(): FormEntryActivityTestRule { - rule.navigateAwayFromActivity() + return rule.navigateAwayFromActivity() .destroyActivity() - - CollectHelpers.simulateProcessRestart() - return rule + .simulateProcessRestart() } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormNavigationTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormNavigationTest.java index 1ddcdf91dd8..07cc0d5fd9d 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormNavigationTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormNavigationTest.java @@ -18,6 +18,8 @@ import static junit.framework.Assert.assertEquals; +import static org.mockito.Mockito.mock; + import android.app.Application; import androidx.test.core.app.ApplicationProvider; @@ -129,7 +131,7 @@ private void testIndices(String formName, String[] expectedIndices) throws Execu Timber.i(e); } - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath(formName), null, null, formEntryControllerFactory); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath(formName), null, null, formEntryControllerFactory, mock()); formLoaderTask.setFormLoaderListener(new FormLoaderListener() { @Override public void loadingComplete(FormLoaderTask task, FormDef fd, String warningMsg) { @@ -162,7 +164,7 @@ public void onProgressStep(String stepMessage) { } }); - formLoaderTask.execute(formPath(formName)).get(); + formLoaderTask.executeSynchronously(formPath(formName)); } /** diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormUtilsTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormUtilsTest.java index 9c725ae2825..80176e90d1a 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormUtilsTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/forms/FormUtilsTest.java @@ -1,5 +1,6 @@ package org.odk.collect.android.instrumented.forms; +import static org.mockito.Mockito.mock; import static org.odk.collect.android.support.StorageUtils.copyFormToStorage; import org.javarosa.core.model.FormDef; @@ -48,8 +49,8 @@ public void setUp() throws IOException { public void sessionRootTranslatorOrderDoesNotMatter() throws Exception { final String formPath = new StoragePathProvider().getOdkDirPath(StorageSubdirectory.FORMS) + File.separator + BASIC_FORM; // Load the form in order to populate the ReferenceManager - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); - formLoaderTask.execute(formPath).get(); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory, mock()); + formLoaderTask.executeSynchronously(formPath); final File formXml = new File(formPath); final File formMediaDir = FileUtils.getFormMediaDir(formXml); diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/tasks/FormLoaderTaskTest.java b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/tasks/FormLoaderTaskTest.java index af2476aef5b..1a782c9c131 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/tasks/FormLoaderTaskTest.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/instrumented/tasks/FormLoaderTaskTest.java @@ -2,6 +2,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.mock; import android.app.Application; @@ -66,8 +67,8 @@ public FormEntryController create(FormDef formDef) { @Test public void loadFormWithSecondaryCSV() throws Exception { final String formPath = storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS) + File.separator + SECONDARY_INSTANCE_EXTERNAL_CSV_FORM; - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); - FormLoaderTask.FECWrapper wrapper = formLoaderTask.execute(formPath).get(); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory, mock()); + FormLoaderTask.FECWrapper wrapper = formLoaderTask.executeSynchronously(formPath); Assert.assertNotNull(wrapper); } @@ -75,16 +76,16 @@ public void loadFormWithSecondaryCSV() throws Exception { @Test public void loadSearchFromExternalCSV() throws Exception { final String formPath = storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS) + File.separator + SIMPLE_SEARCH_EXTERNAL_CSV_FORM; - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); - FormLoaderTask.FECWrapper wrapper = formLoaderTask.execute(formPath).get(); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory, mock()); + FormLoaderTask.FECWrapper wrapper = formLoaderTask.executeSynchronously(formPath); assertThat(wrapper, notNullValue()); } @Test public void loadSearchFromexternalCsvLeavesFileUnchanged() throws Exception { final String formPath = storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS) + File.separator + SIMPLE_SEARCH_EXTERNAL_CSV_FORM; - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); - FormLoaderTask.FECWrapper wrapper = formLoaderTask.execute(formPath).get(); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory, mock()); + FormLoaderTask.FECWrapper wrapper = formLoaderTask.executeSynchronously(formPath); Assert.assertNotNull(wrapper); Assert.assertNotNull(wrapper.getController()); @@ -98,8 +99,8 @@ public void loadSearchFromexternalCsvLeavesFileUnchanged() throws Exception { public void loadSearchFromExternalCSVmultipleTimes() throws Exception { final String formPath = storagePathProvider.getOdkDirPath(StorageSubdirectory.FORMS) + File.separator + SIMPLE_SEARCH_EXTERNAL_CSV_FORM; // initial load with side effects - FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); - FormLoaderTask.FECWrapper wrapper = formLoaderTask.execute(formPath).get(); + FormLoaderTask formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory, mock()); + FormLoaderTask.FECWrapper wrapper = formLoaderTask.executeSynchronously(formPath); Assert.assertNotNull(wrapper); Assert.assertNotNull(wrapper.getController()); @@ -109,8 +110,8 @@ public void loadSearchFromExternalCSVmultipleTimes() throws Exception { long dbLastModified = dbFile.lastModified(); // subsequent load should succeed despite side effects from import - formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory); - wrapper = formLoaderTask.execute(formPath).get(); + formLoaderTask = new FormLoaderTask(formPath, null, null, formEntryControllerFactory, mock()); + wrapper = formLoaderTask.executeSynchronously(formPath); Assert.assertNotNull(wrapper); Assert.assertNotNull(wrapper.getController()); Assert.assertEquals("expected file modification timestamp to be unchanged", dbLastModified, dbFile.lastModified()); diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/FileUtils.java b/collect_app/src/androidTest/java/org/odk/collect/android/support/FileUtils.java deleted file mode 100644 index 358fe9333b8..00000000000 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/FileUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.odk.collect.android.support; - -import android.content.res.AssetManager; - -import androidx.test.platform.app.InstrumentationRegistry; - -import org.apache.commons.io.IOUtils; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public final class FileUtils { - - private FileUtils() { - } - - public static void copyFileFromAssets(String fileSourcePath, String fileDestPath) throws IOException { - AssetManager assetManager = InstrumentationRegistry.getInstrumentation().getContext().getAssets(); - try (InputStream input = assetManager.open(fileSourcePath); - OutputStream output = new FileOutputStream(fileDestPath)) { - IOUtils.copy(input, output); - } - } -} diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/StorageUtils.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/StorageUtils.kt index e310f02dda1..5f59d13fd26 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/StorageUtils.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/StorageUtils.kt @@ -17,11 +17,13 @@ package org.odk.collect.android.support import android.app.Application import androidx.test.core.app.ApplicationProvider +import androidx.test.platform.app.InstrumentationRegistry import org.apache.commons.csv.CSVFormat import org.apache.commons.csv.CSVRecord import org.odk.collect.android.formmanagement.LocalFormUseCases import org.odk.collect.android.injection.DaggerUtils import org.odk.collect.android.storage.StorageSubdirectory +import org.odk.collect.android.utilities.FileUtils import java.io.File import java.io.FileReader import java.io.IOException @@ -83,13 +85,20 @@ object StorageUtils { fun copyInstance(instanceFileName: String, projectName: String) { val instanceDirPath = getInstancesDirPath(projectName) + instanceFileName.split("\\.".toRegex()).toTypedArray()[0] File(instanceDirPath).mkdir() - FileUtils.copyFileFromAssets("instances/$instanceFileName", "$instanceDirPath/$instanceFileName") + FileUtils.copyFileFromAssets( + InstrumentationRegistry.getInstrumentation().getContext(), + "$instanceDirPath/$instanceFileName", + "instances/$instanceFileName" + ) } @Throws(IOException::class) private fun copyForm(formFilename: String, copyTo: String, projectName: String): String { val pathname = getFormsDirPath(projectName) + copyTo - FileUtils.copyFileFromAssets("forms/$formFilename", pathname) + FileUtils.copyFileFromResources( + "forms/$formFilename", + pathname + ) return pathname } @@ -98,7 +107,10 @@ object StorageUtils { val mediaPathName = getFormsDirPath(projectName) + formFilename.replace(".xml", "") + org.odk.collect.android.utilities.FileUtils.MEDIA_SUFFIX + "/" org.odk.collect.android.utilities.FileUtils.checkMediaPath(File(mediaPathName)) for (mediaFilePath in mediaFilePaths) { - FileUtils.copyFileFromAssets("media/$mediaFilePath", mediaPathName + getMediaFileName(mediaFilePath)) + FileUtils.copyFileFromResources( + "media/$mediaFilePath", + mediaPathName + getMediaFileName(mediaFilePath) + ) } } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/StubOpenRosaServer.java b/collect_app/src/androidTest/java/org/odk/collect/android/support/StubOpenRosaServer.java index 7c5f5d0006b..9294de82d69 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/StubOpenRosaServer.java +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/StubOpenRosaServer.java @@ -1,13 +1,11 @@ package org.odk.collect.android.support; +import static org.odk.collect.android.utilities.FileUtils.getResourceAsStream; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; -import android.content.res.AssetManager; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.test.platform.app.InstrumentationRegistry; import org.jetbrains.annotations.NotNull; import org.odk.collect.android.openrosa.CaseInsensitiveEmptyHeaders; @@ -238,13 +236,12 @@ private InputStream getManifestResponse(@NonNull URI uri) throws IOException { .append("\n"); for (String mediaFile : xformItem.getMediaFiles()) { - AssetManager assetManager = InstrumentationRegistry.getInstrumentation().getContext().getAssets(); String mediaFileHash; if (randomHash) { mediaFileHash = RandomString.randomString(8); } else { - mediaFileHash = Md5.getMd5Hash(assetManager.open("media/" + mediaFile)); + mediaFileHash = Md5.getMd5Hash(getResourceAsStream("media/" + mediaFile)); } stringBuilder @@ -270,17 +267,13 @@ private InputStream getManifestResponse(@NonNull URI uri) throws IOException { @NotNull private InputStream getFormXML(String formID) throws IOException { String xmlPath = forms.get(Integer.parseInt(formID)).getFormXML(); - - AssetManager assetManager = InstrumentationRegistry.getInstrumentation().getContext().getAssets(); - return assetManager.open("forms/" + xmlPath); + return getResourceAsStream("forms/" + xmlPath); } @NotNull private InputStream getMediaFile(URI uri) throws IOException { String mediaFileName = uri.getQuery().split("=")[1]; - - AssetManager assetManager = InstrumentationRegistry.getInstrumentation().getContext().getAssets(); - return assetManager.open("media/" + mediaFileName); + return getResourceAsStream("media/" + mediaFileName); } private static class XFormItem { diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/Page.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/Page.kt index 0b292629076..5cb1ac89e4a 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/Page.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/pages/Page.kt @@ -44,6 +44,7 @@ import org.odk.collect.android.support.actions.RotateAction import org.odk.collect.android.support.matchers.CustomMatchers.withIndex import org.odk.collect.androidshared.ui.ToastUtils.popRecordedToasts import org.odk.collect.strings.localization.getLocalizedString +import org.odk.collect.testshared.EspressoHelpers import org.odk.collect.testshared.RecyclerViewMatcher import timber.log.Timber import java.io.File @@ -86,7 +87,7 @@ abstract class Page> { return destination.assertOnPage() } - fun assertTexts(vararg texts: String?): T { + fun assertTexts(vararg texts: String): T { closeSoftKeyboard() for (text in texts) { assertText(text) @@ -99,8 +100,8 @@ abstract class Page> { return this as T } - fun assertText(text: String?): T { - onView(allOf(withText(text), withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))).check(matches(not(doesNotExist()))) + fun assertText(text: String): T { + EspressoHelpers.assertText(text) return this as T } @@ -380,7 +381,7 @@ abstract class Page> { wait250ms() // https://github.com/android/android-test/issues/444 } - protected fun waitForText(text: String?) { + protected fun waitForText(text: String) { waitFor { assertText(text) } } @@ -403,7 +404,7 @@ abstract class Page> { } fun clickOnContentDescription(string: Int): T { - onView(withContentDescription(string)).perform(click()) + EspressoHelpers.clickOnContentDescription(string) return this as T } diff --git a/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/FormEntryActivityTestRule.kt b/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/FormEntryActivityTestRule.kt index b40f544fdb5..4ddab4eb577 100644 --- a/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/FormEntryActivityTestRule.kt +++ b/collect_app/src/androidTest/java/org/odk/collect/android/support/rules/FormEntryActivityTestRule.kt @@ -3,7 +3,6 @@ package org.odk.collect.android.support.rules import android.app.Activity import android.app.Application import android.content.Intent -import android.os.Bundle import androidx.lifecycle.Lifecycle import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider @@ -12,14 +11,12 @@ import org.odk.collect.android.activities.FormFillingActivity import org.odk.collect.android.external.FormsContract import org.odk.collect.android.formmanagement.FormFillingIntentFactory import org.odk.collect.android.injection.DaggerUtils -import org.odk.collect.android.injection.config.AppDependencyModule import org.odk.collect.android.storage.StorageSubdirectory import org.odk.collect.android.support.CollectHelpers import org.odk.collect.android.support.StorageUtils import org.odk.collect.android.support.pages.FormEntryPage import org.odk.collect.android.support.pages.FormHierarchyPage import org.odk.collect.android.support.pages.Page -import org.odk.collect.androidshared.system.SavedInstanceStateProvider import org.odk.collect.androidtest.ActivityScenarioExtensions.saveInstanceState import timber.log.Timber import java.io.IOException @@ -29,20 +26,6 @@ class FormEntryActivityTestRule : ExternalResource() { private lateinit var intent: Intent private lateinit var scenario: ActivityScenario - private var outState: Bundle? = null - - private val savedInstanceStateProvider = InMemSavedInstanceStateProvider() - - override fun before() { - super.before() - - CollectHelpers.overrideAppDependencyModule(object : AppDependencyModule() { - override fun providesSavedInstanceStateProvider(): SavedInstanceStateProvider { - return savedInstanceStateProvider - } - }) - } - override fun after() { try { scenario.close() @@ -81,7 +64,7 @@ class FormEntryActivityTestRule : ExternalResource() { fun navigateAwayFromActivity(): FormEntryActivityTestRule { scenario.moveToState(Lifecycle.State.STARTED) - outState = scenario.saveInstanceState() + scenario.saveInstanceState() return this } @@ -90,9 +73,9 @@ class FormEntryActivityTestRule : ExternalResource() { return this } - fun restoreActivity() { - savedInstanceStateProvider.setState(outState) - scenario = ActivityScenario.launch(intent) + fun simulateProcessRestart(): FormEntryActivityTestRule { + CollectHelpers.simulateProcessRestart() + return this } private fun createNewFormIntent(formFilename: String): Intent { @@ -130,22 +113,3 @@ class FormEntryActivityTestRule : ExternalResource() { ) } } - -private class InMemSavedInstanceStateProvider : SavedInstanceStateProvider { - - private var bundle: Bundle? = null - - fun setState(savedInstanceState: Bundle?) { - bundle = savedInstanceState - } - - override fun getState(savedInstanceState: Bundle?): Bundle? { - return if (bundle != null) { - bundle.also { - bundle = null - } - } else { - savedInstanceState - } - } -} diff --git a/collect_app/src/main/java/org/odk/collect/android/activities/FormFillingActivity.java b/collect_app/src/main/java/org/odk/collect/android/activities/FormFillingActivity.java index e49c3af3d09..fd571c3ea4e 100644 --- a/collect_app/src/main/java/org/odk/collect/android/activities/FormFillingActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/activities/FormFillingActivity.java @@ -176,7 +176,6 @@ import org.odk.collect.android.widgets.utilities.WaitingForDataRegistry; import org.odk.collect.androidshared.system.IntentLauncher; import org.odk.collect.androidshared.system.ProcessRestoreDetector; -import org.odk.collect.androidshared.system.SavedInstanceStateProvider; import org.odk.collect.androidshared.ui.DialogFragmentUtils; import org.odk.collect.androidshared.ui.FragmentFactoryBuilder; import org.odk.collect.androidshared.ui.SnackbarUtils; @@ -371,9 +370,6 @@ public void allowSwiping(boolean doSwipe) { @Inject public InstancesRepositoryProvider instancesRepositoryProvider; - @Inject - public SavedInstanceStateProvider savedInstanceStateProvider; - private final LocationProvidersReceiver locationProvidersReceiver = new LocationProvidersReceiver(); private SwipeHandler swipeHandler; @@ -442,9 +438,15 @@ public void onCreate(Bundle savedInstanceState) { .forClass(SelectOneFromMapDialogFragment.class, () -> new SelectOneFromMapDialogFragment(viewModelFactory)) .build()); - savedInstanceState = savedInstanceStateProvider.getState(savedInstanceState); - if (ProcessRestoreDetector.isProcessRestoring(this, savedInstanceState)) { + if (savedInstanceState.containsKey(KEY_XPATH)) { + startingXPath = savedInstanceState.getString(KEY_XPATH); + } + + if (savedInstanceState.containsKey(KEY_XPATH_WAITING_FOR_DATA)) { + waitingXPath = savedInstanceState.getString(KEY_XPATH_WAITING_FOR_DATA); + } + savedInstanceState = null; } @@ -677,7 +679,7 @@ private void loadForm() { formEntryViewModel.refresh(); } else { Timber.w("Reloading form and restoring state."); - formLoaderTask = new FormLoaderTask(instancePath, startingXPath, waitingXPath, formEntryControllerFactory); + formLoaderTask = new FormLoaderTask(instancePath, startingXPath, waitingXPath, formEntryControllerFactory, scheduler); showIfNotShowing(FormLoadingDialogFragment.class, getSupportFragmentManager()); formLoaderTask.execute(formPath); } @@ -721,7 +723,7 @@ private void loadFromIntent(Intent intent) { instancePath = loadSavePoint(); } - formLoaderTask = new FormLoaderTask(instancePath, null, null, formEntryControllerFactory); + formLoaderTask = new FormLoaderTask(instancePath, startingXPath, waitingXPath, formEntryControllerFactory, scheduler); formLoaderTask.setFormLoaderListener(this); showIfNotShowing(FormLoadingDialogFragment.class, getSupportFragmentManager()); formLoaderTask.execute(formPath); @@ -852,6 +854,13 @@ protected void onSaveInstanceState(Bundle outState) { @Override protected void onActivityResult(int requestCode, int resultCode, final Intent intent) { super.onActivityResult(requestCode, resultCode, intent); + // If we're coming back from the hierarchy view, the user has either tapped the back + // button or another question to jump to so we need to rebuild the view. + if (requestCode == RequestCodes.HIERARCHY_ACTIVITY || requestCode == RequestCodes.CHANGE_SETTINGS) { + formEntryViewModel.refresh(); + return; + } + FormController formController = getFormController(); if (formController == null) { // we must be in the midst of a reload of the FormController. @@ -865,13 +874,6 @@ protected void onActivityResult(int requestCode, int resultCode, final Intent in return; } - // If we're coming back from the hierarchy view, the user has either tapped the back - // button or another question to jump to so we need to rebuild the view. - if (requestCode == RequestCodes.HIERARCHY_ACTIVITY || requestCode == RequestCodes.CHANGE_SETTINGS) { - formEntryViewModel.refresh(); - return; - } - if (resultCode == RESULT_CANCELED) { waitingForDataRegistry.cancelWaitingForData(); return; @@ -1921,7 +1923,7 @@ protected void onResume() { DialogFragmentUtils.dismissDialog(FormLoadingDialogFragment.class, getSupportFragmentManager()); FormLoaderTask t = formLoaderTask; formLoaderTask = null; - t.cancel(true); + t.cancel(); t.destroy(); // there is no formController -- fire MainMenu activity? Timber.w("Starting MainMenuActivity because formController is null/formLoaderTask not null"); @@ -1997,7 +1999,7 @@ protected void onDestroy() { if (formLoaderTask.getStatus() == AsyncTask.Status.FINISHED) { FormLoaderTask t = formLoaderTask; formLoaderTask = null; - t.cancel(true); + t.cancel(); t.destroy(); } } @@ -2072,7 +2074,7 @@ public void loadingComplete(FormLoaderTask task, FormDef formDef, String warning formLoaderTask.setFormLoaderListener(null); FormLoaderTask t = formLoaderTask; formLoaderTask = null; - t.cancel(true); + t.cancel(); t.destroy(); Collect.getInstance().setExternalDataManager(task.getExternalDataManager()); @@ -2096,17 +2098,6 @@ public void loadingComplete(FormLoaderTask task, FormDef formDef, String warning } } - boolean pendingActivityResult = task.hasPendingActivityResult(); - - if (pendingActivityResult) { - Timber.w("Calling onActivityResult from loadingComplete"); - - formControllerAvailable(formController); - formEntryViewModel.refresh(); - onActivityResult(task.getRequestCode(), task.getResultCode(), task.getIntent()); - return; - } - // it can be a normal flow for a pending activity result to restore from a savepoint // (the call flow handled by the above if statement). For all other use cases, the // user should be notified, as it means they wandered off doing other things then @@ -2182,12 +2173,19 @@ && new PlayServicesChecker().isGooglePlayServicesAvailable(this)) { } } - formController.getAuditEventLogger().logEvent(AuditEvent.AuditEventType.FORM_RESUME, true, System.currentTimeMillis()); - formController.getAuditEventLogger().logEvent(AuditEvent.AuditEventType.HIERARCHY, true, System.currentTimeMillis()); - formControllerAvailable(formController); - Intent intent = new Intent(this, FormHierarchyActivity.class); - intent.putExtra(FormHierarchyActivity.EXTRA_SESSION_ID, sessionId); - startActivityForResult(intent, RequestCodes.HIERARCHY_ACTIVITY); + boolean pendingActivityResult = task.hasPendingActivityResult(); + if (pendingActivityResult) { + formControllerAvailable(formController); + formEntryViewModel.refresh(); + onActivityResult(task.getRequestCode(), task.getResultCode(), task.getIntent()); + } else { + formController.getAuditEventLogger().logEvent(AuditEvent.AuditEventType.FORM_RESUME, true, System.currentTimeMillis()); + formController.getAuditEventLogger().logEvent(AuditEvent.AuditEventType.HIERARCHY, true, System.currentTimeMillis()); + formControllerAvailable(formController); + Intent intent = new Intent(this, FormHierarchyActivity.class); + intent.putExtra(FormHierarchyActivity.EXTRA_SESSION_ID, sessionId); + startActivityForResult(intent, RequestCodes.HIERARCHY_ACTIVITY); + } } }); } else { @@ -2345,7 +2343,7 @@ public void onCancelFormLoading() { formLoaderTask.setFormLoaderListener(null); FormLoaderTask t = formLoaderTask; formLoaderTask = null; - t.cancel(true); + t.cancel(); t.destroy(); } exit(); diff --git a/collect_app/src/main/java/org/odk/collect/android/formentry/FormEntryViewModel.java b/collect_app/src/main/java/org/odk/collect/android/formentry/FormEntryViewModel.java index 56e1fe76284..8d9dc11407f 100644 --- a/collect_app/src/main/java/org/odk/collect/android/formentry/FormEntryViewModel.java +++ b/collect_app/src/main/java/org/odk/collect/android/formentry/FormEntryViewModel.java @@ -285,7 +285,9 @@ protected void onCleared() { } public void refresh() { - currentIndex.setValue(formController.getFormIndex()); + if (formController != null) { + currentIndex.setValue(formController.getFormIndex()); + } } public void exit() { diff --git a/collect_app/src/main/java/org/odk/collect/android/formentry/questions/WidgetViewUtils.java b/collect_app/src/main/java/org/odk/collect/android/formentry/questions/WidgetViewUtils.java index 36f2c9f2d11..d2f113e7ae7 100644 --- a/collect_app/src/main/java/org/odk/collect/android/formentry/questions/WidgetViewUtils.java +++ b/collect_app/src/main/java/org/odk/collect/android/formentry/questions/WidgetViewUtils.java @@ -81,6 +81,7 @@ public static Button createSimpleButton(Context context, @IdRes final int withId } else { button.setId(withId); button.setText(text); + button.setContentDescription(text); button.setTextSize(TypedValue.COMPLEX_UNIT_DIP, answerFontSize); TableLayout.LayoutParams params = new TableLayout.LayoutParams(); diff --git a/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyModule.java b/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyModule.java index 30ddac7a060..6ac96235dc4 100644 --- a/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyModule.java +++ b/collect_app/src/main/java/org/odk/collect/android/injection/config/AppDependencyModule.java @@ -107,7 +107,6 @@ import org.odk.collect.androidshared.network.NetworkStateProvider; import org.odk.collect.androidshared.system.IntentLauncher; import org.odk.collect.androidshared.system.IntentLauncherImpl; -import org.odk.collect.androidshared.system.SavedInstanceStateProvider; import org.odk.collect.androidshared.utils.ScreenUtils; import org.odk.collect.async.CoroutineAndWorkManagerScheduler; import org.odk.collect.async.Scheduler; @@ -643,9 +642,4 @@ public ImageCompressionController providesImageCompressorManager() { public FormLoaderTask.FormEntryControllerFactory formEntryControllerFactory(SettingsProvider settingsProvider) { return new CollectFormEntryControllerFactory(settingsProvider.getUnprotectedSettings()); } - - @Provides - public SavedInstanceStateProvider providesSavedInstanceStateProvider() { - return savedInstanceState -> savedInstanceState; - } } diff --git a/collect_app/src/main/java/org/odk/collect/android/tasks/FormLoaderTask.java b/collect_app/src/main/java/org/odk/collect/android/tasks/FormLoaderTask.java index 637b1dca94d..ee195f64e94 100644 --- a/collect_app/src/main/java/org/odk/collect/android/tasks/FormLoaderTask.java +++ b/collect_app/src/main/java/org/odk/collect/android/tasks/FormLoaderTask.java @@ -20,7 +20,6 @@ import android.content.Intent; import android.database.Cursor; import android.database.SQLException; -import android.os.AsyncTask; import androidx.annotation.NonNull; @@ -53,6 +52,8 @@ import org.odk.collect.android.utilities.FileUtils; import org.odk.collect.android.utilities.FormDefCache; import org.odk.collect.android.utilities.ZipUtils; +import org.odk.collect.async.Scheduler; +import org.odk.collect.async.SchedulerAsyncTaskMimic; import org.odk.collect.shared.strings.Md5; import java.io.File; @@ -71,7 +72,7 @@ * @author Carl Hartung (carlhartung@gmail.com) * @author Yaw Anokwa (yanokwa@gmail.com) */ -public class FormLoaderTask extends AsyncTask { +public class FormLoaderTask extends SchedulerAsyncTaskMimic { private static final String ITEMSETS_CSV = "itemsets.csv"; private FormLoaderListener stateListener; @@ -88,6 +89,11 @@ public class FormLoaderTask extends AsyncTask().getSharedPreferences(name, Context.MODE_PRIVATE)) - } } diff --git a/collect_app/src/test/java/org/odk/collect/android/activities/FormFillingActivityTest.kt b/collect_app/src/test/java/org/odk/collect/android/activities/FormFillingActivityTest.kt new file mode 100644 index 00000000000..20def43c8d4 --- /dev/null +++ b/collect_app/src/test/java/org/odk/collect/android/activities/FormFillingActivityTest.kt @@ -0,0 +1,247 @@ +package org.odk.collect.android.activities + +import android.app.Activity +import android.app.Activity.RESULT_OK +import android.app.Application +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.fragment.app.DialogFragment +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.work.WorkManager +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.odk.collect.android.external.FormsContract +import org.odk.collect.android.formmanagement.FormFillingIntentFactory +import org.odk.collect.android.injection.config.AppDependencyComponent +import org.odk.collect.android.injection.config.AppDependencyModule +import org.odk.collect.android.storage.StorageSubdirectory +import org.odk.collect.android.support.CollectHelpers +import org.odk.collect.android.support.CollectHelpers.resetProcess +import org.odk.collect.android.utilities.FileUtils +import org.odk.collect.androidshared.ui.DialogFragmentUtils +import org.odk.collect.androidtest.RecordedIntentsRule +import org.odk.collect.async.Scheduler +import org.odk.collect.externalapp.ExternalAppUtils +import org.odk.collect.forms.Form +import org.odk.collect.formstest.FormFixtures.form +import org.odk.collect.strings.R +import org.odk.collect.testshared.ActivityControllerRule +import org.odk.collect.testshared.AssertIntentsHelper +import org.odk.collect.testshared.EspressoHelpers.assertText +import org.odk.collect.testshared.EspressoHelpers.clickOnContentDescription +import org.odk.collect.testshared.FakeScheduler +import org.odk.collect.testshared.RobolectricHelpers.recreateWithProcessRestore +import org.robolectric.Shadows.shadowOf +import java.io.File + +@RunWith(AndroidJUnit4::class) +class FormFillingActivityTest { + + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @get:Rule + val recordedIntentsRule = RecordedIntentsRule() + + @get:Rule + val activityControllerRule = ActivityControllerRule() + + private val assertIntentsHelper = AssertIntentsHelper() + + private val scheduler = FakeScheduler() + private val dependencies = object : AppDependencyModule() { + override fun providesScheduler(workManager: WorkManager): Scheduler { + return scheduler + } + } + + private val application = ApplicationProvider.getApplicationContext() + private lateinit var component: AppDependencyComponent + + @Before + fun setup() { + component = CollectHelpers.overrideAppDependencyModule(dependencies) + } + + @Test + fun whenProcessIsKilledAndRestored_returnsToHierarchyAtQuestion() { + val projectId = CollectHelpers.setupDemoProject() + + val form = setupForm("forms/two-question.xml") + val intent = FormFillingIntentFactory.newInstanceIntent( + application, + FormsContract.getUri(projectId, form!!.dbId), + FormFillingActivity::class + ) + + // Start activity + val initial = activityControllerRule.build(FormFillingActivity::class.java, intent).setup() + scheduler.flush() + assertText("Two Question") + assertText("What is your name?") + + clickOnContentDescription(R.string.form_forward) + scheduler.flush() + assertText("What is your age?") + + // Recreate and assert we start FormHierarchyActivity + val recreated = activityControllerRule.add { + initial.recreateWithProcessRestore { resetProcess(dependencies) } + } + + scheduler.flush() + assertIntentsHelper.assertNewIntent(FormHierarchyActivity::class) + + // Return to FormFillingActivity from FormHierarchyActivity + val hierarchyIntent = shadowOf(recreated.get()).nextStartedActivityForResult.intent + shadowOf(recreated.get()).receiveResult(hierarchyIntent, Activity.RESULT_CANCELED, null) + scheduler.flush() + + assertText("Two Question") + assertText("What is your age?") + } + + @Test + fun whenProcessIsKilledAndRestored_andHierarchyIsOpen_returnsToHierarchyAtQuestion() { + val projectId = CollectHelpers.setupDemoProject() + + val form = setupForm("forms/two-question.xml") + val intent = FormFillingIntentFactory.newInstanceIntent( + application, + FormsContract.getUri(projectId, form!!.dbId), + FormFillingActivity::class + ) + + // Start activity + val initial = activityControllerRule.build(FormFillingActivity::class.java, intent).setup() + scheduler.flush() + assertText("Two Question") + assertText("What is your name?") + + clickOnContentDescription(R.string.form_forward) + scheduler.flush() + assertText("What is your age?") + + clickOnContentDescription(R.string.view_hierarchy) + assertIntentsHelper.assertNewIntent(FormHierarchyActivity::class) + + // Recreate and assert we start FormHierarchyActivity + val recreated = activityControllerRule.add { + initial.recreateWithProcessRestore { resetProcess(dependencies) } + } + + scheduler.flush() + assertIntentsHelper.assertNewIntent(FormHierarchyActivity::class) + + // Return to FormFillingActivity from FormHierarchyActivity + val hierarchyIntent = shadowOf(recreated.get()).nextStartedActivityForResult.intent + shadowOf(recreated.get()).receiveResult(hierarchyIntent, Activity.RESULT_CANCELED, null) + scheduler.flush() + + assertText("Two Question") + assertText("What is your age?") + } + + @Test + fun whenProcessIsKilledAndRestored_andThereADialogFragmentOpen_doesNotRestoreDialogFragment() { + val projectId = CollectHelpers.setupDemoProject() + + val form = setupForm("forms/two-question.xml") + val intent = FormFillingIntentFactory.newInstanceIntent( + application, + FormsContract.getUri(projectId, form!!.dbId), + FormFillingActivity::class + ) + + // Start activity + val initial = activityControllerRule.build(FormFillingActivity::class.java, intent).setup() + scheduler.flush() + assertText("Two Question") + assertText("What is your name?") + + clickOnContentDescription(R.string.form_forward) + scheduler.flush() + assertText("What is your age?") + + val initialFragmentManager = initial.get().supportFragmentManager + DialogFragmentUtils.showIfNotShowing(TestDialogFragment::class.java, initialFragmentManager) + assertThat( + initialFragmentManager.fragments.any { it::class == TestDialogFragment::class }, + equalTo(true) + ) + + // Recreate and assert we start FormHierarchyActivity + val recreated = activityControllerRule.add { + initial.recreateWithProcessRestore { resetProcess(dependencies) } + } + + scheduler.flush() + assertIntentsHelper.assertNewIntent(FormHierarchyActivity::class) + + // Return to FormFillingActivity from FormHierarchyActivity + val hierarchyIntent = shadowOf(recreated.get()).nextStartedActivityForResult.intent + shadowOf(recreated.get()).receiveResult(hierarchyIntent, Activity.RESULT_CANCELED, null) + scheduler.flush() + + assertText("Two Question") + assertText("What is your age?") + } + + @Test + fun whenProcessIsKilledAndRestored_andIsWaitingForExternalData_dataCanStillBeReturned() { + val projectId = CollectHelpers.setupDemoProject() + + val form = setupForm("forms/two-question-external.xml") + val intent = FormFillingIntentFactory.newInstanceIntent( + application, + FormsContract.getUri(projectId, form!!.dbId), + FormFillingActivity::class + ) + + // Start activity + val initial = activityControllerRule.build(FormFillingActivity::class.java, intent).setup() + scheduler.flush() + assertText("Two Question") + assertText("What is your name?") + + clickOnContentDescription(R.string.form_forward) + scheduler.flush() + assertText("What is your age?") + + // Open external app + clickOnContentDescription(R.string.launch_app) + assertIntentsHelper.assertNewIntent(hasAction("com.example.EXAMPLE")) + + // Recreate with result + val returnData = ExternalAppUtils.getReturnIntent("159") + activityControllerRule.add { + initial.recreateWithProcessRestore(RESULT_OK, returnData) { resetProcess(dependencies) } + } + + scheduler.flush() + + assertIntentsHelper.assertNoNewIntent() + assertText("Two Question") + assertText("What is your age?") + assertText("159") + } + + private fun setupForm(testFormPath: String): Form? { + val formsDir = component.storagePathProvider().getOdkDirPath(StorageSubdirectory.FORMS) + val formFile = FileUtils.copyFileFromResources( + testFormPath, + File(formsDir, "two-question.xml") + ) + + val formsRepository = component.formsRepositoryProvider().get() + val form = formsRepository.save(form(formFile = formFile)) + return form + } + + class TestDialogFragment : DialogFragment() +} diff --git a/collect_app/src/test/java/org/odk/collect/android/support/CollectHelpers.java b/collect_app/src/test/java/org/odk/collect/android/support/CollectHelpers.java index d7419315ab3..e115aaeeae9 100644 --- a/collect_app/src/test/java/org/odk/collect/android/support/CollectHelpers.java +++ b/collect_app/src/test/java/org/odk/collect/android/support/CollectHelpers.java @@ -67,6 +67,15 @@ public static AppDependencyComponent overrideAppDependencyModule(AppDependencyMo return testComponent; } + public static void resetProcess(AppDependencyModule dependencies) { + Collect application = ApplicationProvider.getApplicationContext(); + + application.getState().clear(); + + AppDependencyComponent newComponent = CollectHelpers.overrideAppDependencyModule(dependencies); + newComponent.applicationInitializer().initialize(); + } + public static T createThemedActivity(Class clazz) { return RobolectricHelpers.createThemedActivity(clazz); } diff --git a/formstest/src/main/java/org/odk/collect/formstest/FormFixtures.kt b/formstest/src/main/java/org/odk/collect/formstest/FormFixtures.kt index 0eeca118716..4d291cc228c 100644 --- a/formstest/src/main/java/org/odk/collect/formstest/FormFixtures.kt +++ b/formstest/src/main/java/org/odk/collect/formstest/FormFixtures.kt @@ -10,21 +10,28 @@ object FormFixtures { formId: String = "formId", version: String = "1", mediaFiles: List> = emptyList(), - autoSend: String? = null + autoSend: String? = null, + formFile: File? = null ): Form { val formFilesPath = TempFiles.createTempDir().absolutePath - val mediaFilePath = TempFiles.createTempDir().absolutePath + val mediaFilesPath = TempFiles.createTempDir().absolutePath mediaFiles.forEach { (name, contents) -> - File(mediaFilePath, name).also { it.writeBytes(contents.toByteArray()) } + File(mediaFilesPath, name).also { it.writeBytes(contents.toByteArray()) } } return Form.Builder() .displayName("Test Form") .formId(formId) .version(version) - .formFilePath(FormUtils.createFormFixtureFile(formId, version, formFilesPath)) - .formMediaPath(mediaFilePath) + .formFilePath( + formFile?.absolutePath ?: FormUtils.createFormFixtureFile( + formId, + version, + formFilesPath + ) + ) + .formMediaPath(mediaFilesPath) .autoSend(autoSend) .build() } diff --git a/formstest/src/main/java/org/odk/collect/formstest/FormUtils.kt b/formstest/src/main/java/org/odk/collect/formstest/FormUtils.kt index 0c388d33f16..9602d12bf0a 100644 --- a/formstest/src/main/java/org/odk/collect/formstest/FormUtils.kt +++ b/formstest/src/main/java/org/odk/collect/formstest/FormUtils.kt @@ -9,7 +9,7 @@ import java.nio.charset.Charset object FormUtils { @JvmStatic @JvmOverloads - fun createXFormBody(formId: String, version: String?, title: String = "Form"): String { + fun createXFormBody(formId: String, version: String?, title: String = "Test Form"): String { return """ @@ -17,11 +17,16 @@ object FormUtils { + + + + + """ } diff --git a/settings.gradle b/settings.gradle index 97c248584f9..6e2f81723a1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -38,3 +38,4 @@ apply from: 'secrets.gradle' if (getSecrets().getProperty('MAPBOX_DOWNLOADS_TOKEN', '') != '') { include ':mapbox' } +include ':test-forms' diff --git a/test-forms/.gitignore b/test-forms/.gitignore new file mode 100644 index 00000000000..42afabfd2ab --- /dev/null +++ b/test-forms/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/test-forms/build.gradle.kts b/test-forms/build.gradle.kts new file mode 100644 index 00000000000..a22cf91aa52 --- /dev/null +++ b/test-forms/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("java-library") + id("org.jetbrains.kotlin.jvm") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} diff --git a/collect_app/src/androidTest/assets/forms/1560_DateData.xml b/test-forms/src/main/resources/forms/1560_DateData.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/1560_DateData.xml rename to test-forms/src/main/resources/forms/1560_DateData.xml diff --git a/collect_app/src/androidTest/assets/forms/1560_IntegerData.xml b/test-forms/src/main/resources/forms/1560_IntegerData.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/1560_IntegerData.xml rename to test-forms/src/main/resources/forms/1560_IntegerData.xml diff --git a/collect_app/src/androidTest/assets/forms/1560_IntegerData_instanceID.xml b/test-forms/src/main/resources/forms/1560_IntegerData_instanceID.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/1560_IntegerData_instanceID.xml rename to test-forms/src/main/resources/forms/1560_IntegerData_instanceID.xml diff --git a/collect_app/src/androidTest/assets/forms/3403.xml b/test-forms/src/main/resources/forms/3403.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/3403.xml rename to test-forms/src/main/resources/forms/3403.xml diff --git a/collect_app/src/androidTest/assets/forms/Automated_guidance_hint_form.xml b/test-forms/src/main/resources/forms/Automated_guidance_hint_form.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/Automated_guidance_hint_form.xml rename to test-forms/src/main/resources/forms/Automated_guidance_hint_form.xml diff --git a/collect_app/src/androidTest/assets/forms/Birds-encrypted.xml b/test-forms/src/main/resources/forms/Birds-encrypted.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/Birds-encrypted.xml rename to test-forms/src/main/resources/forms/Birds-encrypted.xml diff --git a/collect_app/src/androidTest/assets/forms/CSVerrorForm.xml b/test-forms/src/main/resources/forms/CSVerrorForm.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/CSVerrorForm.xml rename to test-forms/src/main/resources/forms/CSVerrorForm.xml diff --git a/collect_app/src/androidTest/assets/forms/CalcTest.xml b/test-forms/src/main/resources/forms/CalcTest.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/CalcTest.xml rename to test-forms/src/main/resources/forms/CalcTest.xml diff --git a/collect_app/src/androidTest/assets/forms/Empty First Repeat.xml b/test-forms/src/main/resources/forms/Empty First Repeat.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/Empty First Repeat.xml rename to test-forms/src/main/resources/forms/Empty First Repeat.xml diff --git a/collect_app/src/androidTest/assets/forms/OnePageFormShort.xml b/test-forms/src/main/resources/forms/OnePageFormShort.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/OnePageFormShort.xml rename to test-forms/src/main/resources/forms/OnePageFormShort.xml diff --git a/collect_app/src/androidTest/assets/forms/OnePageFormValid2.xml b/test-forms/src/main/resources/forms/OnePageFormValid2.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/OnePageFormValid2.xml rename to test-forms/src/main/resources/forms/OnePageFormValid2.xml diff --git a/collect_app/src/androidTest/assets/forms/RepeatCount.xml b/test-forms/src/main/resources/forms/RepeatCount.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/RepeatCount.xml rename to test-forms/src/main/resources/forms/RepeatCount.xml diff --git a/collect_app/src/androidTest/assets/forms/RepeatGroupAndGroup.xml b/test-forms/src/main/resources/forms/RepeatGroupAndGroup.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/RepeatGroupAndGroup.xml rename to test-forms/src/main/resources/forms/RepeatGroupAndGroup.xml diff --git a/collect_app/src/androidTest/assets/forms/RepeatTitles_1648.xml b/test-forms/src/main/resources/forms/RepeatTitles_1648.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/RepeatTitles_1648.xml rename to test-forms/src/main/resources/forms/RepeatTitles_1648.xml diff --git a/collect_app/src/androidTest/assets/forms/TestRepeat.xml b/test-forms/src/main/resources/forms/TestRepeat.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/TestRepeat.xml rename to test-forms/src/main/resources/forms/TestRepeat.xml diff --git a/collect_app/src/androidTest/assets/forms/all-widgets.xml b/test-forms/src/main/resources/forms/all-widgets.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/all-widgets.xml rename to test-forms/src/main/resources/forms/all-widgets.xml diff --git a/collect_app/src/androidTest/assets/forms/audio-question.xml b/test-forms/src/main/resources/forms/audio-question.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/audio-question.xml rename to test-forms/src/main/resources/forms/audio-question.xml diff --git a/collect_app/src/androidTest/assets/forms/basic.xml b/test-forms/src/main/resources/forms/basic.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/basic.xml rename to test-forms/src/main/resources/forms/basic.xml diff --git a/collect_app/src/androidTest/assets/forms/default_image_answer.xml b/test-forms/src/main/resources/forms/default_image_answer.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/default_image_answer.xml rename to test-forms/src/main/resources/forms/default_image_answer.xml diff --git a/collect_app/src/androidTest/assets/forms/different-search-appearances.xml b/test-forms/src/main/resources/forms/different-search-appearances.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/different-search-appearances.xml rename to test-forms/src/main/resources/forms/different-search-appearances.xml diff --git a/collect_app/src/androidTest/assets/forms/emptyGroupFieldList.xml b/test-forms/src/main/resources/forms/emptyGroupFieldList.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/emptyGroupFieldList.xml rename to test-forms/src/main/resources/forms/emptyGroupFieldList.xml diff --git a/collect_app/src/androidTest/assets/forms/emptyGroupFieldList2.xml b/test-forms/src/main/resources/forms/emptyGroupFieldList2.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/emptyGroupFieldList2.xml rename to test-forms/src/main/resources/forms/emptyGroupFieldList2.xml diff --git a/collect_app/src/androidTest/assets/forms/encrypted-no-instanceID.xml b/test-forms/src/main/resources/forms/encrypted-no-instanceID.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/encrypted-no-instanceID.xml rename to test-forms/src/main/resources/forms/encrypted-no-instanceID.xml diff --git a/collect_app/src/androidTest/assets/forms/encrypted.xml b/test-forms/src/main/resources/forms/encrypted.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/encrypted.xml rename to test-forms/src/main/resources/forms/encrypted.xml diff --git a/collect_app/src/androidTest/assets/forms/event-odk-new-repeat.xml b/test-forms/src/main/resources/forms/event-odk-new-repeat.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/event-odk-new-repeat.xml rename to test-forms/src/main/resources/forms/event-odk-new-repeat.xml diff --git a/collect_app/src/androidTest/assets/forms/external-audio-question.xml b/test-forms/src/main/resources/forms/external-audio-question.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/external-audio-question.xml rename to test-forms/src/main/resources/forms/external-audio-question.xml diff --git a/collect_app/src/androidTest/assets/forms/external-csv-search.xml b/test-forms/src/main/resources/forms/external-csv-search.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/external-csv-search.xml rename to test-forms/src/main/resources/forms/external-csv-search.xml diff --git a/collect_app/src/androidTest/assets/forms/external_csv_form.xml b/test-forms/src/main/resources/forms/external_csv_form.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/external_csv_form.xml rename to test-forms/src/main/resources/forms/external_csv_form.xml diff --git a/collect_app/src/androidTest/assets/forms/external_data_questions.xml b/test-forms/src/main/resources/forms/external_data_questions.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/external_data_questions.xml rename to test-forms/src/main/resources/forms/external_data_questions.xml diff --git a/collect_app/src/androidTest/assets/forms/external_select_10.xml b/test-forms/src/main/resources/forms/external_select_10.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/external_select_10.xml rename to test-forms/src/main/resources/forms/external_select_10.xml diff --git a/collect_app/src/androidTest/assets/forms/field-list-repeat.xml b/test-forms/src/main/resources/forms/field-list-repeat.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/field-list-repeat.xml rename to test-forms/src/main/resources/forms/field-list-repeat.xml diff --git a/collect_app/src/androidTest/assets/forms/fieldListInFieldList.xml b/test-forms/src/main/resources/forms/fieldListInFieldList.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/fieldListInFieldList.xml rename to test-forms/src/main/resources/forms/fieldListInFieldList.xml diff --git a/collect_app/src/androidTest/assets/forms/fieldListWithQuestionAndRegularGroupInside.xml b/test-forms/src/main/resources/forms/fieldListWithQuestionAndRegularGroupInside.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/fieldListWithQuestionAndRegularGroupInside.xml rename to test-forms/src/main/resources/forms/fieldListWithQuestionAndRegularGroupInside.xml diff --git a/collect_app/src/androidTest/assets/forms/fieldListWithQuestionsAndRegularGroupsInside.xml b/test-forms/src/main/resources/forms/fieldListWithQuestionsAndRegularGroupsInside.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/fieldListWithQuestionsAndRegularGroupsInside.xml rename to test-forms/src/main/resources/forms/fieldListWithQuestionsAndRegularGroupsInside.xml diff --git a/collect_app/src/androidTest/assets/forms/fieldlist-updates.xml b/test-forms/src/main/resources/forms/fieldlist-updates.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/fieldlist-updates.xml rename to test-forms/src/main/resources/forms/fieldlist-updates.xml diff --git a/collect_app/src/androidTest/assets/forms/fieldlist-updates_nocsv.xml b/test-forms/src/main/resources/forms/fieldlist-updates_nocsv.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/fieldlist-updates_nocsv.xml rename to test-forms/src/main/resources/forms/fieldlist-updates_nocsv.xml diff --git a/collect_app/src/androidTest/assets/forms/fixed-count-repeat.xml b/test-forms/src/main/resources/forms/fixed-count-repeat.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/fixed-count-repeat.xml rename to test-forms/src/main/resources/forms/fixed-count-repeat.xml diff --git a/collect_app/src/androidTest/assets/forms/form1.xml b/test-forms/src/main/resources/forms/form1.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/form1.xml rename to test-forms/src/main/resources/forms/form1.xml diff --git a/collect_app/src/androidTest/assets/forms/form2.xml b/test-forms/src/main/resources/forms/form2.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/form2.xml rename to test-forms/src/main/resources/forms/form2.xml diff --git a/collect_app/src/androidTest/assets/forms/form3.xml b/test-forms/src/main/resources/forms/form3.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/form3.xml rename to test-forms/src/main/resources/forms/form3.xml diff --git a/collect_app/src/androidTest/assets/forms/form4.xml b/test-forms/src/main/resources/forms/form4.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/form4.xml rename to test-forms/src/main/resources/forms/form4.xml diff --git a/collect_app/src/androidTest/assets/forms/form5.xml b/test-forms/src/main/resources/forms/form5.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/form5.xml rename to test-forms/src/main/resources/forms/form5.xml diff --git a/collect_app/src/androidTest/assets/forms/form6.xml b/test-forms/src/main/resources/forms/form6.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/form6.xml rename to test-forms/src/main/resources/forms/form6.xml diff --git a/collect_app/src/androidTest/assets/forms/form7.xml b/test-forms/src/main/resources/forms/form7.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/form7.xml rename to test-forms/src/main/resources/forms/form7.xml diff --git a/collect_app/src/androidTest/assets/forms/form8.xml b/test-forms/src/main/resources/forms/form8.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/form8.xml rename to test-forms/src/main/resources/forms/form8.xml diff --git a/collect_app/src/androidTest/assets/forms/form9.xml b/test-forms/src/main/resources/forms/form9.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/form9.xml rename to test-forms/src/main/resources/forms/form9.xml diff --git a/collect_app/src/androidTest/assets/forms/formHierarchy1.xml b/test-forms/src/main/resources/forms/formHierarchy1.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/formHierarchy1.xml rename to test-forms/src/main/resources/forms/formHierarchy1.xml diff --git a/collect_app/src/androidTest/assets/forms/formHierarchy2.xml b/test-forms/src/main/resources/forms/formHierarchy2.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/formHierarchy2.xml rename to test-forms/src/main/resources/forms/formHierarchy2.xml diff --git a/collect_app/src/androidTest/assets/forms/formHierarchy3.xml b/test-forms/src/main/resources/forms/formHierarchy3.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/formHierarchy3.xml rename to test-forms/src/main/resources/forms/formHierarchy3.xml diff --git a/collect_app/src/androidTest/assets/forms/formWithExternalFiles.xml b/test-forms/src/main/resources/forms/formWithExternalFiles.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/formWithExternalFiles.xml rename to test-forms/src/main/resources/forms/formWithExternalFiles.xml diff --git a/collect_app/src/androidTest/assets/forms/form_design_error.xml b/test-forms/src/main/resources/forms/form_design_error.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/form_design_error.xml rename to test-forms/src/main/resources/forms/form_design_error.xml diff --git a/collect_app/src/androidTest/assets/forms/form_styling.xml b/test-forms/src/main/resources/forms/form_styling.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/form_styling.xml rename to test-forms/src/main/resources/forms/form_styling.xml diff --git a/collect_app/src/androidTest/assets/forms/formulaire_adherent.xml b/test-forms/src/main/resources/forms/formulaire_adherent.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/formulaire_adherent.xml rename to test-forms/src/main/resources/forms/formulaire_adherent.xml diff --git a/collect_app/src/androidTest/assets/forms/g6Error.xml b/test-forms/src/main/resources/forms/g6Error.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/g6Error.xml rename to test-forms/src/main/resources/forms/g6Error.xml diff --git a/collect_app/src/androidTest/assets/forms/g6Error2.xml b/test-forms/src/main/resources/forms/g6Error2.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/g6Error2.xml rename to test-forms/src/main/resources/forms/g6Error2.xml diff --git a/collect_app/src/androidTest/assets/forms/guidance_hint_form.xml b/test-forms/src/main/resources/forms/guidance_hint_form.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/guidance_hint_form.xml rename to test-forms/src/main/resources/forms/guidance_hint_form.xml diff --git a/collect_app/src/androidTest/assets/forms/hints_textq.xml b/test-forms/src/main/resources/forms/hints_textq.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/hints_textq.xml rename to test-forms/src/main/resources/forms/hints_textq.xml diff --git a/collect_app/src/androidTest/assets/forms/identify-user-audit-false.xml b/test-forms/src/main/resources/forms/identify-user-audit-false.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/identify-user-audit-false.xml rename to test-forms/src/main/resources/forms/identify-user-audit-false.xml diff --git a/collect_app/src/androidTest/assets/forms/identify-user-audit.xml b/test-forms/src/main/resources/forms/identify-user-audit.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/identify-user-audit.xml rename to test-forms/src/main/resources/forms/identify-user-audit.xml diff --git a/collect_app/src/androidTest/assets/forms/image_widget.xml b/test-forms/src/main/resources/forms/image_widget.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/image_widget.xml rename to test-forms/src/main/resources/forms/image_widget.xml diff --git a/collect_app/src/androidTest/assets/forms/intent-group.xml b/test-forms/src/main/resources/forms/intent-group.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/intent-group.xml rename to test-forms/src/main/resources/forms/intent-group.xml diff --git a/collect_app/src/androidTest/assets/forms/internal-audio-question.xml b/test-forms/src/main/resources/forms/internal-audio-question.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/internal-audio-question.xml rename to test-forms/src/main/resources/forms/internal-audio-question.xml diff --git a/collect_app/src/androidTest/assets/forms/internal_select_10.xml b/test-forms/src/main/resources/forms/internal_select_10.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/internal_select_10.xml rename to test-forms/src/main/resources/forms/internal_select_10.xml diff --git a/collect_app/src/androidTest/assets/forms/invalid-events.xml b/test-forms/src/main/resources/forms/invalid-events.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/invalid-events.xml rename to test-forms/src/main/resources/forms/invalid-events.xml diff --git a/collect_app/src/androidTest/assets/forms/invalid-form.xml b/test-forms/src/main/resources/forms/invalid-form.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/invalid-form.xml rename to test-forms/src/main/resources/forms/invalid-form.xml diff --git a/collect_app/src/androidTest/assets/forms/likert_test.xml b/test-forms/src/main/resources/forms/likert_test.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/likert_test.xml rename to test-forms/src/main/resources/forms/likert_test.xml diff --git a/collect_app/src/androidTest/assets/forms/location-audit.xml b/test-forms/src/main/resources/forms/location-audit.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/location-audit.xml rename to test-forms/src/main/resources/forms/location-audit.xml diff --git a/collect_app/src/androidTest/assets/forms/manyQ.xml b/test-forms/src/main/resources/forms/manyQ.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/manyQ.xml rename to test-forms/src/main/resources/forms/manyQ.xml diff --git a/collect_app/src/androidTest/assets/forms/metadata.xml b/test-forms/src/main/resources/forms/metadata.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/metadata.xml rename to test-forms/src/main/resources/forms/metadata.xml diff --git a/collect_app/src/androidTest/assets/forms/metadata2.xml b/test-forms/src/main/resources/forms/metadata2.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/metadata2.xml rename to test-forms/src/main/resources/forms/metadata2.xml diff --git a/collect_app/src/androidTest/assets/forms/multiple-events.xml b/test-forms/src/main/resources/forms/multiple-events.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/multiple-events.xml rename to test-forms/src/main/resources/forms/multiple-events.xml diff --git a/collect_app/src/androidTest/assets/forms/nested-repeats-complex.xml b/test-forms/src/main/resources/forms/nested-repeats-complex.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/nested-repeats-complex.xml rename to test-forms/src/main/resources/forms/nested-repeats-complex.xml diff --git a/collect_app/src/androidTest/assets/forms/nigeria-wards.xml b/test-forms/src/main/resources/forms/nigeria-wards.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/nigeria-wards.xml rename to test-forms/src/main/resources/forms/nigeria-wards.xml diff --git a/collect_app/src/androidTest/assets/forms/no-track-changes-reason.xml b/test-forms/src/main/resources/forms/no-track-changes-reason.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/no-track-changes-reason.xml rename to test-forms/src/main/resources/forms/no-track-changes-reason.xml diff --git a/collect_app/src/androidTest/assets/forms/numberInCSV.xml b/test-forms/src/main/resources/forms/numberInCSV.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/numberInCSV.xml rename to test-forms/src/main/resources/forms/numberInCSV.xml diff --git a/collect_app/src/androidTest/assets/forms/one-question-audit-constraint.xml b/test-forms/src/main/resources/forms/one-question-audit-constraint.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/one-question-audit-constraint.xml rename to test-forms/src/main/resources/forms/one-question-audit-constraint.xml diff --git a/collect_app/src/androidTest/assets/forms/one-question-audit.xml b/test-forms/src/main/resources/forms/one-question-audit.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/one-question-audit.xml rename to test-forms/src/main/resources/forms/one-question-audit.xml diff --git a/collect_app/src/androidTest/assets/forms/one-question-autosend.xml b/test-forms/src/main/resources/forms/one-question-autosend.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/one-question-autosend.xml rename to test-forms/src/main/resources/forms/one-question-autosend.xml diff --git a/collect_app/src/androidTest/assets/forms/one-question-background-audio-multiple.xml b/test-forms/src/main/resources/forms/one-question-background-audio-multiple.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/one-question-background-audio-multiple.xml rename to test-forms/src/main/resources/forms/one-question-background-audio-multiple.xml diff --git a/collect_app/src/androidTest/assets/forms/one-question-background-audio.xml b/test-forms/src/main/resources/forms/one-question-background-audio.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/one-question-background-audio.xml rename to test-forms/src/main/resources/forms/one-question-background-audio.xml diff --git a/collect_app/src/androidTest/assets/forms/one-question-entity.xml b/test-forms/src/main/resources/forms/one-question-entity.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/one-question-entity.xml rename to test-forms/src/main/resources/forms/one-question-entity.xml diff --git a/collect_app/src/androidTest/assets/forms/one-question-google.xml b/test-forms/src/main/resources/forms/one-question-google.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/one-question-google.xml rename to test-forms/src/main/resources/forms/one-question-google.xml diff --git a/collect_app/src/androidTest/assets/forms/one-question-repeat.xml b/test-forms/src/main/resources/forms/one-question-repeat.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/one-question-repeat.xml rename to test-forms/src/main/resources/forms/one-question-repeat.xml diff --git a/collect_app/src/androidTest/assets/forms/one-question-translation.xml b/test-forms/src/main/resources/forms/one-question-translation.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/one-question-translation.xml rename to test-forms/src/main/resources/forms/one-question-translation.xml diff --git a/collect_app/src/androidTest/assets/forms/one-question-updated.xml b/test-forms/src/main/resources/forms/one-question-updated.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/one-question-updated.xml rename to test-forms/src/main/resources/forms/one-question-updated.xml diff --git a/collect_app/src/androidTest/assets/forms/one-question.xml b/test-forms/src/main/resources/forms/one-question.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/one-question.xml rename to test-forms/src/main/resources/forms/one-question.xml diff --git a/collect_app/src/androidTest/assets/forms/predicate-warning.xml b/test-forms/src/main/resources/forms/predicate-warning.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/predicate-warning.xml rename to test-forms/src/main/resources/forms/predicate-warning.xml diff --git a/collect_app/src/androidTest/assets/forms/random.xml b/test-forms/src/main/resources/forms/random.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/random.xml rename to test-forms/src/main/resources/forms/random.xml diff --git a/collect_app/src/androidTest/assets/forms/randomTest_broken.xml b/test-forms/src/main/resources/forms/randomTest_broken.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/randomTest_broken.xml rename to test-forms/src/main/resources/forms/randomTest_broken.xml diff --git a/collect_app/src/androidTest/assets/forms/ranking_widget.xml b/test-forms/src/main/resources/forms/ranking_widget.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/ranking_widget.xml rename to test-forms/src/main/resources/forms/ranking_widget.xml diff --git a/collect_app/src/androidTest/assets/forms/regularGroupWithFieldListGroupInside.xml b/test-forms/src/main/resources/forms/regularGroupWithFieldListGroupInside.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/regularGroupWithFieldListGroupInside.xml rename to test-forms/src/main/resources/forms/regularGroupWithFieldListGroupInside.xml diff --git a/collect_app/src/androidTest/assets/forms/regularGroupWithQuestionAndRegularGroupInside.xml b/test-forms/src/main/resources/forms/regularGroupWithQuestionAndRegularGroupInside.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/regularGroupWithQuestionAndRegularGroupInside.xml rename to test-forms/src/main/resources/forms/regularGroupWithQuestionAndRegularGroupInside.xml diff --git a/collect_app/src/androidTest/assets/forms/regularGroupWithQuestionsAndRegularGroupInside.xml b/test-forms/src/main/resources/forms/regularGroupWithQuestionsAndRegularGroupInside.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/regularGroupWithQuestionsAndRegularGroupInside.xml rename to test-forms/src/main/resources/forms/regularGroupWithQuestionsAndRegularGroupInside.xml diff --git a/collect_app/src/androidTest/assets/forms/repeat_group_form.xml b/test-forms/src/main/resources/forms/repeat_group_form.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/repeat_group_form.xml rename to test-forms/src/main/resources/forms/repeat_group_form.xml diff --git a/collect_app/src/androidTest/assets/forms/repeat_group_new.xml b/test-forms/src/main/resources/forms/repeat_group_new.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/repeat_group_new.xml rename to test-forms/src/main/resources/forms/repeat_group_new.xml diff --git a/collect_app/src/androidTest/assets/forms/repeat_groups.xml b/test-forms/src/main/resources/forms/repeat_groups.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/repeat_groups.xml rename to test-forms/src/main/resources/forms/repeat_groups.xml diff --git a/collect_app/src/androidTest/assets/forms/requiredJR275.xml b/test-forms/src/main/resources/forms/requiredJR275.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/requiredJR275.xml rename to test-forms/src/main/resources/forms/requiredJR275.xml diff --git a/collect_app/src/androidTest/assets/forms/requiredQuestionInFieldList.xml b/test-forms/src/main/resources/forms/requiredQuestionInFieldList.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/requiredQuestionInFieldList.xml rename to test-forms/src/main/resources/forms/requiredQuestionInFieldList.xml diff --git a/collect_app/src/androidTest/assets/forms/search_and_select.xml b/test-forms/src/main/resources/forms/search_and_select.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/search_and_select.xml rename to test-forms/src/main/resources/forms/search_and_select.xml diff --git a/collect_app/src/androidTest/assets/forms/selectOneExternal.xml b/test-forms/src/main/resources/forms/selectOneExternal.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/selectOneExternal.xml rename to test-forms/src/main/resources/forms/selectOneExternal.xml diff --git a/collect_app/src/androidTest/assets/forms/select_one_external.xml b/test-forms/src/main/resources/forms/select_one_external.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/select_one_external.xml rename to test-forms/src/main/resources/forms/select_one_external.xml diff --git a/collect_app/src/androidTest/assets/forms/setgeopoint-action.xml b/test-forms/src/main/resources/forms/setgeopoint-action.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/setgeopoint-action.xml rename to test-forms/src/main/resources/forms/setgeopoint-action.xml diff --git a/collect_app/src/androidTest/assets/forms/setlocation-action-instance-load.xml b/test-forms/src/main/resources/forms/setlocation-action-instance-load.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/setlocation-action-instance-load.xml rename to test-forms/src/main/resources/forms/setlocation-action-instance-load.xml diff --git a/collect_app/src/androidTest/assets/forms/setlocation-and-audit-location.xml b/test-forms/src/main/resources/forms/setlocation-and-audit-location.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/setlocation-and-audit-location.xml rename to test-forms/src/main/resources/forms/setlocation-and-audit-location.xml diff --git a/collect_app/src/androidTest/assets/forms/simple-search-external-csv.xml b/test-forms/src/main/resources/forms/simple-search-external-csv.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/simple-search-external-csv.xml rename to test-forms/src/main/resources/forms/simple-search-external-csv.xml diff --git a/collect_app/src/androidTest/assets/forms/simpleFieldList.xml b/test-forms/src/main/resources/forms/simpleFieldList.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/simpleFieldList.xml rename to test-forms/src/main/resources/forms/simpleFieldList.xml diff --git a/collect_app/src/androidTest/assets/forms/single-geopoint.xml b/test-forms/src/main/resources/forms/single-geopoint.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/single-geopoint.xml rename to test-forms/src/main/resources/forms/single-geopoint.xml diff --git a/collect_app/src/androidTest/assets/forms/start-geopoint.xml b/test-forms/src/main/resources/forms/start-geopoint.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/start-geopoint.xml rename to test-forms/src/main/resources/forms/start-geopoint.xml diff --git a/collect_app/src/androidTest/assets/forms/string_widgets_in_field_list.xml b/test-forms/src/main/resources/forms/string_widgets_in_field_list.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/string_widgets_in_field_list.xml rename to test-forms/src/main/resources/forms/string_widgets_in_field_list.xml diff --git a/collect_app/src/androidTest/assets/forms/t21257.xml b/test-forms/src/main/resources/forms/t21257.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/t21257.xml rename to test-forms/src/main/resources/forms/t21257.xml diff --git a/collect_app/src/androidTest/assets/forms/test_multiselect_cleared.xml b/test-forms/src/main/resources/forms/test_multiselect_cleared.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/test_multiselect_cleared.xml rename to test-forms/src/main/resources/forms/test_multiselect_cleared.xml diff --git a/collect_app/src/androidTest/assets/forms/threeNestedFieldListGroups.xml b/test-forms/src/main/resources/forms/threeNestedFieldListGroups.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/threeNestedFieldListGroups.xml rename to test-forms/src/main/resources/forms/threeNestedFieldListGroups.xml diff --git a/collect_app/src/androidTest/assets/forms/track-changes-reason-on-edit.xml b/test-forms/src/main/resources/forms/track-changes-reason-on-edit.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/track-changes-reason-on-edit.xml rename to test-forms/src/main/resources/forms/track-changes-reason-on-edit.xml diff --git a/collect_app/src/androidTest/assets/forms/two-question-audit.xml b/test-forms/src/main/resources/forms/two-question-audit.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/two-question-audit.xml rename to test-forms/src/main/resources/forms/two-question-audit.xml diff --git a/test-forms/src/main/resources/forms/two-question-external.xml b/test-forms/src/main/resources/forms/two-question-external.xml new file mode 100644 index 00000000000..9e57d3636a0 --- /dev/null +++ b/test-forms/src/main/resources/forms/two-question-external.xml @@ -0,0 +1,24 @@ + + + + Two Question + + + + + + + + + + + + + + + + + + + + diff --git a/collect_app/src/androidTest/assets/forms/two-question-required.xml b/test-forms/src/main/resources/forms/two-question-required.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/two-question-required.xml rename to test-forms/src/main/resources/forms/two-question-required.xml diff --git a/collect_app/src/androidTest/assets/forms/two-question-save-incomplete-required.xml b/test-forms/src/main/resources/forms/two-question-save-incomplete-required.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/two-question-save-incomplete-required.xml rename to test-forms/src/main/resources/forms/two-question-save-incomplete-required.xml diff --git a/collect_app/src/androidTest/assets/forms/two-question-save-incomplete.xml b/test-forms/src/main/resources/forms/two-question-save-incomplete.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/two-question-save-incomplete.xml rename to test-forms/src/main/resources/forms/two-question-save-incomplete.xml diff --git a/collect_app/src/androidTest/assets/forms/two-question-updated.xml b/test-forms/src/main/resources/forms/two-question-updated.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/two-question-updated.xml rename to test-forms/src/main/resources/forms/two-question-updated.xml diff --git a/collect_app/src/androidTest/assets/forms/two-question.xml b/test-forms/src/main/resources/forms/two-question.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/two-question.xml rename to test-forms/src/main/resources/forms/two-question.xml diff --git a/collect_app/src/androidTest/assets/forms/twoNestedRegularGroups.xml b/test-forms/src/main/resources/forms/twoNestedRegularGroups.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/twoNestedRegularGroups.xml rename to test-forms/src/main/resources/forms/twoNestedRegularGroups.xml diff --git a/collect_app/src/androidTest/assets/forms/validate.xml b/test-forms/src/main/resources/forms/validate.xml similarity index 100% rename from collect_app/src/androidTest/assets/forms/validate.xml rename to test-forms/src/main/resources/forms/validate.xml diff --git a/collect_app/src/androidTest/assets/media/TrapLists.csv b/test-forms/src/main/resources/media/TrapLists.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/TrapLists.csv rename to test-forms/src/main/resources/media/TrapLists.csv diff --git a/collect_app/src/androidTest/assets/media/doc.png b/test-forms/src/main/resources/media/doc.png similarity index 100% rename from collect_app/src/androidTest/assets/media/doc.png rename to test-forms/src/main/resources/media/doc.png diff --git a/collect_app/src/androidTest/assets/media/espece.csv b/test-forms/src/main/resources/media/espece.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/espece.csv rename to test-forms/src/main/resources/media/espece.csv diff --git a/collect_app/src/androidTest/assets/media/external-csv-search-produce.csv b/test-forms/src/main/resources/media/external-csv-search-produce.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/external-csv-search-produce.csv rename to test-forms/src/main/resources/media/external-csv-search-produce.csv diff --git a/collect_app/src/androidTest/assets/media/external_csv_cities.csv b/test-forms/src/main/resources/media/external_csv_cities.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/external_csv_cities.csv rename to test-forms/src/main/resources/media/external_csv_cities.csv diff --git a/collect_app/src/androidTest/assets/media/external_csv_countries.csv b/test-forms/src/main/resources/media/external_csv_countries.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/external_csv_countries.csv rename to test-forms/src/main/resources/media/external_csv_countries.csv diff --git a/collect_app/src/androidTest/assets/media/external_csv_neighbourhoods.csv b/test-forms/src/main/resources/media/external_csv_neighbourhoods.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/external_csv_neighbourhoods.csv rename to test-forms/src/main/resources/media/external_csv_neighbourhoods.csv diff --git a/collect_app/src/androidTest/assets/media/external_data_10.xml b/test-forms/src/main/resources/media/external_data_10.xml similarity index 100% rename from collect_app/src/androidTest/assets/media/external_data_10.xml rename to test-forms/src/main/resources/media/external_data_10.xml diff --git a/collect_app/src/androidTest/assets/media/famous.jpg b/test-forms/src/main/resources/media/famous.jpg similarity index 100% rename from collect_app/src/androidTest/assets/media/famous.jpg rename to test-forms/src/main/resources/media/famous.jpg diff --git a/collect_app/src/androidTest/assets/media/file.gif b/test-forms/src/main/resources/media/file.gif similarity index 100% rename from collect_app/src/androidTest/assets/media/file.gif rename to test-forms/src/main/resources/media/file.gif diff --git a/collect_app/src/androidTest/assets/media/formWithExternalFiles-media/fruits.csv b/test-forms/src/main/resources/media/formWithExternalFiles-media/fruits.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/formWithExternalFiles-media/fruits.csv rename to test-forms/src/main/resources/media/formWithExternalFiles-media/fruits.csv diff --git a/collect_app/src/androidTest/assets/media/formWithExternalFiles-media/fruits.xml b/test-forms/src/main/resources/media/formWithExternalFiles-media/fruits.xml similarity index 100% rename from collect_app/src/androidTest/assets/media/formWithExternalFiles-media/fruits.xml rename to test-forms/src/main/resources/media/formWithExternalFiles-media/fruits.xml diff --git a/collect_app/src/androidTest/assets/media/formWithExternalFiles-media/itemsets.csv b/test-forms/src/main/resources/media/formWithExternalFiles-media/itemsets.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/formWithExternalFiles-media/itemsets.csv rename to test-forms/src/main/resources/media/formWithExternalFiles-media/itemsets.csv diff --git a/collect_app/src/androidTest/assets/media/formWithExternalFiles-media/last-saved.xml b/test-forms/src/main/resources/media/formWithExternalFiles-media/last-saved.xml similarity index 100% rename from collect_app/src/androidTest/assets/media/formWithExternalFiles-media/last-saved.xml rename to test-forms/src/main/resources/media/formWithExternalFiles-media/last-saved.xml diff --git a/collect_app/src/androidTest/assets/media/fruits.csv b/test-forms/src/main/resources/media/fruits.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/fruits.csv rename to test-forms/src/main/resources/media/fruits.csv diff --git a/collect_app/src/androidTest/assets/media/itemSets.csv b/test-forms/src/main/resources/media/itemSets.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/itemSets.csv rename to test-forms/src/main/resources/media/itemSets.csv diff --git a/collect_app/src/androidTest/assets/media/sampleAudio.wav b/test-forms/src/main/resources/media/sampleAudio.wav similarity index 100% rename from collect_app/src/androidTest/assets/media/sampleAudio.wav rename to test-forms/src/main/resources/media/sampleAudio.wav diff --git a/collect_app/src/androidTest/assets/media/sampleVideo.mp4 b/test-forms/src/main/resources/media/sampleVideo.mp4 similarity index 100% rename from collect_app/src/androidTest/assets/media/sampleVideo.mp4 rename to test-forms/src/main/resources/media/sampleVideo.mp4 diff --git a/collect_app/src/androidTest/assets/media/selectOneExternal-media/itemsets.csv b/test-forms/src/main/resources/media/selectOneExternal-media/itemsets.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/selectOneExternal-media/itemsets.csv rename to test-forms/src/main/resources/media/selectOneExternal-media/itemsets.csv diff --git a/collect_app/src/androidTest/assets/media/simple-search-external-csv-fruits.csv b/test-forms/src/main/resources/media/simple-search-external-csv-fruits.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/simple-search-external-csv-fruits.csv rename to test-forms/src/main/resources/media/simple-search-external-csv-fruits.csv diff --git a/collect_app/src/androidTest/assets/media/staff_list.csv b/test-forms/src/main/resources/media/staff_list.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/staff_list.csv rename to test-forms/src/main/resources/media/staff_list.csv diff --git a/collect_app/src/androidTest/assets/media/staff_rights.csv b/test-forms/src/main/resources/media/staff_rights.csv similarity index 100% rename from collect_app/src/androidTest/assets/media/staff_rights.csv rename to test-forms/src/main/resources/media/staff_rights.csv diff --git a/collect_app/src/androidTest/assets/media/test.m4a b/test-forms/src/main/resources/media/test.m4a similarity index 100% rename from collect_app/src/androidTest/assets/media/test.m4a rename to test-forms/src/main/resources/media/test.m4a diff --git a/testshared/src/main/java/org/odk/collect/testshared/ActivityControllerRule.kt b/testshared/src/main/java/org/odk/collect/testshared/ActivityControllerRule.kt new file mode 100644 index 00000000000..9cd8b975a55 --- /dev/null +++ b/testshared/src/main/java/org/odk/collect/testshared/ActivityControllerRule.kt @@ -0,0 +1,29 @@ +package org.odk.collect.testshared + +import android.app.Activity +import android.content.Intent +import org.junit.rules.ExternalResource +import org.robolectric.Robolectric +import org.robolectric.android.controller.ActivityController + +class ActivityControllerRule : ExternalResource() { + + private val controllers = mutableListOf>() + + override fun after() { + controllers.forEach { it.close() } + controllers.clear() + } + + fun build(activityClass: Class, intent: Intent): ActivityController { + return Robolectric.buildActivity(activityClass, intent).also { + controllers.add(it) + } + } + + fun add(supplier: () -> ActivityController): ActivityController { + return supplier().also { + controllers.add(it) + } + } +} diff --git a/testshared/src/main/java/org/odk/collect/testshared/AssertIntentsHelper.kt b/testshared/src/main/java/org/odk/collect/testshared/AssertIntentsHelper.kt new file mode 100644 index 00000000000..69b2043c02d --- /dev/null +++ b/testshared/src/main/java/org/odk/collect/testshared/AssertIntentsHelper.kt @@ -0,0 +1,26 @@ +package org.odk.collect.testshared + +import android.content.Intent +import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent +import org.hamcrest.Matcher +import org.odk.collect.testshared.EspressoHelpers.assertIntents +import kotlin.reflect.KClass + +class AssertIntentsHelper { + + private val matchers = mutableListOf>() + + fun assertNewIntent(matcher: Matcher) { + matchers.add(matcher) + assertIntents(*matchers.toTypedArray()) + } + + fun assertNewIntent(activityClass: KClass<*>) { + matchers.add(hasComponent(activityClass.java.name)) + assertIntents(*matchers.toTypedArray()) + } + + fun assertNoNewIntent() { + assertIntents(*matchers.toTypedArray()) + } +} diff --git a/testshared/src/main/java/org/odk/collect/testshared/EspressoHelpers.kt b/testshared/src/main/java/org/odk/collect/testshared/EspressoHelpers.kt new file mode 100644 index 00000000000..a787828af2c --- /dev/null +++ b/testshared/src/main/java/org/odk/collect/testshared/EspressoHelpers.kt @@ -0,0 +1,38 @@ +package org.odk.collect.testshared + +import android.content.Intent +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.doesNotExist +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.matcher.ViewMatchers.Visibility.VISIBLE +import androidx.test.espresso.matcher.ViewMatchers.withContentDescription +import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility +import androidx.test.espresso.matcher.ViewMatchers.withText +import org.hamcrest.CoreMatchers.not +import org.hamcrest.Matcher +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.equalTo + +object EspressoHelpers { + + fun assertText(text: String) { + onView(allOf(withText(text), withEffectiveVisibility(VISIBLE))) + .check(matches(not(doesNotExist()))) + } + + fun clickOnContentDescription(string: Int) { + onView(withContentDescription(string)).perform(click()) + } + + fun assertIntents(vararg matchers: Matcher) { + val intents = Intents.getIntents() + assertThat(matchers.size, equalTo(intents.size)) + + matchers.forEachIndexed { index, matcher -> + assertThat(intents[index], matcher) + } + } +} diff --git a/testshared/src/main/java/org/odk/collect/testshared/FakeScheduler.kt b/testshared/src/main/java/org/odk/collect/testshared/FakeScheduler.kt index 1d4ead081bb..c5fdc991a7e 100644 --- a/testshared/src/main/java/org/odk/collect/testshared/FakeScheduler.kt +++ b/testshared/src/main/java/org/odk/collect/testshared/FakeScheduler.kt @@ -78,6 +78,11 @@ class FakeScheduler : Scheduler { } } + fun flush() { + runBackground() + runForeground() + } + fun isRepeatRunning(): Boolean { return repeatTasks.isNotEmpty() } diff --git a/testshared/src/main/java/org/odk/collect/testshared/RobolectricHelpers.kt b/testshared/src/main/java/org/odk/collect/testshared/RobolectricHelpers.kt index 1368e4bbc6f..37ab5650fcb 100644 --- a/testshared/src/main/java/org/odk/collect/testshared/RobolectricHelpers.kt +++ b/testshared/src/main/java/org/odk/collect/testshared/RobolectricHelpers.kt @@ -1,9 +1,12 @@ package org.odk.collect.testshared +import android.app.Activity import android.app.Application import android.app.Service +import android.content.Intent import android.graphics.drawable.Drawable import android.media.MediaMetadataRetriever +import android.os.Bundle import android.os.Environment import android.os.Looper import android.view.ViewGroup @@ -17,6 +20,8 @@ import org.odk.collect.servicetest.ServiceScenario import org.odk.collect.servicetest.ServiceScenario.Companion.launch import org.robolectric.Robolectric import org.robolectric.Shadows +import org.robolectric.Shadows.shadowOf +import org.robolectric.android.controller.ActivityController import org.robolectric.shadows.ShadowEnvironment import org.robolectric.shadows.ShadowMediaMetadataRetriever import org.robolectric.shadows.ShadowMediaPlayer @@ -47,7 +52,11 @@ object RobolectricHelpers { @JvmOverloads fun setupMediaPlayerDataSource(testFile: String, duration: Int = 322450): DataSource { val dataSource = DataSource.toDataSource(testFile) - ShadowMediaMetadataRetriever.addMetadata(dataSource, MediaMetadataRetriever.METADATA_KEY_DURATION, duration.toString()) + ShadowMediaMetadataRetriever.addMetadata( + dataSource, + MediaMetadataRetriever.METADATA_KEY_DURATION, + duration.toString() + ) ShadowMediaPlayer.addMediaInfo(dataSource, MediaInfo(duration, 0)) return dataSource } @@ -73,7 +82,10 @@ object RobolectricHelpers { @JvmStatic @Suppress("UNCHECKED_CAST") - fun getFragmentByClass(fragmentManager: FragmentManager, fragmentClass: Class): F? { + fun getFragmentByClass( + fragmentManager: FragmentManager, + fragmentClass: Class + ): F? { val fragments = fragmentManager.fragments for (fragment in fragments) { if (fragment.javaClass.isAssignableFrom(fragmentClass)) { @@ -108,7 +120,8 @@ object RobolectricHelpers { if (services.containsKey(serviceClass)) { services[serviceClass]!!.startWithNewIntent(intent) } else { - val serviceController: ServiceScenario<*> = launch(serviceClass as Class, intent) + val serviceController: ServiceScenario<*> = + launch(serviceClass as Class, intent) services[serviceClass] = serviceController } } else { @@ -132,4 +145,38 @@ object RobolectricHelpers { } } } + + inline fun ActivityController.recreateWithProcessRestore( + resultCode: Int? = null, + result: Intent? = null, + resetProcess: () -> Unit + ): ActivityController { + // Destroy activity with saved instance state + val outState = Bundle() + this.saveInstanceState(outState).pause().stop().destroy() + + // Reset process + resetProcess() + + // Recreate with saved instance state + val recreated = Robolectric.buildActivity(A::class.java, this.intent).create(outState) + .start() + .restoreInstanceState(outState) + .postCreate(outState) + + // Return result + if (resultCode != null) { + val startedActivityForResult = shadowOf(this.get()).nextStartedActivityForResult + shadowOf(recreated.get()).receiveResult( + startedActivityForResult.intent, + resultCode, + result + ) + } + + // Resume activity + return recreated.resume() + .visible() + .topActivityResumed(true) + } }