[go: nahoru, domu]

blob: 5e39177d13f93087a8bc3e6128fd7e0ef8f93d05 [file] [log] [blame]
/*
* Copyright 2020 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.compose.foundation.lazy.list
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@MediumTest
@RunWith(Parameterized::class)
class LazyListLayoutInfoTest(
param: LayoutInfoTestParam
) : BaseLazyListTestWithOrientation(param.orientation) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun initParameters(): Array<Any> = arrayOf(
LayoutInfoTestParam(Orientation.Vertical, false),
LayoutInfoTestParam(Orientation.Vertical, true),
LayoutInfoTestParam(Orientation.Horizontal, false),
LayoutInfoTestParam(Orientation.Horizontal, true),
)
}
private val reverseLayout = param.reverseLayout
private var itemSizePx: Int = 50
private var itemSizeDp: Dp = Dp.Infinity
@Before
fun before() {
with(rule.density) {
itemSizeDp = itemSizePx.toDp()
}
}
@Test
fun visibleItemsAreCorrect() {
lateinit var state: TvLazyListState
rule.setContent {
LazyColumnOrRow(
state = rememberLazyListState().also { state = it },
reverseLayout = reverseLayout,
modifier = Modifier.requiredSize(itemSizeDp * 3.5f)
) {
items((0..5).toList()) {
Box(Modifier.requiredSize(itemSizeDp))
}
}
}
rule.runOnIdle {
state.layoutInfo.assertVisibleItems(count = 4)
}
}
@Test
fun visibleItemsAreCorrectAfterScroll() {
lateinit var state: TvLazyListState
rule.setContent {
LazyColumnOrRow(
state = rememberLazyListState().also { state = it },
reverseLayout = reverseLayout,
modifier = Modifier.requiredSize(itemSizeDp * 3.5f)
) {
items((0..5).toList()) {
Box(Modifier.requiredSize(itemSizeDp))
}
}
}
rule.runOnIdle {
runBlocking {
state.scrollToItem(1, 10)
}
state.layoutInfo.assertVisibleItems(count = 4, startIndex = 1, startOffset = -10)
}
}
@Test
fun visibleItemsAreCorrectWithSpacing() {
lateinit var state: TvLazyListState
rule.setContent {
LazyColumnOrRow(
state = rememberLazyListState().also { state = it },
reverseLayout = reverseLayout,
spacedBy = itemSizeDp,
modifier = Modifier.requiredSize(itemSizeDp * 3.5f)
) {
items((0..5).toList()) {
Box(Modifier.requiredSize(itemSizeDp))
}
}
}
rule.runOnIdle {
state.layoutInfo.assertVisibleItems(count = 2, spacing = itemSizePx)
}
}
@Composable
fun ObservingFun(state: TvLazyListState, currentInfo: StableRef<LazyListLayoutInfo?>) {
currentInfo.value = state.layoutInfo
}
@Test
fun visibleItemsAreObservableWhenWeScroll() {
lateinit var state: TvLazyListState
val currentInfo = StableRef<LazyListLayoutInfo?>(null)
rule.setContent {
LazyColumnOrRow(
state = rememberLazyListState().also { state = it },
reverseLayout = reverseLayout,
modifier = Modifier.requiredSize(itemSizeDp * 3.5f)
) {
items((0..5).toList()) {
Box(Modifier.requiredSize(itemSizeDp))
}
}
ObservingFun(state, currentInfo)
}
rule.runOnIdle {
// empty it here and scrolling should invoke observingFun again
currentInfo.value = null
runBlocking {
state.scrollToItem(1, 0)
}
}
rule.runOnIdle {
assertThat(currentInfo.value).isNotNull()
currentInfo.value!!.assertVisibleItems(count = 4, startIndex = 1)
}
}
@Test
fun visibleItemsAreObservableWhenResize() {
lateinit var state: TvLazyListState
var size by mutableStateOf(itemSizeDp * 2)
var currentInfo: LazyListLayoutInfo? = null
@Composable
fun observingFun() {
currentInfo = state.layoutInfo
}
rule.setContent {
LazyColumnOrRow(
reverseLayout = reverseLayout,
state = rememberLazyListState().also { state = it }
) {
item {
Box(Modifier.requiredSize(size))
}
}
observingFun()
}
rule.runOnIdle {
assertThat(currentInfo).isNotNull()
currentInfo!!.assertVisibleItems(count = 1, expectedSize = itemSizePx * 2)
currentInfo = null
size = itemSizeDp
}
rule.runOnIdle {
assertThat(currentInfo).isNotNull()
currentInfo!!.assertVisibleItems(count = 1, expectedSize = itemSizePx)
}
}
@Test
fun totalCountIsCorrect() {
var count by mutableStateOf(10)
lateinit var state: TvLazyListState
rule.setContent {
LazyColumnOrRow(
reverseLayout = reverseLayout,
state = rememberLazyListState().also { state = it }
) {
items((0 until count).toList()) {
Box(Modifier.requiredSize(10.dp))
}
}
}
rule.runOnIdle {
assertThat(state.layoutInfo.totalItemsCount).isEqualTo(10)
count = 20
}
rule.runOnIdle {
assertThat(state.layoutInfo.totalItemsCount).isEqualTo(20)
}
}
@Test
fun viewportOffsetsAndSizeAreCorrect() {
val sizePx = 45
val sizeDp = with(rule.density) { sizePx.toDp() }
lateinit var state: TvLazyListState
rule.setContent {
LazyColumnOrRow(
Modifier.mainAxisSize(sizeDp).crossAxisSize(sizeDp * 2),
reverseLayout = reverseLayout,
state = rememberLazyListState().also { state = it }
) {
items((0..3).toList()) {
Box(Modifier.requiredSize(sizeDp))
}
}
}
rule.runOnIdle {
assertThat(state.layoutInfo.viewportStartOffset).isEqualTo(0)
assertThat(state.layoutInfo.viewportEndOffset).isEqualTo(sizePx)
assertThat(state.layoutInfo.viewportSize).isEqualTo(
if (vertical) IntSize(sizePx * 2, sizePx) else IntSize(sizePx, sizePx * 2)
)
}
}
@Test
fun viewportOffsetsAndSizeAreCorrectWithContentPadding() {
val sizePx = 45
val startPaddingPx = 10
val endPaddingPx = 15
val sizeDp = with(rule.density) { sizePx.toDp() }
val beforeContentPaddingDp = with(rule.density) {
if (!reverseLayout) startPaddingPx.toDp() else endPaddingPx.toDp()
}
val afterContentPaddingDp = with(rule.density) {
if (!reverseLayout) endPaddingPx.toDp() else startPaddingPx.toDp()
}
lateinit var state: TvLazyListState
rule.setContent {
LazyColumnOrRow(
Modifier.mainAxisSize(sizeDp).crossAxisSize(sizeDp * 2),
contentPadding = PaddingValues(
beforeContent = beforeContentPaddingDp,
afterContent = afterContentPaddingDp,
beforeContentCrossAxis = 2.dp,
afterContentCrossAxis = 2.dp
),
reverseLayout = reverseLayout,
state = rememberLazyListState().also { state = it }
) {
items((0..3).toList()) {
Box(Modifier.requiredSize(sizeDp))
}
}
}
rule.runOnIdle {
assertThat(state.layoutInfo.viewportStartOffset).isEqualTo(-startPaddingPx)
assertThat(state.layoutInfo.viewportEndOffset).isEqualTo(sizePx - startPaddingPx)
assertThat(state.layoutInfo.afterContentPadding).isEqualTo(endPaddingPx)
assertThat(state.layoutInfo.viewportSize).isEqualTo(
if (vertical) IntSize(sizePx * 2, sizePx) else IntSize(sizePx, sizePx * 2)
)
}
}
@Test
fun emptyItemsInVisibleItemsInfo() {
lateinit var state: TvLazyListState
rule.setContent {
LazyColumnOrRow(
state = rememberLazyListState().also { state = it }
) {
item { Box(Modifier) }
item { }
}
}
rule.runOnIdle {
assertThat(state.layoutInfo.visibleItemsInfo.size).isEqualTo(2)
assertThat(state.layoutInfo.visibleItemsInfo.first().index).isEqualTo(0)
assertThat(state.layoutInfo.visibleItemsInfo.last().index).isEqualTo(1)
}
}
@Test
fun emptyContent() {
lateinit var state: TvLazyListState
val sizePx = 45
val startPaddingPx = 10
val endPaddingPx = 15
val sizeDp = with(rule.density) { sizePx.toDp() }
val beforeContentPaddingDp = with(rule.density) {
if (!reverseLayout) startPaddingPx.toDp() else endPaddingPx.toDp()
}
val afterContentPaddingDp = with(rule.density) {
if (!reverseLayout) endPaddingPx.toDp() else startPaddingPx.toDp()
}
rule.setContent {
LazyColumnOrRow(
Modifier.mainAxisSize(sizeDp).crossAxisSize(sizeDp * 2),
state = rememberLazyListState().also { state = it },
reverseLayout = reverseLayout,
contentPadding = PaddingValues(
beforeContent = beforeContentPaddingDp,
afterContent = afterContentPaddingDp
)
) {
}
}
rule.runOnIdle {
assertThat(state.layoutInfo.viewportStartOffset).isEqualTo(-startPaddingPx)
assertThat(state.layoutInfo.viewportEndOffset).isEqualTo(sizePx - startPaddingPx)
assertThat(state.layoutInfo.beforeContentPadding).isEqualTo(startPaddingPx)
assertThat(state.layoutInfo.afterContentPadding).isEqualTo(endPaddingPx)
assertThat(state.layoutInfo.viewportSize).isEqualTo(
if (vertical) IntSize(sizePx * 2, sizePx) else IntSize(sizePx, sizePx * 2)
)
}
}
@Test
fun viewportIsLargerThenTheContent() {
lateinit var state: TvLazyListState
val sizePx = 45
val startPaddingPx = 10
val endPaddingPx = 15
val sizeDp = with(rule.density) { sizePx.toDp() }
val beforeContentPaddingDp = with(rule.density) {
if (!reverseLayout) startPaddingPx.toDp() else endPaddingPx.toDp()
}
val afterContentPaddingDp = with(rule.density) {
if (!reverseLayout) endPaddingPx.toDp() else startPaddingPx.toDp()
}
rule.setContent {
LazyColumnOrRow(
Modifier.mainAxisSize(sizeDp).crossAxisSize(sizeDp * 2),
state = rememberLazyListState().also { state = it },
reverseLayout = reverseLayout,
contentPadding = PaddingValues(
beforeContent = beforeContentPaddingDp,
afterContent = afterContentPaddingDp
)
) {
item {
Box(Modifier.size(sizeDp / 2))
}
}
}
rule.runOnIdle {
assertThat(state.layoutInfo.viewportStartOffset).isEqualTo(-startPaddingPx)
assertThat(state.layoutInfo.viewportEndOffset).isEqualTo(sizePx - startPaddingPx)
assertThat(state.layoutInfo.beforeContentPadding).isEqualTo(startPaddingPx)
assertThat(state.layoutInfo.afterContentPadding).isEqualTo(endPaddingPx)
assertThat(state.layoutInfo.viewportSize).isEqualTo(
if (vertical) IntSize(sizePx * 2, sizePx) else IntSize(sizePx, sizePx * 2)
)
}
}
@Test
fun reverseLayoutIsCorrect() {
lateinit var state: TvLazyListState
rule.setContent {
LazyColumnOrRow(
state = rememberLazyListState().also { state = it },
reverseLayout = reverseLayout,
modifier = Modifier.requiredSize(itemSizeDp * 3.5f)
) {
items((0..5).toList()) {
Box(Modifier.requiredSize(itemSizeDp))
}
}
}
rule.runOnIdle {
assertThat(state.layoutInfo.reverseLayout).isEqualTo(reverseLayout)
}
}
@Test
fun orientationIsCorrect() {
lateinit var state: TvLazyListState
rule.setContent {
LazyColumnOrRow(
state = rememberLazyListState().also { state = it },
modifier = Modifier.requiredSize(itemSizeDp * 3.5f)
) {
items((0..5).toList()) {
Box(Modifier.requiredSize(itemSizeDp))
}
}
}
rule.runOnIdle {
assertThat(state.layoutInfo.orientation)
.isEqualTo(if (vertical) Orientation.Vertical else Orientation.Horizontal)
}
}
fun LazyListLayoutInfo.assertVisibleItems(
count: Int,
startIndex: Int = 0,
startOffset: Int = 0,
expectedSize: Int = itemSizePx,
spacing: Int = 0
) {
assertThat(visibleItemsInfo.size).isEqualTo(count)
var currentIndex = startIndex
var currentOffset = startOffset
visibleItemsInfo.forEach {
assertThat(it.index).isEqualTo(currentIndex)
assertWithMessage("Offset of item $currentIndex").that(it.offset)
.isEqualTo(currentOffset)
assertThat(it.size).isEqualTo(expectedSize)
currentIndex++
currentOffset += it.size + spacing
}
}
}
class LayoutInfoTestParam(
val orientation: Orientation,
val reverseLayout: Boolean
) {
override fun toString(): String {
return "orientation=$orientation;reverseLayout=$reverseLayout"
}
}
@Stable
class StableRef<T>(var value: T)