Skip to content

Commit

Permalink
Fix #138: Topic train fragment Low-fi UI (Part 4) (#204)
Browse files Browse the repository at this point in the history
* All xml files for topic-train except layout folder

* Skill item related files

* TopicTrainFragment implementation

* Topic Train Test Case

* Domain layer code integration

* Start button activated

* Start button activated

* Separate ViewModel for item

* Managing configuration changes

* Checkbox state save

* Nit changes

* Fixing HomeActivityController

* Managing configuration change in checkboxes

* Proper route to QuestionPlayer

* QuestionPlayerActivity test case

* Nit changes

* More test cases

* Nit changes

* EOF added

* Test nit changes

* Test case updated

* Updated RecyclerViewMatcher

* Nit change

* Test added

* Nit changes

* Nit changes

* Test cases

* Test case updated
  • Loading branch information
rt4914 authored Oct 18, 2019
1 parent 36d52f3 commit 117ec95
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,9 @@ class QuestionPlayerActivity : InjectableAppCompatActivity() {
intent.putExtra(QUESTION_PLAYER_ACTIVITY_SKILL_ID_LIST_ARGUMENT_KEY, skillIdList)
return intent
}

fun getIntentKey(): String {
return QUESTION_PLAYER_ACTIVITY_SKILL_ID_LIST_ARGUMENT_KEY
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onIdle
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withSubstring
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
Expand All @@ -26,12 +25,9 @@ import org.oppia.app.testing.BindableAdapterTestActivity
import org.oppia.app.testing.BindableAdapterTestFragment
import org.oppia.app.testing.BindableAdapterTestFragmentPresenter
import org.oppia.app.testing.BindableAdapterTestViewModel
import androidx.test.espresso.matcher.BoundedMatcher
import android.view.View
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.oppia.app.databinding.TestTextViewForIntWithDataBindingBinding
import org.oppia.app.databinding.TestTextViewForStringWithDataBindingBinding
import org.oppia.app.recyclerview.RecyclerViewMatcher.Companion.atPosition

/** Tests for [BindableAdapter]. */
@RunWith(AndroidJUnit4::class)
Expand All @@ -49,6 +45,7 @@ class BindableAdapterTest {
// Ensure that the bindable fragment's test state is properly reset each time.
BindableAdapterTestFragmentPresenter.testBindableAdapter = null
}

@After
fun tearDown() {
// Ensure that the bindable fragment's test state is properly cleaned up.
Expand Down Expand Up @@ -88,7 +85,7 @@ class BindableAdapterTest {
assertThat(recyclerView.childCount).isEqualTo(1)
}
// Perform onView() verification off the the main thread to avoid deadlocking.
onView(withId(R.id.test_recycler_view)).check(matches(atPosition(0, withText(STR_VALUE_0.strValue))))
onView(atPosition(R.id.test_recycler_view, 0)).check(matches(withText(STR_VALUE_0.strValue)))
}
}

Expand All @@ -109,9 +106,9 @@ class BindableAdapterTest {
val recyclerView: RecyclerView = getTestFragment(activity).view!!.findViewById(R.id.test_recycler_view)
assertThat(recyclerView.childCount).isEqualTo(3)
}
onView(withId(R.id.test_recycler_view)).check(matches(atPosition(0, withText(STR_VALUE_1.strValue))))
onView(withId(R.id.test_recycler_view)).check(matches(atPosition(1, withText(STR_VALUE_0.strValue))))
onView(withId(R.id.test_recycler_view)).check(matches(atPosition(2, withText(STR_VALUE_2.strValue))))
onView(atPosition(R.id.test_recycler_view, 0)).check(matches(withText(STR_VALUE_1.strValue)))
onView(atPosition(R.id.test_recycler_view, 1)).check(matches(withText(STR_VALUE_0.strValue)))
onView(atPosition(R.id.test_recycler_view, 2)).check(matches(withText(STR_VALUE_2.strValue)))
}
}

Expand All @@ -133,11 +130,10 @@ class BindableAdapterTest {
val recyclerView: RecyclerView = getTestFragment(activity).view!!.findViewById(R.id.test_recycler_view)
assertThat(recyclerView.childCount).isEqualTo(3)
}
onView(withId(R.id.test_recycler_view)).check(matches(atPosition(0, withText(STR_VALUE_1.strValue))))
onView(withId(R.id.test_recycler_view))
.check(matches(atPosition(1, withSubstring(INT_VALUE_0.intValue.toString()))))
onView(withId(R.id.test_recycler_view))
.check(matches(atPosition(2, withSubstring(INT_VALUE_1.intValue.toString()))))

