diff --git a/packages/web-shell/client/components/panels/EnvironmentPanel.test.tsx b/packages/web-shell/client/components/panels/EnvironmentPanel.test.tsx index 2d74dd95ddd..f4368a75d1c 100644 --- a/packages/web-shell/client/components/panels/EnvironmentPanel.test.tsx +++ b/packages/web-shell/client/components/panels/EnvironmentPanel.test.tsx @@ -3,7 +3,7 @@ import type { DaemonSessionTaskStatus } from '@qwen-code/sdk/daemon'; import { act, type ReactNode } from 'react'; import { createRoot, type Root } from 'react-dom/client'; -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { I18nProvider } from '../../i18n'; import { EnvironmentPanel } from './EnvironmentPanel'; import type { SourcesState } from './SourcesSection'; @@ -44,11 +44,17 @@ Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); let container: HTMLDivElement | null = null; let root: Root | null = null; +beforeEach(() => { + // The add-source button is hidden by default; enable it for these tests. + window.history.replaceState({}, '', '/?addSource=1'); +}); + afterEach(() => { act(() => root?.unmount()); container?.remove(); container = null; root = null; + window.history.replaceState({}, '', '/'); }); function mount( diff --git a/packages/web-shell/client/components/panels/SourcesSection.test.tsx b/packages/web-shell/client/components/panels/SourcesSection.test.tsx new file mode 100644 index 00000000000..c7555b47129 --- /dev/null +++ b/packages/web-shell/client/components/panels/SourcesSection.test.tsx @@ -0,0 +1,62 @@ +// @vitest-environment jsdom + +import { act } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { I18nProvider } from '../../i18n'; +import { SourcesSection, type SourcesState } from './SourcesSection'; + +Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); + +let container: HTMLDivElement | null = null; +let root: Root | null = null; + +afterEach(() => { + act(() => root?.unmount()); + container?.remove(); + container = null; + root = null; + window.history.replaceState({}, '', '/'); +}); + +function supportedState(): SourcesState { + return { + supported: true, + sources: [], + owner: { isCurrent: () => true }, + revision: 1, + hydrated: true, + loading: false, + error: null, + refresh: vi.fn(), + upsert: vi.fn(), + remove: vi.fn(), + }; +} + +function mount(): HTMLDivElement { + container = document.createElement('div'); + document.body.appendChild(container); + root = createRoot(container); + act(() => { + root!.render( + + + , + ); + }); + return container; +} + +describe('SourcesSection add-source button', () => { + it('is hidden by default', () => { + const el = mount(); + expect(el.querySelector('button[aria-label="Add source"]')).toBeNull(); + }); + + it('appears when the addSource URL parameter is set', () => { + window.history.replaceState({}, '', '/?addSource=1'); + const el = mount(); + expect(el.querySelector('button[aria-label="Add source"]')).not.toBeNull(); + }); +}); diff --git a/packages/web-shell/client/components/panels/SourcesSection.tsx b/packages/web-shell/client/components/panels/SourcesSection.tsx index 173d25cedea..81393fac7c9 100644 --- a/packages/web-shell/client/components/panels/SourcesSection.tsx +++ b/packages/web-shell/client/components/panels/SourcesSection.tsx @@ -18,6 +18,13 @@ import styles from './EnvironmentPanel.module.css'; export type SourcesState = ReturnType; +// Escape hatch: the add-source affordance is hidden by default; append +// ?addSource=1 to the URL to bring it back. +function isAddSourceEnabled(): boolean { + if (typeof window === 'undefined') return false; + return new URLSearchParams(window.location.search).get('addSource') === '1'; +} + interface SourcesSectionProps { hidden?: boolean; state?: SourcesState; @@ -97,7 +104,7 @@ export function SourcesSection({ {t('sources.title')}{' '} {entries.length} - {state?.supported && ( + {state?.supported && isAddSourceEnabled() && (