diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4ee012da260..b418a3c1970 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,7 +123,7 @@ jobs: - name: Robolectric tests - FAQ, Help, Mydownloads, Parser, ProfileProgress, RecyclerView, Story, Utility tests # We require 'sudo' to avoid an error of the existing android sdk. See https://github.com/actions/starter-workflows/issues/58 run: | - sudo ./gradlew :app:testDebugUnitTest --tests org.oppia.app.faq* --tests org.oppia.app.help* --tests org.oppia.app.mydownloads* --tests org.oppia.app.parser* --tests org.oppia.app.profileprogress* --tests org.oppia.app.recyclerview* --tests org.oppia.app.splash* --tests org.oppia.app.story* --tests org.oppia.app.utility* + sudo ./gradlew :app:testDebugUnitTest --tests org.oppia.app.faq* --tests org.oppia.app.help* --tests org.oppia.app.mydownloads* --tests org.oppia.app.parser* --tests org.oppia.app.profileprogress* --tests org.oppia.app.recyclerview* --tests org.oppia.app.splash* --tests org.oppia.app.story* --tests org.oppia.app.utility* --tests org.oppia.app.topic.questionplayer* - name: Upload App Test Reports uses: actions/upload-artifact@v2 if: ${{ always() }} # IMPORTANT: Upload reports regardless of status diff --git a/app/src/main/java/org/oppia/app/topic/questionplayer/QuestionPlayerViewModel.kt b/app/src/main/java/org/oppia/app/topic/questionplayer/QuestionPlayerViewModel.kt index fc0607bc5cc..7c5bab42a54 100644 --- a/app/src/main/java/org/oppia/app/topic/questionplayer/QuestionPlayerViewModel.kt +++ b/app/src/main/java/org/oppia/app/topic/questionplayer/QuestionPlayerViewModel.kt @@ -1,6 +1,5 @@ package org.oppia.app.topic.questionplayer -import androidx.databinding.ObservableArrayList import androidx.databinding.ObservableBoolean import androidx.databinding.ObservableField import androidx.databinding.ObservableList @@ -8,6 +7,7 @@ import org.oppia.app.model.UserAnswer import org.oppia.app.player.state.answerhandling.AnswerErrorCategory import org.oppia.app.player.state.answerhandling.InteractionAnswerHandler import org.oppia.app.player.state.itemviewmodel.StateItemViewModel +import org.oppia.app.viewmodel.ObservableArrayList import org.oppia.app.viewmodel.ObservableViewModel import javax.inject.Inject diff --git a/app/src/main/res/layout-land/previous_responses_header_item.xml b/app/src/main/res/layout-land/previous_responses_header_item.xml index 7711cba4f70..ba59271a057 100644 --- a/app/src/main/res/layout-land/previous_responses_header_item.xml +++ b/app/src/main/res/layout-land/previous_responses_header_item.xml @@ -10,6 +10,7 @@ + ): ActivityScenario { + return ActivityScenario.launch( + QuestionPlayerActivity.createQuestionPlayerActivityIntent( + context, skillIdList + ) + ) + } + + private fun submitTwoWrongAnswersToQuestionPlayer() { + submitWrongAnswerToQuestionPlayerFractionInput() + submitWrongAnswerToQuestionPlayerFractionInput() + } + + private fun submitWrongAnswerToQuestionPlayerFractionInput() { + onView(withId(R.id.question_recycler_view)) + .perform(scrollToViewType(StateItemViewModel.ViewType.TEXT_INPUT_INTERACTION)) + onView(withId(R.id.text_input_interaction_view)).perform(appendText("1")) + testCoroutineDispatchers.runCurrent() + + onView(withId(R.id.question_recycler_view)) + .perform(scrollToViewType(StateItemViewModel.ViewType.SUBMIT_ANSWER_BUTTON)) + onView(withId(R.id.submit_answer_button)).perform(click()) + testCoroutineDispatchers.runCurrent() + } + + /** + * Appends the specified text to a view. This is needed because Robolectric doesn't seem to + * properly input digits for text views using 'android:digits'. See + * https://github.com/robolectric/robolectric/issues/5110 for specifics. + */ + private fun appendText(text: String): ViewAction { + return object : ViewAction { + override fun getDescription(): String { + return "appendText($text)" + } + + override fun getConstraints(): Matcher { + return CoreMatchers.allOf(isEnabled()) + } + + override fun perform(uiController: UiController?, view: View?) { + (view as? EditText)?.append(text) + testCoroutineDispatchers.runCurrent() + } + } + } + + private fun scrollToViewType(viewType: StateItemViewModel.ViewType): ViewAction { + return scrollToHolder(StateViewHolderTypeMatcher(viewType)) + } + + /** + * [BaseMatcher] that matches against the first occurrence of the specified view holder type in + * StateFragment's RecyclerView. + */ + private class StateViewHolderTypeMatcher( + private val viewType: StateItemViewModel.ViewType + ) : BaseMatcher() { + override fun describeTo(description: Description?) { + description?.appendText("item view type of $viewType") + } + + override fun matches(item: Any?): Boolean { + return (item as? RecyclerView.ViewHolder)?.itemViewType == viewType.ordinal + } + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them. + // TODO(#1675): Add NetworkModule once data module is migrated off of Moshi. + @Singleton + @Component( + modules = [ + 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, + TestAccessibilityModule::class, LogStorageModule::class, CachingTestModule::class, + PrimeTopicAssetsControllerModule::class, ExpirationMetaDataRetrieverModule::class, + ViewBindingShimModule::class + ] + ) + interface TestApplicationComponent : ApplicationComponent, ApplicationInjector { + @Component.Builder + interface Builder : ApplicationComponent.Builder + + fun inject(questionPlayerActivityLocalTest: QuestionPlayerActivityLocalTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerQuestionPlayerActivityLocalTest_TestApplicationComponent.builder() + .setApplication(this) + .build() as TestApplicationComponent + } + + fun inject(questionPlayerActivityLocalTest: QuestionPlayerActivityLocalTest) { + component.inject(questionPlayerActivityLocalTest) + } + + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { + return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() + } + + override fun getApplicationInjector(): ApplicationInjector = component + } +}