From 2a405c560d970b7b3016b7d2dc94ceb4a3120c64 Mon Sep 17 00:00:00 2001 From: Stephen Edwards Date: Wed, 28 May 2025 10:13:35 -0400 Subject: [PATCH 1/2] Add expectCovariantWorkflow for generic type specification --- .../workflow1/WorkflowIdentifierEx.kt | 1 - workflow-testing/api/workflow-testing.api | 7 + .../workflow1/testing/RealRenderTester.kt | 25 +- .../workflow1/testing/RenderTester.kt | 168 ++++++++- .../workflow1/testing/RealRenderTesterTest.kt | 326 ++++++++++++++++-- 5 files changed, 503 insertions(+), 24 deletions(-) diff --git a/workflow-core/src/jvmMain/kotlin/com/squareup/workflow1/WorkflowIdentifierEx.kt b/workflow-core/src/jvmMain/kotlin/com/squareup/workflow1/WorkflowIdentifierEx.kt index c20fc5a85c..494578db58 100644 --- a/workflow-core/src/jvmMain/kotlin/com/squareup/workflow1/WorkflowIdentifierEx.kt +++ b/workflow-core/src/jvmMain/kotlin/com/squareup/workflow1/WorkflowIdentifierEx.kt @@ -10,7 +10,6 @@ import kotlin.reflect.KClass * This workflow must not be an [ImpostorWorkflow], or this property will throw an * [IllegalArgumentException]. */ -@OptIn(ExperimentalStdlibApi::class) @get:TestOnly public val KClass>.workflowIdentifier: WorkflowIdentifier get() { diff --git a/workflow-testing/api/workflow-testing.api b/workflow-testing/api/workflow-testing.api index efed859886..fc8db0ba34 100644 --- a/workflow-testing/api/workflow-testing.api +++ b/workflow-testing/api/workflow-testing.api @@ -25,6 +25,8 @@ public abstract interface class com/squareup/workflow1/testing/RenderTestResult } public abstract class com/squareup/workflow1/testing/RenderTester { + public static final field Companion Lcom/squareup/workflow1/testing/RenderTester$Companion; + public static final field VERIFY_ALL_LEVELS I public fun ()V public abstract fun expectRemember (Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/testing/RenderTester; public static synthetic fun expectRemember$default (Lcom/squareup/workflow1/testing/RenderTester;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow1/testing/RenderTester; @@ -51,6 +53,9 @@ public final class com/squareup/workflow1/testing/RenderTester$ChildWorkflowMatc public static final field INSTANCE Lcom/squareup/workflow1/testing/RenderTester$ChildWorkflowMatch$NotMatched; } +public final class com/squareup/workflow1/testing/RenderTester$Companion { +} + public final class com/squareup/workflow1/testing/RenderTester$RememberInvocation { public fun (Ljava/lang/String;Lkotlin/reflect/KType;Ljava/util/List;)V public final fun getInputs ()Ljava/util/List; @@ -68,6 +73,8 @@ public final class com/squareup/workflow1/testing/RenderTester$RenderChildInvoca } public final class com/squareup/workflow1/testing/RenderTesterKt { + public static final fun expectCovariantWorkflow (Lcom/squareup/workflow1/testing/RenderTester;Lkotlin/reflect/KClass;Lkotlin/reflect/KType;ILkotlin/reflect/KType;ILjava/lang/Object;Lcom/squareup/workflow1/WorkflowOutput;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/testing/RenderTester; + public static synthetic fun expectCovariantWorkflow$default (Lcom/squareup/workflow1/testing/RenderTester;Lkotlin/reflect/KClass;Lkotlin/reflect/KType;ILkotlin/reflect/KType;ILjava/lang/Object;Lcom/squareup/workflow1/WorkflowOutput;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow1/testing/RenderTester; public static final fun expectRemember (Lcom/squareup/workflow1/testing/RenderTester;Ljava/lang/String;Lkotlin/reflect/KType;[Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/testing/RenderTester; public static synthetic fun expectRemember$default (Lcom/squareup/workflow1/testing/RenderTester;Ljava/lang/String;Lkotlin/reflect/KType;[Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow1/testing/RenderTester; public static final fun expectSideEffect (Lcom/squareup/workflow1/testing/RenderTester;Ljava/lang/String;)Lcom/squareup/workflow1/testing/RenderTester; diff --git a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt index 522a47b2e7..b408f0b579 100644 --- a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt +++ b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt @@ -460,7 +460,7 @@ internal fun createRenderChildInvocation( * Returns true iff this identifier's [WorkflowIdentifier.getRealIdentifierType] is the same type as * or a subtype of [expected]'s. */ -internal fun WorkflowIdentifier.realTypeMatchesExpectation( +internal fun WorkflowIdentifier.realTypeMatchesClassExpectation( expected: WorkflowIdentifier ): Boolean { val expectedType = expected.realType @@ -468,6 +468,17 @@ internal fun WorkflowIdentifier.realTypeMatchesExpectation( return actualType.matchesExpectation(expectedType) } +/** + * Returns true iff this identifier's [WorkflowIdentifier.getRealIdentifierType] has the same + * class (or is a subtype) of the [expectedKClass]. + */ +internal fun WorkflowIdentifier.realTypeMatchesClassExpectation( + expectedKClass: KClass<*> +): Boolean { + val actualType = realType + return actualType.matchesClassExpectation(expectedKClass) +} + internal fun WorkflowIdentifierType.matchesExpectation(expected: WorkflowIdentifierType): Boolean { return when { this is Snapshottable && expected is Snapshottable -> matchesSnapshottable(expected) @@ -476,6 +487,18 @@ internal fun WorkflowIdentifierType.matchesExpectation(expected: WorkflowIdentif } } +internal fun WorkflowIdentifierType.matchesClassExpectation(expectedKClass: KClass<*>): Boolean { + return when (this) { + is Snapshottable -> kClass?.let { actualKClass -> + expectedKClass.isSuperclassOf(actualKClass) || actualKClass.isJavaMockOf(expectedKClass) + } == true + is Unsnapshottable -> (kType.classifier as? KClass<*>)?.let { actualKClass -> + expectedKClass.isSuperclassOf(actualKClass) || actualKClass.isJavaMockOf(expectedKClass) + } == true + else -> false + } +} + private fun Snapshottable.matchesSnapshottable(expected: Snapshottable): Boolean = kClass?.let { actualKClass -> expected.kClass?.let { expectedKClass -> diff --git a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RenderTester.kt b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RenderTester.kt index 1d33901649..4549e38785 100644 --- a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RenderTester.kt +++ b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RenderTester.kt @@ -14,6 +14,7 @@ import com.squareup.workflow1.WorkflowOutput import com.squareup.workflow1.config.JvmTestRuntimeConfigTools import com.squareup.workflow1.identifier import com.squareup.workflow1.testing.RenderTester.ChildWorkflowMatch +import com.squareup.workflow1.testing.RenderTester.Companion import com.squareup.workflow1.workflowIdentifier import kotlinx.coroutines.CoroutineScope import kotlin.reflect.KClass @@ -378,6 +379,10 @@ public abstract class RenderTester { public val output: WorkflowOutput<*>? = null ) : ChildWorkflowMatch() } + + public companion object { + public const val VERIFY_ALL_LEVELS: Int = -1 + } } /** @@ -389,6 +394,13 @@ public abstract class RenderTester { * concrete class, your render tests can pass the class of the interface to this method instead of * the actual class that implements it. * + * Note that Workflow is *not* a sub-type of Workflow because + * it is not covariant for the [OutputT] generic (the same is true for [PropsT]). This means that + * you cannot use the [WorkflowIdentifier] or [KClass] of a Workflow class whose [OutputT] or + * [PropsT] are supertypes to the one you want to match. If this is the only reasonable class + * definition you have access to, then consider using [expectCovariantWorkflow] and specifying + * those types explicitly. + * * ## Expecting impostor workflows * * If the workflow-under-test renders an @@ -448,6 +460,13 @@ public inline fun * concrete class, your render tests can pass the class of the interface to this method instead of * the actual class that implements it. * + * Note that Workflow is *not* a sub-type of Workflow because + * it is not covariant for the [OutputT] generic (the same is true for [PropsT]). This means that + * you cannot use the [WorkflowIdentifier] or [KClass] of a Workflow class whose [OutputT] or + * [PropsT] are supertypes to the one you want to match. If this is the only reasonable class + * definition you have access to, then consider using [expectCovariantWorkflow] and specifying + * those types explicitly. + * * ## Expecting impostor workflows * * If the workflow-under-test renders an @@ -509,7 +528,7 @@ public fun "output=$output" } ) { invocation -> - if (invocation.workflow.identifier.realTypeMatchesExpectation(identifier) && + if (invocation.workflow.identifier.realTypeMatchesClassExpectation(identifier) && invocation.renderKey == key ) { assertProps(invocation.props) @@ -528,6 +547,13 @@ public fun * concrete class, your render tests can pass the class of the interface to this method instead of * the actual class that implements it. * + * Note that Workflow is *not* a sub-type of Workflow because + * it is not covariant for the [OutputT] generic (the same is true for [PropsT]). This means that + * you cannot use the [WorkflowIdentifier] or [KClass] of a Workflow class whose [OutputT] or + * [PropsT] are supertypes to the one you want to match. If this is the only reasonable class + * definition you have access to, then consider using [expectCovariantWorkflow] and specifying + * those types explicitly. + * * ## Expecting impostor workflows * * If the workflow-under-test renders an @@ -549,7 +575,8 @@ public fun * * @param workflowType The [KClass] of the expected workflow. May also be any of the supertypes * of the expected workflow, e.g. if the workflow type is an interface and the workflow-under-test - * injects a fake. + * injects a fake. See note above about covariance with [PropsT] and [OutputT] and how these cannot + * help with supertypes. * * @param rendering The rendering to return from * [renderChild][com.squareup.workflow1.BaseRenderContext.renderChild] when this workflow is @@ -589,6 +616,143 @@ public inline fun ` and level 1 only `Wrapper` would be checked, whereas for + * level 2, `Wrapper` and `*` (the star projection) would be checked against the + * [RenderChildInvocation]. + * + * @param childRenderingType The [KType] of the [RenderingT] of the expected child workflow. + * + * @param renderingTypeVerificationLevel The number of 'levels' of generic arguments to verify in + * the [OutputT], e.g., for `Wrapper<*>` and level 1 only `Wrapper` would be checked, whereas for + * level 2, `Wrapper` and `*` (the star projection) would be checked against the + * [RenderChildInvocation]. + * + * @param rendering The rendering to return from + * [renderChild][com.squareup.workflow1.BaseRenderContext.renderChild] when this workflow is + * rendered. + * + * @param key The key passed to [renderChild][com.squareup.workflow1.BaseRenderContext.renderChild] + * when rendering this workflow. + * + * @param assertProps A function that performs assertions on the props passed to + * [renderChild][com.squareup.workflow1.BaseRenderContext.renderChild]. + * + * @param output If non-null, [WorkflowOutput.value] will be "emitted" when this workflow is + * rendered. The [WorkflowAction] used to handle this output can be verified using methods on + * [RenderTestResult]. + * + * @param description Optional string that will be used to describe this expectation in error + * messages. + */ +public fun + RenderTester.expectCovariantWorkflow( + childWorkflowClass: KClass<*>, + childOutputType: KType, + outputTypeVerificationLevel: Int = RenderTester.VERIFY_ALL_LEVELS, + childRenderingType: KType, + renderingTypeVerificationLevel: Int = Companion.VERIFY_ALL_LEVELS, + rendering: ChildRenderingT, + output: WorkflowOutput? = null, + key: String = "", + description: String = "", + assertProps: (props: Any?) -> Unit = {} +): RenderTester = expectWorkflow( + exactMatch = true, + description = description.ifBlank { + "workflow " + + "workflowClass=$childWorkflowClass, " + + "childOutputType=$childOutputType, " + + "childRenderingType=$childRenderingType, " + + "key=$key, " + + "rendering=$rendering, " + + "output=$output" + } +) { invocation -> + // Recursive function to verify #n levels of types. + fun verifyTypesToLevel( + levels: Int, + type1: KType, + type2: KType + ): Boolean { + if (levels < 1) return true + if (levels == 1) { + // We are at the last level of verification, ignore any further generic type arguments. + return type1.classifier?.equals(type2.classifier) == true + } else { + if (type1.arguments.size != type2.arguments.size) return false + var acc = true + type1.arguments.forEachIndexed { index, kTypeProjection1 -> + val kTypeProjection2 = type2.arguments[index] + if (kTypeProjection1.type == null || kTypeProjection2.type == null) return false + acc = + acc && verifyTypesToLevel(levels - 1, kTypeProjection1.type!!, kTypeProjection2.type!!) + } + return acc + } + } + + val childClassTypeMatches = + invocation.workflow.identifier.realTypeMatchesClassExpectation(childWorkflowClass) + val keyMatches = invocation.renderKey == key + val outputTypeMatches = invocation.outputType.type?.equals(childOutputType) == true || + ( + (outputTypeVerificationLevel > 0 && invocation.outputType.type != null) && + verifyTypesToLevel( + outputTypeVerificationLevel, + invocation.outputType.type!!, + childOutputType + ) + ) + val renderingTypeMatchers = invocation.renderingType.type?.equals(childRenderingType) == true || + ( + (renderingTypeVerificationLevel > 0 && invocation.renderingType.type != null) && + verifyTypesToLevel( + renderingTypeVerificationLevel, + invocation.renderingType.type!!, + childRenderingType + ) + ) + + if (childClassTypeMatches && + keyMatches && + outputTypeMatches && + renderingTypeMatchers + ) { + assertProps(invocation.props) + ChildWorkflowMatch.Matched(rendering, output) + } else { + ChildWorkflowMatch.NotMatched + } +} + /** * Specifies that this render pass is expected to run a particular side effect. * diff --git a/workflow-testing/src/test/java/com/squareup/workflow1/testing/RealRenderTesterTest.kt b/workflow-testing/src/test/java/com/squareup/workflow1/testing/RealRenderTesterTest.kt index 81d0ff0c04..1dae9c2cd6 100644 --- a/workflow-testing/src/test/java/com/squareup/workflow1/testing/RealRenderTesterTest.kt +++ b/workflow-testing/src/test/java/com/squareup/workflow1/testing/RealRenderTesterTest.kt @@ -6,7 +6,6 @@ import com.squareup.workflow1.Sink import com.squareup.workflow1.Snapshot import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.StatelessWorkflow -import com.squareup.workflow1.StatelessWorkflow.RenderContext import com.squareup.workflow1.Worker import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowAction @@ -643,6 +642,293 @@ internal class RealRenderTesterTest { assertEquals("Expected keys to be unique for ${child.identifier}: key=\"\"", error.message) } + @Test + fun `expectCovariantWorkflow succeeds with the types specified`() { + val child = object : StatelessWorkflow() { + override fun render( + renderProps: Unit, + context: RenderContext + ): Int = 42 + } + val workflow = Workflow.stateless { + renderChild(child) { noAction() } + } + + val renderTester: RenderTester = workflow.testRender(Unit) + + renderTester.expectCovariantWorkflow( + childWorkflowClass = StatelessWorkflow::class, + childRenderingType = typeOf(), + childOutputType = typeOf(), + output = null as WorkflowOutput?, + rendering = 42, + ) + .render() + } + + @Test + fun `expectCovariantWorkflow throws with the wrong rendering type specified`() { + val child = object : StatelessWorkflow() { + override fun render( + renderProps: Unit, + context: RenderContext + ): Int = 42 + } + val workflow = Workflow.stateless { + renderChild(child) { noAction() } + } + + val renderTester: RenderTester = workflow.testRender(Unit) + + val error = assertFailsWith { + renderTester.expectCovariantWorkflow( + childWorkflowClass = StatelessWorkflow::class, + childRenderingType = typeOf(), + childOutputType = typeOf(), + output = null as WorkflowOutput?, + rendering = 42, + ) + .render() + } + + assertEquals( + "Tried to render unexpected child ${child.identifier}", + error.message + ) + } + + @Test + fun `expectCovariantWorkflow succeeds with the types specified multi-level`() { + class Wrapper( + val innerValue: T + ) + + val child = object : StatelessWorkflow>() { + override fun render( + renderProps: Unit, + context: RenderContext + ): Wrapper = Wrapper(42) + } + val workflow = Workflow.stateless { + renderChild(child) { noAction() } + } + + val renderTester: RenderTester = workflow.testRender(Unit) + + renderTester.expectCovariantWorkflow( + childWorkflowClass = StatelessWorkflow::class, + childRenderingType = typeOf>(), + childOutputType = typeOf(), + output = null as WorkflowOutput?, + rendering = 42, + ) + .render() + } + + @Test + fun `expectCovariantWorkflow throws with star projection`() { + class Wrapper( + val innerValue: T + ) + + val child = object : StatelessWorkflow>() { + override fun render( + renderProps: Unit, + context: RenderContext + ): Wrapper = Wrapper(42) + } + val workflow = Workflow.stateless { + renderChild(child) { noAction() } + } + + val renderTester: RenderTester = workflow.testRender(Unit) + + val error = assertFailsWith { + renderTester.expectCovariantWorkflow( + childWorkflowClass = StatelessWorkflow::class, + childRenderingType = typeOf>(), + childOutputType = typeOf(), + output = null as WorkflowOutput?, + rendering = 42, + ) + .render() + } + + assertEquals( + "Tried to render unexpected child ${child.identifier}", + error.message + ) + } + + @Test + fun `expectCovariantWorkflow succeeds with recursive rendering 1 level and star projection`() { + class Wrapper( + val innerValue: T + ) + + val child = object : StatelessWorkflow>() { + override fun render( + renderProps: Unit, + context: RenderContext + ): Wrapper = Wrapper(42) + } + val workflow = Workflow.stateless { + renderChild(child) { noAction() } + } + + val renderTester: RenderTester = workflow.testRender(Unit) + + renderTester.expectCovariantWorkflow( + childWorkflowClass = StatelessWorkflow::class, + childRenderingType = typeOf>(), + renderingTypeVerificationLevel = 1, + childOutputType = typeOf(), + output = null as WorkflowOutput?, + rendering = 42, + ) + .render() + } + + @Test + fun `expectCovariantWorkflow throws with with recursive rendering 1 level and star projection wrong type`() { + class Wrapper( + val innerValue: T + ) + + class OtherWrapper( + val innerValue: T + ) + + val child = object : StatelessWorkflow>() { + override fun render( + renderProps: Unit, + context: RenderContext + ): Wrapper = Wrapper(42) + } + val workflow = Workflow.stateless { + renderChild(child) { noAction() } + } + + val renderTester: RenderTester = workflow.testRender(Unit) + + val error = assertFailsWith { + renderTester.expectCovariantWorkflow( + childWorkflowClass = StatelessWorkflow::class, + childRenderingType = typeOf>(), + renderingTypeVerificationLevel = 1, + childOutputType = typeOf(), + output = null as WorkflowOutput?, + rendering = 42, + ) + .render() + } + + assertEquals( + "Tried to render unexpected child ${child.identifier}", + error.message + ) + } + + @Test + fun `expectCovariantWorkflow succeeds with recursive output 1 level and star projection`() { + class Wrapper( + val innerValue: T + ) + + val child = object : StatelessWorkflow, Int>() { + override fun render( + renderProps: Unit, + context: RenderContext + ): Int = 42 + } + val workflow = Workflow.stateless { + renderChild(child) { noAction() } + } + + val renderTester: RenderTester = workflow.testRender(Unit) + + renderTester.expectCovariantWorkflow( + childWorkflowClass = StatelessWorkflow::class, + childRenderingType = typeOf(), + childOutputType = typeOf>(), + outputTypeVerificationLevel = 1, + output = null as WorkflowOutput?, + rendering = 42, + ) + .render() + } + + @Test + fun `expectCovariantWorkflow throws with with recursive output 1 level and star projection wrong type`() { + class Wrapper( + val innerValue: T + ) + + class OtherWrapper( + val innerValue: T + ) + + val child = object : StatelessWorkflow, Int>() { + override fun render( + renderProps: Unit, + context: RenderContext + ): Int = 42 + } + val workflow = Workflow.stateless { + renderChild(child) { noAction() } + } + + val renderTester: RenderTester = workflow.testRender(Unit) + + val error = assertFailsWith { + renderTester.expectCovariantWorkflow( + childWorkflowClass = StatelessWorkflow::class, + childRenderingType = typeOf(), + childOutputType = typeOf>(), + outputTypeVerificationLevel = 1, + output = null as WorkflowOutput?, + rendering = 42, + ) + .render() + } + + assertEquals( + "Tried to render unexpected child ${child.identifier}", + error.message + ) + } + + @Test + fun `expectCovariantWorkflow throws with the wrong output type specified`() { + val child = object : StatelessWorkflow() { + override fun render( + renderProps: Unit, + context: RenderContext + ): Int = 42 + } + val workflow = Workflow.stateless { + renderChild(child) { noAction() } + } + + val renderTester: RenderTester = workflow.testRender(Unit) + + val error = assertFailsWith { + renderTester.expectCovariantWorkflow( + childWorkflowClass = StatelessWorkflow::class, + childRenderingType = typeOf(), + childOutputType = typeOf(), + output = null as WorkflowOutput?, + rendering = 42, + ) + .render() + } + + assertEquals( + "Tried to render unexpected child ${child.identifier}", + error.message + ) + } + @Test fun `runningWorker doesn't throw when none expected`() { val worker = object : Worker { override fun doesSameWorkAs(otherWorker: Worker<*>): Boolean = true @@ -1675,46 +1961,46 @@ internal class RealRenderTesterTest { @Test fun `realTypeMatchesExpectation() matches exact type`() { val expected = unsnapshottableIdentifier(typeOf>()) val actual = unsnapshottableIdentifier(typeOf>()) - assertTrue(actual.realTypeMatchesExpectation(expected)) + assertTrue(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() doesn't match unrelated type`() { val expected = unsnapshottableIdentifier(typeOf()) val actual = unsnapshottableIdentifier(typeOf()) - assertFalse(actual.realTypeMatchesExpectation(expected)) + assertFalse(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() doesn't match unrelated type parameter`() { val expected = unsnapshottableIdentifier(typeOf>()) val actual = unsnapshottableIdentifier(typeOf>()) - assertFalse(actual.realTypeMatchesExpectation(expected)) + assertFalse(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() doesn't match exact invariant type with supertype parameter`() { val expected = unsnapshottableIdentifier(typeOf>()) val actual = unsnapshottableIdentifier(typeOf>()) - assertFalse(actual.realTypeMatchesExpectation(expected)) + assertFalse(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() doesn't match exact invariant type with subtype parameter`() { val expected = unsnapshottableIdentifier(typeOf>()) val actual = unsnapshottableIdentifier(typeOf>()) - assertFalse(actual.realTypeMatchesExpectation(expected)) + assertFalse(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() matches exact covariant type with supertype parameter`() { val expected = unsnapshottableIdentifier(typeOf>()) val actual = unsnapshottableIdentifier(typeOf>()) - assertTrue(actual.realTypeMatchesExpectation(expected)) + assertTrue(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() doesn't match exact covariant type with subtype parameter`() { val expected = unsnapshottableIdentifier(typeOf>()) val actual = unsnapshottableIdentifier(typeOf>()) - assertFalse(actual.realTypeMatchesExpectation(expected)) + assertFalse(actual.realTypeMatchesClassExpectation(expected)) } @Suppress("ktlint:standard:max-line-length") @@ -1722,70 +2008,70 @@ internal class RealRenderTesterTest { fun `realTypeMatchesExpectation() doesn't match exact contravariant type with supertype parameter`() { val expected = unsnapshottableIdentifier(typeOf>()) val actual = unsnapshottableIdentifier(typeOf>()) - assertFalse(actual.realTypeMatchesExpectation(expected)) + assertFalse(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() matches exact contravariant type with subtype parameter`() { val expected = unsnapshottableIdentifier(typeOf>()) val actual = unsnapshottableIdentifier(typeOf>()) - assertTrue(actual.realTypeMatchesExpectation(expected)) + assertTrue(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() matches exact class`() { val expected = TestWorkflow.identifier val actual = TestWorkflow.identifier - assertTrue(actual.realTypeMatchesExpectation(expected)) + assertTrue(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() matches superclass`() { val expected = Workflow::class.workflowIdentifier val actual = TestWorkflow.identifier - assertTrue(actual.realTypeMatchesExpectation(expected)) + assertTrue(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() doesn't match subclass`() { val expected = TestWorkflow.identifier val actual = Workflow::class.workflowIdentifier - assertFalse(actual.realTypeMatchesExpectation(expected)) + assertFalse(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() doesn't match type with class`() { val classId = Workflow::class.workflowIdentifier val typeId = unsnapshottableIdentifier(typeOf>()) - assertFalse(typeId.realTypeMatchesExpectation(classId)) - assertFalse(classId.realTypeMatchesExpectation(typeId)) + assertFalse(typeId.realTypeMatchesClassExpectation(classId)) + assertFalse(classId.realTypeMatchesClassExpectation(typeId)) } @Test fun `realTypeMatchesExpectation() matches mockito mock of expected interface`() { val expected = TestWorkflowInterface::class.workflowIdentifier val actual = mock().identifier - assertTrue(actual.realTypeMatchesExpectation(expected)) + assertTrue(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() matches mockito mock of expected abstract class`() { val expected = ExpectedWorkflowClass::class.workflowIdentifier val actual = mock().identifier - assertTrue(actual.realTypeMatchesExpectation(expected)) + assertTrue(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() doesn't match mockito mock of unexpected interface`() { val expected = TestWorkflowInterface::class.workflowIdentifier val actual = mock>().identifier - assertFalse(actual.realTypeMatchesExpectation(expected)) + assertFalse(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() doesn't match mockito mock of unexpected abstract class`() { val expected = ExpectedWorkflowClass::class.workflowIdentifier val actual = mock().identifier - assertFalse(actual.realTypeMatchesExpectation(expected)) + assertFalse(actual.realTypeMatchesClassExpectation(expected)) } @Test fun `realTypeMatchesExpectation() handles mockk mocks`() { val expected = TestWorkflowInterface::class.workflowIdentifier val actual = mockk().identifier - assertTrue(actual.realTypeMatchesExpectation(expected)) + assertTrue(actual.realTypeMatchesClassExpectation(expected)) } private object TestWorkflow : Workflow { From 0ea94db41f5862b9638dd0de3739f56a0c18b728 Mon Sep 17 00:00:00 2001 From: Stephen Edwards Date: Thu, 29 May 2025 15:02:41 -0400 Subject: [PATCH 2/2] Apply suggestions from code review Updating grammar in the kdoc. Co-authored-by: Ray Ryan --- .../java/com/squareup/workflow1/testing/RenderTester.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RenderTester.kt b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RenderTester.kt index 4549e38785..88062084ba 100644 --- a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RenderTester.kt +++ b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RenderTester.kt @@ -623,15 +623,15 @@ public inline fun