Skip to content

Commit

Permalink
Inherit the exception handler in ViewContentCodeBinding's scope (#1686)
Browse files Browse the repository at this point in the history
This ensures exceptions get reported to the current code session.
  • Loading branch information
squarejesse authored Nov 16, 2023
1 parent f325b46 commit f40b384
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ private class ViewContentCodeBinding<A : AppService>(
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]. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,6 +35,9 @@ internal class FakeProtocolNode : ProtocolNode<FakeWidget>() {

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<FakeWidget>? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ class FakeWidget : Widget<FakeWidget> {
override var modifier: Modifier = Modifier

var label: String? = null
var onClick: (() -> Unit)? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<CancellableService>()
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()")
}
Expand Down Expand Up @@ -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<FakeAppService> {
return TreehouseAppContent(
codeHost = codeHost,
Expand Down

0 comments on commit f40b384

Please sign in to comment.