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 1/3] 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
From 9ad24ef94d41a33b5d10e8ce0340114d4f71f887 Mon Sep 17 00:00:00 2001
From: RD Rama Devi <122200035+Rd4dev@users.noreply.github.com>
Date: Tue, 9 Jan 2024 23:35:08 +0530
Subject: [PATCH 2/3] Fix All warnings in "Usability:Typography",
"Usability:Icons", and "Usability" categories (#5196)
## Explanation
Fixes #5194 [part of #5182]
Fixes all warning in "Usability" category caused by
- **Borderless Buttons :** Fixed by introducing
`style="?android:attr/buttonBarButtonStyle"` to the borderless buttons
- **OK text capitalization :** Fixed by suppressing the lint warnings
for `ButtonCase` and `Typos`
- **Ellipsis/Hyphen unicode replacement :** Fixed by introducing unicode
representations (under review)
- **Image in density drawable folder :** Fixed by creating seperate
density images (under review)
- **Text size too small :** Fixed by upscaling text size to `12sp`
- **To incorporate Autofill :** Fixed by incorporating `autofillHints`
to the EditText
## ScreenShot
![Before After Usability Lint
Issues](https://github.com/oppia/oppia-android/assets/122200035/5a7037f5-ca22-482a-a0f3-8af2bfc8b75c)
## 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)).
---------
Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com>
Co-authored-by: Ben Henning
---
.../review_placeholder.png | Bin
.../testing_fraction.png | Bin
.../topic_fractions_01.png | Bin
.../topic_fractions_02.png | Bin
.../topic_fractions_03.png | Bin
.../topic_fractions_04.png | Bin
.../topic_fractions_05.png | Bin
.../topic_fractions_06.png | Bin
.../topic_fractions_07.png | Bin
.../topic_fractions_08.png | Bin
.../topic_ratios_01.png | Bin
.../topic_lessons_story_summary.xml | 2 +-
.../concept_card_fragment_test_activity.xml | 2 ++
.../res/layout/survey_free_form_layout.xml | 3 ++-
.../res/layout/topic_lessons_story_summary.xml | 2 +-
.../res/mipmap-anydpi-v26/launcher_icon.xml | 17 +++++++++++++++++
app/src/main/res/values-ar/strings.xml | 6 +++---
app/src/main/res/values-pt-rBR/strings.xml | 2 +-
app/src/main/res/values/strings.xml | 14 +++++++-------
19 files changed, 34 insertions(+), 14 deletions(-)
rename app/src/main/res/{drawable => drawable-nodpi}/review_placeholder.png (100%)
rename app/src/main/res/{drawable => drawable-nodpi}/testing_fraction.png (100%)
rename app/src/main/res/{drawable => drawable-nodpi}/topic_fractions_01.png (100%)
rename app/src/main/res/{drawable => drawable-nodpi}/topic_fractions_02.png (100%)
rename app/src/main/res/{drawable => drawable-nodpi}/topic_fractions_03.png (100%)
rename app/src/main/res/{drawable => drawable-nodpi}/topic_fractions_04.png (100%)
rename app/src/main/res/{drawable => drawable-nodpi}/topic_fractions_05.png (100%)
rename app/src/main/res/{drawable => drawable-nodpi}/topic_fractions_06.png (100%)
rename app/src/main/res/{drawable => drawable-nodpi}/topic_fractions_07.png (100%)
rename app/src/main/res/{drawable => drawable-nodpi}/topic_fractions_08.png (100%)
rename app/src/main/res/{drawable => drawable-nodpi}/topic_ratios_01.png (100%)
diff --git a/app/src/main/res/drawable/review_placeholder.png b/app/src/main/res/drawable-nodpi/review_placeholder.png
similarity index 100%
rename from app/src/main/res/drawable/review_placeholder.png
rename to app/src/main/res/drawable-nodpi/review_placeholder.png
diff --git a/app/src/main/res/drawable/testing_fraction.png b/app/src/main/res/drawable-nodpi/testing_fraction.png
similarity index 100%
rename from app/src/main/res/drawable/testing_fraction.png
rename to app/src/main/res/drawable-nodpi/testing_fraction.png
diff --git a/app/src/main/res/drawable/topic_fractions_01.png b/app/src/main/res/drawable-nodpi/topic_fractions_01.png
similarity index 100%
rename from app/src/main/res/drawable/topic_fractions_01.png
rename to app/src/main/res/drawable-nodpi/topic_fractions_01.png
diff --git a/app/src/main/res/drawable/topic_fractions_02.png b/app/src/main/res/drawable-nodpi/topic_fractions_02.png
similarity index 100%
rename from app/src/main/res/drawable/topic_fractions_02.png
rename to app/src/main/res/drawable-nodpi/topic_fractions_02.png
diff --git a/app/src/main/res/drawable/topic_fractions_03.png b/app/src/main/res/drawable-nodpi/topic_fractions_03.png
similarity index 100%
rename from app/src/main/res/drawable/topic_fractions_03.png
rename to app/src/main/res/drawable-nodpi/topic_fractions_03.png
diff --git a/app/src/main/res/drawable/topic_fractions_04.png b/app/src/main/res/drawable-nodpi/topic_fractions_04.png
similarity index 100%
rename from app/src/main/res/drawable/topic_fractions_04.png
rename to app/src/main/res/drawable-nodpi/topic_fractions_04.png
diff --git a/app/src/main/res/drawable/topic_fractions_05.png b/app/src/main/res/drawable-nodpi/topic_fractions_05.png
similarity index 100%
rename from app/src/main/res/drawable/topic_fractions_05.png
rename to app/src/main/res/drawable-nodpi/topic_fractions_05.png
diff --git a/app/src/main/res/drawable/topic_fractions_06.png b/app/src/main/res/drawable-nodpi/topic_fractions_06.png
similarity index 100%
rename from app/src/main/res/drawable/topic_fractions_06.png
rename to app/src/main/res/drawable-nodpi/topic_fractions_06.png
diff --git a/app/src/main/res/drawable/topic_fractions_07.png b/app/src/main/res/drawable-nodpi/topic_fractions_07.png
similarity index 100%
rename from app/src/main/res/drawable/topic_fractions_07.png
rename to app/src/main/res/drawable-nodpi/topic_fractions_07.png
diff --git a/app/src/main/res/drawable/topic_fractions_08.png b/app/src/main/res/drawable-nodpi/topic_fractions_08.png
similarity index 100%
rename from app/src/main/res/drawable/topic_fractions_08.png
rename to app/src/main/res/drawable-nodpi/topic_fractions_08.png
diff --git a/app/src/main/res/drawable/topic_ratios_01.png b/app/src/main/res/drawable-nodpi/topic_ratios_01.png
similarity index 100%
rename from app/src/main/res/drawable/topic_ratios_01.png
rename to app/src/main/res/drawable-nodpi/topic_ratios_01.png
diff --git a/app/src/main/res/layout-sw600dp-land/topic_lessons_story_summary.xml b/app/src/main/res/layout-sw600dp-land/topic_lessons_story_summary.xml
index cd483e4b25e..9da30566990 100644
--- a/app/src/main/res/layout-sw600dp-land/topic_lessons_story_summary.xml
+++ b/app/src/main/res/layout-sw600dp-land/topic_lessons_story_summary.xml
@@ -86,7 +86,7 @@
android:fontFamily="sans-serif"
android:text="@{viewModel.storyPercentage != 100? @string/status_in_progress : @string/status_completed}"
android:textColor="@color/component_color_shared_primary_text_color"
- android:textSize="10sp"
+ android:textSize="12sp"
android:visibility="@{viewModel.storyPercentage != 0 ? View.VISIBLE : View.GONE}" />
diff --git a/app/src/main/res/layout/concept_card_fragment_test_activity.xml b/app/src/main/res/layout/concept_card_fragment_test_activity.xml
index 9f8586d5570..79e5d51f9a2 100644
--- a/app/src/main/res/layout/concept_card_fragment_test_activity.xml
+++ b/app/src/main/res/layout/concept_card_fragment_test_activity.xml
@@ -7,6 +7,7 @@
android:id="@+id/open_dialog_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
android:text="@string/concept_card_one_button_text"
android:textAllCaps="false"/>
@@ -14,6 +15,7 @@
android:id="@+id/open_dialog_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
android:text="@string/concept_card_two_button_text"
android:textAllCaps="false" />
diff --git a/app/src/main/res/layout/survey_free_form_layout.xml b/app/src/main/res/layout/survey_free_form_layout.xml
index fb915d5a13c..6e038e92462 100644
--- a/app/src/main/res/layout/survey_free_form_layout.xml
+++ b/app/src/main/res/layout/survey_free_form_layout.xml
@@ -24,7 +24,8 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:textChangedListener="@{viewModel.answerTextWatcher}" />
+ app:textChangedListener="@{viewModel.answerTextWatcher}"
+ android:autofillHints="@string/survey_free_form_text_hint" />
diff --git a/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml b/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml
index a0b49ed0605..426098910ca 100644
--- a/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml
@@ -20,4 +20,21 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 3791a43ac0b..4d3f7e4b8de 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -7,7 +7,7 @@
* Sarah zoubida
* Seanlip
-->
-
+
أعلى قائمة التنقل
خيارات
التنزيلات
@@ -167,7 +167,7 @@
لا يجب أن يحتوي أي رقم في الكسر العشري على أكثر من 7 أرقام.
من فضلك إبدأ إجابتك برقم (0 او 0.5 على سبيل المثال)
من فضلك قم بإدخال رقم صالح.
- يمكن أن تحتوي الإجابة على 15 رقمًا (0-9) على الأكثر أو الرموز (. أو -)
+ (–يمكن أن تحتوي الإجابة على 15 رقمًا (0-9) على الأكثر أو الرموز (. أو
من فضلك قم بكتابة نسبة تتكون من أرقام مفصولة بنقطتين رأسيتين (1:2 أو 1:2:3 على سبيل المثال).
من فضلك أدخل نسبة صحيحة (1:2 أو 1:2:3 على سبيل المثال)
إجابتك تحتوي على نقطتين رأسيتين (:) متتاليتين.
@@ -411,7 +411,7 @@
عظيم
هيّا نبدأ.
نعم
- لا...
+ …لا
اختر موضوعًا\nآخرًا.
هل أنت مهتم بـ:\n%s
ملاحظة جديدة متاحة
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index d46bcf341da..d76077f8188 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -393,7 +393,7 @@
Ótimo
Vamos começar.
Sim
- Não...
+ Não…
Escolha um\ntópico diferente.
Você está interessado em:\n%s?
Nova dica disponível
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 31dd04601c1..d1250813c09 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,5 +1,5 @@
-
+
Navigation header
Options
My Downloads
@@ -16,13 +16,13 @@
Play audio
Pause audio
%s audio is unavailable.
- Ok
+ Ok
Cancel
Audio Language
Currently Offline
Make sure Wi-Fi or cellular data is turned on, then try again.
- Ok
- Ok
+ Ok
+ Ok
Cancel
Currently on Cellular Data
Streaming audio may use a lot of cellular data.
@@ -377,7 +377,7 @@
Account Actions
Log Out
Cancel
- Ok
+ Ok
Are you sure you want to log out of your profile?
App Version %s
@@ -490,11 +490,11 @@
Beta Notice
Hello! Your app is now being updated to the Beta version. If you experience problems while using the app, or have questions, please contact us at android-feedback@oppia.org.
Don\'t show this message again
- OK
+ Ok
General Availability Notice
Hello! Your app is now being updated to the General Availability version. If you experience problems while using the app, or have questions, please contact us at android-feedback@oppia.org.
Don\'t show this message again
- OK
+ Ok
\u0020to\u0020
Enter a ratio in the form x:y.
Tap here to enter text.
From bb69b2264903d75b305e96864c3c37d3fba261e2 Mon Sep 17 00:00:00 2001
From: "Mr. 17"
Date: Fri, 12 Jan 2024 04:33:11 +0530
Subject: [PATCH 3/3] Fix part of #5070: Display empty answer message in ratio
input interaction (#5263)
## Explanation
Fixes part of #5070
Enables the `submit_answer_button` when the pending answer is empty.
Instead of disabling the button, an error message, stating "_**Enter a
ratio to continue.**_", is now displayed when the user attempts to
submit a blank answer.
`RatioInputInteractionViewTestActivity` is added to accessibility-label
exemption as this is a test activity.
https://github.com/oppia/oppia-android/assets/84731134/33c797c5-af44-4eaa-980e-fafaccf72d26
## 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 +
.../android/app/parser/StringToRatioParser.kt | 4 +-
...atioExpressionInputInteractionViewModel.kt | 39 +-
.../InputInteractionViewTestActivity.kt | 17 -
.../RatioInputInteractionViewTestActivity.kt | 120 +++++
.../activity_input_interaction_view_test.xml | 41 --
...vity_ratio_input_interaction_view_test.xml | 75 +++
app/src/main/res/values/strings.xml | 1 +
.../InputInteractionViewTestActivityTest.kt | 250 ---------
...tioInputInteractionViewTestActivityTest.kt | 476 ++++++++++++++++++
.../accessibility_label_exemptions.textproto | 1 +
12 files changed, 702 insertions(+), 325 deletions(-)
create mode 100644 app/src/main/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivity.kt
create mode 100644 app/src/main/res/layout/activity_ratio_input_interaction_view_test.xml
create mode 100644 app/src/sharedTest/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivityTest.kt
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 76102786288..820522d91e7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -221,6 +221,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 14645028aa5..53f0f35955f 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
@@ -77,6 +77,7 @@ import org.oppia.android.app.testing.NavigationDrawerTestActivity
import org.oppia.android.app.testing.PoliciesFragmentTestActivity
import org.oppia.android.app.testing.ProfileChooserFragmentTestActivity
import org.oppia.android.app.testing.ProfileEditFragmentTestActivity
+import org.oppia.android.app.testing.RatioInputInteractionViewTestActivity
import org.oppia.android.app.testing.SplashTestActivity
import org.oppia.android.app.testing.SpotlightFragmentTestActivity
import org.oppia.android.app.testing.StateAssemblerMarginBindingAdaptersTestActivity
@@ -149,6 +150,7 @@ interface ActivityComponentImpl :
fun inject(imageRegionSelectionTestActivity: ImageRegionSelectionTestActivity)
fun inject(imageViewBindingAdaptersTestActivity: ImageViewBindingAdaptersTestActivity)
fun inject(inputInteractionViewTestActivity: InputInteractionViewTestActivity)
+ fun inject(ratioInputInteractionViewTestActivity: RatioInputInteractionViewTestActivity)
fun inject(licenseListActivity: LicenseListActivity)
fun inject(licenseTextViewerActivity: LicenseTextViewerActivity)
fun inject(listItemLeadingMarginSpanTestActivity: ListItemLeadingMarginSpanTestActivity)
diff --git a/app/src/main/java/org/oppia/android/app/parser/StringToRatioParser.kt b/app/src/main/java/org/oppia/android/app/parser/StringToRatioParser.kt
index 31895402263..ebb62121372 100644
--- a/app/src/main/java/org/oppia/android/app/parser/StringToRatioParser.kt
+++ b/app/src/main/java/org/oppia/android/app/parser/StringToRatioParser.kt
@@ -29,6 +29,7 @@ class StringToRatioParser {
val normalized = text.normalizeWhitespace()
val ratio = parseRatioOrNull(normalized)
return when {
+ normalized.isBlank() -> RatioParsingError.EMPTY_INPUT
!normalized.matches(invalidRatioRegex) || ratio == null -> RatioParsingError.INVALID_FORMAT
numberOfTerms != 0 && ratio.ratioComponentCount != numberOfTerms -> {
RatioParsingError.INVALID_SIZE
@@ -77,7 +78,8 @@ class StringToRatioParser {
INVALID_FORMAT(error = R.string.ratio_error_invalid_format),
INVALID_COLONS(error = R.string.ratio_error_invalid_colons),
INVALID_SIZE(error = R.string.ratio_error_invalid_size),
- INCLUDES_ZERO(error = R.string.ratio_error_includes_zero);
+ INCLUDES_ZERO(error = R.string.ratio_error_includes_zero),
+ EMPTY_INPUT(error = R.string.ratio_error_empty_input);
/**
* Returns the string corresponding to this error's string resources, or null if there is none.
diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/RatioExpressionInputInteractionViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/RatioExpressionInputInteractionViewModel.kt
index 49f64619702..f5c0f323bec 100644
--- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/RatioExpressionInputInteractionViewModel.kt
+++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/RatioExpressionInputInteractionViewModel.kt
@@ -46,12 +46,18 @@ class RatioExpressionInputInteractionViewModel private constructor(
override fun onPropertyChanged(sender: Observable, propertyId: Int) {
errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck(
pendingAnswerError,
- answerText.isNotEmpty()
+ inputAnswerAvailable = true // Allow blank answer submission.
)
}
}
errorMessage.addOnPropertyChangedCallback(callback)
isAnswerAvailable.addOnPropertyChangedCallback(callback)
+
+ // Initializing with default values so that submit button is enabled by default.
+ errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck(
+ pendingAnswerError = null,
+ inputAnswerAvailable = true
+ )
}
override fun getPendingAnswer(): UserAnswer = UserAnswer.newBuilder().apply {
@@ -67,23 +73,24 @@ class RatioExpressionInputInteractionViewModel private constructor(
}
}.build()
- /** It checks the pending error for the current ratio input, and correspondingly updates the error string based on the specified error category. */
+ /**
+ * It checks the pending error for the current ratio 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 ->
- pendingAnswerError =
- stringToRatioParser.getRealTimeAnswerError(answerText.toString())
- .getErrorMessageFromStringRes(resourceHandler)
- AnswerErrorCategory.SUBMIT_TIME ->
- pendingAnswerError =
- stringToRatioParser.getSubmitTimeError(
- answerText.toString(),
- numberOfTerms = numberOfTerms
- ).getErrorMessageFromStringRes(resourceHandler)
- }
- errorMessage.set(pendingAnswerError)
+ pendingAnswerError = when (category) {
+ AnswerErrorCategory.REAL_TIME ->
+ if (answerText.isNotEmpty())
+ stringToRatioParser.getRealTimeAnswerError(answerText.toString())
+ .getErrorMessageFromStringRes(resourceHandler)
+ else null
+ AnswerErrorCategory.SUBMIT_TIME ->
+ stringToRatioParser.getSubmitTimeError(
+ answerText.toString(),
+ numberOfTerms = numberOfTerms
+ ).getErrorMessageFromStringRes(resourceHandler)
}
+ errorMessage.set(pendingAnswerError)
return pendingAnswerError
}
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 ac786bb146a..f033c023d61 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
@@ -17,7 +17,6 @@ import org.oppia.android.app.model.InputInteractionViewTestActivityParams.MathIn
import org.oppia.android.app.model.InputInteractionViewTestActivityParams.MathInteractionType.NUMERIC_EXPRESSION
import org.oppia.android.app.model.InputInteractionViewTestActivityParams.MathInteractionType.UNRECOGNIZED
import org.oppia.android.app.model.Interaction
-import org.oppia.android.app.model.SchemaObject
import org.oppia.android.app.model.UserAnswer
import org.oppia.android.app.model.WrittenTranslationContext
import org.oppia.android.app.player.state.answerhandling.AnswerErrorCategory
@@ -25,7 +24,6 @@ import org.oppia.android.app.player.state.answerhandling.InteractionAnswerErrorO
import org.oppia.android.app.player.state.answerhandling.InteractionAnswerReceiver
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
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.itemviewmodel.TextInputViewModel
@@ -53,9 +51,6 @@ class InputInteractionViewTestActivity :
@Inject
lateinit var textInputViewModelFactory: TextInputViewModel.FactoryImpl
- @Inject
- lateinit var ratioViewModelFactory: RatioExpressionInputInteractionViewModel.FactoryImpl
-
@Inject
lateinit var mathExpViewModelFactoryFactory: MathExpViewModelFactoryFactoryImpl
@@ -63,15 +58,6 @@ class InputInteractionViewTestActivity :
val textInputViewModel by lazy { textInputViewModelFactory.create() }
- val ratioExpressionInputInteractionViewModel by lazy {
- ratioViewModelFactory.create(
- interaction = Interaction.newBuilder().putCustomizationArgs(
- "numberOfTerms",
- SchemaObject.newBuilder().setSignedInt(3).build()
- ).build()
- )
- }
-
lateinit var mathExpressionViewModel: MathExpressionInteractionsViewModel
lateinit var writtenTranslationContext: WrittenTranslationContext
@@ -118,14 +104,11 @@ class InputInteractionViewTestActivity :
binding.numericInputViewModel = numericInputViewModel
binding.textInputViewModel = textInputViewModel
- binding.ratioInteractionInputViewModel = ratioExpressionInputInteractionViewModel
binding.mathExpressionInteractionsViewModel = mathExpressionViewModel
}
fun getPendingAnswerErrorOnSubmitClick(v: View) {
numericInputViewModel.checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME)
- ratioExpressionInputInteractionViewModel
- .checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME)
}
override fun onPendingAnswerErrorOrAvailabilityCheck(
diff --git a/app/src/main/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivity.kt
new file mode 100644
index 00000000000..a16d8e7c12c
--- /dev/null
+++ b/app/src/main/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivity.kt
@@ -0,0 +1,120 @@
+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.RatioInputInteractionView
+import org.oppia.android.app.model.InputInteractionViewTestActivityParams
+import org.oppia.android.app.model.Interaction
+import org.oppia.android.app.model.SchemaObject
+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.RatioExpressionInputInteractionViewModel
+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.ActivityRatioInputInteractionViewTestBinding
+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 [RatioInputInteractionView].
+ */
+class RatioInputInteractionViewTestActivity :
+ InjectableAutoLocalizedAppCompatActivity(),
+ StateKeyboardButtonListener,
+ InteractionAnswerErrorOrAvailabilityCheckReceiver,
+ InteractionAnswerReceiver {
+ private lateinit var binding: ActivityRatioInputInteractionViewTestBinding
+
+ @Inject
+ lateinit var ratioViewModelFactory: RatioExpressionInputInteractionViewModel.FactoryImpl
+
+ /**
+ * Gives access to the [RatioExpressionInputInteractionViewModel].
+ */
+ val ratioExpressionInputInteractionViewModel by lazy {
+ ratioViewModelFactory.create(
+ interaction = Interaction.newBuilder().putCustomizationArgs(
+ "numberOfTerms",
+ SchemaObject.newBuilder().setSignedInt(3).build()
+ ).build()
+ )
+ }
+
+ /**
+ * 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_ratio_input_interaction_view_test
+ )
+
+ val params =
+ intent.getProtoExtra(
+ TEST_ACTIVITY_PARAMS_ARGUMENT_KEY,
+ InputInteractionViewTestActivityParams.getDefaultInstance()
+ )
+ writtenTranslationContext = params.writtenTranslationContext
+
+ binding.ratioInteractionInputViewModel = ratioExpressionInputInteractionViewModel
+ }
+
+ /**
+ * Checks for submit time errors.
+ */
+ fun getPendingAnswerErrorOnSubmitClick(v: View) {
+ ratioExpressionInputInteractionViewModel
+ .checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME)
+ }
+
+ 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@RatioInputInteractionViewTestActivity,
+ answerErrorReceiver = this@RatioInputInteractionViewTestActivity,
+ hasPreviousButton = false,
+ isSplitView = false,
+ writtenTranslationContext,
+ timeToStartNoticeAnimationMs = null
+ ) as T
+ }
+
+ companion object {
+ private const val TEST_ACTIVITY_PARAMS_ARGUMENT_KEY =
+ "RatioInputInteractionViewTestActivity.params"
+
+ /**
+ * Creates an intent to open [RatioInputInteractionViewTestActivity].
+ */
+ fun createIntent(
+ context: Context,
+ extras: InputInteractionViewTestActivityParams
+ ): Intent {
+ return Intent(context, RatioInputInteractionViewTestActivity::class.java).also {
+ it.putProtoExtra(TEST_ACTIVITY_PARAMS_ARGUMENT_KEY, extras)
+ }
+ }
+ }
+}
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 a8e798252c0..780294fa6b4 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
@@ -15,10 +15,6 @@
name="textInputViewModel"
type="org.oppia.android.app.player.state.itemviewmodel.TextInputViewModel" />
-
-
@@ -36,43 +32,6 @@
android:orientation="vertical"
tools:context=".testing.InputInteractionViewTestActivity">
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d1250813c09..cc2b39169e5 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -175,6 +175,7 @@
Your answer has two colons (:) next to each other.
Number of terms is not equal to the required terms.
Ratios cannot have 0 as an element.
+ Enter a ratio to continue.
Unknown size
%s Bytes
%s KB
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 20692243b6c..0bd03beb6ae 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
@@ -2,26 +2,20 @@ package org.oppia.android.app.testing
import android.app.Application
import android.content.res.Configuration
-import android.view.View
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.UiController
-import androidx.test.espresso.ViewAction
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.isAssignableFrom
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.hamcrest.CoreMatchers.allOf
-import org.hamcrest.Matcher
import org.junit.After
import org.junit.Before
import org.junit.Ignore
@@ -38,7 +32,6 @@ 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.customview.interaction.RatioInputInteractionView
import org.oppia.android.app.devoptions.DeveloperOptionsModule
import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
import org.oppia.android.app.model.InteractionObject
@@ -462,254 +455,11 @@ class InputInteractionViewTestActivityTest {
.check(matches(withText("abc")))
}
- @Test
- fun testRatioInput_withNoInput_hasCorrectPendingAnswerType() {
- val activityScenario = ActivityScenario.launch(
- InputInteractionViewTestActivity::class.java
- )
- activityScenario.onActivity { activity ->
- val pendingAnswer = activity.ratioExpressionInputInteractionViewModel.getPendingAnswer()
- assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
- assertThat(pendingAnswer.answer.ratioExpression.ratioComponentCount).isEqualTo(0)
- }
- }
-
- @Test
- fun testRatioInput_withRatioOfNumber_hasCorrectPendingAnswer() {
- val activityScenario = ActivityScenario.launch(
- InputInteractionViewTestActivity::class.java
- )
- onView(withId(R.id.test_ratio_input_interaction_view))
- .perform(
- setTextToRatioInputInteractionView(
- "1:2:3"
- )
- )
- activityScenario.onActivity { activity ->
- val pendingAnswer = activity.ratioExpressionInputInteractionViewModel.getPendingAnswer()
- assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
- assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
- InteractionObject.ObjectTypeCase.RATIO_EXPRESSION
- )
- assertThat(pendingAnswer.answer.ratioExpression.ratioComponentList)
- .isEqualTo(listOf(1, 2, 3))
- }
- }
-
- @Test
- @Ignore("Landscape not properly supported") // TODO(#56): Reenable once landscape is supported.
- fun testRatioInput_withRatio_configChange_hasCorrectPendingAnswer() {
- val activityScenario = ActivityScenario.launch(
- InputInteractionViewTestActivity::class.java
- )
- onView(withId(R.id.test_ratio_input_interaction_view))
- .perform(
- editTextInputAction.appendText(
- "1:2"
- )
- )
- activityScenario.onActivity { activity ->
- activity.requestedOrientation = Configuration.ORIENTATION_LANDSCAPE
- }
- onView(withId(R.id.test_ratio_input_interaction_view)).check(matches(isDisplayed()))
- .check(matches(withText("1:2")))
- }
-
- @Test
- fun testRatioInput_withTwoColonsTogether_colonsTogetherFormatErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java).use {
- testCoroutineDispatchers.runCurrent()
- onView(withId(R.id.test_ratio_input_interaction_view))
- .perform(
- setTextToRatioInputInteractionView(
- "1::2"
- )
- )
- testCoroutineDispatchers.runCurrent()
- onView(withId(R.id.ratio_input_error))
- .check(
- matches(
- withText(
- R.string.ratio_error_invalid_colons
- )
- )
- )
- }
- }
-
- @Test
- fun testRatioInput_withNegativeRatioOfNumber_numberFormatErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java).use {
- testCoroutineDispatchers.runCurrent()
- onView(withId(R.id.test_ratio_input_interaction_view))
- .perform(
- setTextToRatioInputInteractionView(
- "-1:2:3:4"
- )
- )
- testCoroutineDispatchers.runCurrent()
- onView(withId(R.id.ratio_input_error))
- .check(
- matches(
- withText(
- R.string.ratio_error_invalid_chars
- )
- )
- )
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testRatioInput_withZeroRatio_submit_numberWithZerosErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java).use {
- testCoroutineDispatchers.runCurrent()
- onView(withId(R.id.test_ratio_input_interaction_view))
- .perform(
- setTextToRatioInputInteractionView(
- "1:0:4"
- )
- )
- testCoroutineDispatchers.runCurrent()
- scrollToSubmitButton()
- onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
- onView(withId(R.id.ratio_input_error))
- .check(
- matches(
- withText(
- R.string.ratio_error_includes_zero
- )
- )
- )
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testRatioInput_withInvalidRatio_submit_numberFormatErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java).use {
- testCoroutineDispatchers.runCurrent()
- onView(withId(R.id.test_ratio_input_interaction_view))
- .perform(
- setTextToRatioInputInteractionView(
- "1: 1 2 :4"
- )
- )
- closeSoftKeyboard()
- testCoroutineDispatchers.runCurrent()
- scrollToSubmitButton()
- onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
- testCoroutineDispatchers.runCurrent()
- onView(withId(R.id.ratio_input_error))
- .check(
- matches(
- withText(
- R.string.ratio_error_invalid_format
- )
- )
- )
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testRatioInput_withRatioHaving4Terms_submit_invalidSizeErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java).use {
- testCoroutineDispatchers.runCurrent()
- onView(withId(R.id.test_ratio_input_interaction_view))
- .perform(
- setTextToRatioInputInteractionView(
- "1:2:3:4"
- )
- )
- closeSoftKeyboard()
- testCoroutineDispatchers.runCurrent()
- scrollToSubmitButton()
- onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
- onView(withId(R.id.ratio_input_error))
- .check(
- matches(
- withText(
- R.string.ratio_error_invalid_size
- )
- )
- )
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testRatioInput_withRatioHaving2Terms_submit_invalidSizeErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java).use {
- testCoroutineDispatchers.runCurrent()
- onView(withId(R.id.test_ratio_input_interaction_view))
- .perform(
- setTextToRatioInputInteractionView(
- "1:2"
- )
- )
- closeSoftKeyboard()
- testCoroutineDispatchers.runCurrent()
- scrollToSubmitButton()
- onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
- onView(withId(R.id.ratio_input_error))
- .check(
- matches(
- withText(
- R.string.ratio_error_invalid_size
- )
- )
- )
- }
- }
-
- @Test
- @DisableAccessibilityChecks // Disabled, as InputInteractionViewTestActivity is a test file and
- // will not be used by user
- fun testRatioInput_withRatioHaving3Terms_submit_noErrorIsDisplayed() {
- ActivityScenario.launch(InputInteractionViewTestActivity::class.java).use {
- testCoroutineDispatchers.runCurrent()
- onView(withId(R.id.test_ratio_input_interaction_view))
- .perform(
- setTextToRatioInputInteractionView(
- "1:2:3"
- )
- )
- closeSoftKeyboard()
- scrollToSubmitButton()
- onView(withId(R.id.submit_button)).check(matches(isDisplayed())).perform(click())
- onView(withId(R.id.ratio_input_error)).check(matches(withText("")))
- }
- }
-
private fun scrollToSubmitButton() {
onView(withId(R.id.submit_button)).perform(scrollTo())
testCoroutineDispatchers.runCurrent()
}
- private fun setTextToRatioInputInteractionView(
- newText: String?
- ): ViewAction? {
- return object : ViewAction {
- override fun getConstraints(): Matcher {
- return allOf(isDisplayed(), isAssignableFrom(RatioInputInteractionView::class.java))
- }
-
- override fun getDescription(): String {
- return "Update the text from the custom EditText"
- }
-
- override fun perform(uiController: UiController?, view: View) {
- (view as RatioInputInteractionView).setText(newText)
- uiController?.loopMainThreadUntilIdle()
- }
- }
- }
-
// TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them.
@Singleton
@Component(
diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivityTest.kt
new file mode 100644
index 00000000000..5a8249a9088
--- /dev/null
+++ b/app/src/sharedTest/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivityTest.kt
@@ -0,0 +1,476 @@
+package org.oppia.android.app.testing
+
+import android.app.Application
+import android.content.res.Configuration
+import android.view.View
+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.UiController
+import androidx.test.espresso.ViewAction
+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.isAssignableFrom
+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.hamcrest.CoreMatchers
+import org.hamcrest.Matcher
+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.customview.interaction.RatioInputInteractionView
+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 [RatioInputInteractionViewTestActivity]. */
+@RunWith(AndroidJUnit4::class)
+@LooperMode(LooperMode.Mode.PAUSED)
+@Config(
+ application = RatioInputInteractionViewTestActivityTest.TestApplication::class,
+ qualifiers = "port-xxhdpi"
+)
+class RatioInputInteractionViewTestActivityTest {
+ @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 testRatioInput_withNoInput_hasCorrectPendingAnswerType() {
+ val activityScenario = ActivityScenario.launch(
+ RatioInputInteractionViewTestActivity::class.java
+ )
+ activityScenario.onActivity { activity ->
+ val pendingAnswer = activity.ratioExpressionInputInteractionViewModel.getPendingAnswer()
+ assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
+ assertThat(pendingAnswer.answer.ratioExpression.ratioComponentCount).isEqualTo(0)
+ }
+ }
+
+ @Test
+ fun testRatioInput_withRatioOfNumber_hasCorrectPendingAnswer() {
+ val activityScenario = ActivityScenario.launch(
+ RatioInputInteractionViewTestActivity::class.java
+ )
+ onView(withId(R.id.test_ratio_input_interaction_view))
+ .perform(
+ setTextToRatioInputInteractionView(
+ "1:2:3"
+ )
+ )
+ activityScenario.onActivity { activity ->
+ val pendingAnswer = activity.ratioExpressionInputInteractionViewModel.getPendingAnswer()
+ assertThat(pendingAnswer.answer).isInstanceOf(InteractionObject::class.java)
+ assertThat(pendingAnswer.answer.objectTypeCase).isEqualTo(
+ InteractionObject.ObjectTypeCase.RATIO_EXPRESSION
+ )
+ assertThat(pendingAnswer.answer.ratioExpression.ratioComponentList)
+ .isEqualTo(listOf(1, 2, 3))
+ }
+ }
+
+ @Test
+ @Ignore("Landscape not properly supported") // TODO(#56): Reenable once landscape is supported.
+ fun testRatioInput_withRatio_configChange_hasCorrectPendingAnswer() {
+ val activityScenario = ActivityScenario.launch(
+ RatioInputInteractionViewTestActivity::class.java
+ )
+ onView(withId(R.id.test_ratio_input_interaction_view))
+ .perform(
+ editTextInputAction.appendText(
+ "1:2"
+ )
+ )
+ activityScenario.onActivity { activity ->
+ activity.requestedOrientation = Configuration.ORIENTATION_LANDSCAPE
+ }
+ onView(withId(R.id.test_ratio_input_interaction_view))
+ .check(matches(isDisplayed()))
+ .check(matches(withText("1:2")))
+ }
+
+ @Test
+ fun testRatioInput_withTwoColonsTogether_colonsTogetherFormatErrorIsDisplayed() {
+ ActivityScenario.launch(RatioInputInteractionViewTestActivity::class.java).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.test_ratio_input_interaction_view))
+ .perform(
+ setTextToRatioInputInteractionView(
+ "1::2"
+ )
+ )
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.ratio_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.ratio_error_invalid_colons
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testRatioInput_withNegativeRatioOfNumber_numberFormatErrorIsDisplayed() {
+ ActivityScenario.launch(RatioInputInteractionViewTestActivity::class.java).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.test_ratio_input_interaction_view))
+ .perform(
+ setTextToRatioInputInteractionView(
+ "-1:2:3:4"
+ )
+ )
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.ratio_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.ratio_error_invalid_chars
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as RatioInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testRatioInput_withBlankInput_submit_numberWithZerosErrorIsDisplayed() {
+ ActivityScenario.launch(RatioInputInteractionViewTestActivity::class.java).use {
+ testCoroutineDispatchers.runCurrent()
+ scrollToSubmitButton()
+ onView(withId(R.id.submit_button))
+ .check(matches(isDisplayed())).perform(click())
+ onView(withId(R.id.ratio_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.ratio_error_empty_input
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as RatioInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testRatioInput_withZeroRatio_submit_numberWithZerosErrorIsDisplayed() {
+ ActivityScenario.launch(RatioInputInteractionViewTestActivity::class.java).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.test_ratio_input_interaction_view))
+ .perform(
+ setTextToRatioInputInteractionView(
+ "1:0:4"
+ )
+ )
+ testCoroutineDispatchers.runCurrent()
+ scrollToSubmitButton()
+ onView(withId(R.id.submit_button))
+ .check(matches(isDisplayed())).perform(click())
+ onView(withId(R.id.ratio_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.ratio_error_includes_zero
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as RatioInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testRatioInput_withInvalidRatio_submit_numberFormatErrorIsDisplayed() {
+ ActivityScenario.launch(RatioInputInteractionViewTestActivity::class.java).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.test_ratio_input_interaction_view))
+ .perform(
+ setTextToRatioInputInteractionView(
+ "1: 1 2 :4"
+ )
+ )
+ closeSoftKeyboard()
+ testCoroutineDispatchers.runCurrent()
+ scrollToSubmitButton()
+ onView(withId(R.id.submit_button))
+ .check(matches(isDisplayed())).perform(click())
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.ratio_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.ratio_error_invalid_format
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as RatioInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testRatioInput_withRatioHaving4Terms_submit_invalidSizeErrorIsDisplayed() {
+ ActivityScenario.launch(RatioInputInteractionViewTestActivity::class.java).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.test_ratio_input_interaction_view))
+ .perform(
+ setTextToRatioInputInteractionView(
+ "1:2:3:4"
+ )
+ )
+ closeSoftKeyboard()
+ testCoroutineDispatchers.runCurrent()
+ scrollToSubmitButton()
+ onView(withId(R.id.submit_button))
+ .check(matches(isDisplayed())).perform(click())
+ onView(withId(R.id.ratio_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.ratio_error_invalid_size
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as RatioInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testRatioInput_withRatioHaving2Terms_submit_invalidSizeErrorIsDisplayed() {
+ ActivityScenario.launch(RatioInputInteractionViewTestActivity::class.java).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.test_ratio_input_interaction_view))
+ .perform(
+ setTextToRatioInputInteractionView(
+ "1:2"
+ )
+ )
+ closeSoftKeyboard()
+ testCoroutineDispatchers.runCurrent()
+ scrollToSubmitButton()
+ onView(withId(R.id.submit_button))
+ .check(matches(isDisplayed())).perform(click())
+ onView(withId(R.id.ratio_input_error))
+ .check(
+ matches(
+ withText(
+ R.string.ratio_error_invalid_size
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ @DisableAccessibilityChecks // Disabled, as RatioInputInteractionViewTestActivity is a test file and
+ // will not be used by user
+ fun testRatioInput_withRatioHaving3Terms_submit_noErrorIsDisplayed() {
+ ActivityScenario.launch(RatioInputInteractionViewTestActivity::class.java).use {
+ testCoroutineDispatchers.runCurrent()
+ onView(withId(R.id.test_ratio_input_interaction_view))
+ .perform(
+ setTextToRatioInputInteractionView(
+ "1:2:3"
+ )
+ )
+ closeSoftKeyboard()
+ scrollToSubmitButton()
+ onView(withId(R.id.submit_button))
+ .check(matches(isDisplayed())).perform(click())
+ onView(withId(R.id.ratio_input_error))
+ .check(matches(withText("")))
+ }
+ }
+
+ private fun scrollToSubmitButton() {
+ onView(withId(R.id.submit_button)).perform(scrollTo())
+ testCoroutineDispatchers.runCurrent()
+ }
+
+ private fun setTextToRatioInputInteractionView(
+ newText: String?
+ ): ViewAction? {
+ return object : ViewAction {
+ override fun getConstraints(): Matcher {
+ return CoreMatchers.allOf(
+ isDisplayed(),
+ isAssignableFrom(RatioInputInteractionView::class.java)
+ )
+ }
+
+ override fun getDescription(): String {
+ return "Update the text from the custom EditText"
+ }
+
+ override fun perform(uiController: UiController?, view: View) {
+ (view as RatioInputInteractionView).setText(newText)
+ uiController?.loopMainThreadUntilIdle()
+ }
+ }
+ }
+
+ // 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(ratioInputInteractionViewTestActivityTest: RatioInputInteractionViewTestActivityTest)
+ }
+
+ class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider {
+ private val component: TestApplicationComponent by lazy {
+ DaggerRatioInputInteractionViewTestActivityTest_TestApplicationComponent.builder()
+ .setApplication(this)
+ .build() as TestApplicationComponent
+ }
+
+ fun inject(inputInteractionViewTestActivityTest: RatioInputInteractionViewTestActivityTest) {
+ component.inject(inputInteractionViewTestActivityTest)
+ }
+
+ override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent {
+ return component.getActivityComponentBuilderProvider().get().setActivity(activity).build()
+ }
+
+ override fun getApplicationInjector(): ApplicationInjector = component
+ }
+}
diff --git a/scripts/assets/accessibility_label_exemptions.textproto b/scripts/assets/accessibility_label_exemptions.textproto
index dd1db64c796..4f2d9616133 100644
--- a/scripts/assets/accessibility_label_exemptions.textproto
+++ b/scripts/assets/accessibility_label_exemptions.textproto
@@ -22,6 +22,7 @@ exempted_activity: "app/src/main/java/org/oppia/android/app/testing/HtmlParserTe
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/ImageRegionSelectionTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/ImageViewBindingAdaptersTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity"
+exempted_activity: "app/src/main/java/org/oppia/android/app/testing/RatioInputInteractionViewTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/LessonThumbnailImageViewTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/ListItemLeadingMarginSpanTestActivity"
exempted_activity: "app/src/main/java/org/oppia/android/app/testing/MarginBindingAdaptersTestActivity"