Skip to content

Commit

Permalink
New snapshot test for LazyList loading and unloading (#2265)
Browse files Browse the repository at this point in the history
* New snapshot test for LazyList loading and unloading

This is in the wrong module as I intend to reuse the test infrastructure
in redwood-layout-uiview to test the iOS implementation.

* Test UIViewLazyList

* Delay snapshots one frame

* record: false

* Copy-paste snapshot testing infrastructure for lazylist

Rather than using the wrong module, or introducing build infrastructure
to abstract it up.
  • Loading branch information
squarejesse authored Aug 29, 2024
1 parent 2bfd69b commit 10ae978
Show file tree
Hide file tree
Showing 36 changed files with 1,040 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ jobs:

- run: xcodebuild -project redwood-layout-uiview/RedwoodLayoutUIViewTests.xcodeproj -scheme RedwoodLayoutUIViewTests -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.5' test

- run: xcodebuild -project redwood-lazylayout-uiview/RedwoodLazylayoutUIViewTests.xcodeproj -scheme RedwoodLazylayoutUIViewTests -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.5' test

- uses: actions/upload-artifact@v4
if: ${{ always() }}
with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ final class SnapshotTestingCallback : UIViewSnapshotCallback {
self.fileName = fileName
}

func verifySnapshot(view: UIView, name: String?) {
func verifySnapshot(view: UIView, name: String?, delay: TimeInterval = 0.0) {
// Set `record` to true to generate new snapshots. Be sure to revert that before committing!
// Note that tests always fail when `record` is true.
assertSnapshot(of: view, as: .image, named: name, record: false, file: fileName, testName: testName)
assertSnapshot(of: view, as: .wait(for: delay, on: .image), named: name, record: false, file: fileName, testName: testName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ package app.cash.redwood.layout.uiview
import platform.UIKit.UIView

interface UIViewSnapshotCallback {
fun verifySnapshot(view: UIView, name: String?)
fun verifySnapshot(view: UIView, name: String?, delay: Double = 0.0)
}
28 changes: 28 additions & 0 deletions redwood-lazylayout-shared-test/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import static app.cash.redwood.buildsupport.TargetGroup.ToolkitAllWithoutAndroid

redwoodBuild {
targets(ToolkitAllWithoutAndroid)
}

kotlin {
sourceSets {
commonMain {
dependencies {
api projects.redwoodLazylayoutApi
api projects.redwoodLazylayoutWidget
api projects.redwoodRuntime
api projects.redwoodWidget
api projects.redwoodYoga
api libs.kotlin.test
}
}
jvmMain {
dependencies {
// The kotlin.test library provides JVM variants for multiple testing frameworks. When used
// as a test dependency this selection is transparent. But since we are publishing a library
// we need to select one ourselves at compilation time.
api libs.kotlin.test.junit
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* 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 app.cash.redwood.lazylayout

import app.cash.redwood.Modifier
import app.cash.redwood.layout.api.Constraint
import app.cash.redwood.layout.api.CrossAxisAlignment
import app.cash.redwood.lazylayout.api.ScrollItemIndex
import app.cash.redwood.lazylayout.widget.LazyList
import app.cash.redwood.ui.Margin
import app.cash.redwood.ui.dp
import app.cash.redwood.widget.ChangeListener
import app.cash.redwood.widget.Widget
import kotlin.test.Test

abstract class AbstractLazyListTest<T : Any> {
abstract fun text(): Text<T>

private fun coloredText(
modifier: Modifier = Modifier,
text: String,
backgroundColor: Int = Green,
) = text().apply {
this.modifier = modifier
text(text)
bgColor(backgroundColor)
}

abstract fun lazyList(
backgroundColor: Int = argb(51, 0, 0, 255),
): LazyList<T>

private fun defaultLazyList(): LazyList<T> {
val result = lazyList()
for (i in 0 until 10) {
result.placeholder.insert(i, coloredText(text = "..."))
}
result.isVertical(true)
result.itemsBefore(0)
result.itemsAfter(0)
result.width(Constraint.Fill)
result.height(Constraint.Fill)
result.margin(Margin(all = 0.dp))
result.crossAxisAlignment(CrossAxisAlignment.Stretch)
result.scrollItemIndex(ScrollItemIndex(id = 0, index = 0))
return result
}

abstract fun verifySnapshot(
container: Widget<T>,
name: String? = null,
)

@Test fun testHappyPath() {
val lazyList = defaultLazyList()

for ((index, value) in movies.take(5).withIndex()) {
lazyList.items.insert(index, coloredText(text = value))
}
(lazyList as? ChangeListener)?.onEndChanges()

verifySnapshot(lazyList)
}

@Test fun testPlaceholderToLoadedAndLoadedToPlaceholder() {
val lazyList = defaultLazyList()

(lazyList as? ChangeListener)?.onEndChanges()
verifySnapshot(lazyList, "0 empty")

lazyList.itemsBefore(0)
lazyList.itemsAfter(10)
(lazyList as? ChangeListener)?.onEndChanges()
verifySnapshot(lazyList, "1 placeholders")

lazyList.itemsBefore(0)
lazyList.itemsAfter(0)
for ((index, value) in movies.take(10).withIndex()) {
lazyList.items.insert(index, coloredText(text = value))
}
(lazyList as? ChangeListener)?.onEndChanges()
verifySnapshot(lazyList, "2 loaded")

lazyList.itemsBefore(0)
lazyList.itemsAfter(10)
lazyList.items.remove(0, 10)
(lazyList as? ChangeListener)?.onEndChanges()
verifySnapshot(lazyList, "3 placeholders")

lazyList.itemsBefore(0)
lazyList.itemsAfter(0)
(lazyList as? ChangeListener)?.onEndChanges()
verifySnapshot(lazyList, "4 empty")
}

@Test fun testPlaceholdersExhausted() {
val lazyList = defaultLazyList()

lazyList.itemsBefore(11)
for ((index, value) in movies.take(1).withIndex()) {
lazyList.items.insert(index, coloredText(text = value))
}
(lazyList as? ChangeListener)?.onEndChanges()
verifySnapshot(lazyList)
}
}

private val movies = listOf(
"The Godfather",
"The Dark Knight",
"12 Angry Men",
"Schindler's List",
"Pulp Fiction",
"Forrest Gump",
"Fight Club",
"Inception",
"The Matrix",
"Goodfellas",
"Se7en",
"Seven Samurai",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* 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.
*/
@file:Suppress("ktlint:standard:property-naming")

package app.cash.redwood.lazylayout

import app.cash.redwood.widget.Widget

const val Green: Int = 0xff00ff00.toInt()

fun argb(
alpha: Int,
red: Int,
green: Int,
blue: Int,
): Int {
return (alpha shl 24) or (red shl 16) or (green shl 8) or (blue)
}

interface Text<T : Any> : Widget<T> {
fun text(text: String)
fun bgColor(color: Int)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2024 Square, Inc.
*
* 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 app.cash.redwood.lazylayout

import platform.UIKit.UIColor

fun Int.toUIColor(): UIColor {
return UIColor(
red = ((this shr 16) and 0xff) / 255.0,
green = ((this shr 8) and 0xff) / 255.0,
blue = (this and 0xff) / 255.0,
alpha = ((this shr 24) and 0xff) / 255.0,
)
}
Loading

0 comments on commit 10ae978

Please sign in to comment.