diff --git a/collect_app/src/main/java/org/odk/collect/android/readytosend/InstanceUploaderListActivity.java b/collect_app/src/main/java/org/odk/collect/android/readytosend/InstanceUploaderListActivity.java index 32780110779..bef9070c072 100644 --- a/collect_app/src/main/java/org/odk/collect/android/readytosend/InstanceUploaderListActivity.java +++ b/collect_app/src/main/java/org/odk/collect/android/readytosend/InstanceUploaderListActivity.java @@ -469,7 +469,7 @@ public void onLoadFinished(@NonNull Loader loader, Cursor cursor) { hideProgressBarAndAllow(); listAdapter.changeCursor(cursor); toggleButtonLabel(findViewById(R.id.toggle_button), listView); - binding.readyToSendBanner.init(instancesRepositoryProvider.get()); + binding.readyToSendBanner.init(instancesRepositoryProvider.get(), System::currentTimeMillis); } @Override diff --git a/collect_app/src/main/java/org/odk/collect/android/readytosend/ReadyToSendBanner.kt b/collect_app/src/main/java/org/odk/collect/android/readytosend/ReadyToSendBanner.kt index 30fc0ab7ff3..7f00cf0ad67 100644 --- a/collect_app/src/main/java/org/odk/collect/android/readytosend/ReadyToSendBanner.kt +++ b/collect_app/src/main/java/org/odk/collect/android/readytosend/ReadyToSendBanner.kt @@ -8,18 +8,19 @@ import org.odk.collect.android.databinding.ReadyToSendBannerBinding import org.odk.collect.forms.instances.Instance import org.odk.collect.forms.instances.InstancesRepository import org.odk.collect.strings.R +import java.util.function.Supplier -private const val ONE_SECOND = 1000 -private const val ONE_MINUTE = 60000 -private const val ONE_HOUR = 3600000 -private const val ONE_DAY = 86400000 +const val ONE_SECOND = 1000L +const val ONE_MINUTE = 60000L +const val ONE_HOUR = 3600000L +const val ONE_DAY = 86400000L class ReadyToSendBanner(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs) { constructor(context: Context) : this(context, null) private val binding = ReadyToSendBannerBinding.inflate(LayoutInflater.from(context), this, true) - fun init(instancesRepository: InstancesRepository) { + fun init(instancesRepository: InstancesRepository, clock: Supplier) { val sentInstances: List = instancesRepository.getAllByStatus(Instance.STATUS_SUBMITTED) val numberOfInstancesReadyToSend = instancesRepository.getCountByStatus( Instance.STATUS_COMPLETE, @@ -28,7 +29,7 @@ class ReadyToSendBanner(context: Context, attrs: AttributeSet?) : ConstraintLayo if (sentInstances.isNotEmpty() && numberOfInstancesReadyToSend > 0) { val lastSentInstance = sentInstances.maxBy { instance -> instance.lastStatusChangeDate } - val millisecondsAgo = System.currentTimeMillis() - lastSentInstance.lastStatusChangeDate + val millisecondsAgo = clock.get() - lastSentInstance.lastStatusChangeDate if (millisecondsAgo >= ONE_DAY) { binding.title.text = context.getString(R.string.last_form_sent_days_ago, millisecondsAgo / ONE_DAY) } else if (millisecondsAgo >= ONE_HOUR) { diff --git a/collect_app/src/test/java/org/odk/collect/android/readytosend/ReadyToSendBannerTest.kt b/collect_app/src/test/java/org/odk/collect/android/readytosend/ReadyToSendBannerTest.kt new file mode 100644 index 00000000000..3b7d4f29ef1 --- /dev/null +++ b/collect_app/src/test/java/org/odk/collect/android/readytosend/ReadyToSendBannerTest.kt @@ -0,0 +1,315 @@ +package org.odk.collect.android.readytosend + +import android.app.Application +import android.view.View +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.matcher.ViewMatchers.assertThat +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.android.material.textview.MaterialTextView +import org.hamcrest.Matchers.equalTo +import org.junit.Test +import org.junit.runner.RunWith +import org.odk.collect.android.R +import org.odk.collect.forms.instances.Instance +import org.odk.collect.formstest.InMemInstancesRepository + +@RunWith(AndroidJUnit4::class) +class ReadyToSendBannerTest { + private val context: Application = + ApplicationProvider.getApplicationContext().also { + it.setTheme(R.style.Theme_Collect) + } + + private val instancesRepository = InMemInstancesRepository().also { + it.save( + Instance.Builder() + .formId("1") + .status(Instance.STATUS_INCOMPLETE) + .build() + ) + } + + @Test + fun `if there are no sent instances do not display the banner`() { + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { 0 } + } + + assertThat(view.findViewById(R.id.banner).visibility, equalTo(View.GONE)) + } + + @Test + fun `if there are no instances ready to send do not display the banner`() { + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { 0 } + } + + assertThat(view.findViewById(R.id.banner).visibility, equalTo(View.GONE)) + } + + @Test + fun `if there are sent instances but no instances ready to send do not display the banner`() { + instancesRepository.save( + Instance.Builder() + .formId("2") + .status(Instance.STATUS_SUBMITTED) + .build() + ) + + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { 0 } + } + + assertThat(view.findViewById(R.id.banner).visibility, equalTo(View.GONE)) + } + + @Test + fun `if there are instances ready to send (complete) but no sent instances do not display the banner`() { + instancesRepository.save( + Instance.Builder() + .formId("2") + .status(Instance.STATUS_COMPLETE) + .build() + ) + + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { 0 } + } + + assertThat(view.findViewById(R.id.banner).visibility, equalTo(View.GONE)) + } + + @Test + fun `if there are instances ready to send (submission failed) but no sent instances do not display the banner`() { + instancesRepository.save( + Instance.Builder() + .formId("2") + .status(Instance.STATUS_SUBMISSION_FAILED) + .build() + ) + + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { 0 } + } + + assertThat(view.findViewById(R.id.banner).visibility, equalTo(View.GONE)) + } + + @Test + fun `if there are both sent and ready to send instances display the banner`() { + instancesRepository.save( + Instance.Builder() + .formId("2") + .status(Instance.STATUS_COMPLETE) + .build() + ) + + instancesRepository.save( + Instance.Builder() + .formId("3") + .status(Instance.STATUS_SUBMITTED) + .build() + ) + + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { 0 } + } + + assertThat( + view.findViewById(R.id.banner).visibility, + equalTo(View.VISIBLE) + ) + } + + @Test + fun `the banner should display how long ago in seconds the last instance was sent if it was less than a minute ago`() { + instancesRepository.save( + Instance.Builder() + .formId("2") + .status(Instance.STATUS_COMPLETE) + .build() + ) + + instancesRepository.save( + Instance.Builder() + .formId("3") + .status(Instance.STATUS_SUBMITTED) + .lastStatusChangeDate(0) + .build() + ) + + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { ONE_SECOND * 5 } + } + + assertThat( + view.findViewById(R.id.title).text, + equalTo("Last form sent: 5 second(s) ago") + ) + } + + @Test + fun `the banner should display how long ago in minutes the last instance was sent if it was less than an hour ago`() { + instancesRepository.save( + Instance.Builder() + .formId("2") + .status(Instance.STATUS_COMPLETE) + .build() + ) + + instancesRepository.save( + Instance.Builder() + .formId("3") + .status(Instance.STATUS_SUBMITTED) + .lastStatusChangeDate(0) + .build() + ) + + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { ONE_MINUTE * 10 } + } + + assertThat( + view.findViewById(R.id.title).text, + equalTo("Last form sent: 10 minute(s) ago") + ) + } + + @Test + fun `the banner should display how long ago in hours the last instance was sent if it was less than a day ago`() { + instancesRepository.save( + Instance.Builder() + .formId("2") + .status(Instance.STATUS_COMPLETE) + .build() + ) + + instancesRepository.save( + Instance.Builder() + .formId("3") + .status(Instance.STATUS_SUBMITTED) + .lastStatusChangeDate(0) + .build() + ) + + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { ONE_HOUR * 2 } + } + + assertThat( + view.findViewById(R.id.title).text, + equalTo("Last form sent: 2 hour(s) ago") + ) + } + + @Test + fun `the banner should display how long ago in days the last instance was sent if it was more than 24 hours ago`() { + instancesRepository.save( + Instance.Builder() + .formId("2") + .status(Instance.STATUS_COMPLETE) + .build() + ) + + instancesRepository.save( + Instance.Builder() + .formId("3") + .status(Instance.STATUS_SUBMITTED) + .lastStatusChangeDate(0) + .build() + ) + + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { ONE_DAY * 34 } + } + + assertThat( + view.findViewById(R.id.title).text, + equalTo("Last form sent: 34 day(s) ago") + ) + } + + @Test + fun `the banner should display how long ago the last instance was sent if there are multiple sent instances`() { + instancesRepository.save( + Instance.Builder() + .formId("2") + .status(Instance.STATUS_COMPLETE) + .build() + ) + + instancesRepository.save( + Instance.Builder() + .formId("3") + .status(Instance.STATUS_SUBMITTED) + .lastStatusChangeDate(0) + .build() + ) + + instancesRepository.save( + Instance.Builder() + .formId("4") + .status(Instance.STATUS_SUBMITTED) + .lastStatusChangeDate(ONE_SECOND * 5) + .build() + ) + + instancesRepository.save( + Instance.Builder() + .formId("5") + .status(Instance.STATUS_SUBMITTED) + .lastStatusChangeDate(ONE_SECOND * 4) + .build() + ) + + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { ONE_SECOND * 10 } + } + + assertThat( + view.findViewById(R.id.title).text, + equalTo("Last form sent: 5 second(s) ago") + ) + } + + @Test + fun `the banner should display the number of instances ready to send`() { + instancesRepository.save( + Instance.Builder() + .formId("2") + .status(Instance.STATUS_COMPLETE) + .build() + ) + + instancesRepository.save( + Instance.Builder() + .formId("3") + .status(Instance.STATUS_SUBMISSION_FAILED) + .build() + ) + + instancesRepository.save( + Instance.Builder() + .formId("4") + .status(Instance.STATUS_COMPLETE) + .build() + ) + + instancesRepository.save( + Instance.Builder() + .formId("5") + .status(Instance.STATUS_SUBMITTED) + .build() + ) + + val view = ReadyToSendBanner(context).also { + it.init(instancesRepository) { 0 } + } + + assertThat( + view.findViewById(R.id.subtext).text, + equalTo("3 form(s) ready to send") + ) + } +}