From 6723268c87de074b264022eac0e2dbad5db55d58 Mon Sep 17 00:00:00 2001
From: masclot <103062089+masclot@users.noreply.github.com>
Date: Tue, 9 Jan 2024 18:29:19 +0100
Subject: [PATCH] Fix #4135, Fix part of #5070: In FractionInteraction UI,
leave submit button enabled when answer is empty. (#5224)
Fix part of https://github.com/oppia/oppia-android/issues/5070: In
FractionInteraction UI, leave submit button enabled when answer is
empty. Show an error on submitting an empty answer. The error message
already exists and is the same as in
https://github.com/oppia/oppia/pull/18379.
Demo video:
[leave_submit_button_enabled_on_empty_answer_v3.webm](https://github.com/oppia/oppia-android/assets/103062089/d072ae88-c462-455c-a324-57680d4a82c5)
The new error messages for empty inputs on submit are listed here:
[oppia/design-team#71(comment)](https://github.com/oppia/design-team/issues/71#issuecomment-1581232000)
I added an accessibility-label exemption for
FractionInputInteractionViewTestActivity as this activity is only used
in tests.
Fix #4135: incidentaly, this change also fixes #4135, since I had to
split the tests for FractionInputInteraction
## Essential Checklist
- [x] The PR title and explanation each start with "Fix #bugnum: " (If
this PR fixes part of an issue, prefix the title with "Fix part of
#bugnum: ...".)
- [x] Any changes to
[scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets)
files have their rationale included in the PR explanation.
- [x] The PR follows the [style
guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide).
- [x] The PR does not contain any unnecessary code changes from Android
Studio
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)).
- [x] The PR is made from a branch that's **not** called "develop" and
is up-to-date with "develop".
- [x] The PR is **assigned** to the appropriate reviewers
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)).
## For UI-specific PRs only
If your PR includes UI-related changes, then:
- Add screenshots for portrait/landscape for both a tablet & phone of
the before & after UI changes
- For the screenshots above, include both English and pseudo-localized
(RTL) screenshots (see [RTL
guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines))
- Add a video showing the full UX flow with a screen reader enabled (see
[accessibility
guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide))
- For PRs introducing new UI elements or color changes, both light and
dark mode screenshots must be included
- Add a screenshot demonstrating that you ran affected Espresso tests
locally & that they're passing
---------
Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com>
---
app/src/main/AndroidManifest.xml | 1 +
.../app/activity/ActivityComponentImpl.kt | 2 +
.../app/parser/FractionParsingUiError.kt | 6 +-
.../FractionInteractionViewModel.kt | 29 +-
.../MathExpressionInteractionsViewModel.kt | 8 +-
...ractionInputInteractionViewTestActivity.kt | 112 ++++
.../InputInteractionViewTestActivity.kt | 13 +-
...y_fraction_input_interaction_view_test.xml | 75 +++
.../activity_input_interaction_view_test.xml | 41 --
app/src/main/res/values/strings.xml | 1 +
.../app/player/state/StateFragmentTest.kt | 7 +-
...ionInputInteractionViewTestActivityTest.kt | 608 ++++++++++++++++++
.../InputInteractionViewTestActivityTest.kt | 394 ------------
.../app/parser/FractionParsingUiErrorTest.kt | 2 +-
.../accessibility_label_exemptions.textproto | 1 +
.../oppia/android/util/math/FractionParser.kt | 8 +-
.../android/util/math/FractionParserTest.kt | 4 +-
17 files changed, 847 insertions(+), 465 deletions(-)
create mode 100644 app/src/main/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivity.kt
create mode 100644 app/src/main/res/layout/activity_fraction_input_interaction_view_test.xml
create mode 100644 app/src/sharedTest/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivityTest.kt
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c324a1a7119..76102786288 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -213,6 +213,7 @@
+
diff --git a/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt b/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt
index 6641b39e76c..14645028aa5 100644
--- a/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt
+++ b/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt
@@ -64,6 +64,7 @@ import org.oppia.android.app.testing.DragDropTestActivity
import org.oppia.android.app.testing.DrawableBindingAdaptersTestActivity
import org.oppia.android.app.testing.ExplorationInjectionActivity
import org.oppia.android.app.testing.ExplorationTestActivity
+import org.oppia.android.app.testing.FractionInputInteractionViewTestActivity
import org.oppia.android.app.testing.HomeFragmentTestActivity
import org.oppia.android.app.testing.HomeTestActivity
import org.oppia.android.app.testing.HtmlParserTestActivity
@@ -139,6 +140,7 @@ interface ActivityComponentImpl :
fun inject(faqSingleActivity: FAQSingleActivity)
fun inject(forceNetworkTypeActivity: ForceNetworkTypeActivity)
fun inject(forceNetworkTypeTestActivity: ForceNetworkTypeTestActivity)
+ fun inject(fractionInputInteractionViewTestActivity: FractionInputInteractionViewTestActivity)
fun inject(helpActivity: HelpActivity)
fun inject(homeActivity: HomeActivity)
fun inject(homeFragmentTestActivity: HomeFragmentTestActivity)
diff --git a/app/src/main/java/org/oppia/android/app/parser/FractionParsingUiError.kt b/app/src/main/java/org/oppia/android/app/parser/FractionParsingUiError.kt
index 731d26e0590..81cd9e14035 100644
--- a/app/src/main/java/org/oppia/android/app/parser/FractionParsingUiError.kt
+++ b/app/src/main/java/org/oppia/android/app/parser/FractionParsingUiError.kt
@@ -20,7 +20,10 @@ enum class FractionParsingUiError(@StringRes private var error: Int?) {
DIVISION_BY_ZERO(error = R.string.fraction_error_divide_by_zero),
/** Corresponds to [FractionParsingError.NUMBER_TOO_LONG]. */
- NUMBER_TOO_LONG(error = R.string.fraction_error_larger_than_seven_digits);
+ NUMBER_TOO_LONG(error = R.string.fraction_error_larger_than_seven_digits),
+
+ /** Corresponds to [FractionParsingError.EMPTY_INPUT]. */
+ EMPTY_INPUT(error = R.string.fraction_error_empty_input);
/**
* Returns the string corresponding to this error's string resources, or null if there is none.
@@ -39,6 +42,7 @@ enum class FractionParsingUiError(@StringRes private var error: Int?) {
FractionParsingError.INVALID_FORMAT -> INVALID_FORMAT
FractionParsingError.DIVISION_BY_ZERO -> DIVISION_BY_ZERO
FractionParsingError.NUMBER_TOO_LONG -> NUMBER_TOO_LONG
+ FractionParsingError.EMPTY_INPUT -> EMPTY_INPUT
}
}
}
diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt
index 22d42a74744..193248effe7 100644
--- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt
+++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt
@@ -43,12 +43,17 @@ class FractionInteractionViewModel private constructor(
override fun onPropertyChanged(sender: Observable, propertyId: Int) {
errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck(
pendingAnswerError,
- answerText.isNotEmpty()
+ true // Allow submit on empty answer.
)
}
}
errorMessage.addOnPropertyChangedCallback(callback)
isAnswerAvailable.addOnPropertyChangedCallback(callback)
+ // Force-update the UI to reflect the state of the errorMessage and isAnswerAvailable property:
+ errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck(
+ /* pendingAnswerError= */null,
+ /* inputAnswerAvailable= */true
+ )
}
override fun getPendingAnswer(): UserAnswer = UserAnswer.newBuilder().apply {
@@ -64,23 +69,25 @@ class FractionInteractionViewModel private constructor(
/** It checks the pending error for the current fraction input, and correspondingly updates the error string based on the specified error category. */
override fun checkPendingAnswerError(category: AnswerErrorCategory): String? {
- if (answerText.isNotEmpty()) {
- when (category) {
- AnswerErrorCategory.REAL_TIME -> {
+ when (category) {
+ AnswerErrorCategory.REAL_TIME -> {
+ if (answerText.isNotEmpty()) {
pendingAnswerError =
FractionParsingUiError.createFromParsingError(
fractionParser.getRealTimeAnswerError(answerText.toString())
).getErrorMessageFromStringRes(resourceHandler)
+ } else {
+ pendingAnswerError = null
}
- AnswerErrorCategory.SUBMIT_TIME -> {
- pendingAnswerError =
- FractionParsingUiError.createFromParsingError(
- fractionParser.getSubmitTimeError(answerText.toString())
- ).getErrorMessageFromStringRes(resourceHandler)
- }
}
- errorMessage.set(pendingAnswerError)
+ AnswerErrorCategory.SUBMIT_TIME -> {
+ pendingAnswerError =
+ FractionParsingUiError.createFromParsingError(
+ fractionParser.getSubmitTimeError(answerText.toString())
+ ).getErrorMessageFromStringRes(resourceHandler)
+ }
}
+ errorMessage.set(pendingAnswerError)
return pendingAnswerError
}
diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt
index 5d0822fae6c..132c988774b 100644
--- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt
+++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt
@@ -73,6 +73,12 @@ class MathExpressionInteractionsViewModel private constructor(
* bound to the corresponding edit text.
*/
var answerText: CharSequence = ""
+ // The value of ths field is set from the Binding and from the TextWatcher. Any
+ // programmatic modification needs to be done here, so that the Binding and the TextWatcher
+ // do not step on each other.
+ set(value) {
+ field = value.toString().trim()
+ }
/**
* Defines whether an answer is currently available to parse. This is expected to be directly
@@ -166,7 +172,7 @@ class MathExpressionInteractionsViewModel private constructor(
}
override fun onTextChanged(answer: CharSequence, start: Int, before: Int, count: Int) {
- answerText = answer.toString().trim()
+ answerText = answer
val isAnswerTextAvailable = answerText.isNotEmpty()
if (isAnswerTextAvailable != isAnswerAvailable.get()) {
isAnswerAvailable.set(isAnswerTextAvailable)
diff --git a/app/src/main/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivity.kt
new file mode 100644
index 00000000000..bb812a550a9
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivity.kt
@@ -0,0 +1,112 @@
+package org.oppia.android.app.testing
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.databinding.DataBindingUtil
+import org.oppia.android.R
+import org.oppia.android.app.activity.ActivityComponentImpl
+import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity
+import org.oppia.android.app.customview.interaction.FractionInputInteractionView
+import org.oppia.android.app.model.InputInteractionViewTestActivityParams
+import org.oppia.android.app.model.Interaction
+import org.oppia.android.app.model.UserAnswer
+import org.oppia.android.app.model.WrittenTranslationContext
+import org.oppia.android.app.player.state.answerhandling.AnswerErrorCategory
+import org.oppia.android.app.player.state.answerhandling.InteractionAnswerErrorOrAvailabilityCheckReceiver
+import org.oppia.android.app.player.state.answerhandling.InteractionAnswerReceiver
+import org.oppia.android.app.player.state.itemviewmodel.FractionInteractionViewModel
+import org.oppia.android.app.player.state.itemviewmodel.StateItemViewModel
+import org.oppia.android.app.player.state.itemviewmodel.StateItemViewModel.InteractionItemFactory
+import org.oppia.android.app.player.state.listener.StateKeyboardButtonListener
+import org.oppia.android.databinding.ActivityFractionInputInteractionViewTestBinding
+import org.oppia.android.util.extensions.getProtoExtra
+import org.oppia.android.util.extensions.putProtoExtra
+import javax.inject.Inject
+
+/**
+ * This is a dummy activity to test [FractionInputInteractionView].
+ */
+class FractionInputInteractionViewTestActivity :
+ InjectableAutoLocalizedAppCompatActivity(),
+ StateKeyboardButtonListener,
+ InteractionAnswerErrorOrAvailabilityCheckReceiver,
+ InteractionAnswerReceiver {
+ private lateinit var binding: ActivityFractionInputInteractionViewTestBinding
+
+ @Inject
+ lateinit var fractionInteractionViewModelFactory: FractionInteractionViewModel.FactoryImpl
+
+ /** Gives access to the [FractionInteractionViewModel]. */
+ val fractionInteractionViewModel by lazy {
+ fractionInteractionViewModelFactory.create()
+ }
+
+ /** Gives access to the translation context. */
+ lateinit var writtenTranslationContext: WrittenTranslationContext
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ (activityComponent as ActivityComponentImpl).inject(this)
+ binding = DataBindingUtil.setContentView(
+ this, R.layout.activity_fraction_input_interaction_view_test
+ )
+
+ val params =
+ intent.getProtoExtra(
+ TEST_ACTIVITY_PARAMS_ARGUMENT_KEY,
+ InputInteractionViewTestActivityParams.getDefaultInstance()
+ )
+ writtenTranslationContext = params.writtenTranslationContext
+ binding.fractionInteractionViewModel = fractionInteractionViewModel
+ }
+
+ /** Checks submit-time errors. */
+ fun getPendingAnswerErrorOnSubmitClick(v: View) {
+ fractionInteractionViewModel.checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME)
+ }
+
+ override fun onPendingAnswerErrorOrAvailabilityCheck(
+ pendingAnswerError: String?,
+ inputAnswerAvailable: Boolean
+ ) {
+ }
+
+ override fun onAnswerReadyForSubmission(answer: UserAnswer) {
+ }
+
+ override fun onEditorAction(actionCode: Int) {
+ }
+
+ private inline fun InteractionItemFactory.create(
+ interaction: Interaction = Interaction.getDefaultInstance()
+ ): T {
+ return create(
+ entityId = "fake_entity_id",
+ hasConversationView = false,
+ interaction = interaction,
+ interactionAnswerReceiver = this@FractionInputInteractionViewTestActivity,
+ answerErrorReceiver = this@FractionInputInteractionViewTestActivity,
+ hasPreviousButton = false,
+ isSplitView = false,
+ writtenTranslationContext,
+ timeToStartNoticeAnimationMs = null
+ ) as T
+ }
+
+ companion object {
+ private const val TEST_ACTIVITY_PARAMS_ARGUMENT_KEY =
+ "FractionInputInteractionViewTestActivity.params"
+
+ /** Creates an intent to open this activity. */
+ fun createIntent(
+ context: Context,
+ extras: InputInteractionViewTestActivityParams
+ ): Intent {
+ return Intent(context, FractionInputInteractionViewTestActivity::class.java).also {
+ it.putProtoExtra(TEST_ACTIVITY_PARAMS_ARGUMENT_KEY, extras)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt
index 7190382efa0..ac786bb146a 100644
--- a/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt
+++ b/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt
@@ -8,7 +8,6 @@ import androidx.databinding.DataBindingUtil
import org.oppia.android.R
import org.oppia.android.app.activity.ActivityComponentImpl
import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity
-import org.oppia.android.app.customview.interaction.FractionInputInteractionView
import org.oppia.android.app.customview.interaction.NumericInputInteractionView
import org.oppia.android.app.customview.interaction.TextInputInteractionView
import org.oppia.android.app.model.InputInteractionViewTestActivityParams
@@ -24,7 +23,6 @@ import org.oppia.android.app.model.WrittenTranslationContext
import org.oppia.android.app.player.state.answerhandling.AnswerErrorCategory
import org.oppia.android.app.player.state.answerhandling.InteractionAnswerErrorOrAvailabilityCheckReceiver
import org.oppia.android.app.player.state.answerhandling.InteractionAnswerReceiver
-import org.oppia.android.app.player.state.itemviewmodel.FractionInteractionViewModel
import org.oppia.android.app.player.state.itemviewmodel.MathExpressionInteractionsViewModel
import org.oppia.android.app.player.state.itemviewmodel.NumericInputViewModel
import org.oppia.android.app.player.state.itemviewmodel.RatioExpressionInputInteractionViewModel
@@ -40,7 +38,7 @@ import org.oppia.android.app.player.state.itemviewmodel.MathExpressionInteractio
/**
* This is a dummy activity to test input interaction views.
- * It contains [FractionInputInteractionView], [NumericInputInteractionView],and [TextInputInteractionView].
+ * It contains [NumericInputInteractionView],and [TextInputInteractionView].
*/
class InputInteractionViewTestActivity :
InjectableAutoLocalizedAppCompatActivity(),
@@ -55,9 +53,6 @@ class InputInteractionViewTestActivity :
@Inject
lateinit var textInputViewModelFactory: TextInputViewModel.FactoryImpl
- @Inject
- lateinit var fractionInteractionViewModelFactory: FractionInteractionViewModel.FactoryImpl
-
@Inject
lateinit var ratioViewModelFactory: RatioExpressionInputInteractionViewModel.FactoryImpl
@@ -68,10 +63,6 @@ class InputInteractionViewTestActivity :
val textInputViewModel by lazy { textInputViewModelFactory.create() }
- val fractionInteractionViewModel by lazy {
- fractionInteractionViewModelFactory.create()
- }
-
val ratioExpressionInputInteractionViewModel by lazy {
ratioViewModelFactory.create(
interaction = Interaction.newBuilder().putCustomizationArgs(
@@ -127,13 +118,11 @@ class InputInteractionViewTestActivity :
binding.numericInputViewModel = numericInputViewModel
binding.textInputViewModel = textInputViewModel
- binding.fractionInteractionViewModel = fractionInteractionViewModel
binding.ratioInteractionInputViewModel = ratioExpressionInputInteractionViewModel
binding.mathExpressionInteractionsViewModel = mathExpressionViewModel
}
fun getPendingAnswerErrorOnSubmitClick(v: View) {
- fractionInteractionViewModel.checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME)
numericInputViewModel.checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME)
ratioExpressionInputInteractionViewModel
.checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME)
diff --git a/app/src/main/res/layout/activity_fraction_input_interaction_view_test.xml b/app/src/main/res/layout/activity_fraction_input_interaction_view_test.xml
new file mode 100644
index 00000000000..ddd18d752d9
--- /dev/null
+++ b/app/src/main/res/layout/activity_fraction_input_interaction_view_test.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_input_interaction_view_test.xml b/app/src/main/res/layout/activity_input_interaction_view_test.xml
index dba00bfb1e8..a8e798252c0 100644
--- a/app/src/main/res/layout/activity_input_interaction_view_test.xml
+++ b/app/src/main/res/layout/activity_input_interaction_view_test.xml
@@ -7,10 +7,6 @@
-
-
@@ -40,43 +36,6 @@
android:orientation="vertical"
tools:context=".testing.InputInteractionViewTestActivity">
-
-
-
-
Please enter a valid fraction (e.g., 5/3 or 1 2/3)
Please do not put 0 in the denominator
None of the numbers in the fraction should have more than 7 digits.
+ Enter a fraction to continue.
Please begin your answer with a number (e.g.,”0” in 0.5).
Please enter a valid number.
The answer can contain at most 15 digits (0–9) or symbols (. or -).
diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt
index 24ac673be8d..2888906d8fd 100644
--- a/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt
+++ b/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt
@@ -306,7 +306,7 @@ class StateFragmentTest {
clickContinueInteractionButton()
- verifySubmitAnswerButtonIsDisabled()
+ verifySubmitAnswerButtonIsEnabled()
onView(withId(R.id.submit_answer_button)).check(
matches(withText(R.string.state_submit_button))
)
@@ -4702,6 +4702,11 @@ class StateFragmentTest {
onView(withId(R.id.submit_answer_button)).check(matches(not(isEnabled())))
}
+ private fun verifySubmitAnswerButtonIsEnabled() {
+ scrollToViewType(SUBMIT_ANSWER_BUTTON)
+ onView(withId(R.id.submit_answer_button)).check(matches(isEnabled()))
+ }
+
private fun verifyViewTypeIsPresent(viewType: StateItemViewModel.ViewType) {
// Attempting to scroll to the specified view type is sufficient to verify that it's present.
scrollToViewType(viewType)
diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivityTest.kt
new file mode 100644
index 00000000000..606f7b2f945
--- /dev/null
+++ b/app/src/sharedTest/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivityTest.kt
@@ -0,0 +1,608 @@
+package org.oppia.android.app.testing
+
+import android.app.Application
+import android.content.res.Configuration
+import androidx.appcompat.app.AppCompatActivity
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.Espresso.closeSoftKeyboard
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.scrollTo
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import dagger.Component
+import org.junit.After
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.oppia.android.R
+import org.oppia.android.app.activity.ActivityComponent
+import org.oppia.android.app.activity.ActivityComponentFactory
+import org.oppia.android.app.activity.route.ActivityRouterModule
+import org.oppia.android.app.application.ApplicationComponent
+import org.oppia.android.app.application.ApplicationInjector
+import org.oppia.android.app.application.ApplicationInjectorProvider
+import org.oppia.android.app.application.ApplicationModule
+import org.oppia.android.app.application.ApplicationStartupListenerModule
+import org.oppia.android.app.application.testing.TestingBuildFlavorModule
+import org.oppia.android.app.devoptions.DeveloperOptionsModule
+import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
+import org.oppia.android.app.model.InteractionObject
+import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule
+import org.oppia.android.app.shim.ViewBindingShimModule
+import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule
+import org.oppia.android.data.backends.gae.NetworkConfigProdModule
+import org.oppia.android.data.backends.gae.NetworkModule
+import org.oppia.android.domain.classify.InteractionsModule
+import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule
+import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule
+import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule
+import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule
+import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule
+import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule
+import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule
+import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule
+import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule
+import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule
+import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule
+import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule
+import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule
+import org.oppia.android.domain.exploration.ExplorationProgressModule
+import org.oppia.android.domain.exploration.ExplorationStorageModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigModule
+import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule
+import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule
+import org.oppia.android.domain.oppialogger.LogStorageModule
+import org.oppia.android.domain.oppialogger.LoggingIdentifierModule
+import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule
+import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule
+import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule
+import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule
+import org.oppia.android.domain.platformparameter.PlatformParameterModule
+import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule
+import org.oppia.android.domain.question.QuestionModule
+import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule
+import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule
+import org.oppia.android.testing.DisableAccessibilityChecks
+import org.oppia.android.testing.OppiaTestRule
+import org.oppia.android.testing.TestLogReportingModule
+import org.oppia.android.testing.espresso.EditTextInputAction
+import org.oppia.android.testing.junit.InitializeDefaultLocaleRule
+import org.oppia.android.testing.robolectric.RobolectricModule
+import org.oppia.android.testing.threading.TestCoroutineDispatchers
+import org.oppia.android.testing.threading.TestDispatcherModule
+import org.oppia.android.testing.time.FakeOppiaClockModule
+import org.oppia.android.util.accessibility.AccessibilityTestModule
+import org.oppia.android.util.caching.AssetModule
+import org.oppia.android.util.caching.testing.CachingTestModule
+import org.oppia.android.util.gcsresource.GcsResourceModule
+import org.oppia.android.util.locale.LocaleProdModule
+import org.oppia.android.util.logging.EventLoggingConfigurationModule
+import org.oppia.android.util.logging.LoggerModule
+import org.oppia.android.util.logging.SyncStatusModule
+import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule
+import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule
+import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule
+import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule
+import org.oppia.android.util.parser.image.GlideImageLoaderModule
+import org.oppia.android.util.parser.image.ImageParsingModule
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.LooperMode
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/** Tests for [FractionInputInteractionViewTestActivity]. */
+@RunWith(AndroidJUnit4::class)
+@LooperMode(LooperMode.Mode.PAUSED)
+@Config(
+ application = FractionInputInteractionViewTestActivityTest.TestApplication::class,
+ qualifiers = "port-xxhdpi"
+)
+class FractionInputInteractionViewTestActivityTest {
+ @get:Rule
+ val initializeDefaultLocaleRule = InitializeDefaultLocaleRule()
+
+ @Inject
+ lateinit var testCoroutineDispatchers: TestCoroutineDispatchers
+
+ @get:Rule
+ val oppiaTestRule = OppiaTestRule()
+
+ @Inject
+ lateinit var editTextInputAction: EditTextInputAction
+
+ @Before
+ fun setUp() {
+ setUpTestApplicationComponent()
+ testCoroutineDispatchers.registerIdlingResource()
+ }
+
+ @After
+ fun tearDown() {
+ testCoroutineDispatchers.unregisterIdlingResource()
+ }
+
+ private fun setUpTestApplicationComponent() {
+ ApplicationProvider.getApplicationContext().inject(this)
+ }
+
+ @Test
+ fun testFractionInput_withNoInput_hasCorrectPendingAnswerType() {
+ val activityScenario = ActivityScenario.launch(
+ FractionInputInteractionViewTestActivity::class.java
+ )
+ activityScenario.onActivity { activity ->
+ val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
+ assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
+ assertThat(pendingAnswer.answer.fraction.denominator).isEqualTo(0)
+ assertThat(pendingAnswer.answer.fraction.numerator).isEqualTo(0)
+ assertThat(pendingAnswer.answer.fraction.wholeNumber).isEqualTo(0)
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and will not be used by user
+ fun testFractionInput_withNegativeNumber_hasCorrectPendingAnswer() {
+ val activityScenario = ActivityScenario.launch(
+ FractionInputInteractionViewTestActivity::class.java
+ )
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(editTextInputAction.appendText("-9"))
+ activityScenario.onActivity { activity ->
+ val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
+ assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
+ assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
+ InteractionObject.ObjectTypeCase.FRACTION
+ )
+ assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(true)
+ assertThat(pendingAnswer.answer.fraction.wholeNumber).isEqualTo(9)
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and will not be used by user
+ fun testFractionInput_withWholeNumber_hasCorrectPendingAnswer() {
+ val activityScenario = ActivityScenario.launch(
+ FractionInputInteractionViewTestActivity::class.java
+ )
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(editTextInputAction.appendText("9"))
+ activityScenario.onActivity { activity ->
+ val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
+ assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
+ assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
+ InteractionObject.ObjectTypeCase.FRACTION
+ )
+ assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(false)
+ assertThat(pendingAnswer.answer.fraction.wholeNumber).isEqualTo(9)
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and will not be used by user
+ fun testFractionInput_withFraction_hasCorrectPendingAnswer() {
+ val activityScenario = ActivityScenario.launch(
+ FractionInputInteractionViewTestActivity::class.java
+ )
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "9/10"
+ )
+ )
+ activityScenario.onActivity { activity ->
+ val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
+ assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
+ assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
+ InteractionObject.ObjectTypeCase.FRACTION
+ )
+ assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(false)
+ assertThat(pendingAnswer.answer.fraction.numerator).isEqualTo(9)
+ assertThat(pendingAnswer.answer.fraction.denominator).isEqualTo(10)
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and will not be used by user
+ fun testFractionInput_withNegativeFraction_hasCorrectPendingAnswer() {
+ val activityScenario = ActivityScenario.launch(
+ FractionInputInteractionViewTestActivity::class.java
+ )
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "-9/10"
+ )
+ )
+ activityScenario.onActivity { activity ->
+ val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
+ assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
+ assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
+ InteractionObject.ObjectTypeCase.FRACTION
+ )
+ assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(true)
+ assertThat(pendingAnswer.answer.fraction.numerator).isEqualTo(9)
+ assertThat(pendingAnswer.answer.fraction.denominator).isEqualTo(10)
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and will not be used by user
+ fun testFractionInput_withMixedNumber_hasCorrectPendingAnswer() {
+ val activityScenario = ActivityScenario.launch(
+ FractionInputInteractionViewTestActivity::class.java
+ )
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "5 9/10"
+ )
+ )
+ activityScenario.onActivity { activity ->
+ val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
+ assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
+ assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
+ InteractionObject.ObjectTypeCase.FRACTION
+ )
+ assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(false)
+ assertThat(pendingAnswer.answer.fraction.wholeNumber).isEqualTo(5)
+ assertThat(pendingAnswer.answer.fraction.numerator).isEqualTo(9)
+ assertThat(pendingAnswer.answer.fraction.denominator).isEqualTo(10)
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and will not be used by user
+ fun testFractionInput_withNegativeMixedNumber_hasCorrectPendingAnswer() {
+ val activityScenario = ActivityScenario.launch(
+ FractionInputInteractionViewTestActivity::class.java
+ )
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "-55 59/9"
+ )
+ )
+ activityScenario.onActivity { activity ->
+ val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
+ assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
+ assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
+ InteractionObject.ObjectTypeCase.FRACTION
+ )
+ assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(true)
+ assertThat(pendingAnswer.answer.fraction.wholeNumber).isEqualTo(55)
+ assertThat(pendingAnswer.answer.fraction.numerator).isEqualTo(59)
+ assertThat(pendingAnswer.answer.fraction.denominator).isEqualTo(9)
+ }
+ }
+
+ @Test
+ @Ignore("Landscape not properly supported") // TODO(#56): Reenable once landscape is supported.
+ fun testFractionInput_withFraction_configChange_hasCorrectPendingAnswer() {
+ val activityScenario = ActivityScenario.launch(
+ FractionInputInteractionViewTestActivity::class.java
+ )
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "9/5"
+ )
+ )
+ activityScenario.onActivity { activity ->
+ activity.requestedOrientation = Configuration.ORIENTATION_LANDSCAPE
+ }
+ onView(withId(R.id.test_fraction_input_interaction_view)).check(matches(isDisplayed()))
+ .check(matches(withText("9/5")))
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and will not be used by user
+ fun testFractionInput_withNegativeSignOtherThanAt0_numberFormatErrorIsDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java)
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "55-"
+ )
+ )
+ onView(withId(R.id.fraction_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.fraction_error_invalid_format
+ )
+ )
+ )
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testFractionInput_withNegativeSignAt0MoreThan1_numberFormatErrorIsDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java)
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "--55"
+ )
+ )
+ onView(withId(R.id.fraction_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.fraction_error_invalid_format
+ )
+ )
+ )
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testFractionInput_withDividerMoreThanOnce_numberFormatErrorIsDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java)
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "5/5/"
+ )
+ )
+ onView(withId(R.id.fraction_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.fraction_error_invalid_format
+ )
+ )
+ )
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testFractionInput_withDividerAtStart_numberFormatErrorIsDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java)
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "/5"
+ )
+ )
+ onView(withId(R.id.fraction_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.fraction_error_invalid_format
+ )
+ )
+ )
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testFractionInput_withPartialMixedNumber_numberFormatErrorIsNotDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java)
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "5 5/"
+ )
+ )
+ onView(withId(R.id.fraction_input_error)).check(matches(withText("")))
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testFractionInput_withPartialMixedNumberSubmit_numberFormatErrorIsDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java)
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "5 5/"
+ )
+ )
+ closeSoftKeyboard()
+ scrollToSubmitButton()
+ onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
+ onView(withId(R.id.fraction_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.fraction_error_invalid_format
+ )
+ )
+ )
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testFractionInput_withMixedNumber_submit_noErrorIsDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java)
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "3 1/2"
+ )
+ )
+ closeSoftKeyboard()
+ scrollToSubmitButton()
+ onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
+ onView(withId(R.id.fraction_input_error)).check(matches(withText("")))
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testFractionInput_withDivideByZero_errorIsNotDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java)
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "1/0"
+ )
+ )
+ onView(withId(R.id.fraction_input_error)).check(matches(withText("")))
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testFractionInput_withDivideByZero_submit_divideByZeroErrorIsDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java)
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "1/0"
+ )
+ )
+ closeSoftKeyboard()
+ scrollToSubmitButton()
+ onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
+ onView(withId(R.id.fraction_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.fraction_error_divide_by_zero
+ )
+ )
+ )
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testFractionInput_withInvalidCharacter_invalidCharacterErrorIsDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java).use {
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "."
+ )
+ )
+ onView(withId(R.id.fraction_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.fraction_error_invalid_chars
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testFractionInput_withLong_submit_numberTooLongErrorIsDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java).use {
+ onView(withId(R.id.test_fraction_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "12345678"
+ )
+ )
+ closeSoftKeyboard()
+ scrollToSubmitButton()
+ onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
+ onView(withId(R.id.fraction_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.fraction_error_larger_than_seven_digits
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as FractionInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testFractionInput_emptyInput_submit_errorIsDisplayed() {
+ ActivityScenario.launch(FractionInputInteractionViewTestActivity::class.java).use {
+ testCoroutineDispatchers.runCurrent()
+ scrollToSubmitButton()
+ onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.fraction_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.fraction_error_empty_input
+ )
+ )
+ )
+ }
+ }
+
+ private fun scrollToSubmitButton() {
+ onView(withId(R.id.submit_button)).perform(scrollTo())
+ testCoroutineDispatchers.runCurrent()
+ }
+
+ // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them.
+ @Singleton
+ @Component(
+ modules = [
+ RobolectricModule::class,
+ PlatformParameterModule::class, PlatformParameterSingletonModule::class,
+ TestDispatcherModule::class, ApplicationModule::class,
+ LoggerModule::class, ContinueModule::class, FractionInputModule::class,
+ ItemSelectionInputModule::class, MultipleChoiceInputModule::class,
+ NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class,
+ DragDropSortInputModule::class, ImageClickInputModule::class, InteractionsModule::class,
+ GcsResourceModule::class, GlideImageLoaderModule::class, ImageParsingModule::class,
+ HtmlParserEntityTypeModule::class, QuestionModule::class, TestLogReportingModule::class,
+ AccessibilityTestModule::class, LogStorageModule::class, CachingTestModule::class,
+ PrimeTopicAssetsControllerModule::class, ExpirationMetaDataRetrieverModule::class,
+ ViewBindingShimModule::class, RatioInputModule::class, WorkManagerConfigurationModule::class,
+ ApplicationStartupListenerModule::class, LogReportWorkerModule::class,
+ HintsAndSolutionConfigModule::class, HintsAndSolutionProdModule::class,
+ FirebaseLogUploaderModule::class, FakeOppiaClockModule::class,
+ DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class,
+ ExplorationStorageModule::class, NetworkModule::class, NetworkConfigProdModule::class,
+ NetworkConnectionUtilDebugModule::class, NetworkConnectionDebugUtilModule::class,
+ AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class,
+ NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class,
+ MathEquationInputModule::class, SplitScreenInteractionModule::class,
+ LoggingIdentifierModule::class, ApplicationLifecycleModule::class,
+ SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class,
+ EventLoggingConfigurationModule::class, ActivityRouterModule::class,
+ CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class
+ ]
+ )
+ interface TestApplicationComponent : ApplicationComponent {
+ @Component.Builder
+ interface Builder : ApplicationComponent.Builder
+
+ fun inject(inputInteractionViewTestActivityTest: FractionInputInteractionViewTestActivityTest)
+ }
+
+ class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider {
+ private val component: TestApplicationComponent by lazy {
+ DaggerFractionInputInteractionViewTestActivityTest_TestApplicationComponent.builder()
+ .setApplication(this)
+ .build() as TestApplicationComponent
+ }
+
+ fun inject(inputInteractionViewTestActivityTest: FractionInputInteractionViewTestActivityTest) {
+ component.inject(inputInteractionViewTestActivityTest)
+ }
+
+ override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent {
+ return component.getActivityComponentBuilderProvider().get().setActivity(activity).build()
+ }
+
+ override fun getApplicationInjector(): ApplicationInjector = component
+ }
+}
diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt
index 8ed1123cc41..20692243b6c 100644
--- a/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt
+++ b/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt
@@ -140,400 +140,6 @@ class InputInteractionViewTestActivityTest {
ApplicationProvider.getApplicationContext().inject(this)
}
- // TODO(#4135): Move fraction input tests to a dedicated test suite.
-
- @Test
- fun testFractionInput_withNoInput_hasCorrectPendingAnswerType() {
- val activityScenario = ActivityScenario.launch(
- InputInteractionViewTestActivity::class.java
- )
- activityScenario.onActivity { activity ->
- val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
- assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
- assertThat(pendingAnswer.answer.fraction.denominator).isEqualTo(0)
- assertThat(pendingAnswer.answer.fraction.numerator).isEqualTo(0)
- assertThat(pendingAnswer.answer.fraction.wholeNumber).isEqualTo(0)
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and will not be used by user
- fun testFractionInput_withNegativeNumber_hasCorrectPendingAnswer() {
- val activityScenario = ActivityScenario.launch(
- InputInteractionViewTestActivity::class.java
- )
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(editTextInputAction.appendText("-9"))
- activityScenario.onActivity { activity ->
- val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
- assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
- assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
- InteractionObject.ObjectTypeCase.FRACTION
- )
- assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(true)
- assertThat(pendingAnswer.answer.fraction.wholeNumber).isEqualTo(9)
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and will not be used by user
- fun testFractionInput_withWholeNumber_hasCorrectPendingAnswer() {
- val activityScenario = ActivityScenario.launch(
- InputInteractionViewTestActivity::class.java
- )
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(editTextInputAction.appendText("9"))
- activityScenario.onActivity { activity ->
- val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
- assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
- assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
- InteractionObject.ObjectTypeCase.FRACTION
- )
- assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(false)
- assertThat(pendingAnswer.answer.fraction.wholeNumber).isEqualTo(9)
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and will not be used by user
- fun testFractionInput_withFraction_hasCorrectPendingAnswer() {
- val activityScenario = ActivityScenario.launch(
- InputInteractionViewTestActivity::class.java
- )
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "9/10"
- )
- )
- activityScenario.onActivity { activity ->
- val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
- assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
- assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
- InteractionObject.ObjectTypeCase.FRACTION
- )
- assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(false)
- assertThat(pendingAnswer.answer.fraction.numerator).isEqualTo(9)
- assertThat(pendingAnswer.answer.fraction.denominator).isEqualTo(10)
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and will not be used by user
- fun testFractionInput_withNegativeFraction_hasCorrectPendingAnswer() {
- val activityScenario = ActivityScenario.launch(
- InputInteractionViewTestActivity::class.java
- )
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "-9/10"
- )
- )
- activityScenario.onActivity { activity ->
- val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
- assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
- assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
- InteractionObject.ObjectTypeCase.FRACTION
- )
- assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(true)
- assertThat(pendingAnswer.answer.fraction.numerator).isEqualTo(9)
- assertThat(pendingAnswer.answer.fraction.denominator).isEqualTo(10)
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and will not be used by user
- fun testFractionInput_withMixedNumber_hasCorrectPendingAnswer() {
- val activityScenario = ActivityScenario.launch(
- InputInteractionViewTestActivity::class.java
- )
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "5 9/10"
- )
- )
- activityScenario.onActivity { activity ->
- val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
- assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
- assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
- InteractionObject.ObjectTypeCase.FRACTION
- )
- assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(false)
- assertThat(pendingAnswer.answer.fraction.wholeNumber).isEqualTo(5)
- assertThat(pendingAnswer.answer.fraction.numerator).isEqualTo(9)
- assertThat(pendingAnswer.answer.fraction.denominator).isEqualTo(10)
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and will not be used by user
- fun testFractionInput_withNegativeMixedNumber_hasCorrectPendingAnswer() {
- val activityScenario = ActivityScenario.launch(
- InputInteractionViewTestActivity::class.java
- )
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "-55 59/9"
- )
- )
- activityScenario.onActivity { activity ->
- val pendingAnswer = activity.fractionInteractionViewModel.getPendingAnswer()
- assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
- assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
- InteractionObject.ObjectTypeCase.FRACTION
- )
- assertThat(pendingAnswer.answer.fraction.isNegative).isEqualTo(true)
- assertThat(pendingAnswer.answer.fraction.wholeNumber).isEqualTo(55)
- assertThat(pendingAnswer.answer.fraction.numerator).isEqualTo(59)
- assertThat(pendingAnswer.answer.fraction.denominator).isEqualTo(9)
- }
- }
-
- @Test
- @Ignore("Landscape not properly supported") // TODO(#56): Reenable once landscape is supported.
- fun testFractionInput_withFraction_configChange_hasCorrectPendingAnswer() {
- val activityScenario = ActivityScenario.launch(
- InputInteractionViewTestActivity::class.java
- )
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "9/5"
- )
- )
- activityScenario.onActivity { activity ->
- activity.requestedOrientation = Configuration.ORIENTATION_LANDSCAPE
- }
- onView(withId(R.id.test_fraction_input_interaction_view)).check(matches(isDisplayed()))
- .check(matches(withText("9/5")))
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and will not be used by user
- fun testFractionInput_withNegativeSignOtherThanAt0_numberFormatErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java)
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "55-"
- )
- )
- onView(withId(R.id.fraction_input_error))
- .check(
- matches(
- withText(
- R.string.fraction_error_invalid_format
- )
- )
- )
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testFractionInput_withNegativeSignAt0MoreThan1_numberFormatErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java)
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "--55"
- )
- )
- onView(withId(R.id.fraction_input_error))
- .check(
- matches(
- withText(
- R.string.fraction_error_invalid_format
- )
- )
- )
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testFractionInput_withDividerMoreThanOnce_numberFormatErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java)
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "5/5/"
- )
- )
- onView(withId(R.id.fraction_input_error))
- .check(
- matches(
- withText(
- R.string.fraction_error_invalid_format
- )
- )
- )
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testFractionInput_withDividerAtStart_numberFormatErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java)
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "/5"
- )
- )
- onView(withId(R.id.fraction_input_error))
- .check(
- matches(
- withText(
- R.string.fraction_error_invalid_format
- )
- )
- )
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testFractionInput_withPartialMixedNumber_numberFormatErrorIsNotDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java)
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "5 5/"
- )
- )
- onView(withId(R.id.fraction_input_error)).check(matches(withText("")))
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testFractionInput_withPartialMixedNumberSubmit_numberFormatErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java)
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "5 5/"
- )
- )
- closeSoftKeyboard()
- scrollToSubmitButton()
- onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
- onView(withId(R.id.fraction_input_error))
- .check(
- matches(
- withText(
- R.string.fraction_error_invalid_format
- )
- )
- )
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testFractionInput_withMixedNumber_submit_noErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java)
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "3 1/2"
- )
- )
- closeSoftKeyboard()
- scrollToSubmitButton()
- onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
- onView(withId(R.id.fraction_input_error)).check(matches(withText("")))
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testFractionInput_withDivideByZero_errorIsNotDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java)
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "1/0"
- )
- )
- onView(withId(R.id.fraction_input_error)).check(matches(withText("")))
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testFractionInput_withDivideByZero_submit_divideByZeroErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java)
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "1/0"
- )
- )
- closeSoftKeyboard()
- scrollToSubmitButton()
- onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
- onView(withId(R.id.fraction_input_error))
- .check(
- matches(
- withText(
- R.string.fraction_error_divide_by_zero
- )
- )
- )
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testFractionInput_withInvalidCharacter_invalidCharacterErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java).use {
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "."
- )
- )
- onView(withId(R.id.fraction_input_error))
- .check(
- matches(
- withText(
- R.string.fraction_error_invalid_chars
- )
- )
- )
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testFractionInput_withLong_submit_numberTooLongErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java).use {
- onView(withId(R.id.test_fraction_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "12345678"
- )
- )
- closeSoftKeyboard()
- scrollToSubmitButton()
- onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
- onView(withId(R.id.fraction_input_error))
- .check(
- matches(
- withText(
- R.string.fraction_error_larger_than_seven_digits
- )
- )
- )
- }
- }
-
@Test
fun testNumericInput_withNoInput_hasCorrectPendingAnswerType() {
val activityScenario = ActivityScenario.launch(
diff --git a/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt b/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt
index 6016d1d4894..58b622c0ede 100644
--- a/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt
+++ b/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt
@@ -165,7 +165,7 @@ class FractionParsingUiErrorTest {
.toUiError()
.getErrorMessageFromStringRes(activity.appLanguageResourceHandler)
assertThat(errorMessage)
- .isEqualTo("Please enter a valid fraction (e.g., 5/3 or 1 2/3)")
+ .isEqualTo("Enter a fraction to continue.")
}
}
diff --git a/scripts/assets/accessibility_label_exemptions.textproto b/scripts/assets/accessibility_label_exemptions.textproto
index 5e2c693ed1d..dd1db64c796 100644
--- a/scripts/assets/accessibility_label_exemptions.textproto
+++ b/scripts/assets/accessibility_label_exemptions.textproto
@@ -15,6 +15,7 @@ exempted_activity: "app/src/main/java/org/oppia/android/app/testing/DragDropTest
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/DrawableBindingAdaptersTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/ExplorationInjectionActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/ExplorationTestActivity"
+exempted_activity: "app/src/main/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/HomeFragmentTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/HomeTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/HtmlParserTestActivity"
diff --git a/utility/src/main/java/org/oppia/android/util/math/FractionParser.kt b/utility/src/main/java/org/oppia/android/util/math/FractionParser.kt
index 7e2288cda95..354f354487d 100644
--- a/utility/src/main/java/org/oppia/android/util/math/FractionParser.kt
+++ b/utility/src/main/java/org/oppia/android/util/math/FractionParser.kt
@@ -24,6 +24,9 @@ class FractionParser {
* detection should be done using [getRealTimeAnswerError], instead.
*/
fun getSubmitTimeError(text: String): FractionParsingError {
+ if (text.isNullOrBlank()) {
+ return FractionParsingError.EMPTY_INPUT
+ }
if (invalidCharsLengthRegex.find(text) != null) {
return FractionParsingError.NUMBER_TOO_LONG
}
@@ -130,6 +133,9 @@ class FractionParser {
* Indicates that at least one of the numbers present in the string is too long to be
* precisely represented in a fraction.
*/
- NUMBER_TOO_LONG
+ NUMBER_TOO_LONG,
+
+ /** Indicates that the input text was empty. */
+ EMPTY_INPUT
}
}
diff --git a/utility/src/test/java/org/oppia/android/util/math/FractionParserTest.kt b/utility/src/test/java/org/oppia/android/util/math/FractionParserTest.kt
index 864a429f753..6079f7ec92c 100644
--- a/utility/src/test/java/org/oppia/android/util/math/FractionParserTest.kt
+++ b/utility/src/test/java/org/oppia/android/util/math/FractionParserTest.kt
@@ -85,9 +85,9 @@ class FractionParserTest {
}
@Test
- fun testSubmitTimeError_emptyString_returnsInvalidFormat() {
+ fun testSubmitTimeError_emptyString_returnsEmptyInput() {
val error = fractionParser.getSubmitTimeError("")
- assertThat(error).isEqualTo(FractionParser.FractionParsingError.INVALID_FORMAT)
+ assertThat(error).isEqualTo(FractionParser.FractionParsingError.EMPTY_INPUT)
}
@Test