Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions .github/workflows/kotlin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,27 @@ jobs :
with :
report_paths : '**/build/test-results/test/TEST-*.xml'

jvm-partial-runtime-test:
name: Partial Tree Rendering Only Runtime JVM Tests
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- name: Check with Gradle
uses: ./.github/actions/gradle-task
with:
task: jvmTest --continue -Pworkflow.runtime=baseline-partial
restore-cache-key: main-build-artifacts

# Report as GitHub Pull Request Check.
- name: Publish Test Report
uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4
if: always() # always run even if the previous step fails
with:
report_paths: '**/build/test-results/test/TEST-*.xml'

jvm-conflate-stateChange-runtime-test :
name : Render On State Change Only and Conflate Stale Runtime JVM Tests
runs-on : ubuntu-latest
Expand All @@ -243,6 +264,27 @@ jobs :
with :
report_paths : '**/build/test-results/test/TEST-*.xml'

jvm-conflate-partial-runtime-test:
name: Render On State Change Only and Conflate Stale Runtime JVM Tests
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- name: Check with Gradle
uses: ./.github/actions/gradle-task
with:
task: jvmTest --continue -Pworkflow.runtime=conflate-partial
restore-cache-key: main-build-artifacts

# Report as GitHub Pull Request Check.
- name: Publish Test Report
uses: mikepenz/action-junit-report@5f47764eec0e1c1f19f40c8e60a5ba47e47015c5 # v4
if: always() # always run even if the previous step fails
with:
report_paths: '**/build/test-results/test/TEST-*.xml'

ios-tests :
name : iOS Tests
runs-on : macos-latest
Expand Down Expand Up @@ -349,7 +391,7 @@ jobs :
### <start-connected-check-shards>
shardNum: [ 1, 2, 3 ]
### <end-connected-check-shards>
runtime : [ conflate, baseline-stateChange, conflate-stateChange ]
runtime : [ conflate, baseline-stateChange, conflate-stateChange, baseline-partial, conflate-partial ]
steps :
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Expand Down Expand Up @@ -378,8 +420,10 @@ jobs :
- ios-tests
- js-tests
- jvm-conflate-runtime-test
- jvm-conflate-stateChange-runtime-test
- jvm-stateChange-runtime-test
- jvm-partial-runtime-test
- jvm-conflate-stateChange-runtime-test
- jvm-conflate-partial-runtime-test
- ktlint
- performance-tests
- runtime-instrumentation-tests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.squareup.workflow1.config
import com.squareup.workflow1.RuntimeConfig
import com.squareup.workflow1.RuntimeConfigOptions
import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS
import com.squareup.workflow1.RuntimeConfigOptions.PARTIAL_TREE_RENDERING
import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES
import com.squareup.workflow1.WorkflowExperimentalRuntime

Expand All @@ -27,6 +28,9 @@ public class AndroidRuntimeConfigTools {
* Then, these can be combined (via '-') with:
* "stateChange" : Only re-render when the state of some WorkflowNode has been changed by an
* action cascade.
* "partial" : Which includes "stateChange" as well as partial tree rendering, which only
* re-renders each Workflow node if: 1) its state changed; or 2) one of its descendant's state
* changed.
*
* E.g., "baseline-stateChange" to turn on the stateChange option with the baseline runtime.
*
Expand All @@ -37,6 +41,12 @@ public class AndroidRuntimeConfigTools {
"conflate" -> setOf(CONFLATE_STALE_RENDERINGS)
"conflate-stateChange" -> setOf(CONFLATE_STALE_RENDERINGS, RENDER_ONLY_WHEN_STATE_CHANGES)
"baseline-stateChange" -> setOf(RENDER_ONLY_WHEN_STATE_CHANGES)
"conflate-partial" -> setOf(
CONFLATE_STALE_RENDERINGS,
RENDER_ONLY_WHEN_STATE_CHANGES,
PARTIAL_TREE_RENDERING
)
"baseline-partial" -> setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING)
"", "baseline" -> RuntimeConfigOptions.RENDER_PER_ACTION
else ->
throw IllegalArgumentException("Unrecognized config \"${BuildConfig.WORKFLOW_RUNTIME}\"")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.squareup.workflow1.config
import com.squareup.workflow1.RuntimeConfig
import com.squareup.workflow1.RuntimeConfigOptions
import com.squareup.workflow1.RuntimeConfigOptions.CONFLATE_STALE_RENDERINGS
import com.squareup.workflow1.RuntimeConfigOptions.PARTIAL_TREE_RENDERING
import com.squareup.workflow1.RuntimeConfigOptions.RENDER_ONLY_WHEN_STATE_CHANGES
import com.squareup.workflow1.WorkflowExperimentalRuntime

Expand All @@ -27,6 +28,9 @@ public class JvmTestRuntimeConfigTools {
* Then, these can be combined (via '-') with:
* "stateChange" : Only re-render when the state of some WorkflowNode has been changed by an
* action cascade.
* "partial" : Which includes "stateChange" as well as partial tree rendering, which only
* re-renders each Workflow node if: 1) its state changed; or 2) one of its descendant's state
* changed.
*
* E.g., "baseline-stateChange" to turn on the stateChange option with the baseline runtime.
*
Expand All @@ -38,6 +42,12 @@ public class JvmTestRuntimeConfigTools {
"conflate" -> setOf(CONFLATE_STALE_RENDERINGS)
"conflate-stateChange" -> setOf(CONFLATE_STALE_RENDERINGS, RENDER_ONLY_WHEN_STATE_CHANGES)
"baseline-stateChange" -> setOf(RENDER_ONLY_WHEN_STATE_CHANGES)
"conflate-partial" -> setOf(
CONFLATE_STALE_RENDERINGS,
RENDER_ONLY_WHEN_STATE_CHANGES,
PARTIAL_TREE_RENDERING
)
"baseline-partial" -> setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING)
"", "baseline" -> RuntimeConfigOptions.RENDER_PER_ACTION
else ->
throw IllegalArgumentException("Unrecognized config \"$runtimeConfig\"")
Expand Down
20 changes: 20 additions & 0 deletions workflow-core/api/workflow-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,26 @@ public abstract class com/squareup/workflow1/LifecycleWorker : com/squareup/work
public final fun run ()Lkotlinx/coroutines/flow/Flow;
}

