Skip to content

Commit

Permalink
Merge pull request getodk#5734 from seadowg/bulk-finalization
Browse files Browse the repository at this point in the history
Add bulk finalization
  • Loading branch information
seadowg committed Oct 17, 2023
1 parent d8fb190 commit c49c8f7
Show file tree
Hide file tree
Showing 55 changed files with 1,314 additions and 295 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ jobs:

- run:
name: Run code quality checks
command: ./gradlew checkCode
command: ./gradlew pmd ktlintCheck checkstyle lintDebug -PlintStrings

test_modules:
<<: *android_config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import android.app.Application
import android.app.Service
import android.content.Context
import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

/**
Expand Down Expand Up @@ -41,13 +43,21 @@ class AppState {

@Suppress("UNCHECKED_CAST")
fun <T> get(key: String): T? {
return map.get(key) as T?
return map[key] as T?
}

fun <T> getLive(key: String, default: T): LiveData<T> {
return get(key, MutableLiveData(default))
}

fun set(key: String, value: Any?) {
map[key] = value
}

fun <T> setLive(key: String, value: T?) {
get(key, MutableLiveData<T>()).postValue(value)
}

fun clear() {
map.clear()
}
Expand Down
4 changes: 4 additions & 0 deletions collect_app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ android {
htmlReport true
lintConfig file("$rootDir/config/lint.xml")
xmlReport true

if (!project.hasProperty("lintStrings")) {
disable += ["HardcodedText"]
}
}
namespace 'org.odk.collect.android'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.odk.collect.android.feature.formmanagement

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.support.pages.FormEntryPage.QuestionAndAnswer
import org.odk.collect.android.support.pages.MainMenuPage
import org.odk.collect.android.support.pages.SaveOrDiscardFormDialog
import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.TestRuleChain
import org.odk.collect.strings.R.plurals
import org.odk.collect.strings.R.string

@RunWith(AndroidJUnit4::class)
class BulkFinalizationTest {

val rule = CollectTestRule()

@get:Rule
val chain: RuleChain = TestRuleChain.chain().around(rule)

@Test
fun canBulkFinalizeDrafts() {
rule.startAtMainMenu()
.copyForm("one-question.xml")
.startBlankForm("One Question")
.fillOutAndSave(QuestionAndAnswer("what is your age", "97"))
.startBlankForm("One Question")
.fillOutAndSave(QuestionAndAnswer("what is your age", "98"))

.clickEditSavedForm(2)
.clickOptionsIcon(string.finalize_all_forms)
.clickOnString(string.finalize_all_forms)
.checkIsSnackbarWithQuantityDisplayed(plurals.bulk_finalize_success, 2)
.assertTextDoesNotExist("One Question")
.pressBack(MainMenuPage())

.assertNumberOfFinalizedForms(2)
}

@Test
fun whenThereAreDraftsWithConstraintViolations_marksFormsAsHavingErrors() {
rule.startAtMainMenu()
.copyForm("two-question-required.xml")
.startBlankForm("Two Question Required")
.fillOut(QuestionAndAnswer("What is your name?", "Dan"))
.pressBack(SaveOrDiscardFormDialog(MainMenuPage()))
.clickSaveChanges()

.startBlankForm("Two Question Required")
.fillOutAndSave(
QuestionAndAnswer("What is your name?", "Tim"),
QuestionAndAnswer("What is your age?", "45", true)
)

.clickEditSavedForm(2)
.clickOptionsIcon(string.finalize_all_forms)
.clickOnString(string.finalize_all_forms)
.checkIsSnackbarWithMessageDisplayed(string.bulk_finalize_partial_success, 1, 1)
.assertText("Two Question Required")
.pressBack(MainMenuPage())

.assertNumberOfEditableForms(1)
.assertNumberOfFinalizedForms(1)
}

@Test
fun whenADraftPreviouslyHadConstraintViolations_marksFormsAsHavingErrors() {
rule.startAtMainMenu()
.copyForm("two-question-required.xml")
.startBlankForm("Two Question Required")
.fillOut(QuestionAndAnswer("What is your name?", "Dan"))
.pressBack(SaveOrDiscardFormDialog(MainMenuPage()))
.clickSaveChanges()

.clickEditSavedForm(1)
.clickOptionsIcon(string.finalize_all_forms)
.clickOnString(string.finalize_all_forms)
.checkIsSnackbarWithQuantityDisplayed(plurals.bulk_finalize_failure, 1)

.clickOptionsIcon(string.finalize_all_forms)
.clickOnString(string.finalize_all_forms)
.checkIsSnackbarWithQuantityDisplayed(plurals.bulk_finalize_failure, 1)
}

@Test
fun doesNotFinalizeOtherTypesOfInstance() {
rule.startAtMainMenu()
.copyForm("one-question.xml")
.startBlankForm("One Question")
.fillOutAndSave(QuestionAndAnswer("what is your age", "97"))
.startBlankForm("One Question")
.fillOutAndFinalize(QuestionAndAnswer("what is your age", "98"))

.clickEditSavedForm(1)
.clickOptionsIcon(string.finalize_all_forms)
.clickOnString(string.finalize_all_forms)
.checkIsSnackbarWithQuantityDisplayed(plurals.bulk_finalize_success, 1)
.assertTextDoesNotExist("One Question")
.pressBack(MainMenuPage())

.assertNumberOfFinalizedForms(2)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.odk.collect.android.feature.instancemanagement

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import org.kxml2.io.KXmlParser
import org.kxml2.kdom.Document
import org.odk.collect.android.support.TestDependencies
import org.odk.collect.android.support.pages.FormEntryPage
import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.TestRuleChain
import java.io.File
import java.io.StringReader

@RunWith(AndroidJUnit4::class)
class PartialSubmissionTet {

private val testDependencies = TestDependencies()
private val rule = CollectTestRule(useDemoProject = false)

@get:Rule
val chain: RuleChain = TestRuleChain.chain(testDependencies)
.around(rule)

@Test
fun canFillAndSubmitAFormWithPartialSubmission() {
rule.withProject(testDependencies.server.url)
.copyForm("one-question-partial.xml", testDependencies.server.hostName)
.startBlankForm("One Question")
.fillOutAndFinalize(FormEntryPage.QuestionAndAnswer("what is your age", "123"))

.clickSendFinalizedForm(1)
.clickSelectAll()
.clickSendSelected()

val submissions = testDependencies.server.submissions
assertThat(submissions.size, equalTo(1))

val root = parseXml(submissions[0]).rootElement
assertThat(root.name, equalTo("age"))
assertThat(root.childCount, equalTo(1))
assertThat(root.getChild(0), equalTo("123"))
}

private fun parseXml(file: File): Document {
return StringReader(String(file.readBytes())).use { reader ->
val parser = KXmlParser()
parser.setInput(reader)
Document().also { it.parse(parser) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import org.odk.collect.android.openrosa.HttpPostResult;
import org.odk.collect.android.openrosa.OpenRosaConstants;
import org.odk.collect.android.openrosa.OpenRosaHttpInterface;
import org.odk.collect.android.utilities.FileUtils;
import org.odk.collect.shared.TempFiles;
import org.odk.collect.shared.strings.Md5;
import org.odk.collect.shared.strings.RandomString;

Expand Down Expand Up @@ -44,6 +46,8 @@ public class StubOpenRosaServer implements OpenRosaHttpInterface {
private boolean noHashPrefixInMediaFiles;
private boolean randomHash;

private final File submittedFormsDir = TempFiles.createTempDir();

@NonNull
@Override
public HttpGetResult executeGetRequest(@NonNull URI uri, @Nullable String contentType, @Nullable HttpCredentialsInterface credentials) throws Exception {
Expand Down Expand Up @@ -111,6 +115,8 @@ public HttpPostResult uploadSubmissionAndFiles(@NonNull File submissionFile, @No
} else if (credentialsIncorrect(credentials)) {
return new HttpPostResult("", 401, "");
} else if (uri.getPath().equals(OpenRosaConstants.SUBMISSION)) {
File destFile = new File(submittedFormsDir, String.valueOf(submittedFormsDir.listFiles().length));
FileUtils.copyFile(submissionFile, destFile);
return new HttpPostResult("", 201, "");
} else {
return new HttpPostResult("", 404, "");
Expand Down Expand Up @@ -162,6 +168,10 @@ public String getHostName() {
return HOST;
}

public List<File> getSubmissions() {
return asList(submittedFormsDir.listFiles());
}

private boolean credentialsIncorrect(HttpCredentialsInterface credentials) {
if (username == null && password == null) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ public <D extends Page<D>> D fillOutAndSave(D destination, QuestionAndAnswer...
.clickSaveChanges();
}

public MainMenuPage fillOutAndSave(QuestionAndAnswer... questionsAndAnswers) {
return fillOut(questionsAndAnswers)
.pressBack(new SaveOrDiscardFormDialog<>(new MainMenuPage()))
.clickSaveChanges();
}

public MainMenuPage fillOutAndFinalize(QuestionAndAnswer... questionsAndAnswers) {
return fillOut(questionsAndAnswers)
.swipeToEndScreen()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.odk.collect.android.support.pages

import android.app.Application
import android.content.pm.ActivityInfo
import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView
Expand Down Expand Up @@ -181,7 +182,18 @@ abstract class Page<T : Page<T>> {
return this as T
}

fun checkIsSnackbarWithMessageDisplayed(message: Int): T {
fun checkIsSnackbarWithQuantityDisplayed(message: Int, quantity: Int): T {
return checkIsSnackbarWithMessageDisplayed(
ApplicationProvider.getApplicationContext<Application>()
.getLocalizedQuantityString(message, quantity, quantity)
)
}

fun checkIsSnackbarWithMessageDisplayed(message: Int, vararg formatArgs: Any): T {
return checkIsSnackbarWithMessageDisplayed(getTranslatedString(message, *formatArgs))
}

fun checkIsSnackbarWithMessageDisplayed(message: String): T {
onView(withText(message)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
return this as T
}
Expand Down Expand Up @@ -441,6 +453,10 @@ abstract class Page<T : Page<T>> {
}

fun clickOptionsIcon(@StringRes expectedOptionString: Int): T {
return clickOptionsIcon(getTranslatedString(expectedOptionString))
}

fun clickOptionsIcon(expectedOptionString: String): T {
tryAgainOnFail({
Espresso.openActionBarOverflowOrOptionsMenu(ActivityHelpers.getActivity())
assertText(expectedOptionString)
Expand Down
Loading

0 comments on commit c49c8f7

Please sign in to comment.