Quellcode durchsuchen

fix(ui): resolve toast icon inside the toast component (#34874)

Luke Parker vor 1 Monat
Ursprung
Commit
f52424e05f

+ 26 - 0
packages/app/test-browser/toast-owner.test.ts

@@ -0,0 +1,26 @@
+import { describe, expect, test } from "bun:test"
+import { createSignal, type JSX } from "solid-js"
+import { showToastV2, toasterV2 } from "@opencode-ai/ui/v2/toast-v2"
+
+describe("showToastV2", () => {
+  test("creates no reactive computations at call time", () => {
+    const [tick, setTick] = createSignal(0)
+    let reads = 0
+    const icon = (() => {
+      reads++
+      tick()
+      return undefined
+    }) as unknown as JSX.Element
+
+    const id = showToastV2({ description: "test", icon })
+
+    // Resolving the icon at call time creates an ownerless computation that is
+    // never disposed and tracks its dependencies forever; it must only resolve
+    // once the toast component renders.
+    expect(reads).toBe(0)
+    setTick(1)
+    expect(reads).toBe(0)
+
+    toasterV2.dismiss(id)
+  })
+})

+ 39 - 37
packages/ui/src/v2/components/toast-v2.tsx

@@ -97,44 +97,46 @@ export interface ToastV2Options {
 
 export function showToastV2(options: ToastV2Options | string) {
   const opts = typeof options === "string" ? { description: options } : options
-  const resolvedIcon = children(() => opts.icon)
-  return toaster.show((props) => (
-    <ToastV2 toastId={props.toastId} duration={opts.duration} persistent={opts.persistent}>
-      <div data-slot="toast-v2-header">
-        <Show when={resolvedIcon()}>
-          <ToastV2.Icon>{resolvedIcon()}</ToastV2.Icon>
-        </Show>
-        <ToastV2.Content>
-          <Show when={opts.title}>
-            <ToastV2.Title>{opts.title}</ToastV2.Title>
-          </Show>
-          <Show when={opts.description}>
-            <ToastV2.Description>{opts.description}</ToastV2.Description>
+  return toaster.show((props) => {
+    const resolvedIcon = children(() => opts.icon)
+    return (
+      <ToastV2 toastId={props.toastId} duration={opts.duration} persistent={opts.persistent}>
+        <div data-slot="toast-v2-header">
+          <Show when={resolvedIcon()}>
+            <ToastV2.Icon>{resolvedIcon()}</ToastV2.Icon>
           </Show>
-        </ToastV2.Content>
-        <ToastV2.CloseButton />
-      </div>
-      <Show when={opts.actions?.length}>
-        <ToastV2.Actions>
-          {opts.actions!.map((action) => (
-            <ButtonV2
-              variant={action.variant === "secondary" ? "ghost" : "neutral"}
-              size="small"
-              data-action-variant={action.variant ?? "primary"}
-              onClick={() => {
-                if (typeof action.onClick === "function") {
-                  action.onClick()
-                }
-                toaster.dismiss(props.toastId)
-              }}
-            >
-              {action.label}
-            </ButtonV2>
-          ))}
-        </ToastV2.Actions>
-      </Show>
-    </ToastV2>
-  ))
+          <ToastV2.Content>
+            <Show when={opts.title}>
+              <ToastV2.Title>{opts.title}</ToastV2.Title>
+            </Show>
+            <Show when={opts.description}>
+              <ToastV2.Description>{opts.description}</ToastV2.Description>
+            </Show>
+          </ToastV2.Content>
+          <ToastV2.CloseButton />
+        </div>
+        <Show when={opts.actions?.length}>
+          <ToastV2.Actions>
+            {opts.actions!.map((action) => (
+              <ButtonV2
+                variant={action.variant === "secondary" ? "ghost" : "neutral"}
+                size="small"
+                data-action-variant={action.variant ?? "primary"}
+                onClick={() => {
+                  if (typeof action.onClick === "function") {
+                    action.onClick()
+                  }
+                  toaster.dismiss(props.toastId)
+                }}
+              >
+                {action.label}
+              </ButtonV2>
+            ))}
+          </ToastV2.Actions>
+        </Show>
+      </ToastV2>
+    )
+  })
 }
 
 export interface ToastV2PromiseOptions<T, U = unknown> {