Skip to content

Commit

Permalink
Add UI tests for new save and restore classes
Browse files Browse the repository at this point in the history
  • Loading branch information
rubensousa committed May 29, 2024
1 parent 7812444 commit a5c5f17
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import com.rubensousa.dpadrecyclerview.DpadRecyclerView
import com.rubensousa.dpadrecyclerview.OnViewFocusedListener
import com.rubensousa.dpadrecyclerview.state.DpadScrollState
import com.rubensousa.dpadrecyclerview.state.DpadStateRegistry
import com.rubensousa.dpadrecyclerview.test.tests.AbstractTestAdapter
import com.rubensousa.dpadrecyclerview.testfixtures.DpadFocusEvent

Expand All @@ -34,6 +36,7 @@ class TestNestedListFragment : Fragment(R.layout.dpadrecyclerview_test_container
itemLayoutId = R.layout.dpadrecyclerview_item_horizontal,
numberOfItems = 200
)
private val stateRegistry = DpadStateRegistry(this)
private val parentFocusEvents = arrayListOf<DpadFocusEvent>()
private val childFocusEvents = arrayListOf<DpadFocusEvent>()
private val parentFocusListener = object : OnViewFocusedListener {
Expand All @@ -52,7 +55,11 @@ class TestNestedListFragment : Fragment(R.layout.dpadrecyclerview_test_container
childFocusEvents.add(DpadFocusEvent(parent, child, parent.layoutPosition))
}
}
private val nestedAdapter = NestedAdapter(configuration, childFocusListener)
private val nestedAdapter = NestedAdapter(
configuration = configuration,
onViewFocusedListener = childFocusListener,
scrollState = stateRegistry.getScrollState()
)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Expand All @@ -70,48 +77,63 @@ class TestNestedListFragment : Fragment(R.layout.dpadrecyclerview_test_container

class NestedAdapter(
private val configuration: TestAdapterConfiguration,
private val onViewFocusedListener: OnViewFocusedListener
private val onViewFocusedListener: OnViewFocusedListener,
private val scrollState: DpadScrollState,
) : AbstractTestAdapter<ListViewHolder>(configuration) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
return ListViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.dpadrecyclerview_nested_list, parent, false),
configuration,
onViewFocusedListener
onViewFocusedListener,
)
}

override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
holder.bind(position)
scrollState.restore(
recyclerView = holder.recyclerView,
key = position.toString(),
adapter = holder.adapter
)
}

}
override fun onViewRecycled(holder: ListViewHolder) {
super.onViewRecycled(holder)
scrollState.save(
recyclerView = holder.recyclerView,
key = holder.absoluteAdapterPosition.toString(),
detachAdapter = true
)
}

}

class ListViewHolder(
val view: View,
val configuration: TestAdapterConfiguration,
onViewFocusedListener: OnViewFocusedListener,
) : RecyclerView.ViewHolder(view) {

private val adapter = TestAdapter(
val adapter = TestAdapter(
adapterConfiguration = configuration,
onViewHolderSelected = { position -> },
onViewHolderDeselected = { position -> }
)
private val textView = view.findViewById<TextView>(R.id.textView)
private val recyclerView = view.findViewById<DpadRecyclerView>(R.id.nestedRecyclerView)

val recyclerView = view.findViewById<DpadRecyclerView>(R.id.nestedRecyclerView)

init {
recyclerView.adapter = adapter
recyclerView.addOnViewFocusedListener(onViewFocusedListener)
}

fun bind(position: Int) {
recyclerView.tag = position
textView.text = "List $position"
textView.freezesText = true
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,48 @@

package com.rubensousa.dpadrecyclerview.test.tests.selection

import androidx.recyclerview.widget.RecyclerView
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.Espresso
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withTagValue
import com.google.common.truth.Truth.assertThat
import com.rubensousa.dpadrecyclerview.ChildAlignment
import com.rubensousa.dpadrecyclerview.ParentAlignment
import com.rubensousa.dpadrecyclerview.ParentAlignment.Edge
import com.rubensousa.dpadrecyclerview.test.TestLayoutConfiguration
import com.rubensousa.dpadrecyclerview.test.TestNestedListFragment
import com.rubensousa.dpadrecyclerview.test.helpers.assertFocusPosition
import com.rubensousa.dpadrecyclerview.test.helpers.assertOnRecyclerView
import com.rubensousa.dpadrecyclerview.test.helpers.getItemViewBounds
import com.rubensousa.dpadrecyclerview.test.helpers.getRecyclerViewBounds
import com.rubensousa.dpadrecyclerview.test.tests.DpadRecyclerViewTest
import com.rubensousa.dpadrecyclerview.test.helpers.waitForCondition
import com.rubensousa.dpadrecyclerview.testing.KeyEvents
import com.rubensousa.dpadrecyclerview.testing.R
import com.rubensousa.dpadrecyclerview.testing.assertions.DpadRecyclerViewAssertions
import com.rubensousa.dpadrecyclerview.testing.rules.DisableIdleTimeoutRule
import org.hamcrest.Matchers
import org.hamcrest.Matchers.allOf
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class SaveRestoreStateTest : DpadRecyclerViewTest() {
class SaveRestoreStateTest {

@get:Rule
val idleTimeoutRule = DisableIdleTimeoutRule()

override fun getDefaultLayoutConfiguration(): TestLayoutConfiguration {
return TestLayoutConfiguration(
spans = 1,
orientation = RecyclerView.VERTICAL,
parentAlignment = ParentAlignment(
edge = Edge.MIN_MAX
),
childAlignment = ChildAlignment(offset = 0)
)
}
private lateinit var fragmentScenario: FragmentScenario<TestNestedListFragment>

@Before
fun setup() {
launchFragment()
fragmentScenario = launchFragment()
}

@Test
fun testSelectionStateIsSavedAndRestored() {
KeyEvents.pressDown(times = 5)
assertFocusPosition(5)

recreateFragment()
fragmentScenario.recreate()

val recyclerViewBounds = getRecyclerViewBounds()
val viewBounds = getItemViewBounds(position = 5)
Expand All @@ -70,4 +66,52 @@ class SaveRestoreStateTest : DpadRecyclerViewTest() {
assertFocusPosition(5)
}

@Test
fun testSelectionStateAcrossNestedListsIsSaved() {
// given
KeyEvents.pressRight(times = 5)

// when
KeyEvents.pressDown(times = 25)
KeyEvents.pressUp(times = 25)

// then
Espresso.onView(
allOf(
withId(com.rubensousa.dpadrecyclerview.test.R.id.nestedRecyclerView),
withTagValue(Matchers.`is`(0))
)
).check(DpadRecyclerViewAssertions.isSelected(position = 5))
}

@Test
fun testSelectionStateAcrossNestedListsSurvivesConfigurationChanges() {
// given
KeyEvents.pressRight(times = 5)
KeyEvents.pressDown(times = 25)
KeyEvents.pressUp(times = 25)

// when
fragmentScenario.recreate()

// then
Espresso.onView(
allOf(
withId(com.rubensousa.dpadrecyclerview.test.R.id.nestedRecyclerView),
withTagValue(Matchers.`is`(0))
)
).check(DpadRecyclerViewAssertions.isSelected(position = 5))
}

private fun launchFragment(): FragmentScenario<TestNestedListFragment> {
return launchFragmentInContainer<TestNestedListFragment>(
themeResId = R.style.DpadRecyclerViewTestTheme
).also {
fragmentScenario = it
waitForCondition("Waiting for layout pass") { recyclerView ->
!recyclerView.isLayoutRequested
}
}
}

}

0 comments on commit a5c5f17

Please sign in to comment.