public final class com/squareup/workflow1/NullableInitBox {
public static final synthetic fun box-impl (Ljava/lang/Object;)Lcom/squareup/workflow1/NullableInitBox;
public static fun constructor-impl (Ljava/lang/Object;)Ljava/lang/Object;
public static synthetic fun constructor-impl$default (Ljava/lang/Object;ILkotlin/jvm/internal/DefaultConstructorMarker;)Ljava/lang/Object;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Ljava/lang/Object;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Ljava/lang/Object;Ljava/lang/Object;)Z
public static final fun getOrThrow-impl (Ljava/lang/Object;)Ljava/lang/Object;
public fun hashCode ()I
public static fun hashCode-impl (Ljava/lang/Object;)I
public static final fun isInitialized-impl (Ljava/lang/Object;)Z
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Ljava/lang/Object;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Ljava/lang/Object;
}

public final class com/squareup/workflow1/NullableInitBox$Uninitialized {
public static final field INSTANCE Lcom/squareup/workflow1/NullableInitBox$Uninitialized;
}

public final class com/squareup/workflow1/PropsUpdated : com/squareup/workflow1/ActionProcessingResult {
public static final field INSTANCE Lcom/squareup/workflow1/PropsUpdated;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.squareup.workflow1

import kotlin.jvm.JvmInline

/**
* Used to wrap immutable nullable values whose holder may not yet be initialized.
* Check [isInitialized] to see if the value has been assigned.
*/
@JvmInline
public value class NullableInitBox<T>(private val _value: Any? = Uninitialized) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't take in at first that this is a value class. Cool!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a @zach-klippenstein special when we were noodling on this.

/**
* Whether or not a value has been set for this [NullableInitBox]
*/
public val isInitialized: Boolean get() = _value !== Uninitialized

/**
* Get the value this has been initialized with.
*
* @throws [IllegalStateException] if the value in the box has not been initialized.
*/
@Suppress("UNCHECKED_CAST")
public fun getOrThrow(): T {
check(isInitialized) { "NullableInitBox was fetched before it was initialized with a value." }
return _value as T
}

public object Uninitialized
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.squareup.workflow1

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class NullableInitBoxTest {

@Test fun reports_not_initialized() {
val box = NullableInitBox<String>()

assertFalse(box.isInitialized)
}

@Test fun reports_initialized() {
val box = NullableInitBox<String>("Hello")

assertTrue(box.isInitialized)
}

@Test fun returns_value() {
val box = NullableInitBox<String>("Hello")

assertEquals("Hello", box.getOrThrow())
}

@Test fun throws_exceptions() {
val box = NullableInitBox<String>()

val exception = assertFailsWith<IllegalStateException> {
box.getOrThrow()
}

assertEquals(
"NullableInitBox was fetched before it was initialized with a value.",
exception.message
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import kotlin.test.assertNotSame
import kotlin.test.assertTrue

@ExperimentalCoroutinesApi
@OptIn(ExperimentalStdlibApi::class)
class WorkerTest {

@Test fun timer_returns_equivalent_workers_keyed() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
import kotlin.test.assertNull

@OptIn(ExperimentalStdlibApi::class)
internal class WorkflowIdentifierTest {

@Test fun restored_identifier_toString() {
Expand Down
1 change: 1 addition & 0 deletions workflow-runtime/api/workflow-runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public final class com/squareup/workflow1/RenderingAndSnapshot {
public final class com/squareup/workflow1/RuntimeConfigOptions : java/lang/Enum {
public static final field CONFLATE_STALE_RENDERINGS Lcom/squareup/workflow1/RuntimeConfigOptions;
public static final field Companion Lcom/squareup/workflow1/RuntimeConfigOptions$Companion;
public static final field PARTIAL_TREE_RENDERING Lcom/squareup/workflow1/RuntimeConfigOptions;
public static final field RENDER_ONLY_WHEN_STATE_CHANGES Lcom/squareup/workflow1/RuntimeConfigOptions;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lcom/squareup/workflow1/RuntimeConfigOptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ public enum class RuntimeConfigOptions {
@WorkflowExperimentalRuntime
RENDER_ONLY_WHEN_STATE_CHANGES,

/**
* Only re-render each active Workflow node if:
* 1. Its own state changed, OR
* 2. One of its descendant's state has changed.
*
* Otherwise return the cached rendering (as there is no way it could have changed).
*
* Note however that you must be careful using this because there may be external
* state that your Workflow draws in and re-renders, and if that is not explicitly
* tracked within that Workflow's state then the Workflow will not re-render.
* In this case make sure that the implicit state is tracked within the Workflow's
* `StateT` in some way, even if only via a hash token.
*/
@WorkflowExperimentalRuntime
PARTIAL_TREE_RENDERING,

/**
* If we have more actions to process, do so before passing the rendering to the UI layer.
*/
Expand Down
Loading
Loading