onView(atPosition(R.id.test_recycler_view, 0)).check(matches(withText(STR_VALUE_1.strValue)))
onView(atPosition(R.id.test_recycler_view, 1)).check(matches(withSubstring(INT_VALUE_0.intValue.toString())))
onView(atPosition(R.id.test_recycler_view, 2)).check(matches(withSubstring(INT_VALUE_1.intValue.toString())))
}
}

Expand All @@ -159,7 +155,7 @@ class BindableAdapterTest {
assertThat(recyclerView.childCount).isEqualTo(1)
}
// Perform onView() verification off the the main thread to avoid deadlocking.
onView(withId(R.id.test_recycler_view)).check(matches(atPosition(0, withText(STR_VALUE_0.strValue))))
onView(atPosition(R.id.test_recycler_view, 0)).check(matches(withText(STR_VALUE_0.strValue)))
}
}

Expand All @@ -180,9 +176,10 @@ class BindableAdapterTest {
val recyclerView: RecyclerView = getTestFragment(activity).view!!.findViewById(R.id.test_recycler_view)
assertThat(recyclerView.childCount).isEqualTo(3)
}
onView(withId(R.id.test_recycler_view)).check(matches(atPosition(0, withText(STR_VALUE_1.strValue))))
onView(withId(R.id.test_recycler_view)).check(matches(atPosition(1, withText(STR_VALUE_0.strValue))))
onView(withId(R.id.test_recycler_view)).check(matches(atPosition(2, withText(STR_VALUE_2.strValue))))

onView(atPosition(R.id.test_recycler_view, 0)).check(matches(withText(STR_VALUE_1.strValue)))
onView(atPosition(R.id.test_recycler_view, 1)).check(matches(withText(STR_VALUE_0.strValue)))
onView(atPosition(R.id.test_recycler_view, 2)).check(matches(withText(STR_VALUE_2.strValue)))
}
}

Expand All @@ -204,11 +201,10 @@ class BindableAdapterTest {
val recyclerView: RecyclerView = getTestFragment(activity).view!!.findViewById(R.id.test_recycler_view)
assertThat(recyclerView.childCount).isEqualTo(3)
}
onView(withId(R.id.test_recycler_view)).check(matches(atPosition(0, withText(STR_VALUE_1.strValue))))
onView(withId(R.id.test_recycler_view))
.check(matches(atPosition(1, withSubstring(INT_VALUE_0.intValue.toString()))))
onView(withId(R.id.test_recycler_view))
.check(matches(atPosition(2, withSubstring(INT_VALUE_1.intValue.toString()))))

onView(atPosition(R.id.test_recycler_view, 0)).check(matches(withText(STR_VALUE_1.strValue)))
onView(atPosition(R.id.test_recycler_view, 1)).check(matches(withSubstring(INT_VALUE_0.intValue.toString())))
onView(atPosition(R.id.test_recycler_view, 2)).check(matches(withSubstring(INT_VALUE_1.intValue.toString())))
}
}

Expand Down Expand Up @@ -273,13 +269,15 @@ class BindableAdapterTest {
private fun inflateTextViewForStringWithoutDataBinding(viewGroup: ViewGroup): TextView {
val inflater = LayoutInflater.from(ApplicationProvider.getApplicationContext())
return inflater.inflate(
R.layout.test_text_view_for_string_no_data_binding, viewGroup, /* attachToRoot= */ false) as TextView
R.layout.test_text_view_for_string_no_data_binding, viewGroup, /* attachToRoot= */ false
) as TextView
}

private fun inflateTextViewForIntWithoutDataBinding(viewGroup: ViewGroup): TextView {
val inflater = LayoutInflater.from(ApplicationProvider.getApplicationContext())
return inflater.inflate(
R.layout.test_text_view_for_int_no_data_binding, viewGroup, /* attachToRoot= */ false) as TextView
R.layout.test_text_view_for_int_no_data_binding, viewGroup, /* attachToRoot= */ false
) as TextView
}

