From dab6a5c59d683b18a8247d72af04444db30d7775 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Mon, 7 Sep 2026 14:21:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E9=80=89=E6=8B=A9=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E6=94=AF=E6=8C=81=E7=82=B9=E5=87=BB=E5=A4=96=E9=83=A8?= =?UTF-8?q?=E4=B8=8E=20Esc=20=E8=87=AA=E5=8A=A8=E6=94=B6=E8=B5=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 ConversationModelSelect 增加点击外部关闭和 Esc 关闭,首页与项目右侧对话共用生效 补充点击外部与 Esc 收起的回归测试 --- .../ConversationModelSelect.tsx | 23 ++++++++++++++++ .../tests/conversationModelSelect.test.tsx | 26 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx index b4acba71b..6869ede23 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx @@ -25,6 +25,7 @@ export function ConversationModelSelect({ const [error, setError] = useState(''); const [open, setOpen] = useState(false); const initializedRef = useRef(false); + const containerRef = useRef(null); const refresh = useCallback(async () => { setBusy(true); setError(''); @@ -63,6 +64,27 @@ export function ConversationModelSelect({ void refresh(); }, [refresh, lazy]); + useEffect(() => { + if (!open) return; + function handleOutsidePointerDown(event: MouseEvent) { + const target = event.target as Node | null; + if (containerRef.current && !containerRef.current.contains(target)) { + setOpen(false); + } + } + function handleEscape(event: KeyboardEvent) { + if (event.key === 'Escape') { + setOpen(false); + } + } + document.addEventListener('mousedown', handleOutsidePointerDown); + document.addEventListener('keydown', handleEscape); + return () => { + document.removeEventListener('mousedown', handleOutsidePointerDown); + document.removeEventListener('keydown', handleEscape); + }; + }, [open]); + async function select(id: string) { onReady?.(false); setBusy(true); @@ -87,6 +109,7 @@ export function ConversationModelSelect({ return (
{ await screen.findByText('模型选择保存失败'); expect(onReady).toHaveBeenLastCalledWith(false); }); + +test('closes the menu when clicking outside', async () => { + const onReady = vi.fn(); + render(); + await screen.findByRole('button', { name: '对话模型' }); + fireEvent.click(screen.getByRole('button', { name: '对话模型' })); + expect(screen.getByRole('option', { name: '快速' })).not.toBeNull(); + + fireEvent.mouseDown(document.body); + await waitFor(() => + expect(screen.queryByRole('option', { name: '快速' })).toBeNull(), + ); +}); + +test('closes the menu on Escape', async () => { + const onReady = vi.fn(); + render(); + await screen.findByRole('button', { name: '对话模型' }); + fireEvent.click(screen.getByRole('button', { name: '对话模型' })); + expect(screen.getByRole('option', { name: '快速' })).not.toBeNull(); + + fireEvent.keyDown(document, { key: 'Escape' }); + await waitFor(() => + expect(screen.queryByRole('option', { name: '快速' })).toBeNull(), + ); +});