Skip to content

Commit 952951c

Browse files
Merge pull request #1331 from square/sedwards/update-expect-remember
Update expectRemember to provide a result
2 parents f21e91c + fa75ef8 commit 952951c

File tree

4 files changed

+121
-52
lines changed

4 files changed

+121
-52
lines changed

workflow-testing/api/workflow-testing.api

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ public abstract class com/squareup/workflow1/testing/RenderTester {
3434
public static synthetic fun expectSideEffect$default (Lcom/squareup/workflow1/testing/RenderTester;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow1/testing/RenderTester;
3535
public abstract fun render (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/testing/RenderTestResult;
3636
public static synthetic fun render$default (Lcom/squareup/workflow1/testing/RenderTester;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow1/testing/RenderTestResult;
37-
public abstract fun requireExplicitRememberExpectations ()Lcom/squareup/workflow1/testing/RenderTester;
3837
public abstract fun requireExplicitSideEffectExpectations ()Lcom/squareup/workflow1/testing/RenderTester;
3938
public abstract fun requireExplicitWorkerExpectations ()Lcom/squareup/workflow1/testing/RenderTester;
4039
}
@@ -63,6 +62,18 @@ public final class com/squareup/workflow1/testing/RenderTester$RememberInvocatio
6362
public final fun getResultType ()Lkotlin/reflect/KType;
6463
}
6564

65+
public abstract class com/squareup/workflow1/testing/RenderTester$RememberMatch {
66+
}
67+
68+
public final class com/squareup/workflow1/testing/RenderTester$RememberMatch$Matched : com/squareup/workflow1/testing/RenderTester$RememberMatch {
69+
public fun <init> (Ljava/lang/Object;)V
70+
public final fun getResult ()Ljava/lang/Object;
71+
}
72+
73+
public final class com/squareup/workflow1/testing/RenderTester$RememberMatch$NotMatched : com/squareup/workflow1/testing/RenderTester$RememberMatch {
74+
public static final field INSTANCE Lcom/squareup/workflow1/testing/RenderTester$RememberMatch$NotMatched;
75+
}
76+
6677
public final class com/squareup/workflow1/testing/RenderTester$RenderChildInvocation {
6778
public fun <init> (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Lkotlin/reflect/KTypeProjection;Lkotlin/reflect/KTypeProjection;Ljava/lang/String;)V
6879
public final fun getOutputType ()Lkotlin/reflect/KTypeProjection;
@@ -75,8 +86,8 @@ public final class com/squareup/workflow1/testing/RenderTester$RenderChildInvoca
7586
public final class com/squareup/workflow1/testing/RenderTesterKt {
7687
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;
7788
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;
78-
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;
79-
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;
89+
public static final fun expectRemember (Lcom/squareup/workflow1/testing/RenderTester;Ljava/lang/String;Lkotlin/reflect/KType;Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/testing/RenderTester;
90+
public static synthetic fun expectRemember$default (Lcom/squareup/workflow1/testing/RenderTester;Ljava/lang/String;Lkotlin/reflect/KType;Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/squareup/workflow1/testing/RenderTester;
8091
public static final fun expectSideEffect (Lcom/squareup/workflow1/testing/RenderTester;Ljava/lang/String;)Lcom/squareup/workflow1/testing/RenderTester;
8192
public static final fun expectWorkflow (Lcom/squareup/workflow1/testing/RenderTester;Lcom/squareup/workflow1/WorkflowIdentifier;Ljava/lang/Object;Lcom/squareup/workflow1/WorkflowOutput;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/testing/RenderTester;
8293
public static final fun expectWorkflow (Lcom/squareup/workflow1/testing/RenderTester;Lcom/squareup/workflow1/WorkflowIdentifier;Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/testing/RenderTester;

workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
9494
}
9595

9696
data class ExpectedRemember(
97-
val matcher: (RememberInvocation) -> Boolean,
97+
val matcher: (RememberInvocation) -> RememberMatch,
9898
val exactMatch: Boolean,
9999
val description: String,
100100
) : Expectation<Nothing>() {
@@ -106,7 +106,6 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
106106

107107
private var explicitWorkerExpectationsRequired: Boolean = false
108108
private var explicitSideEffectExpectationsRequired: Boolean = false
109-
private var explicitRememberExpectationsRequired: Boolean = false
110109
private val stateAndOutput: Pair<StateT, WorkflowOutput<OutputT>?> by lazy {
111110
val action = processedAction ?: noAction()
112111
val (state, actionApplied) = action.applyTo(props, state)
@@ -153,7 +152,7 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
153152
override fun expectRemember(
154153
description: String,
155154
exactMatch: Boolean,
156-
matcher: (RememberInvocation) -> Boolean
155+
matcher: (RememberInvocation) -> RememberMatch
157156
): RenderTester<PropsT, StateT, OutputT, RenderingT> = apply {
158157
expectations += ExpectedRemember(matcher, exactMatch, description)
159158
}
@@ -171,11 +170,6 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
171170
expectSideEffect(description = "unexpected side effect", exactMatch = false) { true }
172171
}
173172

174-
if (!explicitRememberExpectationsRequired) {
175-
// Allow unexpected remember calls.
176-
expectRemember(description = "unexpected remembered value", exactMatch = false) { true }
177-
}
178-
179173
frozen = false
180174
// Clone the expectations to run a "dry" render pass.
181175
val noopContext = deepCloneForRender()
@@ -317,27 +311,36 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
317311
val description = "remember with key \"$key\""
318312

319313
val matches = expectations.filterIsInstance<ExpectedRemember>()
320-
.mapNotNull { if (it.matcher(invocation)) it else null }
314+
.mapNotNull {
315+
val matchResult = it.matcher(invocation)
316+
if (matchResult is RememberMatch.Matched) Pair(it, matchResult) else null
317+
}
321318
if (matches.isEmpty()) {
322319
throw AssertionError("Unexpected $description")
323320
}
324321

325-
val exactMatches = matches.filter { it.exactMatch }
326-
if (exactMatches.size > 1) {
327-
throw AssertionError(
328-
"Multiple expectations matched $description: \n" +
329-
exactMatches.joinToString(separator = "\n") { " ${it.describe()}" }
330-
)
331-
}
322+
val exactMatches = matches.filter { it.first.exactMatch }
323+
val (_, match) = when {
324+
exactMatches.size == 1 -> {
325+
exactMatches.single()
326+
.also { (expected, _) ->
327+
expectations -= expected
328+
consumedExpectations += expected
329+
}
330+
}
332331

333-
// Inexact matches are not consumable.
334-
exactMatches.singleOrNull()
335-
?.let { expected ->
336-
expectations -= expected
337-
consumedExpectations += expected
332+
exactMatches.size > 1 -> {
333+
throw AssertionError(
334+
"Multiple expectations matched $description:\n" +
335+
exactMatches.joinToString(separator = "\n") { " ${it.first.describe()}" }
336+
)
338337
}
338+
// Inexact matches are not consumable.
339+
else -> matches.first()
340+
}
339341

340-
return calculation()
342+
@Suppress("UNCHECKED_CAST")
343+
return match.result as ResultT
341344
}
342345

343346
override fun requireExplicitWorkerExpectations():
@@ -350,11 +353,6 @@ internal class RealRenderTester<PropsT, StateT, OutputT, RenderingT>(
350353
explicitSideEffectExpectationsRequired = true
351354
}
352355

353-
override fun requireExplicitRememberExpectations():
354-
RenderTester<PropsT, StateT, OutputT, RenderingT> = this.apply {
355-
explicitRememberExpectationsRequired = true
356-
}
357-
358356
override fun send(value: WorkflowAction<PropsT, StateT, OutputT>) {
359357
if (!frozen) {
360358
throw UnsupportedOperationException(

workflow-testing/src/main/java/com/squareup/workflow1/testing/RenderTester.kt

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.squareup.workflow1.config.JvmTestRuntimeConfigTools
1515
import com.squareup.workflow1.identifier
1616
import com.squareup.workflow1.testing.RenderTester.ChildWorkflowMatch
1717
import com.squareup.workflow1.testing.RenderTester.Companion
18+
import com.squareup.workflow1.testing.RenderTester.RememberMatch
1819
import com.squareup.workflow1.workflowIdentifier
1920
import kotlinx.coroutines.CoroutineScope
2021
import kotlin.reflect.KClass
@@ -284,13 +285,14 @@ public abstract class RenderTester<PropsT, StateT, OutputT, RenderingT> {
284285
* which case the first match will be used), and the expectation may match multiple side effects.
285286
*
286287
* @param matcher A function that is passed the parameters from
287-
* [RenderContext.remember][com.squareup.workflow1.BaseRenderContext.remember] and return
288-
* true if such a call expected.
288+
* [RenderContext.remember][com.squareup.workflow1.BaseRenderContext.remember] and determines if
289+
* they match what the workflow specified. If they do match, this includes the result that should
290+
* be provided to the workflow.
289291
*/
290292
public abstract fun expectRemember(
291293
description: String,
292294
exactMatch: Boolean = true,
293-
matcher: (RememberInvocation) -> Boolean
295+
matcher: (RememberInvocation) -> RememberMatch
294296
): RenderTester<PropsT, StateT, OutputT, RenderingT>
295297

296298
/**
@@ -318,9 +320,6 @@ public abstract class RenderTester<PropsT, StateT, OutputT, RenderingT> {
318320
public abstract fun requireExplicitSideEffectExpectations():
319321
RenderTester<PropsT, StateT, OutputT, RenderingT>
320322

321-
public abstract fun requireExplicitRememberExpectations():
322-
RenderTester<PropsT, StateT, OutputT, RenderingT>
323-
324323
/**
325324
* Describes a call to
326325
* [RenderContext.renderChild][com.squareup.workflow1.BaseRenderContext.renderChild].
@@ -358,6 +357,22 @@ public abstract class RenderTester<PropsT, StateT, OutputT, RenderingT> {
358357
public val inputs: List<Any?>,
359358
)
360359

360+
public sealed class RememberMatch {
361+
/**
362+
* Indicates that the remember specifications did not match what was used by the Workflow.
363+
*/
364+
public object NotMatched : RememberMatch()
365+
366+
/**
367+
* Indicates that the remember specifications were matched.
368+
*
369+
* @param result the result to return from the remember call.
370+
*/
371+
public class Matched(
372+
public val result: Any?,
373+
) : RememberMatch()
374+
}
375+
361376
public sealed class ChildWorkflowMatch {
362377
/**
363378
* Indicates that the child workflow did not match the predicate and must match a different
@@ -774,6 +789,9 @@ public fun <PropsT, StateT, OutputT, RenderingT>
774789
* @param resultType The type of the value returned by the `calculation` function passed
775790
* to [remember][com.squareup.workflow1.BaseRenderContext.remember].
776791
*
792+
* @param result The result to be provided from
793+
* [remember][com.squareup.workflow1.BaseRenderContext.remember] if the invocation is matched.
794+
*
777795
* @param inputs The `inputs` values passed to
778796
* [remember][com.squareup.workflow1.BaseRenderContext.remember], if any
779797
*
@@ -787,6 +805,7 @@ public fun <PropsT, StateT, OutputT, RenderingT>
787805
RenderTester<PropsT, StateT, OutputT, RenderingT>.expectRemember(
788806
key: String,
789807
resultType: KType,
808+
result: Any?,
790809
vararg inputs: Any?,
791810
description: String = "",
792811
assertInputs: (inputs: List<Any?>) -> Unit = {},
@@ -807,9 +826,9 @@ public fun <PropsT, StateT, OutputT, RenderingT>
807826
key == invocation.key
808827
) {
809828
assertInputs(invocation.inputs)
810-
true
829+
RememberMatch.Matched(result)
811830
} else {
812-
false
831+
RememberMatch.NotMatched
813832
}
814833
}
815834
}

workflow-testing/src/test/java/com/squareup/workflow1/testing/RealRenderTesterTest.kt

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -157,31 +157,40 @@ internal class RealRenderTesterTest {
157157
)
158158
}
159159

160-
@Test fun `remember runs and returns calculations`() {
160+
@Test fun `remember throws when not expected`() {
161161
val workflow = Workflow.stateless<Unit, Nothing, String> {
162162
val numOutput = remember("the key") { 36 }
163-
val stringInputs = remember("the key", "the", "inputs") { "string with string inputs" }
164-
val noInputs = remember("the key", 1, 2, 3) { "string with number inputs" }
165-
"$numOutput-$stringInputs-$noInputs"
163+
"$numOutput"
166164
}
167-
workflow.testRender(Unit).render {
168-
assertEquals("36-string with string inputs-string with number inputs", it)
165+
val failure = assertFailsWith<AssertionError> {
166+
workflow.testRender(Unit).render()
169167
}
168+
assertEquals(
169+
"Unexpected remember with key \"the key\"",
170+
failure.message
171+
)
170172
}
171173

172174
@Test fun `expectRemember throws when already expecting remember with same key`() {
173175
val workflow = Workflow.stateless<Unit, Nothing, String> {
174176
remember("the key", "the", "inputs") { "theOutput" }
175177
}
176178
val tester = workflow.testRender(Unit)
177-
.expectRemember("the key", typeOf<String>(), "the", "inputs")
178-
.expectRemember("the key", typeOf<String>(), "the", "inputs", description = "duplicate match")
179+
.expectRemember("the key", typeOf<String>(), result = "theOutput", "the", "inputs")
180+
.expectRemember(
181+
"the key",
182+
typeOf<String>(),
183+
result = "theOutput",
184+
"the",
185+
"inputs",
186+
description = "duplicate match"
187+
)
179188

180189
val error = assertFailsWith<AssertionError> {
181190
tester.render()
182191
}
183192
assertEquals(
184-
"Multiple expectations matched remember with key \"the key\": \n" +
193+
"Multiple expectations matched remember with key \"the key\":\n" +
185194
" remember key=the key, inputs=[the, inputs], resultType=kotlin.String\n" +
186195
" duplicate match",
187196
error.message
@@ -197,16 +206,48 @@ internal class RealRenderTesterTest {
197206
}
198207

199208
workflow.testRender(Unit)
200-
.expectRemember("the key", typeOf<Int>())
201-
.expectRemember("the key", typeOf<String>(), "the", "inputs")
202-
.expectRemember("the key", typeOf<String>(), 1, 2, 3)
209+
.expectRemember("the key", typeOf<Int>(), result = 36)
210+
.expectRemember(
211+
"the key",
212+
typeOf<String>(),
213+
result = "string with string inputs",
214+
"the",
215+
"inputs"
216+
)
217+
.expectRemember("the key", typeOf<String>(), result = "string with number inputs", 1, 2, 3)
203218
.render()
204219
}
205220

221+
@Test fun `expectRemember uses the result provided`() {
222+
val workflow = Workflow.stateless<Unit, Nothing, String> {
223+
val numOutput = remember("the key") { 42 }
224+
val stringInputs = remember("the key", "the", "inputs") { "a different string" }
225+
val noInputs = remember("the key", 1, 2, 3) { "yet another string not used." }
226+
"$numOutput-$stringInputs-$noInputs"
227+
}
228+
229+
workflow.testRender(Unit)
230+
.expectRemember("the key", typeOf<Int>(), result = 36)
231+
.expectRemember(
232+
"the key",
233+
typeOf<String>(),
234+
result = "string with string inputs",
235+
"the",
236+
"inputs"
237+
)
238+
.expectRemember("the key", typeOf<String>(), result = "string with number inputs", 1, 2, 3)
239+
.render {
240+
assertEquals(
241+
"36-string with string inputs-string with number inputs",
242+
it
243+
)
244+
}
245+
}
246+
206247
@Test fun `expectRemember doesn't match key`() {
207248
val workflow = Workflow.stateless<Unit, Nothing, Unit> {}
208249
val tester = workflow.testRender(Unit)
209-
.expectRemember("the key", typeOf<String>(), "the", "inputs")
250+
.expectRemember("the key", typeOf<String>(), result = "test", "the", "inputs")
210251

211252
val error = assertFailsWith<AssertionError> {
212253
tester.render {}
@@ -225,7 +266,6 @@ internal class RealRenderTesterTest {
225266
remember("the key", "the", "inputs") { "theOutput" }
226267
}
227268
val tester = workflow.testRender(Unit)
228-
.requireExplicitRememberExpectations()
229269

230270
val error = assertFailsWith<AssertionError> {
231271
tester.render {}
@@ -241,6 +281,7 @@ internal class RealRenderTesterTest {
241281
.expectRemember(
242282
key = "the key",
243283
resultType = typeOf<String>(),
284+
result = "theOutput",
244285
"the",
245286
"inputs",
246287
assertInputs = { inputs ->

0 commit comments

Comments
 (0)