From f40b384ec5bc366f2977122fac31fd3558e71b8a Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Thu, 16 Nov 2023 10:29:44 -0800 Subject: [PATCH] Inherit the exception handler in ViewContentCodeBinding's scope (#1686) This ensures exceptions get reported to the current code session. --- .../redwood/treehouse/TreehouseAppContent.kt | 2 +- .../redwood/treehouse/FakeProtocolNode.kt | 6 ++++ .../app/cash/redwood/treehouse/FakeWidget.kt | 1 + .../treehouse/FakeZiplineTreehouseUi.kt | 13 ++++++++- .../treehouse/TreehouseAppContentTest.kt | 28 +++++++++++++++++++ 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseAppContent.kt b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseAppContent.kt index fdbc3431a6..04ad7a12f1 100644 --- a/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseAppContent.kt +++ b/redwood-treehouse-host/src/commonMain/kotlin/app/cash/redwood/treehouse/TreehouseAppContent.kt @@ -320,7 +320,7 @@ private class ViewContentCodeBinding( private val uiConfigurationFlow = SequentialStateFlow(firstUiConfiguration) private val bindingScope = CoroutineScope( - SupervisorJob(codeSession.scope.coroutineContext.job), + codeSession.scope.coroutineContext + SupervisorJob(codeSession.scope.coroutineContext.job), ) /** Only accessed on [TreehouseDispatchers.ui]. Null before [initView] and after [cancel]. */ diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNode.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNode.kt index c11d1bde6b..c7bb6a9e2a 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNode.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNode.kt @@ -17,7 +17,10 @@ package app.cash.redwood.treehouse import app.cash.redwood.RedwoodCodegenApi import app.cash.redwood.protocol.ChildrenTag +import app.cash.redwood.protocol.Event import app.cash.redwood.protocol.EventSink +import app.cash.redwood.protocol.EventTag +import app.cash.redwood.protocol.Id import app.cash.redwood.protocol.PropertyChange import app.cash.redwood.protocol.widget.ProtocolChildren import app.cash.redwood.protocol.widget.ProtocolNode @@ -32,6 +35,9 @@ internal class FakeProtocolNode : ProtocolNode() { override fun apply(change: PropertyChange, eventSink: EventSink) { widget.label = (change.value as JsonPrimitive).content + widget.onClick = { + eventSink.sendEvent(Event(Id(1), EventTag(1))) + } } override fun children(tag: ChildrenTag): ProtocolChildren? { diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeWidget.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeWidget.kt index 6defb8ca4e..b121031d5f 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeWidget.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeWidget.kt @@ -25,4 +25,5 @@ class FakeWidget : Widget { override var modifier: Modifier = Modifier var label: String? = null + var onClick: (() -> Unit)? = null } diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeZiplineTreehouseUi.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeZiplineTreehouseUi.kt index d30a1dd968..e9593cad7e 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeZiplineTreehouseUi.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeZiplineTreehouseUi.kt @@ -37,6 +37,7 @@ class FakeZiplineTreehouseUi( private val eventLog: EventLog, ) : ZiplineTreehouseUi { private var nextWidgetId = 1 + private var messageToThrowOnNextEvent: String? = null private lateinit var host: ZiplineTreehouseUi.Host private val extraServicesToClose = mutableListOf() @@ -57,8 +58,18 @@ class FakeZiplineTreehouseUi( ) } + fun throwOnNextEvent(message: String) { + messageToThrowOnNextEvent = message + } + override fun sendEvent(event: Event) { - eventLog += "$name.sendEvent($event)" + eventLog += "$name.sendEvent()" + + val toThrow = messageToThrowOnNextEvent + if (toThrow != null) { + messageToThrowOnNextEvent = null + throw Exception(toThrow) + } } fun addBackHandler(isEnabled: Boolean): CancellableService { diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/TreehouseAppContentTest.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/TreehouseAppContentTest.kt index 833457f780..c17fa3cfc2 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/TreehouseAppContentTest.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/TreehouseAppContentTest.kt @@ -84,6 +84,9 @@ class TreehouseAppContentTest { eventLog.takeEvent("codeListener.onCodeLoaded(view1, initial = true)") assertThat(view1.children.single().value.label).isEqualTo("hello") + view1.children.single().value.onClick!!.invoke() + eventLog.takeEvent("codeSessionA.app.uis[0].sendEvent()") + content.unbind() eventLog.takeEvent("codeSessionA.app.uis[0].close()") } @@ -418,6 +421,31 @@ class TreehouseAppContentTest { content.unbind() } + @Test + fun bind_session_addWidget_eventException_unbind() = runTest { + val content = treehouseAppContent() + + val view1 = treehouseView("view1") + content.bind(view1) + eventLog.takeEvent("codeListener.onInitialCodeLoading(view1)") + + val codeSessionA = codeHost.startCodeSession("codeSessionA") + eventLog.takeEvent("codeSessionA.start()") + eventLog.takeEvent("codeSessionA.app.uis[0].start()") + + codeSessionA.appService.uis.single().addWidget("hello") + eventLog.takeEvent("codeListener.onCodeLoaded(view1, initial = true)") + + codeSessionA.appService.uis.single().throwOnNextEvent("boom!") + view1.children.single().value.onClick!!.invoke() + eventLog.takeEvent("codeSessionA.app.uis[0].sendEvent()") + eventLog.takeEvent("codeListener.onUncaughtException(view1, kotlin.Exception: boom!)") + eventLog.takeEvent("codeSessionA.app.uis[0].close()") + eventLog.takeEvent("codeSessionA.stop()") + + content.unbind() + } + private fun treehouseAppContent(): TreehouseAppContent { return TreehouseAppContent( codeHost = codeHost,