Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to bulk finalize #5776

Merged
merged 19 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.lifecycle.Observer
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import org.odk.collect.androidshared.data.Consumable

/**
* Convenience wrapper around Android's [Snackbar] API.
Expand All @@ -40,6 +42,11 @@ object SnackbarUtils {
return showSnackbar(parentView, message, 3500, anchorView, action, displayDismissButton)
}

@JvmStatic
fun showLongSnackbar(parentView: View, snackbarDetails: SnackbarDetails) {
showLongSnackbar(parentView, snackbarDetails.text, action = snackbarDetails.action)
}

@JvmStatic
@JvmOverloads
fun showLongSnackbar(
Expand Down Expand Up @@ -74,7 +81,8 @@ object SnackbarUtils {

lastSnackbar?.dismiss()
lastSnackbar = Snackbar.make(parentView, message.trim(), duration).apply {
val textView = view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
val textView =
view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
textView.isSingleLine = false

if (anchorView?.visibility != View.GONE) {
Expand All @@ -88,7 +96,8 @@ object SnackbarUtils {
setOnClickListener {
dismiss()
}
contentDescription = context.getString(org.odk.collect.strings.R.string.close_snackbar)
contentDescription =
context.getString(org.odk.collect.strings.R.string.close_snackbar)
}

val params = LinearLayout.LayoutParams(
Expand Down Expand Up @@ -116,8 +125,23 @@ object SnackbarUtils {
lastSnackbar?.show()
}

data class Action(
data class SnackbarDetails @JvmOverloads constructor(
val text: String,
val listener: () -> Unit
val action: Action? = null
)

data class Action(val text: String, val listener: () -> Unit)

abstract class SnackbarPresenterObserver<T : Any?>(private val parentView: View) :
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably use this in a few more places, but as with other PRs for v2023.3 I wanted to minimise the merge conflict risk.

Observer<Consumable<T>> {

abstract fun getSnackbarDetails(value: T): SnackbarDetails

override fun onChanged(consumable: Consumable<T>) {
if (!consumable.isConsumed()) {
showLongSnackbar(parentView, getSnackbarDetails(consumable.value))
consumable.consume()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package org.odk.collect.android.feature.formmanagement

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.odk.collect.android.support.TestDependencies
import org.odk.collect.android.support.pages.AccessControlPage
import org.odk.collect.android.support.pages.EditSavedFormPage
import org.odk.collect.android.support.pages.FormEntryPage.QuestionAndAnswer
import org.odk.collect.android.support.pages.MainMenuPage
import org.odk.collect.android.support.pages.ProjectSettingsPage
import org.odk.collect.android.support.pages.SaveOrDiscardFormDialog
import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.TestRuleChain
Expand All @@ -16,23 +22,24 @@ import org.odk.collect.strings.R.string
@RunWith(AndroidJUnit4::class)
class BulkFinalizationTest {

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

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

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

.clickDrafts(2)
.clickOptionsIcon(string.finalize_all_forms)
.clickOnString(string.finalize_all_forms)
.clickFinalizeAll(2)
.clickFinalize()
.checkIsSnackbarWithQuantityDisplayed(plurals.bulk_finalize_success, 2)
.assertTextDoesNotExist("One Question")
.pressBack(MainMenuPage())
Expand All @@ -42,8 +49,8 @@ class BulkFinalizationTest {

@Test
fun whenThereAreDraftsWithConstraintViolations_marksFormsAsHavingErrors() {
rule.startAtMainMenu()
.copyForm("two-question-required.xml")
rule.withProject("http://example.com")
.copyForm("two-question-required.xml", "example.com")
.startBlankForm("Two Question Required")
.fillOut(QuestionAndAnswer("What is your name?", "Dan"))
.pressBack(SaveOrDiscardFormDialog(MainMenuPage()))
Expand All @@ -56,8 +63,8 @@ class BulkFinalizationTest {
)

.clickDrafts(2)
.clickOptionsIcon(string.finalize_all_forms)
.clickOnString(string.finalize_all_forms)
.clickFinalizeAll(2)
.clickFinalize()
.checkIsSnackbarWithMessageDisplayed(string.bulk_finalize_partial_success, 1, 1)
.assertText("Two Question Required")
.pressBack(MainMenuPage())
Expand All @@ -68,27 +75,28 @@ class BulkFinalizationTest {

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

.clickDrafts(1)
.clickOptionsIcon(string.finalize_all_forms)
.clickOnString(string.finalize_all_forms)
.clickFinalizeAll(1)
.clickFinalize()
.checkIsSnackbarWithQuantityDisplayed(plurals.bulk_finalize_failure, 1)

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

@Test
fun doesNotFinalizeInstancesWithSavePoints() {
rule.startAtMainMenu()
.copyForm("one-question.xml")
rule.withProject("http://example.com")
.copyForm("one-question.xml", "example.com")
.startBlankForm("One Question")
.swipeToEndScreen()
.clickSaveAsDraft()
Expand All @@ -97,33 +105,109 @@ class BulkFinalizationTest {
.clickOnForm("One Question")
.killAndReopenApp(MainMenuPage())

.clickDrafts()
.clickOptionsIcon(string.finalize_all_forms)
.clickOnString(string.finalize_all_forms)
.checkIsSnackbarWithQuantityDisplayed(plurals.bulk_finalize_failure, 1)
.clickDrafts(1)
.clickFinalizeAll(1)
.clickFinalize()
.checkIsSnackbarWithMessageDisplayed(string.bulk_finalize_unsupported, 0)
.assertText("One Question")
.pressBack(MainMenuPage())

.assertNumberOfEditableForms(1)
.assertNumberOfFinalizedForms(0)
}

@Test
fun doesNotFinalizeInstancesFromEncryptedForms() {
rule.withProject("http://example.com")
.copyForm("encrypted.xml", "example.com")
.startBlankForm("encrypted")
.swipeToEndScreen()
.clickSaveAsDraft()

.clickDrafts(1)
.clickFinalizeAll(1)
.clickFinalize()
.checkIsSnackbarWithMessageDisplayed(string.bulk_finalize_unsupported, 0)
.assertText("encrypted")
.pressBack(MainMenuPage())

.assertNumberOfEditableForms(1)
.assertNumberOfFinalizedForms(0)
}

@Test
fun doesNotFinalizeAlreadyFinalizedInstances() {
rule.startAtMainMenu()
.copyForm("one-question.xml")
rule.withProject("http://example.com")
.copyForm("one-question.xml", "example.com")
.startBlankForm("One Question")
.fillOutAndSave(QuestionAndAnswer("what is your age", "97"))
.startBlankForm("One Question")
.fillOutAndFinalize(QuestionAndAnswer("what is your age", "98"))

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

.assertNumberOfFinalizedForms(2)
}

@Test
fun whenAutoSendIsEnabled_draftsAreSentAfterFinalizing() {
val mainMenuPage = rule.withProject(testDependencies.server.url)
.enableAutoSend()

.copyForm("one-question.xml", testDependencies.server.hostName)
.startBlankForm("One Question")
.fillOutAndSave(QuestionAndAnswer("what is your age", "97"))

.clickDrafts(1)
.clickFinalizeAll(1)
.clickFinalize()
.pressBack(MainMenuPage())

testDependencies.scheduler.runDeferredTasks()

mainMenuPage.clickViewSentForm(1)
.assertText("One Question")

assertThat(testDependencies.server.submissions.size, equalTo(1))
}

@Test
fun canCancel() {
rule.withProject("http://example.com")
.copyForm("one-question.xml", "example.com")
.startBlankForm("One Question")
.fillOutAndSave(QuestionAndAnswer("what is your age", "97"))

.clickDrafts(1)
.clickFinalizeAll(1)
.clickCancel()
.assertText("One Question")
.pressBack(MainMenuPage())

.assertNumberOfEditableForms(1)
}

@Test
fun canBeDisabled() {
rule.withProject("http://example.com")
.openProjectSettingsDialog()
.clickSettings()
.clickAccessControl()
.clickFormEntrySettings()
.clickOnString(string.finalize_all_forms)
.pressBack(AccessControlPage())
.pressBack(ProjectSettingsPage())
.pressBack(MainMenuPage())

.copyForm("one-question.xml", "example.com")
.startBlankForm("One Question")
.fillOutAndSave(QuestionAndAnswer("what is your age", "1892"))
.clickDrafts()
.assertNoOptionsMenu()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.odk.collect.android.support.pages

import android.app.Application
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText
import org.odk.collect.strings.R
import org.odk.collect.strings.R.plurals
import org.odk.collect.strings.localization.getLocalizedQuantityString

class BulkFinalizationConfirmationDialogPage(private val count: Int) : Page<BulkFinalizationConfirmationDialogPage>() {
override fun assertOnPage(): BulkFinalizationConfirmationDialogPage {
val title = ApplicationProvider.getApplicationContext<Application>()
.getLocalizedQuantityString(plurals.bulk_finalize_confirmation, count, count)

onView(withText(title)).inRoot(isDialog()).check(matches(isDisplayed()))
return this
}

fun clickFinalize(): EditSavedFormPage {
return this.clickOnButtonInDialog(R.string.finalize, EditSavedFormPage())
}

fun clickCancel(): EditSavedFormPage {
return this.clickOnButtonInDialog(R.string.cancel, EditSavedFormPage())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@

package org.odk.collect.android.support.pages;

import android.widget.RelativeLayout;

import androidx.appcompat.widget.Toolbar;

import org.odk.collect.android.R;
import org.odk.collect.android.adapters.InstanceListCursorAdapter;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.replaceText;
Expand All @@ -37,11 +30,19 @@
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.not;

import android.widget.RelativeLayout;

import androidx.appcompat.widget.Toolbar;

import org.odk.collect.android.R;
import org.odk.collect.android.adapters.InstanceListCursorAdapter;
import org.odk.collect.strings.R.string;

public class EditSavedFormPage extends Page<EditSavedFormPage> {

@Override
public EditSavedFormPage assertOnPage() {
assertText(org.odk.collect.strings.R.string.review_data);
assertText(string.review_data);
return this;
}

Expand Down Expand Up @@ -93,4 +94,11 @@ public EditSavedFormPage searchInBar(String query) {
onView(withId(androidx.appcompat.R.id.search_src_text)).perform(replaceText(query));
return this;
}

public BulkFinalizationConfirmationDialogPage clickFinalizeAll(int count) {
this.clickOptionsIcon(string.finalize_all_forms)
.clickOnString(string.finalize_all_forms);

return new BulkFinalizationConfirmationDialogPage(count).assertOnPage();
}
}
Loading