private fun bindTextViewForStringWithoutDataBinding(textView: TextView, data: TestModel) {
Expand Down Expand Up @@ -310,20 +308,4 @@ class BindableAdapterTest {
// This must be done off the main thread for Espresso otherwise it deadlocks.
onIdle()
}

// TODO(#89): Move this to a consolidated test library.
// https://stackoverflow.com/a/34795431
private fun atPosition(position: Int, itemMatcher: Matcher<View>): Matcher<View> {
return object : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
override fun describeTo(description: Description) {
description.appendText("has item at position $position: ")
itemMatcher.describeTo(description)
}

override fun matchesSafely(view: RecyclerView): Boolean {
val viewHolder = view.findViewHolderForAdapterPosition(position) ?: return false
return itemMatcher.matches(viewHolder.itemView)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.oppia.app.recyclerview

import android.content.res.Resources
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher

// Reference Link: https://github.com/dannyroa/espresso-samples/blob/master/RecyclerView/app/src/androidTest/java/com/dannyroa/espresso_samples/recyclerview/RecyclerViewMatcher.java
class RecyclerViewMatcher {
companion object {
/**
* This function returns a Matcher for an item inside RecyclerView from a specified position.
*/
fun atPosition(recyclerViewId: Int, position: Int): Matcher<View> {
return atPositionOnView(recyclerViewId, position, -1)
}

/**
* This function returns a Matcher for a specific view within the item inside RecyclerView from a specified position.
*/
fun atPositionOnView(recyclerViewId: Int, position: Int, targetViewId: Int): Matcher<View> {
return object : TypeSafeMatcher<View>() {
var resources: Resources? = null
var childView: View? = null

override fun describeTo(description: Description) {
var idDescription = Integer.toString(recyclerViewId)
if (this.resources != null) {
idDescription = try {
this.resources!!.getResourceName(recyclerViewId)
} catch (var4: Resources.NotFoundException) {
String.format(
"%s (resource name not found)",
recyclerViewId
)
}
}
description.appendText("with id: $idDescription")
}

public override fun matchesSafely(view: View): Boolean {
this.resources = view.resources
if (childView == null) {
val recyclerView = view.rootView.findViewById<View>(recyclerViewId) as RecyclerView
if (recyclerView.id == recyclerViewId) {
childView = recyclerView.findViewHolderForAdapterPosition(position)!!.itemView
} else {
return false
}
}
return if (targetViewId == -1) {
view === childView
} else {
val targetView = childView!!.findViewById<View>(targetViewId)
view === targetView
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import kotlinx.coroutines.CoroutineDispatcher
import org.junit.Test
import org.junit.runner.RunWith
import org.oppia.app.R
import org.oppia.app.player.exploration.ExplorationActivity
import org.oppia.util.threading.BackgroundDispatcher
import org.oppia.util.threading.BlockingDispatcher
import javax.inject.Singleton
Expand All @@ -27,7 +26,7 @@ class TopicActivityTest {

@Test
fun testTopicActivity_loadTopicFragment_hasDummyString() {
ActivityScenario.launch(ExplorationActivity::class.java).use {
ActivityScenario.launch(TopicActivity::class.java).use {
onView(withId(R.id.dummy_text_view)).check(matches(withText("This is dummy TextView for testing")))
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.oppia.app.topic.questionplayer

import android.app.Application
import android.content.Context
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
import org.junit.Test
import org.junit.runner.RunWith
import org.oppia.app.R
import org.oppia.util.threading.BackgroundDispatcher
import org.oppia.util.threading.BlockingDispatcher
import javax.inject.Singleton

/** Tests for [QuestionPlayerActivity]. */
@RunWith(AndroidJUnit4::class)
class QuestionPlayerActivityTest {

@Test
fun testQuestionPlayerActivity_loadQuestionPlayerFragment_hasDummyString() {
ActivityScenario.launch(QuestionPlayerActivity::class.java).use {
onView(withId(R.id.dummy_text_view)).check(matches(withText("This is dummy TextView for testing")))
}
}

@Module
class TestModule {
@Provides
@Singleton
fun provideContext(application: Application): Context {
return application
}

// TODO(#89): Introduce a proper IdlingResource for background dispatchers to ensure they all complete before
// proceeding in an Espresso test. This solution should also be interoperative with Robolectric contexts by using a
// test coroutine dispatcher.

@Singleton
@Provides
@BackgroundDispatcher
fun provideBackgroundDispatcher(@BlockingDispatcher blockingDispatcher: CoroutineDispatcher): CoroutineDispatcher {
return blockingDispatcher
}
}

@Singleton
@Component(modules = [TestModule::class])
interface TestApplicationComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun setApplication(application: Application): Builder

fun build(): TestApplicationComponent
}
}
}
Loading

0 comments on commit 117ec95

Please sign in to comment.