From 82a43a72404a04deb6bfdd15d3346e6717a20b2b Mon Sep 17 00:00:00 2001
From: Yif <877193178@qq.com>
Date: Wed, 29 Jul 2026 19:59:42 +0800
Subject: [PATCH] test(client): cover menu dismiss, goal pause, and permission
label paths
Close the per-file coverage gaps the new UI behavior introduced: MenuView
pointer-outside dismiss (all guard branches), the GoalBar pause action,
the ui-slash injected onDismiss face, and the non-kebab permission name
passthrough.
---
.../client/ui-goal/tests/goalbar.spec.tsx | 7 ++++
.../tests/browser-plugin.spec.ts | 5 +++
packages/client/ui-slash/tests/apply.spec.ts | 3 ++
.../client/ui-slash/tests/menu-view.spec.tsx | 42 +++++++++++++++++++
4 files changed, 57 insertions(+)
diff --git a/packages/client/ui-goal/tests/goalbar.spec.tsx b/packages/client/ui-goal/tests/goalbar.spec.tsx
index ce622bccd4..38a943d457 100644
--- a/packages/client/ui-goal/tests/goalbar.spec.tsx
+++ b/packages/client/ui-goal/tests/goalbar.spec.tsx
@@ -104,6 +104,13 @@ describe('GoalBar', () => {
expect(screen.getByRole('textbox', { name: 'Goal objective' })).toBeTruthy()
})
+ it('active goal: the pause action pauses', () => {
+ const actions = makeActions()
+ render()
+ fireEvent.click(screen.getByRole('button', { name: 'Pause goal' }))
+ expect(actions.onPause).toHaveBeenCalledTimes(1)
+ })
+
it('paused goal: "Paused Goal" with a resume action before edit', () => {
const actions = makeActions()
render()
diff --git a/packages/client/ui-permission/tests/browser-plugin.spec.ts b/packages/client/ui-permission/tests/browser-plugin.spec.ts
index 167cf17362..5f9125db53 100644
--- a/packages/client/ui-permission/tests/browser-plugin.spec.ts
+++ b/packages/client/ui-permission/tests/browser-plugin.spec.ts
@@ -85,6 +85,11 @@ describe('ui-permission browser plugin', () => {
const again = await c.ui.options(proj, new AbortController().signal)
expect(again.find(option => option.id === 'workspace-write')?.active).toBe(true)
expect(again.find(option => option.id === 'read-only')?.detail).toBe('Reads only.')
+ // Kebab-case names title-case; non-kebab host-configured names pass through.
+ expect(again.map(option => option.label)).toEqual(['Read Only', 'Workspace Write', 'Danger Full Access'])
+ b.values.set(sid('s1'), { ...SELECT, options: [{ value: 'plain', name: 'Ask Every Time' }] })
+ const passthrough = await c.ui.options(proj, new AbortController().signal)
+ expect(passthrough[0]?.label).toBe('Ask Every Time')
// A projection that vanished between availability and open throws.
expect(() => c.ui.options({ sessionId: sid('ghost') }, new AbortController().signal))
.toThrow(/not available on this host/)
diff --git a/packages/client/ui-slash/tests/apply.spec.ts b/packages/client/ui-slash/tests/apply.spec.ts
index 5a8b2b6c37..2e20dc96f8 100644
--- a/packages/client/ui-slash/tests/apply.spec.ts
+++ b/packages/client/ui-slash/tests/apply.spec.ts
@@ -84,6 +84,9 @@ describe('apply', () => {
// The pick face routes into the controller pipeline (closed menu → no-op).
injected.onPick('command', 0)
expect(controller.menu.getSnapshot().open).toBe(false)
+ // The dismiss face routes into the controller too (closed menu → no-op).
+ injected.onDismiss()
+ expect(controller.menu.getSnapshot().open).toBe(false)
// An unknown session id fails loud (no silent scope miss).
expect(() => injectEntry(sid('ghost'))).toThrow(/resolved no scope/)
})
diff --git a/packages/client/ui-slash/tests/menu-view.spec.tsx b/packages/client/ui-slash/tests/menu-view.spec.tsx
index b18bbd6b30..1c74340575 100644
--- a/packages/client/ui-slash/tests/menu-view.spec.tsx
+++ b/packages/client/ui-slash/tests/menu-view.spec.tsx
@@ -142,6 +142,48 @@ describe('MenuView', () => {
expect(screen.getByRole('listbox').style.maxHeight).toBe('88px')
})
+ it('pointerdown outside the menu (no composer card ancestor) dismisses', () => {
+ const { onDismiss } = mount(openState())
+ fireEvent.pointerDown(document.body)
+ expect(onDismiss).toHaveBeenCalledTimes(1)
+ })
+
+ it('pointerdown inside the list does not dismiss', () => {
+ const { onDismiss } = mount(openState())
+ fireEvent.pointerDown(screen.getAllByRole('option')[0]!)
+ expect(onDismiss).not.toHaveBeenCalled()
+ })
+
+ it('pointerdown inside the surrounding composer card does not dismiss; outside it does', () => {
+ const menu = createSnapshotStore(openState())
+ const onDismiss = vi.fn()
+ render(
+
+
+
+
,
+ )
+ fireEvent.pointerDown(screen.getByTestId('composer-button'))
+ expect(onDismiss).not.toHaveBeenCalled()
+ fireEvent.pointerDown(document.body)
+ expect(onDismiss).toHaveBeenCalledTimes(1)
+ })
+
+ it('ignores a pointerdown whose target is not a DOM node', () => {
+ const { onDismiss } = mount(openState())
+ const ev = new Event('pointerdown', { bubbles: true })
+ Object.defineProperty(ev, 'target', { value: {} })
+ document.dispatchEvent(ev)
+ expect(onDismiss).not.toHaveBeenCalled()
+ })
+
+ it('closing the menu removes the dismiss listener', () => {
+ const { menu, onDismiss } = mount(openState())
+ act(() => { menu.set(CLOSED) })
+ fireEvent.pointerDown(document.body)
+ expect(onDismiss).not.toHaveBeenCalled()
+ })
+
it('mousedown on a row picks (source, index) and prevents the focus steal', () => {
const { onPick } = mount(openState())
const options = screen.getAllByRole('option')