From 495d8514c1c11e2be55dce4483dcc64e39f1b5c3 Mon Sep 17 00:00:00 2001 From: 777genius Date: Thu, 16 Apr 2026 22:23:46 +0300 Subject: [PATCH] fix(extensions): clear detail selections on sub-tab switch --- src/renderer/hooks/useExtensionsTabState.ts | 12 ++ .../hooks/useExtensionsTabState.test.ts | 122 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 test/renderer/hooks/useExtensionsTabState.test.ts diff --git a/src/renderer/hooks/useExtensionsTabState.ts b/src/renderer/hooks/useExtensionsTabState.ts index cffc4e9a..ce71077d 100644 --- a/src/renderer/hooks/useExtensionsTabState.ts +++ b/src/renderer/hooks/useExtensionsTabState.ts @@ -68,6 +68,18 @@ export function useExtensionsTabState() { }; }, []); + useEffect(() => { + if (activeSubTab !== 'plugins' && selectedPluginId !== null) { + setSelectedPluginId(null); + } + if (activeSubTab !== 'mcp-servers' && selectedMcpServerId !== null) { + setSelectedMcpServerId(null); + } + if (activeSubTab !== 'skills' && selectedSkillId !== null) { + setSelectedSkillId(null); + } + }, [activeSubTab, selectedMcpServerId, selectedPluginId, selectedSkillId]); + const mcpSearch = useCallback((query: string) => { setMcpSearchQuery(query); diff --git a/test/renderer/hooks/useExtensionsTabState.test.ts b/test/renderer/hooks/useExtensionsTabState.test.ts new file mode 100644 index 00000000..52ae6fbb --- /dev/null +++ b/test/renderer/hooks/useExtensionsTabState.test.ts @@ -0,0 +1,122 @@ +import React, { act } from 'react'; +import { createRoot } from 'react-dom/client'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { useExtensionsTabState } from '../../../src/renderer/hooks/useExtensionsTabState'; + +type ExtensionsTabState = ReturnType; + +let capturedState: ExtensionsTabState | null = null; + +vi.mock('@renderer/api', () => ({ + api: { + mcpRegistry: null, + }, +})); + +function Harness(): null { + capturedState = useExtensionsTabState(); + return null; +} + +describe('useExtensionsTabState', () => { + afterEach(() => { + capturedState = null; + document.body.innerHTML = ''; + }); + + it('clears selected plugin when leaving the plugins sub-tab', async () => { + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); + const host = document.createElement('div'); + document.body.appendChild(host); + const root = createRoot(host); + + await act(async () => { + root.render(React.createElement(Harness)); + await Promise.resolve(); + }); + + await act(async () => { + capturedState?.setSelectedPluginId('context7@claude-plugins-official'); + await Promise.resolve(); + }); + expect(capturedState?.selectedPluginId).toBe('context7@claude-plugins-official'); + + await act(async () => { + capturedState?.setActiveSubTab('mcp-servers'); + await Promise.resolve(); + }); + expect(capturedState?.selectedPluginId).toBeNull(); + + await act(async () => { + root.unmount(); + await Promise.resolve(); + }); + }); + + it('clears selected MCP server when leaving the MCP sub-tab', async () => { + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); + const host = document.createElement('div'); + document.body.appendChild(host); + const root = createRoot(host); + + await act(async () => { + root.render(React.createElement(Harness)); + await Promise.resolve(); + }); + + await act(async () => { + capturedState?.setActiveSubTab('mcp-servers'); + await Promise.resolve(); + }); + await act(async () => { + capturedState?.setSelectedMcpServerId('server-1'); + await Promise.resolve(); + }); + expect(capturedState?.selectedMcpServerId).toBe('server-1'); + + await act(async () => { + capturedState?.setActiveSubTab('skills'); + await Promise.resolve(); + }); + expect(capturedState?.selectedMcpServerId).toBeNull(); + + await act(async () => { + root.unmount(); + await Promise.resolve(); + }); + }); + + it('clears selected skill when leaving the skills sub-tab', async () => { + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); + const host = document.createElement('div'); + document.body.appendChild(host); + const root = createRoot(host); + + await act(async () => { + root.render(React.createElement(Harness)); + await Promise.resolve(); + }); + + await act(async () => { + capturedState?.setActiveSubTab('skills'); + await Promise.resolve(); + }); + await act(async () => { + capturedState?.setSelectedSkillId('skill-1'); + await Promise.resolve(); + }); + expect(capturedState?.selectedSkillId).toBe('skill-1'); + + await act(async () => { + capturedState?.setActiveSubTab('api-keys'); + await Promise.resolve(); + }); + expect(capturedState?.selectedSkillId).toBeNull(); + + await act(async () => { + root.unmount(); + await Promise.resolve(); + }); + }); +});