Skip to content

Commit

Permalink
Move the widget children tester to its own module (cashapp#1009)
Browse files Browse the repository at this point in the history
This allows us to consume it in the tests for the main implementations but also the Compose one. Someone could also consume it for testing a third-party implementation, too.
  • Loading branch information
JakeWharton authored May 3, 2023
1 parent 27ef2bb commit 624d8c5
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 20 deletions.
6 changes: 6 additions & 0 deletions redwood-widget-compose/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,11 @@ kotlin {
api libs.jetbrains.compose.runtime
}
}
commonTest {
dependencies {
implementation libs.kotlinx.coroutines.core
implementation projects.redwoodWidgetTesting
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2023 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.widget.compose

import androidx.compose.runtime.AbstractApplier
import androidx.compose.runtime.BroadcastFrameClock
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Recomposer
import androidx.compose.runtime.compositionLocalOf
import app.cash.redwood.widget.testing.AbstractWidgetChildrenTest
import kotlinx.coroutines.Job

class ComposeWidgetChildrenTest : AbstractWidgetChildrenTest<@Composable () -> Unit>() {
override val children = ComposeWidgetChildren()

override fun widget(name: String): @Composable () -> Unit {
// There's no way to peek inside a composable function to identify it. Instead we
// side-effect into a mutable list composition local in order to observe them.
return { NameList.current += name }
}

override fun names(): List<String> {
val clock = BroadcastFrameClock()
val job = Job()
val composeContext = clock + job

val recomposer = Recomposer(composeContext)
val composition = Composition(ThrowingApplier(), recomposer)

// The initial call to setContent will recompose synchronously allowing us to
// record the names and then immediately cancel the composition.
val names = mutableListOf<String>()
composition.setContent {
CompositionLocalProvider(NameList provides names) {
children.render()
}
}

job.cancel()
composition.dispose()

return names
}
}

private val NameList = compositionLocalOf<MutableList<String>> {
throw AssertionError()
}

private class ThrowingApplier : AbstractApplier<Unit>(Unit) {
override fun insertBottomUp(index: Int, instance: Unit) = throw AssertionError()
override fun insertTopDown(index: Int, instance: Unit) = throw AssertionError()
override fun move(from: Int, to: Int, count: Int) = throw AssertionError()
override fun remove(index: Int, count: Int) = throw AssertionError()
override fun onClear() {}
}
43 changes: 43 additions & 0 deletions redwood-widget-testing/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import app.cash.redwood.buildsupport.KmpTargets

apply plugin: 'org.jetbrains.kotlin.multiplatform'
apply plugin: 'com.android.library'
apply plugin: 'com.vanniktech.maven.publish'
apply plugin: 'org.jetbrains.dokka' // Must be applied here for publish plugin.

kotlin {
KmpTargets.addAllTargets(project)

sourceSets {
commonMain {
dependencies {
api libs.kotlin.test
implementation libs.assertk
api projects.redwoodWidget
}
}
}
}

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.
def configurationNames = [
"jvmMainImplementation",
"debugImplementation",
"releaseImplementation",
]
for (configurationName in configurationNames) {
add(configurationName, libs.kotlin.test) {
capabilities {
requireCapability(
"org.jetbrains.kotlin:kotlin-test-framework-junit:${libs.versions.kotlin.get()}")
}
}
}
}

android {
namespace 'app.cash.redwood.widget.testing'
}
2 changes: 2 additions & 0 deletions redwood-widget-testing/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
POM_ARTIFACT_ID=redwood-widget-testing
POM_NAME=Redwood widget testing helpers
Original file line number Diff line number Diff line change
Expand Up @@ -13,48 +13,49 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.redwood.widget
package app.cash.redwood.widget.testing

import app.cash.redwood.LayoutModifier
import app.cash.redwood.widget.Widget
import assertk.assertThat
import assertk.assertions.containsExactly
import kotlin.test.Test

abstract class AbstractWidgetChildrenTest<W : Any> {
abstract val children: Widget.Children<W>
abstract fun widget(name: String): W
abstract fun names(): List<String>
public abstract class AbstractWidgetChildrenTest<W : Any> {
public abstract val children: Widget.Children<W>
public abstract fun widget(name: String): W
public abstract fun names(): List<String>

@Test fun insertAppend() {
@Test public fun insertAppend() {
children.insert(0, widget("one"))
children.insert(1, widget("two"))
children.insert(2, widget("three"))
assertThat(names()).containsExactly("one", "two", "three")
}

@Test fun insertPrepend() {
@Test public fun insertPrepend() {
children.insert(0, widget("one"))
children.insert(0, widget("two"))
children.insert(0, widget("three"))
assertThat(names()).containsExactly("three", "two", "one")
}

@Test fun insertRandom() {
@Test public fun insertRandom() {
children.insert(0, widget("one"))
children.insert(0, widget("two"))
children.insert(1, widget("three"))
assertThat(names()).containsExactly("two", "three", "one")
}

@Test fun moveSingleForward() {
@Test public fun moveSingleForward() {
children.insert(0, widget("one"))
children.insert(1, widget("two"))
children.insert(2, widget("three"))
children.move(0, 3, 1)
assertThat(names()).containsExactly("two", "three", "one")
}

@Test fun moveSingleComposeApplierDocumentation() {
@Test public fun moveSingleComposeApplierDocumentation() {
// This test comes from Compose Applier's `move` documentation.
children.insert(0, widget("A"))
children.insert(1, widget("B"))
Expand All @@ -65,31 +66,31 @@ abstract class AbstractWidgetChildrenTest<W : Any> {
assertThat(names()).containsExactly("A", "C", "B", "D", "E")
}

@Test fun moveMultipleForward() {
@Test public fun moveMultipleForward() {
children.insert(0, widget("one"))
children.insert(1, widget("two"))
children.insert(2, widget("three"))
children.move(0, 3, 2)
assertThat(names()).containsExactly("three", "one", "two")
}

@Test fun moveSingleBackward() {
@Test public fun moveSingleBackward() {
children.insert(0, widget("one"))
children.insert(1, widget("two"))
children.insert(2, widget("three"))
children.move(2, 0, 1)
assertThat(names()).containsExactly("three", "one", "two")
}

@Test fun moveMultipleBackward() {
@Test public fun moveMultipleBackward() {
children.insert(0, widget("one"))
children.insert(1, widget("two"))
children.insert(2, widget("three"))
children.move(1, 0, 2)
assertThat(names()).containsExactly("two", "three", "one")
}

@Test fun moveZero() {
@Test public fun moveZero() {
children.insert(0, widget("one"))
children.insert(1, widget("two"))
children.insert(2, widget("three"))
Expand All @@ -99,39 +100,39 @@ abstract class AbstractWidgetChildrenTest<W : Any> {
assertThat(names()).containsExactly("one", "two", "three")
}

@Test fun removeSingleStart() {
@Test public fun removeSingleStart() {
children.insert(0, widget("one"))
children.insert(1, widget("two"))
children.insert(2, widget("three"))
children.remove(0, 1)
assertThat(names()).containsExactly("two", "three")
}

@Test fun removeMultipleStart() {
@Test public fun removeMultipleStart() {
children.insert(0, widget("one"))
children.insert(1, widget("two"))
children.insert(2, widget("three"))
children.remove(0, 2)
assertThat(names()).containsExactly("three")
}

@Test fun removeSingleEnd() {
@Test public fun removeSingleEnd() {
children.insert(0, widget("one"))
children.insert(1, widget("two"))
children.insert(2, widget("three"))
children.remove(2, 1)
assertThat(names()).containsExactly("one", "two")
}

@Test fun removeMultipleEnd() {
@Test public fun removeMultipleEnd() {
children.insert(0, widget("one"))
children.insert(1, widget("two"))
children.insert(2, widget("three"))
children.remove(1, 2)
assertThat(names()).containsExactly("one")
}

@Test fun removeZero() {
@Test public fun removeZero() {
children.insert(0, widget("one"))
children.insert(1, widget("two"))
children.insert(2, widget("three"))
Expand Down
1 change: 1 addition & 0 deletions redwood-widget/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ kotlin {
dependencies {
implementation libs.kotlin.test
implementation libs.assertk
implementation projects.redwoodWidgetTesting
}
}
androidUnitTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package app.cash.redwood.widget

import android.view.View
import android.widget.FrameLayout
import app.cash.redwood.widget.testing.AbstractWidgetChildrenTest
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package app.cash.redwood.widget

import app.cash.redwood.widget.testing.AbstractWidgetChildrenTest

class MutableListChildrenTest : AbstractWidgetChildrenTest<String>() {
override val children = MutableListChildren<String>()
override fun widget(name: String) = name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
*/
package app.cash.redwood.widget

import app.cash.redwood.widget.testing.AbstractWidgetChildrenTest
import platform.UIKit.UILabel
import platform.UIKit.UIView
import platform.UIKit.subviews

class UIViewChildrenTest : AbstractWidgetChildrenTest<UIView>() {
private val parent = UIView()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package app.cash.redwood.widget

import app.cash.redwood.widget.testing.AbstractWidgetChildrenTest
import kotlinx.browser.document
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.HTMLElement
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ include ':redwood-treehouse-lazylayout-view'
include ':redwood-treehouse-lazylayout-widget'
include ':redwood-widget'
include ':redwood-widget-compose'
include ':redwood-widget-testing'

include ':test-schema'
include ':test-schema:compose'
Expand Down

0 comments on commit 624d8c5

Please sign in to comment.