[go: nahoru, domu]

blob: cd16ae9643f4eab593254d737c451c5e00e43f62 [file] [log] [blame]
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.tv.foundation.lazy.grid
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.google.common.truth.Truth
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class LazyGridSlotsReuseTest {
@get:Rule
val rule = createComposeRule()
val itemsSizePx = 30f
val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
@Test
fun scroll1ItemScrolledOffItemIsKeptForReuse() {
lateinit var state: TvLazyGridState
rule.setContent {
state = rememberTvLazyGridState()
TvLazyVerticalGrid(
TvGridCells.Fixed(1),
Modifier.height(itemsSizeDp * 1.5f),
state
) {
items(100) {
Spacer(Modifier.height(itemsSizeDp).fillMaxWidth().testTag("$it"))
}
}
}
rule.onNodeWithTag("0")
.assertIsDisplayed()
rule.runOnIdle {
runBlocking {
state.scrollToItem(1)
}
}
rule.onNodeWithTag("0")
.assertExists()
.assertIsNotDisplayed()
rule.onNodeWithTag("1")
.assertIsDisplayed()
}
@Test
fun scroll2ItemsScrolledOffItemsAreKeptForReuse() {
lateinit var state: TvLazyGridState
rule.setContent {
state = rememberTvLazyGridState()
TvLazyVerticalGrid(
TvGridCells.Fixed(1),
Modifier.height(itemsSizeDp * 1.5f),
state
) {
items(100) {
Spacer(Modifier.height(itemsSizeDp).fillMaxWidth().testTag("$it"))
}
}
}
rule.onNodeWithTag("0")
.assertIsDisplayed()
rule.onNodeWithTag("1")
.assertIsDisplayed()
rule.runOnIdle {
runBlocking {
state.scrollToItem(2)
}
}
rule.onNodeWithTag("0")
.assertExists()
.assertIsNotDisplayed()
rule.onNodeWithTag("1")
.assertExists()
.assertIsNotDisplayed()
rule.onNodeWithTag("2")
.assertIsDisplayed()
}
@Test
fun checkMaxItemsKeptForReuse() {
lateinit var state: TvLazyGridState
rule.setContent {
state = rememberTvLazyGridState()
TvLazyVerticalGrid(
TvGridCells.Fixed(1),
Modifier.height(itemsSizeDp * (DefaultMaxItemsToRetain + 0.5f)),
state
) {
items(100) {
Spacer(Modifier.height(itemsSizeDp).fillMaxWidth().testTag("$it"))
}
}
}
rule.runOnIdle {
runBlocking {
state.scrollToItem(DefaultMaxItemsToRetain + 1)
}
}
repeat(DefaultMaxItemsToRetain) {
rule.onNodeWithTag("$it")
.assertExists()
.assertIsNotDisplayed()
}
rule.onNodeWithTag("$DefaultMaxItemsToRetain")
.assertDoesNotExist()
rule.onNodeWithTag("${DefaultMaxItemsToRetain + 1}")
.assertIsDisplayed()
}
@Test
fun scroll3Items2OfScrolledOffItemsAreKeptForReuse() {
lateinit var state: TvLazyGridState
rule.setContent {
state = rememberTvLazyGridState()
TvLazyVerticalGrid(
TvGridCells.Fixed(1),
Modifier.height(itemsSizeDp * 1.5f),
state
) {
items(100) {
Spacer(Modifier.height(itemsSizeDp).fillMaxWidth().testTag("$it"))
}
}
}
rule.onNodeWithTag("0")
.assertIsDisplayed()
rule.onNodeWithTag("1")
.assertIsDisplayed()
rule.runOnIdle {
runBlocking {
// after this step 0 and 1 are in reusable buffer
state.scrollToItem(2)
// this step requires one item and will take the last item from the buffer - item
// 1 plus will put 2 in the buffer. so expected buffer is items 2 and 0
state.scrollToItem(3)
}
}
// recycled
rule.onNodeWithTag("1")
.assertDoesNotExist()
// in buffer
rule.onNodeWithTag("0")
.assertExists()
.assertIsNotDisplayed()
rule.onNodeWithTag("2")
.assertExists()
.assertIsNotDisplayed()
// visible
rule.onNodeWithTag("3")
.assertIsDisplayed()
rule.onNodeWithTag("4")
.assertIsDisplayed()
}
@Test
fun doMultipleScrollsOneByOne() {
lateinit var state: TvLazyGridState
rule.setContent {
state = rememberTvLazyGridState()
TvLazyVerticalGrid(
TvGridCells.Fixed(1),
Modifier.height(itemsSizeDp * 1.5f),
state
) {
items(100) {
Spacer(Modifier.height(itemsSizeDp).fillMaxWidth().testTag("$it"))
}
}
}
rule.runOnIdle {
runBlocking {
state.scrollToItem(1) // buffer is [0]
state.scrollToItem(2) // 0 used, buffer is [1]
state.scrollToItem(3) // 1 used, buffer is [2]
state.scrollToItem(4) // 2 used, buffer is [3]
}
}
// recycled
rule.onNodeWithTag("0")
.assertDoesNotExist()
rule.onNodeWithTag("1")
.assertDoesNotExist()
rule.onNodeWithTag("2")
.assertDoesNotExist()
// in buffer
rule.onNodeWithTag("3")
.assertExists()
.assertIsNotDisplayed()
// visible
rule.onNodeWithTag("4")
.assertIsDisplayed()
rule.onNodeWithTag("5")
.assertIsDisplayed()
}
@Test
fun scrollBackwardOnce() {
lateinit var state: TvLazyGridState
rule.setContent {
state = rememberTvLazyGridState(10)
TvLazyVerticalGrid(
TvGridCells.Fixed(1),
Modifier.height(itemsSizeDp * 1.5f),
state
) {
items(100) {
Spacer(Modifier.height(itemsSizeDp).fillMaxWidth().testTag("$it"))
}
}
}
rule.runOnIdle {
runBlocking {
state.scrollToItem(8) // buffer is [10, 11]
}
}
// in buffer
rule.onNodeWithTag("10")
.assertExists()
.assertIsNotDisplayed()
rule.onNodeWithTag("11")
.assertExists()
.assertIsNotDisplayed()
// visible
rule.onNodeWithTag("8")
.assertIsDisplayed()
rule.onNodeWithTag("9")
.assertIsDisplayed()
}
@Test
fun scrollBackwardOneByOne() {
lateinit var state: TvLazyGridState
rule.setContent {
state = rememberTvLazyGridState(10)
TvLazyVerticalGrid(
TvGridCells.Fixed(1),
Modifier.height(itemsSizeDp * 1.5f),
state
) {
items(100) {
Spacer(Modifier.height(itemsSizeDp).fillMaxWidth().testTag("$it"))
}
}
}
rule.runOnIdle {
runBlocking {
state.scrollToItem(9) // buffer is [11]
state.scrollToItem(7) // 11 reused, buffer is [9]
state.scrollToItem(6) // 9 reused, buffer is [8]
}
}
// in buffer
rule.onNodeWithTag("8")
.assertExists()
.assertIsNotDisplayed()
// visible
rule.onNodeWithTag("6")
.assertIsDisplayed()
rule.onNodeWithTag("7")
.assertIsDisplayed()
}
@Test
fun scrollingBackReusesTheSameSlot() {
lateinit var state: TvLazyGridState
var counter0 = 0
var counter1 = 10
var rememberedValue0 = -1
var rememberedValue1 = -1
rule.setContent {
state = rememberTvLazyGridState()
TvLazyVerticalGrid(
TvGridCells.Fixed(1),
Modifier.height(itemsSizeDp * 1.5f),
state
) {
items(100) {
if (it == 0) {
rememberedValue0 = remember { counter0++ }
}
if (it == 1) {
rememberedValue1 = remember { counter1++ }
}
Spacer(Modifier.height(itemsSizeDp).fillMaxWidth().testTag("$it"))
}
}
}
rule.runOnIdle {
runBlocking {
state.scrollToItem(2) // buffer is [0, 1]
state.scrollToItem(0) // scrolled back, 0 and 1 are reused back. buffer: [2, 3]
}
}
rule.runOnIdle {
Truth.assertWithMessage("Item 0 restored remembered value is $rememberedValue0")
.that(rememberedValue0).isEqualTo(0)
Truth.assertWithMessage("Item 1 restored remembered value is $rememberedValue1")
.that(rememberedValue1).isEqualTo(10)
}
rule.onNodeWithTag("0")
.assertIsDisplayed()
rule.onNodeWithTag("1")
.assertIsDisplayed()
rule.onNodeWithTag("2")
.assertExists()
.assertIsNotDisplayed()
rule.onNodeWithTag("3")
.assertExists()
.assertIsNotDisplayed()
}
@Test
fun differentContentTypes() {
lateinit var state: TvLazyGridState
val visibleItemsCount = (DefaultMaxItemsToRetain + 1) * 2
val startOfType1 = DefaultMaxItemsToRetain + 1
rule.setContent {
state = rememberTvLazyGridState()
TvLazyVerticalGrid(
TvGridCells.Fixed(1),
Modifier.height(itemsSizeDp * (visibleItemsCount - 0.5f)),
state
) {
items(
100,
contentType = { if (it >= startOfType1) 1 else 0 }
) {
Spacer(Modifier.height(itemsSizeDp).fillMaxWidth().testTag("$it"))
}
}
}
for (i in 0 until visibleItemsCount) {
rule.onNodeWithTag("$i")
.assertIsDisplayed()
}
rule.runOnIdle {
runBlocking {
state.scrollToItem(visibleItemsCount)
}
}
rule.onNodeWithTag("$visibleItemsCount")
.assertIsDisplayed()
// [DefaultMaxItemsToRetain] items of type 0 are left for reuse
for (i in 0 until DefaultMaxItemsToRetain) {
rule.onNodeWithTag("$i")
.assertExists()
.assertIsNotDisplayed()
}
rule.onNodeWithTag("$DefaultMaxItemsToRetain")
.assertDoesNotExist()
// and 7 items of type 1
for (i in startOfType1 until startOfType1 + DefaultMaxItemsToRetain) {
rule.onNodeWithTag("$i")
.assertExists()
.assertIsNotDisplayed()
}
rule.onNodeWithTag("${startOfType1 + DefaultMaxItemsToRetain}")
.assertDoesNotExist()
}
@Test
fun differentTypesFromDifferentItemCalls() {
lateinit var state: TvLazyGridState
rule.setContent {
state = rememberTvLazyGridState()
TvLazyVerticalGrid(
TvGridCells.Fixed(1),
Modifier.height(itemsSizeDp * 2.5f),
state
) {
val content = @Composable { tag: String ->
Spacer(Modifier.height(itemsSizeDp).width(10.dp).testTag(tag))
}
item(contentType = "not-to-reuse-0") {
content("0")
}
item(contentType = "reuse") {
content("1")
}
items(
List(100) { it + 2 },
contentType = { if (it == 10) "reuse" else "not-to-reuse-$it" }) {
content("$it")
}
}
}
rule.runOnIdle {
runBlocking {
state.scrollToItem(2)
// now items 0 and 1 are put into reusables
}
}
rule.onNodeWithTag("0")
.assertExists()
.assertIsNotDisplayed()
rule.onNodeWithTag("1")
.assertExists()
.assertIsNotDisplayed()
rule.runOnIdle {
runBlocking {
state.scrollToItem(9)
// item 10 should reuse slot 1
}
}
rule.onNodeWithTag("0")
.assertExists()
.assertIsNotDisplayed()
rule.onNodeWithTag("1")
.assertDoesNotExist()
rule.onNodeWithTag("9")
.assertIsDisplayed()
rule.onNodeWithTag("10")
.assertIsDisplayed()
rule.onNodeWithTag("11")
.assertIsDisplayed()
}
}
private val DefaultMaxItemsToRetain = 7