Skip to content

Commit

Permalink
Test scrolling of LazyList (#1485)
Browse files Browse the repository at this point in the history
Integrates `WindowedLazyList` in the test, so any change to `WindowedLazyList#updateViewport` will be tested here.
  • Loading branch information
veyndan authored Sep 18, 2023
1 parent a287adc commit 8b9c315
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,31 @@
package app.cash.redwood.lazylayout.compose

import app.cash.redwood.Modifier
import app.cash.redwood.RedwoodCodegenApi
import app.cash.redwood.layout.api.Constraint
import app.cash.redwood.layout.api.CrossAxisAlignment
import app.cash.redwood.layout.widget.RedwoodLayoutTestingWidgetFactory
import app.cash.redwood.lazylayout.api.ScrollItemIndex
import app.cash.redwood.lazylayout.widget.LazyListValue
import app.cash.redwood.lazylayout.widget.ListUpdateCallback
import app.cash.redwood.lazylayout.widget.RedwoodLazyLayoutTestingWidgetFactory
import app.cash.redwood.lazylayout.widget.RedwoodLazyLayoutWidgetFactory
import app.cash.redwood.lazylayout.widget.WindowedLazyList
import app.cash.redwood.testing.TestRedwoodComposition
import app.cash.redwood.testing.WidgetValue
import app.cash.redwood.ui.Margin
import app.cash.redwood.ui.UiConfiguration
import app.cash.redwood.ui.dp
import app.cash.redwood.widget.MutableListChildren
import assertk.assertThat
import assertk.assertions.containsExactly
import com.example.redwood.testing.compose.Text
import com.example.redwood.testing.widget.TestSchemaTester
import com.example.redwood.testing.widget.TestSchemaTestingWidgetFactory
import com.example.redwood.testing.widget.TestSchemaWidgetFactories
import com.example.redwood.testing.widget.TextValue
import kotlin.test.Test
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest

class LazyListTest {
Expand Down Expand Up @@ -86,6 +98,136 @@ class LazyListTest {
),
)
}

@OptIn(RedwoodCodegenApi::class)
@Test
fun scrollPopulatedLazyColumn() = runTest {
val windowedLazyList = FakeWindowedLazyList()
val factories = TestSchemaWidgetFactories(
TestSchema = TestSchemaTestingWidgetFactory(),
RedwoodLayout = RedwoodLayoutTestingWidgetFactory(),
RedwoodLazyLayout = RedwoodLazyLayoutFakeWidgetFactory(windowedLazyList),
)
val snapshot = TestSchemaTester(factories) {
setContent {
LazyColumn(placeholder = { Text("Placeholder") }) {
items(100) {
Text(it.toString())
}
}
}
awaitSnapshot()
windowedLazyList.updateViewport(50, 60)
awaitSnapshot()
}

assertThat(snapshot)
.containsExactly(
DefaultLazyListValue.copy(
itemsBefore = 35,
itemsAfter = 25,
placeholder = List(20) { TextValue(Modifier, "Placeholder") },
items = List(40) { TextValue(Modifier, (it + 35).toString()) },
),
)
}
}

@OptIn(RedwoodCodegenApi::class)
private suspend fun <R> TestSchemaTester(
factories: TestSchemaWidgetFactories<WidgetValue> = TestSchemaWidgetFactories(
TestSchema = TestSchemaTestingWidgetFactory(),
RedwoodLayout = RedwoodLayoutTestingWidgetFactory(),
RedwoodLazyLayout = RedwoodLazyLayoutTestingWidgetFactory(),
),
body: suspend TestRedwoodComposition<List<WidgetValue>>.() -> R,
): R =
coroutineScope {
val container = MutableListChildren<WidgetValue>()
val tester = TestRedwoodComposition(
this,
factories,
container,
MutableStateFlow(UiConfiguration()),
) {
container.map { it.value }
}
try {
tester.body()
} finally {
tester.cancel()
}
}

private class RedwoodLazyLayoutFakeWidgetFactory(
private val windowedLazyList: WindowedLazyList<WidgetValue>,
) : RedwoodLazyLayoutWidgetFactory<WidgetValue> {
override fun LazyList() = windowedLazyList
override fun RefreshableLazyList() = error("unexpected call")
}

private class FakeWindowedLazyList : WindowedLazyList<WidgetValue>(NoOpListUpdateCallback) {
override val `value`: WidgetValue
get() = LazyListValue(
modifier = modifier,
isVertical = isVertical!!,
onViewportChanged = onViewportChanged!!,
itemsBefore = items.itemsBefore,
itemsAfter = items.itemsAfter,
width = width!!,
height = height!!,
margin = margin!!,
crossAxisAlignment = crossAxisAlignment!!,
scrollItemIndex = scrollItemIndex!!,
placeholder = placeholder.map { it.`value` },
items = items.filterNotNull().map { it.`value` },
)

override var modifier: Modifier = Modifier

private var isVertical: Boolean? = null

private var width: Constraint? = null

private var height: Constraint? = null

private var margin: Margin? = null

private var crossAxisAlignment: CrossAxisAlignment? = null

private var scrollItemIndex: ScrollItemIndex? = null

override val placeholder: MutableListChildren<WidgetValue> = MutableListChildren()

override fun isVertical(isVertical: Boolean) {
this.isVertical = isVertical
}

override fun width(width: Constraint) {
this.width = width
}

override fun height(height: Constraint) {
this.height = height
}

override fun margin(margin: Margin) {
this.margin = margin
}

override fun crossAxisAlignment(crossAxisAlignment: CrossAxisAlignment) {
this.crossAxisAlignment = crossAxisAlignment
}

override fun scrollItemIndex(scrollItemIndex: ScrollItemIndex) {
this.scrollItemIndex = scrollItemIndex
}
}

private object NoOpListUpdateCallback : ListUpdateCallback {
override fun onInserted(position: Int, count: Int) = Unit
override fun onMoved(fromPosition: Int, toPosition: Int, count: Int) = Unit
override fun onRemoved(position: Int, count: Int) = Unit
}

private val DefaultLazyListValue = LazyListValue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ public abstract class WindowedLazyList<W : Any>(

private var firstVisibleItemIndex = 0
private var lastVisibleItemIndex = 0
private var onViewportChanged: ((firstVisibleItemIndex: Int, lastVisibleItemIndex: Int) -> Unit)? = null
protected var onViewportChanged: ((firstVisibleItemIndex: Int, lastVisibleItemIndex: Int) -> Unit)? = null

final override val items: WindowedChildren<W> = WindowedChildren(listUpdateCallback)

final override fun onViewportChanged(onViewportChanged: (Int, Int) -> Unit) {
this.onViewportChanged = onViewportChanged
}

protected fun updateViewport(firstVisibleItemIndex: Int, lastVisibleItemIndex: Int) {
public fun updateViewport(firstVisibleItemIndex: Int, lastVisibleItemIndex: Int) {
if (firstVisibleItemIndex != this.firstVisibleItemIndex || lastVisibleItemIndex != this.lastVisibleItemIndex) {
this.firstVisibleItemIndex = firstVisibleItemIndex
this.lastVisibleItemIndex = lastVisibleItemIndex
Expand Down

0 comments on commit 8b9c315

Please sign in to comment.