Files
deepseek-harness/packages/client/ui-plugin-manager/tests/components.client.spec.tsx
T
ZiyaZhang 30dafd2106 Merge feat/creation-recommendations into plugin recommendation experiment
Takes the card packages' new names and master up to e115cc98d65. The plugin
recommendation packages and their bundle rows now name
@deepseek-ai/dsh-action-cards and @deepseek-ai/dsh-client-ui-action-cards;
generated catalogs are regenerated, and both card Web fixtures record the
renamed bundles in their skills' plugin catalogs.
2026-10-22 20:58:11 -07:00

2881 lines
183 KiB
TypeScript

// @vitest-environment jsdom
import type { ConfigPageForm } from '../src/client/slot-contract.ts'
import { act, cleanup, fireEvent, render, screen, within } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, onTestFinished, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import { LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client'
import type { ChangeResult, PluginEntryId, PluginInstallRequestId, PluginUpdateResult, PluginVersion, RegistryAttempt } from '@deepseek-ai/dsh-api-remotes/client'
import { allowAllAccess, bindSnapshotSelector, stubConfigForm } from '@deepseek-ai/dsh-client-test-runtime'
import type { ConfigForm, ConfigFormSnapshot, SettingsMirrorSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client'
import { createSnapshotStore } from '@deepseek-ai/dsh-client-store'
import { MenuItemButton } from '@deepseek-ai/dsh-client-ui-primitives'
import { StrictMode, type ReactNode } from 'react'
import type { LocaleSnapshot } from '@deepseek-ai/dsh-client-locale/client'
import { bundleDetailKey, type BundleDetailsState } from '../src/client/detail-store.ts'
import { createNavigationStore } from '../src/client/navigation-store.ts'
import { PluginManagerPage } from '../src/client/PluginManagerPage.tsx'
import { PluginRefreshToast } from '../src/client/PluginRefreshToast.tsx'
import type { PluginManagerPageProps } from '../src/client/index.ts'
import type { PluginTab } from '../src/client/tab-ledger.ts'
import type { ConfigLedger } from '../src/client/config-ledger.ts'
import { rowKey, type InstallState, type PackageRow, type PackageView, type PluginManagerState, type UpdateState, type VersionCheck } from '../src/client/manager-store.ts'
import { INSTALL_GIT_EXAMPLE, INSTALL_PATH_EXAMPLE, en, zh, type PluginManagerLocaleKey } from '../src/client/locales.ts'
import type { PluginActivationOwnerProps, PluginAddActionsProps, PluginDetailProps, PluginTabProps, PluginsSubject } from '../src/client/slot-contract.ts'
beforeEach(() => { localStorage.clear() })
afterEach(cleanup)
const translate = (dict: typeof en): PluginManagerPageProps['t'] => ((key: PluginManagerLocaleKey, params?: Record<string, string>): string =>
Object.entries(params ?? {}).reduce(
(text, [name, value]) => text.replaceAll(`{${name}}`, value),
dict[key],
)) as PluginManagerPageProps['t']
const t = translate(en)
function pkg(overrides: Partial<PackageView> = {}): PackageView {
return {
name: 'dsh-better-sidebar',
official: overrides.optional === true,
availability: overrides.installed === false ? 'installation' : 'profile',
version: '0.16.0',
installed: true,
optional: false,
removable: true,
updatable: false,
enabled: true,
rows: [],
...overrides,
}
}
function row(overrides: Partial<PackageRow> = {}): PackageRow {
return { entryId: 'include:sidebar' as PluginEntryId, rowId: 'sidebar', moduleName: 'dsh-better-sidebar', enabled: true, phase: 'active', ...overrides }
}
const INCOMPATIBLE = { name: 'dsh-late', version: '2.0.0', runtimeVersion: '0.1.0', requirements: { '@deepseek-ai/dsh': '^0.2.0', '@deepseek-ai/dsh-core': '^0.2.0' } }
/** The English sentence an incompatibility of {@link INCOMPATIBLE}, optionally renamed, reads as. */
const incompatibleText = (name = INCOMPATIBLE.name): string => en.reasonIncompatibleVersion
.replace('{plugin}', `${name}@2.0.0`).replace('{runtime}', '0.1.0').replace('{requirements}', '@deepseek-ai/dsh ^0.2.0, @deepseek-ai/dsh-core ^0.2.0')
const MIRROR = 'https://registry.npmmirror.com/'
const OFFICIAL = 'https://registry.npmjs.org/'
/** The registries the Host asks: pnpm's own first, which names npm's own registry, then the mirror. */
const REGISTRIES = { registry: null, fallbackRegistries: [MIRROR], resolved: OFFICIAL }
/** pnpm's own registry as the options label it: by npm's own name once read, the neutral default name until then. */
const OFFICIAL_OPTION = `${en.registryOfficial} registry.npmjs.org`
const UNREAD_OPTION = `${en.registryDefault} registry.npmjs.org`
const MIRROR_OPTION = `${en.registryNpmmirror} registry.npmmirror.com`
const IDLE_INSTALL: InstallState = {
open: false, spec: '', registries: null, registry: { kind: 'offered', registry: null }, registryOpen: false, registryError: false, attempts: null,
phase: 'idle', inputError: null, subject: null, runs: [], detailsOpen: false,
installed: null, installedVersion: null, restartRequired: false, failure: null, approvedBuilds: [], enabling: false,
}
const READY: PluginManagerState = {
status: 'ready',
refreshStatus: 'idle',
packages: [],
busy: [],
notice: null,
install: IDLE_INSTALL,
versions: {},
update: null,
confirm: null,
highlight: null,
}
/**
* Slot entries a test supplies: what each slot cell renders, by `<slot>:<cell>`
* (a list slot's cell is empty), the view asked for — `detail` for a detail
* contribution — and the owner props.
*/
type SlotBodies = Record<string, (view: 'summary' | 'page' | 'activation' | 'detail' | 'add' | 'tab', owner: unknown, form?: ConfigPageForm) => ReactNode>
/** The subject a detail contribution was rendered with. */
function subjectOf(owner: unknown): PluginsSubject | undefined {
return typeof owner === 'object' && owner !== null && 'subject' in owner ? (owner as PluginDetailProps).subject : undefined
}
const NO_CONFIG: ConfigLedger = { items: [], bundles: new Set(), rows: new Set() }
function renderTab(
state: Partial<PluginManagerState> = {}, config: Partial<ConfigLedger> = {},
bodies: SlotBodies = {}, forms: Record<string, ConfigPageForm> = {}, tabs: readonly PluginTab[] = [],
) {
const ctx = new Context()
onTestFinished(async () => { await ctx.fiber.dispose() })
const locale = new LocaleRuntime(ctx)
locale.setLocale('en')
const resolveText: PluginManagerPageProps['resolveText'] = text => locale.resolveText(text)
const store = createSnapshotStore<PluginManagerState>({ ...READY, ...state })
const ledger = createSnapshotStore<ConfigLedger>({ ...NO_CONFIG, ...config })
const tabLedger = createSnapshotStore<readonly PluginTab[]>(tabs)
const bundleDetails = createSnapshotStore<BundleDetailsState>({})
const actions = {
ensure: vi.fn(),
refresh: vi.fn(),
openInstall: vi.fn(),
installFrom: vi.fn(),
closeInstall: vi.fn(),
editInstallSpec: vi.fn(),
runInstall: vi.fn(),
update: vi.fn(),
cancelInstall: vi.fn(),
reconcileInstall: vi.fn(),
toggleInstallDetails: vi.fn(),
toggleRegistryOptions: vi.fn(),
chooseRegistry: vi.fn(),
changeRegistry: vi.fn(),
useGithubMirror: vi.fn(),
approveBuildsAndRetry: vi.fn(),
enableInstalled: vi.fn(),
clearHighlight: vi.fn(),
setEnabled: vi.fn(),
uninstall: vi.fn(),
confirm: vi.fn(),
cancelConfirm: vi.fn(),
setRowEnabled: vi.fn(),
dismissNotice: vi.fn(),
checkVersions: vi.fn(),
ensureVersions: vi.fn(),
openUpdate: vi.fn(),
runUpdate: vi.fn(),
closeUpdate: vi.fn(),
toggleUpdateOutput: vi.fn(),
loadDetail: vi.fn(),
retryDetail: vi.fn(),
detailImage: vi.fn((name: string, document: string, destination: string, _fromNpm: boolean) =>
`https://dsh.example/api/plugin-manager/detail-file?bundle=${name}&document=${document}&path=${destination}`),
}
const unusedStandardHook = (): never => { throw new Error('Plugin manager fixture does not provide global state') }
const standard = {
usePanelInfo: unusedStandardHook,
useWorkspaces: unusedStandardHook,
useSessions: unusedStandardHook,
useSessionStatus: unusedStandardHook,
useSessionRetainInfo: unusedStandardHook,
useResource: unusedStandardHook,
...allowAllAccess,
}
const navigation = createNavigationStore().create()
const props: PluginManagerPageProps = {
createHeaderRef: vi.fn(() => vi.fn()),
useStore: bindSnapshotSelector(navigation), actions: navigation.actions,
...standard,
t,
resolveText,
...actions,
usePluginManager: bindSnapshotSelector(store),
useConfigLedger: bindSnapshotSelector(ledger),
usePluginTabs: bindSnapshotSelector(tabLedger),
useBundleDetails: bindSnapshotSelector(bundleDetails),
useLocale: bindSnapshotSelector(createSnapshotStore<LocaleSnapshot>({ active: 'en', locales: [], revision: 0 })),
useConfigurations: bindSnapshotSelector(createSnapshotStore<SettingsMirrorSnapshot>({
status: 'ready', error: null,
view: { writable: true, hasDocument: true, namespaces: Object.keys(forms).map(ns => ({
ns, schema: {}, value: {}, applies: 'live' as const, secrets: [], revision: 0, autoGenerate: true,
})) },
})),
configForm: <T,>(id: string): ConfigForm<T> => {
const stub = stubConfigForm<T>()
stub.publish(forms[id]!.state as ConfigFormSnapshot<T>)
return { ...stub.scope, mutate: forms[id]!.mutate }
},
renderSlot: (name, owner, opts) => {
const body = bodies[`${name}:${opts?.only ?? opts?.entryKey ?? ''}`]
if (body === undefined) return null
if (name === 'plugins.bundle.activation') return body('activation', owner)
if (name === 'plugins.add.actions') return body('add', owner)
if (name === 'plugins.tab') return body('tab', owner)
if (name.startsWith('plugins.detail.')) return body('detail', owner)
if (!('view' in owner) || (owner.view !== 'summary' && owner.view !== 'page')) {
throw new Error('Plugin configuration fixture requires a summary or page view')
}
return body(owner.view, owner, 'form' in owner ? owner.form as ConfigPageForm | undefined : undefined)
},
}
let showPage = true
let currentT = t
const contents = () => (
<>
{showPage ? <PluginManagerPage {...props} t={currentT} /> : null}
<PluginRefreshToast usePluginManager={props.usePluginManager} dismissNotice={actions.dismissNotice} t={currentT} />
</>
)
const { rerender, unmount } = render(contents())
return {
navigation,
setTabs: (next: readonly PluginTab[]) => { act(() => { tabLedger.set(next) }) },
hidePage: () => { showPage = false; rerender(contents()) },
showPage: () => { showPage = true; rerender(contents()) },
props,
unmount,
store,
actions,
set: (next: Partial<PluginManagerState>) => { act(() => { store.set({ ...store.getSnapshot(), ...next }) }) },
setDetails: (next: BundleDetailsState) => { act(() => { bundleDetails.set(next) }) },
setLanguage: (dict: typeof en) => {
locale.setLocale(dict === zh ? 'zh' : 'en')
currentT = translate(dict)
rerender(contents())
},
}
}
describe('PluginManagerPage', () => {
it('shows no tabs without contributions, and switches between Installed and a contributed tab', () => {
const { actions, navigation, setTabs } = renderTab({ packages: [pkg()] }, {}, {
'plugins.tab:discover': (_view, owner) => {
const tab = owner as PluginTabProps
return (
<>
<button type="button" onClick={() => { tab.install({ spec: '[email protected]', registry: 'https://r.example/', title: 'Alpha', version: '1.0.0' }) }}>Discover install</button>
<button type="button" onClick={() => { tab.openPackage('dsh-better-sidebar') }}>Discover open</button>
</>
)
},
})
expect(screen.queryByRole('tablist')).toBeNull()
setTabs([{ id: 'discover', label: 'Discover' }])
expect(screen.getByRole('tab', { name: en.installedTab }).getAttribute('aria-selected')).toBe('true')
// The tabs stay in the pinned page head with the title; the tab panel scrolls in the page body.
expect(screen.getByRole('tablist').closest('header')?.closest('[data-plugin-bar]')).not.toBeNull()
expect(document.querySelector('[data-plugin-group="bundles"]')).not.toBeNull()
fireEvent.click(screen.getByRole('tab', { name: 'Discover' }))
expect(navigation.getSnapshot().tab).toBe('discover')
expect(screen.getByRole('tabpanel').getAttribute('aria-labelledby')).toBe(screen.getByRole('tab', { name: 'Discover' }).id)
expect(screen.getByRole('tabpanel').closest('[data-plugin-body]')).not.toBeNull()
expect(document.querySelector('[data-plugin-group="bundles"]')).toBeNull()
expect(screen.queryByRole('button', { name: en.refresh })).toBeNull()
fireEvent.click(screen.getByRole('button', { name: 'Discover install' }))
expect(actions.installFrom).toHaveBeenCalledWith({ spec: '[email protected]', registry: 'https://r.example/', title: 'Alpha', version: '1.0.0' })
fireEvent.click(screen.getByRole('button', { name: 'Discover open' }))
expect(navigation.getSnapshot()).toEqual({ tab: 'installed', view: { kind: 'package', name: 'dsh-better-sidebar', returnTab: 'discover' } })
expect(document.querySelector('[data-plugin-detail="dsh-better-sidebar"]')).not.toBeNull()
// A detail page hides the tabs; its back button returns to the tab that opened it.
expect(screen.queryByRole('tablist')).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.backToList }))
expect(navigation.getSnapshot()).toEqual({ tab: 'discover', view: { kind: 'list' } })
expect(screen.getByRole('tab', { name: 'Discover' }).getAttribute('aria-selected')).toBe('true')
// A contributed tab that leaves the ledger falls back to Installed.
act(() => { navigation.actions.setView({ kind: 'list' }); navigation.actions.setTab('discover') })
setTabs([])
expect(document.querySelector('[data-plugin-group="bundles"]')).not.toBeNull()
expect(navigation.getSnapshot().tab).toBe('installed')
// A contributed tab that returns can be selected again.
setTabs([{ id: 'discover', label: 'Discover' }])
fireEvent.click(screen.getByRole('tab', { name: 'Discover' }))
expect(screen.getByRole('tabpanel', { name: 'Discover' })).toBeTruthy()
})
it('marks the bundle an install enabled without leaving a contributed tab', () => {
const { navigation, set } = renderTab({ packages: [pkg()] }, {}, {}, {}, [{ id: 'discover', label: 'Discover' }])
fireEvent.click(screen.getByRole('tab', { name: 'Discover' }))
expect(screen.getByRole('tabpanel', { name: 'Discover' })).toBeTruthy()
// Enable now publishes the new bundle's mark; the mark scrolls to a visible card and selects no page.
set({ highlight: 'dsh-better-sidebar' })
expect(navigation.getSnapshot().tab).toBe('discover')
expect(screen.getByRole('tabpanel', { name: 'Discover' })).toBeTruthy()
})
it('keeps the header controls in the scrolling page without remounting them', () => {
const { setTabs, props, hidePage } = renderTab({ packages: [pkg()] })
const bar = document.querySelector('[data-plugin-bar]')
const body = document.querySelector('[data-plugin-body]')!
const refresh = screen.getByRole('button', { name: en.refresh })
expect(bar?.parentElement).toBe(body)
expect(props.createHeaderRef).toHaveBeenCalledTimes(1)
setTabs([{ id: 'discover', label: 'Discover' }])
expect(screen.getByRole('tablist').closest('header')?.closest('[data-plugin-bar]')).toBe(bar)
act(() => { body.scrollTop = 160; fireEvent.scroll(body) })
expect(screen.getByRole('button', { name: en.refresh })).toBe(refresh)
expect(props.createHeaderRef).toHaveBeenCalledTimes(1)
hidePage()
expect(document.querySelector('[data-plugin-bar]')).toBeNull()
})
it('keeps the Installed tab panel through loading and the More list out of the tabs, and returns a More plugin opened from Discover to Discover', () => {
const worktree = '@deepseek-ai/dsh-experimental-tool-worktree'
const { navigation, set } = renderTab({ status: 'loading', packages: [] }, {}, {
'plugins.tab:discover': (_view, owner) => (
<button type="button" onClick={() => { (owner as PluginTabProps).openPackage(worktree) }}>Discover open</button>
),
}, {}, [{ id: 'discover', label: 'Discover' }])
const installed = screen.getByRole('tab', { name: en.installedTab })
expect(document.getElementById(installed.getAttribute('aria-controls') as string)?.getAttribute('role')).toBe('tabpanel')
set({ status: 'ready', packages: [] })
expect(screen.getByRole('tabpanel', { name: en.installedTab }).textContent).toBe(en.empty)
set({ packages: [pkg({ name: worktree, official: true, optional: true, installed: false, enabled: false })] })
act(() => { navigation.actions.setView({ kind: 'more' }) })
expect(screen.queryByRole('tablist')).toBeNull()
expect(screen.queryByRole('tabpanel')).toBeNull()
act(() => { navigation.actions.setView({ kind: 'list' }) })
fireEvent.click(screen.getByRole('tab', { name: 'Discover' }))
fireEvent.click(screen.getByRole('button', { name: 'Discover open' }))
expect(document.querySelector(`[data-plugin-detail="${worktree}"]`)).not.toBeNull()
// An official plugin of the More list opened from Discover returns to Discover, not to the More list.
fireEvent.click(screen.getByRole('button', { name: en.backToList }))
expect(navigation.getSnapshot()).toEqual({ tab: 'discover', view: { kind: 'list' } })
fireEvent.click(screen.getByRole('tab', { name: en.installedTab }))
act(() => { navigation.actions.setView({ kind: 'more' }) })
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', worktree) }))
fireEvent.click(screen.getByRole('button', { name: en.backToPackage.replace('{name}', en.moreTitle) }))
expect(screen.getByRole('heading', { name: en.moreTitle, level: 1 })).toBeTruthy()
})
it('keeps the opening tab through a row page opened from a bundle page', () => {
const { navigation, setTabs } = renderTab({ packages: [pkg({ rows: [row()] })] }, { rows: new Set(['dsh-better-sidebar#sidebar']) }, {
'plugins.tab:discover': (_view, owner) => (
<button type="button" onClick={() => { (owner as PluginTabProps).openPackage('dsh-better-sidebar') }}>Discover open</button>
),
'plugins.row.config:dsh-better-sidebar#sidebar': view => view === 'summary' ? 'Config summary' : <form aria-label="row settings" />,
}, {}, [{ id: 'discover', label: 'Discover' }])
fireEvent.click(screen.getByRole('tab', { name: 'Discover' }))
fireEvent.click(screen.getByRole('button', { name: 'Discover open' }))
fireEvent.click(screen.getByRole('button', { name: en.configureRow.replace('{name}', 'dsh-better-sidebar') }))
expect(navigation.getSnapshot().view).toEqual({ kind: 'row', name: 'dsh-better-sidebar', rowId: 'sidebar', returnTab: 'discover' })
fireEvent.click(screen.getByRole('button', { name: en.backToPackage.replace('{name}', 'dsh-better-sidebar') }))
expect(navigation.getSnapshot().view).toEqual({ kind: 'package', name: 'dsh-better-sidebar', returnTab: 'discover' })
fireEvent.click(screen.getByRole('button', { name: en.backToList }))
expect(navigation.getSnapshot()).toEqual({ tab: 'discover', view: { kind: 'list' } })
// A calling tab that left the ledger while the page was open falls back to Installed.
fireEvent.click(screen.getByRole('button', { name: 'Discover open' }))
setTabs([])
fireEvent.click(screen.getByRole('button', { name: en.backToList }))
expect(navigation.getSnapshot().tab).toBe('installed')
expect(document.querySelector('[data-plugin-group="bundles"]')).not.toBeNull()
})
it('shows a confirmed version by name above its spec, as a check and a refusal with retry, without the spec form or another registry', () => {
const confirmed: InstallState = { ...IDLE_INSTALL, open: true, spec: '[email protected]', confirmed: { title: 'Alpha', version: '1.0.0' }, registries: REGISTRIES }
const { actions, set } = renderTab({ install: { ...confirmed, phase: 'checking' } })
const dialog = screen.getByRole('dialog', { name: en.installConfirmedTitle })
expect([...dialog.querySelectorAll('[data-install-subject] p')].map(line => line.textContent))
.toEqual(['Alpha', en.installVersion.replace('{version}', '1.0.0'), '[email protected]'])
expect(screen.getByRole('status').textContent).toBe(en.installChecking)
expect(screen.queryByRole('textbox')).toBeNull()
expect(screen.queryByRole('button', { name: en.retry })).toBeNull()
set({ install: { ...confirmed, inputError: { problem: 'not-found', reason: 'dsh-a' } } })
expect(screen.getByRole('dialog', { name: en.installConfirmedRefused })).toBeTruthy()
expect(screen.getByRole('alert').textContent).toBe(en.installProblemNotFound.replace('{reason}', 'dsh-a'))
fireEvent.click(screen.getByRole('button', { name: en.retry }))
expect(actions.runInstall).toHaveBeenCalledOnce()
set({ install: { ...confirmed, inputError: { problem: 'network', reason: 'r', registries: [null, MIRROR] } } })
expect(screen.getByRole('alert').textContent).toMatch(/^No registry could be reached/)
set({ install: { ...confirmed, registries: null, inputError: { problem: 'not-found', reason: 'dsh-a' } } })
expect(screen.getByRole('alert')).toBeTruthy()
set({ install: confirmed })
expect(screen.queryByRole('alert')).toBeNull()
expect(screen.getByRole('dialog', { name: en.installConfirmedTitle })).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.installRun }))
expect(actions.runInstall).toHaveBeenCalledTimes(2)
fireEvent.click(screen.getAllByRole('button', { name: en.close }).at(-1)!)
expect(actions.closeInstall).toHaveBeenCalled()
set({
install: {
...confirmed, phase: 'failed', subject: { spec: '[email protected]', status: 'accepted', kind: 'registry', bundle: true, registry: null },
failure: { reason: 'failed', kind: 'network', failedAt: 'registry' },
},
})
expect(screen.queryByRole('button', { name: en.installChangeRegistry })).toBeNull()
// No edit to return to after a failure, and no GitHub mirror recovery, which reopens the spec form.
expect(screen.queryByRole('button', { name: en.installEditAria })).toBeNull()
set({
install: {
...confirmed, phase: 'failed',
subject: { spec: 'git+https://github.com/o/r.git#abc', status: 'accepted', kind: 'git', bundle: null, registry: null, host: 'github.com' },
failure: { reason: 'failed', kind: 'network', failedAt: 'spec-host' },
},
})
expect(screen.getByText(en.installFailedTitle)).toBeTruthy()
expect(screen.queryByRole('button', { name: en.installUseGithubMirror })).toBeNull()
// A Git spec checks without a name or version; the confirmed name and version head the card, above the spec.
expect([...document.querySelectorAll('[data-install-subject] p')].map(line => line.textContent))
.toEqual(['Alpha', en.installSubjectGit, en.installVersion.replace('{version}', '1.0.0'), 'git+https://github.com/o/r.git#abc'])
set({
install: {
...confirmed, phase: 'running', requestId: 'r' as never,
subject: { spec: '[email protected]', status: 'accepted', kind: 'registry', bundle: true, registry: null },
},
})
fireEvent.click(screen.getByRole('button', { name: en.cancel }))
expect(actions.cancelInstall).toHaveBeenCalledOnce()
})
it('opens the add-plugin menu with ArrowDown and returns focus to its trigger on Escape', () => {
const { actions } = renderTab({}, {}, {
'plugins.add.actions:': (_view, owner) => <MenuItemButton
onSelect={() => { (owner as PluginAddActionsProps).onDismiss() }}>Create a plugin</MenuItemButton>,
})
const add = screen.getByRole('button', { name: en.chooseAddMethod })
add.focus()
expect(fireEvent.keyDown(add, { key: 'Tab' })).toBe(true)
expect(screen.queryByRole('menu')).toBeNull()
fireEvent.keyDown(add, { key: 'ArrowDown' })
const install = screen.getByRole('menuitem', { name: new RegExp(en.installExisting) })
const create = screen.getByRole('menuitem', { name: 'Create a plugin' })
expect(screen.getAllByRole('menuitem')).toEqual([install, create])
expect(document.activeElement).toBe(install)
fireEvent.keyDown(install, { key: 'ArrowDown' })
expect(document.activeElement).toBe(create)
fireEvent.keyDown(create, { key: 'Escape' })
expect(screen.queryByRole('menu')).toBeNull()
expect(document.activeElement).toBe(add)
expect(actions.openInstall).not.toHaveBeenCalled()
})
it('opens the existing install dialog from the menu and keeps focus on its input', async () => {
const b = renderTab()
b.actions.openInstall.mockImplementation(() => {
b.set({ install: { ...IDLE_INSTALL, open: true } })
})
const add = screen.getByRole('button', { name: en.chooseAddMethod })
add.focus()
fireEvent.click(add)
expect(b.actions.openInstall).not.toHaveBeenCalled()
await act(async () => {
fireEvent.click(screen.getByRole('menuitem', { name: new RegExp(en.installExisting) }))
})
expect(b.actions.openInstall).toHaveBeenCalledOnce()
expect(screen.queryByRole('menu')).toBeNull()
expect(document.activeElement).toBe(screen.getByRole('textbox', { name: en.installSpecLabel }))
})
it('keeps primary installation and mouse-selected creation on separate buttons', () => {
const createPlugin = vi.fn()
const { actions } = renderTab({}, {}, {
'plugins.add.actions:': (_view, owner) => <MenuItemButton onSelect={() => {
(owner as PluginAddActionsProps).onDismiss()
createPlugin()
}}><span>Create a plugin</span><span>Describe it to the agent</span></MenuItemButton>,
})
const add = screen.getByRole('button', { name: en.addPlugin })
const more = screen.getByRole('button', { name: en.chooseAddMethod })
expect(add.getAttribute('aria-haspopup')).toBeNull()
expect(more.getAttribute('aria-haspopup')).toBe('menu')
fireEvent.pointerDown(add, { pointerType: 'mouse', button: 0 })
fireEvent.pointerUp(add, { pointerType: 'mouse', button: 0 })
fireEvent.click(add)
expect(actions.openInstall).toHaveBeenCalledOnce()
expect(screen.queryByRole('menu')).toBeNull()
actions.openInstall.mockClear()
for (const label of ['Create a plugin', 'Describe it to the agent']) {
fireEvent.pointerDown(more, { pointerType: 'mouse', button: 0 })
fireEvent.pointerUp(more, { pointerType: 'mouse', button: 0 })
fireEvent.click(more)
expect(more.getAttribute('aria-expanded')).toBe('true')
expect(actions.openInstall).not.toHaveBeenCalled()
const target = screen.getByText(label)
fireEvent.pointerDown(target, { pointerType: 'mouse', button: 0 })
fireEvent.pointerUp(target, { pointerType: 'mouse', button: 0 })
fireEvent.click(target)
expect(screen.queryByRole('menu')).toBeNull()
expect(more.getAttribute('aria-expanded')).toBe('false')
expect(actions.openInstall).not.toHaveBeenCalled()
}
expect(createPlugin).toHaveBeenCalledTimes(2)
fireEvent.click(more)
const install = screen.getByText(en.installExisting)
fireEvent.pointerDown(install, { pointerType: 'mouse', button: 0 })
fireEvent.pointerUp(install, { pointerType: 'mouse', button: 0 })
fireEvent.click(install)
expect(actions.openInstall).toHaveBeenCalledOnce()
expect(createPlugin).toHaveBeenCalledTimes(2)
expect(screen.queryByRole('menu')).toBeNull()
})
it('opens the requested bundle after its inventory arrives and falls back when it is absent', () => {
const b = renderTab({ status: 'loading' })
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'dsh-better-sidebar' }) })
b.set({ status: 'ready', packages: [pkg()] })
expect(document.querySelector('[data-plugin-detail="dsh-better-sidebar"]')).not.toBeNull()
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'missing' }) })
expect(document.querySelector('[data-plugin-detail]')).toBeNull()
expect(document.querySelector('[data-plugin-package="dsh-better-sidebar"]')).not.toBeNull()
})
it('shows a bundle\'s detail document first among its sections, and its failures with a retry', () => {
const b = renderTab({ packages: [pkg()] })
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'dsh-better-sidebar' }) })
expect(b.actions.loadDetail).toHaveBeenCalledWith('dsh-better-sidebar', '0.16.0', 'en', false)
expect(screen.getByRole('status', { name: en.detailLoading })).toBeTruthy()
const key = bundleDetailKey('dsh-better-sidebar', '0.16.0', 'en')
b.setDetails({ [key]: { status: 'ready', detail: { status: 'found', origin: 'locale', path: 'guide/DETAIL.md', markdown: '# Sidebar\n\n![shot](img/a.png)' } } })
const sections = [...document.querySelectorAll('[data-plugin-detail="dsh-better-sidebar"] section')]
const order = sections.map(section => section.hasAttribute('data-detail-document') ? 'detail' : section.hasAttribute('data-plugin-source') ? 'source' : 'other')
expect(order[0]).toBe('detail')
expect(order).toContain('source')
expect(screen.getByRole('heading', { name: 'Sidebar' })).toBeTruthy()
expect(screen.getByRole('img', { name: 'shot' }).getAttribute('src'))
.toBe('https://dsh.example/api/plugin-manager/detail-file?bundle=dsh-better-sidebar&document=guide/DETAIL.md&path=img/a.png')
const failures = [['too-large', en.detailTooLarge], ['network', en.detailNetwork], ['denied', en.detailFailed], ['invalid', en.detailInvalid], ['other', en.detailFailed]] as const
for (const [failure, text] of failures) {
b.setDetails({ [key]: { status: 'error', failure } })
expect(screen.getByRole('alert').textContent).toBe(text)
}
fireEvent.click(screen.getByRole('button', { name: en.retry }))
expect(b.actions.retryDetail).toHaveBeenCalledWith('dsh-better-sidebar', '0.16.0', 'en', false)
b.setDetails({ [key]: { status: 'ready', detail: { status: 'none' } } })
expect(document.querySelector('[data-detail-document]')).toBeNull()
// A read the controller forgot, after a profile change, starts again.
b.setDetails({})
expect(b.actions.loadDetail).toHaveBeenLastCalledWith('dsh-better-sidebar', '0.16.0', 'en', false)
})
it('says where a bundle comes from: the spec that installs it, or built in, with its version', () => {
const b = renderTab({ packages: [
pkg({ source: 'github:someone/dsh-better-sidebar' }),
{ name: 'dsh-official', installed: false, official: true, availability: 'installation', optional: true, removable: false, updatable: false, enabled: false, rows: [] },
{ name: 'dsh-shadowed', installed: true, official: false, availability: 'profile', optional: false, removable: false, updatable: false, enabled: true, rows: [] },
{ name: 'dsh-missing', installed: false, official: false, availability: 'missing', optional: false, removable: true, updatable: false, enabled: true, error: { code: 'unknown-plugin' }, rows: [] },
] })
const facts = (): string[] => [...document.querySelectorAll('[data-plugin-source] dt, [data-plugin-source] dd')].map(node => node.textContent)
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'dsh-better-sidebar' }) })
expect(within(document.querySelector('[data-plugin-source]') as HTMLElement).getByRole('heading').textContent).toBe(en.sourceTitle)
expect(facts()).toEqual([en.sourceSpec, 'github:someone/dsh-better-sidebar', en.sourceVersion, '0.16.0'])
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'dsh-official' }) })
expect(facts()).toEqual([en.sourceSpec, en.sourceBuiltIn])
// A profile dependency the installation also supplies loads the installation's copy.
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'dsh-shadowed' }) })
expect(facts()).toEqual([en.sourceSpec, en.sourceBuiltIn])
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'dsh-missing' }) })
expect(facts()).toEqual([en.sourceSpec, en.sourceNotInstalled])
// A bundle whose files are not installed is read from the npm file CDNs.
expect(b.actions.loadDetail).toHaveBeenLastCalledWith('dsh-missing', undefined, 'en', true)
})
it('preserves the requested bundle through StrictMode effect replay and page remounts', () => {
const b = renderTab({ packages: [pkg()] })
b.unmount()
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'dsh-better-sidebar' }) })
const view = render(<StrictMode><PluginManagerPage {...b.props} /></StrictMode>)
expect(document.querySelector('[data-plugin-detail="dsh-better-sidebar"]')).not.toBeNull()
view.unmount()
render(<PluginManagerPage {...b.props} />)
expect(document.querySelector('[data-plugin-detail="dsh-better-sidebar"]')).not.toBeNull()
})
it('asks the store once mounted and renders the loading, unavailable, error, and empty states', () => {
const { actions, set } = renderTab({ status: 'loading' })
expect(actions.ensure).toHaveBeenCalledTimes(1)
const loading = screen.getByRole('status', { name: en.loading })
expect(loading.querySelectorAll('li')).toHaveLength(4)
expect(loading.querySelector('ul')?.getAttribute('aria-hidden')).toBe('true')
expect(loading.querySelector('button, input, [data-state="ongoing"]')).toBeNull()
expect(document.querySelector('[data-plugin-panel]')?.getAttribute('aria-busy')).toBe('true')
expect(screen.getByRole('button', { name: en.addPlugin })).toHaveProperty('disabled', true)
expect(screen.getByRole('button', { name: en.chooseAddMethod })).toHaveProperty('disabled', true)
set({ status: 'unavailable' })
expect(screen.queryByRole('status', { name: en.loading })).toBeNull()
expect(screen.getByRole('status').querySelector('[data-state="idle"]')).not.toBeNull()
set({ status: 'error' })
expect(screen.getByRole('alert').querySelector('[data-state="error"]')).not.toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.retry }))
expect(actions.refresh).toHaveBeenCalledTimes(1)
fireEvent.click(screen.getByRole('button', { name: en.refresh }))
expect(actions.refresh).toHaveBeenCalledTimes(2)
set({ status: 'ready' })
expect(screen.getByText(en.empty)).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.addPlugin }))
expect(actions.openInstall).toHaveBeenCalledTimes(1)
})
it('shows skeletons only while loading and keeps ready cards during refresh and switching', () => {
const { actions, set } = renderTab({ status: 'idle' })
expect(screen.queryByRole('status', { name: en.loading })).toBeNull()
set({ status: 'loading' })
expect(screen.getByRole('status', { name: en.loading })).toBeTruthy()
set({ status: 'ready', packages: [pkg()] })
expect(screen.queryByRole('status', { name: en.loading })).toBeNull()
expect(document.querySelector('[data-plugin-panel]')?.getAttribute('aria-busy')).toBe('false')
fireEvent.click(screen.getByRole('button', { name: en.refresh }))
expect(actions.refresh).toHaveBeenCalledTimes(1)
set({ busy: ['dsh-better-sidebar'] })
expect(screen.queryByRole('status', { name: en.loading })).toBeNull()
expect(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') })).toBeTruthy()
expect(screen.getByRole('switch', { name: en.enableToggle.replace('{name}', 'dsh-better-sidebar') })).toHaveProperty('disabled', true)
})
it('shows refresh progress on the disabled button while preserving cards, then restores it without a toast', () => {
const { actions, set } = renderTab({ packages: [pkg()] })
const refresh = screen.getByRole('button', { name: en.refresh })
const detailName = en.openDetail.replace('{name}', 'dsh-better-sidebar')
const card = screen.getByRole('button', { name: detailName })
expect(refresh).toHaveProperty('disabled', false)
expect(refresh.getAttribute('title')).toBeNull()
fireEvent.click(refresh)
expect(actions.refresh).toHaveBeenCalledOnce()
set({ refreshStatus: 'refreshing' })
expect(refresh).toHaveProperty('disabled', true)
expect(refresh.getAttribute('aria-busy')).toBe('true')
expect(refresh.querySelector('[data-state="ongoing"]')).not.toBeNull()
expect(screen.getByRole('button', { name: detailName })).toBe(card)
expect(screen.queryByRole('status', { name: en.loading })).toBeNull()
expect(screen.queryByRole('alert')).toBeNull()
fireEvent.click(refresh)
expect(actions.refresh).toHaveBeenCalledOnce()
set({ refreshStatus: 'idle' })
expect(refresh).toHaveProperty('disabled', false)
expect(refresh.getAttribute('aria-busy')).toBe('false')
expect(refresh.querySelector('[data-state="ongoing"]')).toBeNull()
expect(screen.getByRole('button', { name: detailName })).toBe(card)
expect(screen.queryByRole('alert')).toBeNull()
expect(actions.dismissNotice).not.toHaveBeenCalled()
})
it.each(['list', 'detail'] as const)('keeps cached %s content with one localized refresh toast and no inline retry', (view) => {
const { set, setLanguage } = renderTab({ packages: [pkg()] })
if (view === 'detail') fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') }))
const content = document.querySelector(view === 'detail' ? '[data-plugin-detail]' : '[data-plugin-package]')
for (const dict of [en, zh]) {
setLanguage(dict)
set({ status: 'ready', refreshStatus: 'idle', notice: { kind: 'refresh-failed', seq: 1 } })
expect(screen.getAllByRole('alert')).toHaveLength(1)
const toast = screen.getByRole('alert')
expect(toast.textContent).toBe(dict.refreshError)
expect(toast.parentElement).toBe(document.body)
expect(document.querySelector('[data-plugin-panel]')?.contains(toast)).toBe(false)
expect(screen.queryByRole('button', { name: dict.retry })).toBeNull()
expect(content?.isConnected).toBe(true)
set({ refreshStatus: 'refreshing', notice: null })
expect(screen.queryByRole('alert')).toBeNull()
expect(content?.isConnected).toBe(true)
set({ refreshStatus: 'idle' })
expect(screen.queryByRole('alert')).toBeNull()
}
})
it.each(['list', 'configuration detail'] as const)('keeps an uncached refresh failure inline in %s and hides it during retry', (view) => {
const { actions, set, setLanguage } = renderTab(
{ status: 'error', refreshStatus: 'failed' },
{ items: [{ id: 'bash', label: 'Shell' }] },
)
if (view === 'configuration detail') fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'Shell') }))
for (const dict of [en, zh]) {
setLanguage(dict)
set({ status: 'error', refreshStatus: 'failed' })
const alert = screen.getByRole('alert')
expect(alert.textContent).toBe(dict.refreshError)
expect(document.querySelector('[data-plugin-panel]')?.contains(alert)).toBe(true)
fireEvent.click(screen.getByRole('button', { name: dict.retry }))
set({ refreshStatus: 'refreshing' })
expect(screen.queryByRole('alert')).toBeNull()
expect(screen.queryByRole('button', { name: dict.retry })).toBeNull()
}
expect(actions.refresh).toHaveBeenCalledTimes(2)
})
it.each(['before', 'after'] as const)('keeps the refresh toast when navigation leaves %s the failure arrives', (navigation) => {
vi.useFakeTimers()
try {
const { actions, set, hidePage } = renderTab({ packages: [pkg()] })
set({ refreshStatus: 'refreshing' })
if (navigation === 'before') hidePage()
set({ refreshStatus: 'idle', notice: { kind: 'refresh-failed', seq: 1 } })
const toast = screen.getByRole('alert')
expect(toast.textContent).toBe(en.refreshError)
if (navigation === 'after') hidePage()
expect(document.querySelector('[data-plugin-panel]')).toBeNull()
expect(screen.getByRole('alert')).toBe(toast)
expect(toast.style.getPropertyValue('--dsh-toast-hold')).toBe('3000ms')
act(() => { vi.advanceTimersByTime(3_999) })
expect(actions.dismissNotice).not.toHaveBeenCalled()
act(() => { vi.advanceTimersByTime(1) })
expect(actions.dismissNotice).toHaveBeenCalledOnce()
set({ notice: null })
expect(screen.queryByRole('alert')).toBeNull()
} finally {
cleanup()
vi.useRealTimers()
}
})
it('gives a repeated refresh failure its own toast lifetime without extending it for locale changes', () => {
vi.useFakeTimers()
try {
const { actions, set, setLanguage } = renderTab({ notice: { kind: 'refresh-failed', seq: 1 } })
act(() => { vi.advanceTimersByTime(2_000) })
set({ notice: { kind: 'refresh-failed', seq: 2 } })
act(() => { vi.advanceTimersByTime(2_000) })
expect(actions.dismissNotice).not.toHaveBeenCalled()
setLanguage(zh)
expect(screen.getByRole('alert').textContent).toBe(zh.refreshError)
act(() => { vi.advanceTimersByTime(2_000) })
expect(actions.dismissNotice).toHaveBeenCalledOnce()
} finally {
cleanup()
vi.useRealTimers()
}
})
it('keeps the read failure and its retry visible while a detail page is open', () => {
const { actions, set } = renderTab({ packages: [pkg()] })
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') }))
expect(screen.queryByRole('alert')).toBeNull()
set({ status: 'error' })
expect(screen.getByRole('alert').querySelector('[data-state="error"]')).not.toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.retry }))
expect(actions.refresh).toHaveBeenCalledTimes(1)
// The detail keeps showing the kept data behind the alert.
expect(document.querySelector('[data-plugin-detail]')).not.toBeNull()
})
it('lists the installed bundles as cards, the installation\'s offered ones as official, and tags a problem the Host reports', () => {
const { actions } = renderTab({
packages: [
pkg({ meta: { description: { en: 'A sidebar.' } } }),
pkg({ name: 'dsh-broken', enabled: false, error: { code: 'not-bundle' } }),
pkg({ name: '@deepseek-ai/dsh-web-app', installed: false, removable: false }),
pkg({ name: 'dsh-protected', readOnlyReason: 'management-required' }),
pkg({ name: '@acme/dsh-tool', enabled: false }),
// Selected by the profile but not a bundle: a problem the person can switch off, in the profile's own group.
pkg({ name: 'dsh-selected', installed: false, error: { code: 'not-bundle' } }),
pkg({ name: '@deepseek-ai/dsh-experimental-agent-team-profile', installed: false, optional: true, removable: false, enabled: false }),
],
busy: ['dsh-protected'],
})
const cards = screen.getAllByRole('listitem')
// Offered extensions precede installed bundles.
expect(cards.map(card => card.getAttribute('data-plugin-package'))).toEqual([
'@deepseek-ai/dsh-experimental-agent-team-profile', 'dsh-better-sidebar', 'dsh-broken', 'dsh-protected', '@acme/dsh-tool', 'dsh-selected',
])
expect(cards.map(card => card.getAttribute('data-plugin-status'))).toEqual(['disabled', 'running', 'problem', 'running', 'disabled', 'problem'])
// Group titles have no counts; the official bundle retains its experimental marker.
expect(screen.getByRole('heading', { name: en.bundlesTitle })).toBeTruthy()
expect(screen.getByRole('heading', { name: en.extensionsTitle })).toBeTruthy()
expect(document.querySelector('[data-plugin-count]')).toBeNull()
expect(screen.getAllByText(en.statusBeta)).toHaveLength(1)
expect(screen.getByRole('switch', { name: en.enableToggle.replace('{name}', '@acme/dsh-tool') })).toHaveProperty('disabled', false)
expect(screen.getByText('A sidebar.')).toBeTruthy()
expect(screen.getAllByText(en.statusProblem)).toHaveLength(2)
// The switch acts on the bundle; a bundle the Host cannot read stays off, a protected one stays as it is.
fireEvent.click(screen.getByRole('switch', { name: en.enableToggle.replace('{name}', 'dsh-better-sidebar') }))
expect(actions.setEnabled).toHaveBeenCalledWith('dsh-better-sidebar', false)
expect(screen.getByRole('switch', { name: en.enableToggle.replace('{name}', 'dsh-broken') })).toHaveProperty('disabled', true)
const locked = screen.getByRole('switch', { name: en.enableToggle.replace('{name}', 'dsh-protected') })
expect(locked).toHaveProperty('disabled', true)
expect(locked.getAttribute('title')).toBe(en.reasonManagementRequired)
})
it('omits profile and persistent-terminal bundles while retaining other optional and third-party bundles', () => {
renderTab({
packages: [
...[
'@deepseek-ai/dsh-base',
'@deepseek-ai/dsh-web-app',
'@deepseek-ai/dsh-headless',
'@deepseek-ai/dsh-sdk-app',
'@deepseek-ai/dsh-acp-app',
'@deepseek-ai/dsh-sdk-minimal',
'@deepseek-ai/dsh-experimental-terminal-bundle',
].map(name => pkg({ name })),
pkg({ name: '@acme/dsh-base', readOnlyReason: 'management-required' }),
pkg({ name: 'dsh-better-sidebar' }),
pkg({ name: '@deepseek-ai/dsh-experimental-agent-team-profile', installed: false, optional: true, removable: false }),
],
})
expect(screen.getAllByRole('listitem').map(card => card.getAttribute('data-plugin-package'))).toEqual([
'@deepseek-ai/dsh-experimental-agent-team-profile', '@acme/dsh-base', 'dsh-better-sidebar',
])
expect(document.querySelector('[data-plugin-count]')).toBeNull()
})
it.each([false, true])('hides persistent terminals from both lists and direct navigation without changing enabled=%s', (enabled) => {
const name = '@deepseek-ai/dsh-experimental-terminal-bundle'
const b = renderTab({ packages: [
pkg({ name, official: true, enabled, rows: [row()] }),
pkg({ name: '@deepseek-ai/dsh-experimental-agent-team-profile', official: true }),
] })
expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
expect(screen.getByRole('heading', { name: en.moreTitle, level: 1 })).toBeTruthy()
expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeNull()
act(() => { b.navigation.actions.setView({ kind: 'package', name }) })
expect(document.querySelector('[data-plugin-detail]')).toBeNull()
act(() => { b.navigation.actions.setView({ kind: 'row', name, rowId: 'sidebar' }) })
expect(document.querySelector('[data-plugin-row-detail]')).toBeNull()
expect(screen.queryByRole('switch', { name: en.enableToggle.replace('{name}', name) })).toBeNull()
expect(b.actions.setEnabled).not.toHaveBeenCalled()
expect(b.actions.setRowEnabled).not.toHaveBeenCalled()
expect(b.actions.uninstall).not.toHaveBeenCalled()
})
it.each([false, true])('shows an empty list for hidden bundles with errors and installed=%s', (installed) => {
renderTab({
packages: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app', '@deepseek-ai/dsh-experimental-terminal-bundle'].map(name => pkg({
name, installed, error: { code: 'operation-error', diagnostic: 'Unreadable bundle' },
})),
})
expect(screen.getByText(en.empty)).toBeTruthy()
expect(screen.queryAllByRole('listitem')).toHaveLength(0)
expect(document.querySelectorAll('[data-plugin-count]')).toHaveLength(0)
})
it('offers uninstall for a selected bundle that no dependency holds', () => {
const { actions } = renderTab({
packages: [pkg({ name: 'dsh-retired', installed: false, removable: true, error: { code: 'operation-error', diagnostic: 'cannot resolve' } })],
})
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-retired') }))
const detail = document.querySelector('[data-plugin-detail]') as HTMLElement
fireEvent.click(within(detail).getByRole('button', { name: en.uninstallLabel.replace('{name}', 'dsh-retired') }))
expect(actions.uninstall).toHaveBeenCalledExactlyOnceWith('dsh-retired')
})
it('opens an official bundle\'s page with its beta tag and no uninstall, and switches it on', () => {
const title = 'Agent Teams'
const { actions } = renderTab({
packages: [pkg({ name: '@deepseek-ai/dsh-experimental-agent-team-profile', meta: { title }, installed: false, optional: true, removable: false, enabled: false })],
})
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', title) }))
const detail = document.querySelector('[data-plugin-detail]') as HTMLElement
expect(within(detail).getByText(en.statusBeta)).toBeTruthy()
expect(within(detail).queryByRole('button', { name: en.uninstallLabel.replace('{name}', title) })).toBeNull()
fireEvent.click(within(detail).getByRole('switch', { name: en.enableToggle.replace('{name}', title) }))
expect(actions.setEnabled).toHaveBeenCalledExactlyOnceWith('@deepseek-ai/dsh-experimental-agent-team-profile', true)
})
it.each([
['@deepseek-ai/dsh-experimental-agent-team-profile', true],
['@deepseek-ai/dsh-experimental-auto-review', true],
['@deepseek-ai/dsh-experimental-fixture-input', false],
['@deepseek-ai/dsh-cua-bundle', true],
['@acme/dsh-local-tools', false],
] as const)('localizes Host metadata for %s across cards, details, switches, and uninstall confirmation', (name, experimental) => {
const meta = {
title: { en: 'Installed tools', zh: '已安装工具' },
description: { en: 'Tools on this host.', zh: '本机工具。' },
}
const title = (dict: typeof en): string => dict === zh ? meta.title.zh : meta.title.en
const description = (dict: typeof en): string => dict === zh ? meta.description.zh : meta.description.en
const { actions, set, setLanguage } = renderTab({ packages: [pkg({ name, meta, description: 'Original metadata.' })] })
const assertCard = (dict: typeof en) => {
const card = screen.getByRole('button', { name: dict.openDetail.replace('{name}', title(dict)) })
expect(card.textContent).toBe(title(dict))
expect(screen.getByText(description(dict))).toBeTruthy()
expect(document.getElementById(card.getAttribute('aria-describedby')!)?.textContent).toBe(description(dict))
expect(screen.getByRole('switch', { name: dict.enableToggle.replace('{name}', title(dict)) })).toBeTruthy()
expect(screen.queryByText('Original metadata.')).toBeNull()
expect(screen.queryByText(dict.statusBeta) !== null).toBe(experimental)
}
assertCard(en)
setLanguage(zh)
assertCard(zh)
fireEvent.click(screen.getByRole('switch', { name: zh.enableToggle.replace('{name}', title(zh)) }))
expect(actions.setEnabled).toHaveBeenCalledExactlyOnceWith(name, false)
fireEvent.click(screen.getByRole('button', { name: zh.openDetail.replace('{name}', title(zh)) }))
for (const dict of [zh, en]) {
setLanguage(dict)
expect(screen.getByRole('heading', { level: 3 }).textContent).toBe(title(dict))
expect(screen.getByText(description(dict))).toBeTruthy()
expect(document.querySelector('[data-plugin-name]')?.textContent).toBe(name)
expect(screen.queryByText(dict.statusBeta) !== null).toBe(experimental)
expect(screen.getByRole('switch', { name: dict.enableToggle.replace('{name}', title(dict)) })).toBeTruthy()
expect(screen.getByRole('button', { name: dict.uninstallLabel.replace('{name}', title(dict)) })).toBeTruthy()
}
fireEvent.click(screen.getByRole('button', { name: en.uninstallLabel.replace('{name}', title(en)) }))
expect(actions.uninstall).toHaveBeenCalledExactlyOnceWith(name)
set({ confirm: { action: 'uninstall', packageName: name } })
for (const dict of [en, zh]) {
setLanguage(dict)
expect(screen.getByRole('dialog', { name: dict.confirmUninstallTitle.replace('{name}', title(dict)) })).toBeTruthy()
}
})
it('renders manifest icons for arbitrary bundles and rows, with decode fallback and source recovery', () => {
const icon = 'data:image/svg+xml;base64,PHN2Zy8+'
const updatedIcon = 'data:image/png;base64,cG5n'
const bundle = pkg({ meta: { icon }, rows: [row({ meta: { icon } }), row({ entryId: 'plain' as PluginEntryId, rowId: 'plain', moduleName: 'plain' })] })
const { set } = renderTab({ packages: [bundle] }, { rows: new Set(['dsh-better-sidebar#sidebar']) })
const image = () => document.querySelector<HTMLImageElement>('[data-plugin-package] img, [data-plugin-detail] img')!
expect(image().getAttribute('src')).toBe(icon)
expect(image().getAttribute('alt')).toBe('')
expect(image().width).toBe(36)
fireEvent.error(image())
expect(document.querySelector('[data-plugin-package] img')).toBeNull()
expect(document.querySelector('[data-plugin-package] svg')).not.toBeNull()
set({ packages: [{ ...bundle, meta: { icon: updatedIcon } }] })
expect(image().getAttribute('src')).toBe(updatedIcon)
set({ packages: [bundle] })
expect(image().getAttribute('src')).toBe(icon)
fireEvent.click(screen.getByRole('button', { name: 'View dsh-better-sidebar' }))
expect(image().getAttribute('src')).toBe(icon)
const rowImage = document.querySelector<HTMLImageElement>('[data-plugin-row] img')!
expect(rowImage.getAttribute('src')).toBe(icon)
expect(rowImage.width).toBe(30)
expect(document.querySelector('[data-plugin-row="plain"] img')).toBeNull()
expect(document.querySelector('[data-plugin-row="plain"] svg')).not.toBeNull()
fireEvent.error(rowImage)
expect(document.querySelector('[data-plugin-row] img')).toBeNull()
expect(document.querySelector('[data-plugin-row] svg')).not.toBeNull()
fireEvent.click(screen.getByRole('button', { name: 'Configure dsh-better-sidebar' }))
const detailImage = document.querySelector<HTMLImageElement>('[data-plugin-row-detail] img')!
expect(detailImage.getAttribute('src')).toBe(icon)
expect(detailImage.width).toBe(36)
})
it('shows metadata diagnostics without blocking management or displaying legacy descriptions', () => {
const error = 'locale/zh.json: invalid title'
const { actions, set, setLanguage } = renderTab({
packages: [pkg({ enabled: false, description: 'Legacy description.', meta: { error } })],
})
expect(screen.getByText(en.metadataError.replace('{error}', error))).toBeTruthy()
expect(screen.queryByText('Legacy description.')).toBeNull()
const enable = screen.getByRole('switch', { name: en.enableToggle.replace('{name}', 'dsh-better-sidebar') })
expect(enable).toHaveProperty('disabled', false)
fireEvent.click(enable)
expect(actions.setEnabled).toHaveBeenCalledExactlyOnceWith('dsh-better-sidebar', true)
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') }))
expect(screen.getByText(en.metadataError.replace('{error}', error))).toBeTruthy()
const uninstall = screen.getByRole('button', { name: en.uninstallLabel.replace('{name}', 'dsh-better-sidebar') })
expect(uninstall).toHaveProperty('disabled', false)
fireEvent.click(uninstall)
expect(actions.uninstall).toHaveBeenCalledExactlyOnceWith('dsh-better-sidebar')
setLanguage(zh)
expect(screen.getByText(zh.metadataError.replace('{error}', error))).toBeTruthy()
set({ packages: [pkg({ meta: { title: 'Literal title', description: { en: 'English fallback.' } } })] })
expect(screen.getByRole('heading', { level: 3 }).textContent).toBe('Literal title')
expect(screen.getByText('English fallback.')).toBeTruthy()
expect(document.querySelector('[data-package-meta-error]')).toBeNull()
})
it.each<{
field: string
meta: NonNullable<PackageView['meta']>
englishTitle: string
chineseTitle: string
englishDescription?: string
chineseDescription: string
}>([
{
field: 'title',
meta: { title: { en: '@acme/dsh-sidebar', zh: '侧栏套件' }, description: 'Package description.' },
englishTitle: '@acme/dsh-sidebar', chineseTitle: '侧栏套件',
englishDescription: 'Package description.', chineseDescription: 'Package description.',
},
{
field: 'description',
meta: { title: { en: 'English title' }, description: { en: '', zh: '中文套件说明。' } },
englishTitle: 'English title', chineseTitle: 'English title', chineseDescription: '中文套件说明。',
},
])('resolves bundle $field independently and hides empty descriptions on cards and details', ({ meta, englishTitle, chineseTitle, englishDescription, chineseDescription }) => {
const { setLanguage } = renderTab({ packages: [pkg({ name: '@acme/dsh-sidebar', meta })] })
const languages = [
{ dict: en, title: englishTitle, description: englishDescription },
{ dict: zh, title: chineseTitle, description: chineseDescription },
{ dict: en, title: englishTitle, description: englishDescription },
]
for (const { dict, title, description } of languages) {
setLanguage(dict)
const card = screen.getByRole('button', { name: dict.openDetail.replace('{name}', title) })
expect(card.textContent).toBe(title)
if (description === undefined) {
expect(card.getAttribute('aria-describedby')).toBeNull()
expect(screen.queryByText(chineseDescription)).toBeNull()
} else {
expect(screen.getByText(description)).toBeTruthy()
expect(document.getElementById(card.getAttribute('aria-describedby')!)?.textContent).toBe(description)
}
}
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', englishTitle) }))
for (const { dict, title, description } of languages) {
setLanguage(dict)
expect(screen.getByRole('heading', { level: 3 }).textContent).toBe(title)
if (description === undefined) {
expect(screen.queryByText(chineseDescription)).toBeNull()
expect(screen.queryByText(dict === zh ? '暂无描述。' : 'No description.')).toBeNull()
} else {
expect(screen.getByText(description)).toBeTruthy()
}
}
})
it('resolves row fields independently from bundle metadata and preserves subpath specifiers', () => {
const rows = [
row({ moduleName: '@acme/dsh-sidebar/navigation', meta: { description: { en: 'Navigation description.' } } }),
row({
rowId: 'theme', entryId: 'include:theme' as PluginEntryId, moduleName: '@acme/dsh-theme/client',
meta: { title: { en: '@acme/dsh-theme', zh: '主题插件' }, description: { en: '', zh: '中文主题说明。' } },
}),
]
const { actions, setLanguage } = renderTab(
{ packages: [pkg({ meta: { title: 'Bundle title', description: 'Bundle description.' }, rows })] },
{ rows: new Set(['dsh-better-sidebar#theme']) },
{ 'plugins.row.config:dsh-better-sidebar#theme': view => view === 'page' ? <form aria-label="theme settings" /> : null },
)
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'Bundle title') }))
expect(screen.getByRole('switch', { name: en.partToggle.replace('{name}', '@acme/dsh-sidebar/navigation') })).toBeTruthy()
expect(screen.getByRole('switch', { name: en.partToggle.replace('{name}', '@acme/dsh-theme') })).toBeTruthy()
expect(screen.queryByText('中文主题说明。')).toBeNull()
setLanguage(zh)
const navigation = document.querySelector('[data-plugin-row="include:sidebar"]') as HTMLElement
expect(within(navigation).getByText('Navigation description.')).toBeTruthy()
expect(within(navigation).queryByText('Bundle title')).toBeNull()
expect(within(navigation).queryByText('Bundle description.')).toBeNull()
fireEvent.click(within(navigation).getByRole('switch', { name: zh.partToggle.replace('{name}', '@acme/dsh-sidebar/navigation') }))
expect(actions.setRowEnabled).toHaveBeenCalledExactlyOnceWith('include:sidebar', false)
expect(screen.getByText('中文主题说明。')).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: zh.configureRow.replace('{name}', '主题插件') }))
expect(document.querySelector('[data-plugin-row-detail]')?.getAttribute('data-plugin-row-detail')).toBe('dsh-better-sidebar#theme')
expect(screen.getByRole('heading', { level: 3 }).textContent).toBe('主题插件')
expect(screen.getByText('中文主题说明。')).toBeTruthy()
setLanguage(en)
expect(screen.getByRole('heading', { level: 3 }).textContent).toBe('@acme/dsh-theme')
expect(screen.getByText('@acme/dsh-theme/client')).toBeTruthy()
expect(screen.getByText('theme')).toBeTruthy()
expect(screen.queryByText('中文主题说明。')).toBeNull()
expect(screen.getByRole('form', { name: 'theme settings' })).toBeTruthy()
})
it('shows a row id only once when it is the localized title in lists and configuration pages', () => {
const moduleName = '@acme/dsh-sidebar/navigation'
const { setLanguage } = renderTab(
{ packages: [pkg({ rows: [row({ moduleName, meta: { title: { en: 'Sidebar component', zh: 'sidebar' } } })] })] },
{ rows: new Set(['dsh-better-sidebar#sidebar']) },
{ 'plugins.row.config:dsh-better-sidebar#sidebar': view => view === 'page' ? <form aria-label="sidebar settings" /> : null },
)
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') }))
const listed = document.querySelector('[data-plugin-row="include:sidebar"]') as HTMLElement
expect(within(listed).getByText('Sidebar component')).toBeTruthy()
expect(within(listed).getByText('sidebar', { selector: 'code' })).toBeTruthy()
setLanguage(zh)
expect(within(listed).getAllByText('sidebar')).toHaveLength(1)
expect(within(listed).queryByText('sidebar', { selector: 'code' })).toBeNull()
expect(within(listed).getByText(moduleName, { selector: 'code' })).toBeTruthy()
fireEvent.click(within(listed).getByRole('button', { name: zh.configureRow.replace('{name}', 'sidebar') }))
const detail = document.querySelector('[data-plugin-row-detail="dsh-better-sidebar#sidebar"]') as HTMLElement
expect(within(detail).getByRole('heading', { level: 3 }).textContent).toBe('sidebar')
expect(within(detail).getAllByText('sidebar')).toHaveLength(1)
expect(within(detail).queryByText('sidebar', { selector: 'code' })).toBeNull()
expect(within(detail).getByText(moduleName, { selector: 'code' })).toBeTruthy()
expect(within(detail).getByRole('form', { name: 'sidebar settings' })).toBeTruthy()
setLanguage(en)
expect(within(detail).getByRole('heading', { level: 3 }).textContent).toBe('Sidebar component')
expect(within(detail).getByText('sidebar', { selector: 'code' })).toBeTruthy()
})
it('translates row text, searches current copy and technical identities, and keeps configuration keys unchanged', () => {
const error = 'locale/zh.json: invalid description'
const localized = row({
moduleName: '@acme/dsh-sidebar-widget',
meta: {
title: { en: 'Sidebar component', zh: '导航组件' },
description: { en: 'Sidebar navigation', zh: '侧边导航' },
error,
},
})
const rows = [localized, ...Array.from({ length: 10 }, (_, index) => row({
rowId: `extra-${String(index)}`, entryId: `include:extra-${String(index)}` as PluginEntryId, moduleName: '@acme/other',
}))]
const { actions, setLanguage } = renderTab(
{ packages: [pkg({ meta: { title: { en: 'Personal tools', zh: '个人工具' } }, rows })] },
{ rows: new Set(['dsh-better-sidebar#sidebar']) },
{ 'plugins.row.config:dsh-better-sidebar#sidebar': view => view === 'summary' ? 'Config summary' : <form aria-label="row settings" /> },
)
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'Personal tools') }))
setLanguage(zh)
const search = screen.getByRole('searchbox', { name: zh.partsFilter })
for (const query of ['导航组件', '侧边导航', 'sidebar', '@acme/dsh-sidebar-widget']) {
fireEvent.change(search, { target: { value: query } })
expect(document.querySelectorAll('[data-plugin-row]')).toHaveLength(1)
expect(screen.getByRole('button', { name: zh.configureRow.replace('{name}', '导航组件') })).toBeTruthy()
}
expect(screen.getByText('sidebar')).toBeTruthy()
expect(screen.getByText('@acme/dsh-sidebar-widget')).toBeTruthy()
expect(screen.getByText('侧边导航')).toBeTruthy()
expect(screen.getByText(zh.metadataError.replace('{error}', error))).toBeTruthy()
const toggle = screen.getByRole('switch', { name: zh.partToggle.replace('{name}', '导航组件') })
expect(toggle).toHaveProperty('disabled', false)
fireEvent.click(toggle)
expect(actions.setRowEnabled).toHaveBeenCalledExactlyOnceWith('include:sidebar', false)
fireEvent.change(search, { target: { value: 'Sidebar component' } })
expect(screen.getByText(zh.partsFilterEmpty)).toBeTruthy()
setLanguage(en)
expect(screen.getByRole('button', { name: en.configureRow.replace('{name}', 'Sidebar component') })).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.configureRow.replace('{name}', 'Sidebar component') }))
expect(document.querySelector('[data-plugin-row-detail]')?.getAttribute('data-plugin-row-detail')).toBe('dsh-better-sidebar#sidebar')
expect(screen.getByRole('form', { name: 'row settings' })).toBeTruthy()
expect(screen.getByRole('heading', { level: 3 }).textContent).toBe('Sidebar component')
expect(screen.getByText('Sidebar navigation')).toBeTruthy()
expect(screen.queryByText('Config summary')).toBeNull()
setLanguage(zh)
expect(screen.getByRole('heading', { level: 3 }).textContent).toBe('导航组件')
expect(screen.getByText('侧边导航')).toBeTruthy()
expect(screen.getByText('sidebar')).toBeTruthy()
expect(screen.getByText('@acme/dsh-sidebar-widget')).toBeTruthy()
expect(screen.getByText(zh.metadataError.replace('{error}', error))).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: zh.backToPackage.replace('{name}', '个人工具') }))
expect(screen.getByRole('heading', { level: 3 }).textContent).toBe('个人工具')
})
describe('configuration pages', () => {
const bodies: SlotBodies = {
'plugins.item:bash': view => view === 'summary' ? 'Limits every command.' : <form aria-label="bash form" />,
'plugins.bundle.config:dsh-better-sidebar': view => view === 'page' ? <form aria-label="sidebar form" /> : null,
'plugins.row.config:dsh-better-sidebar#sidebar': view => view === 'summary' ? 'The sidebar row.' : <form aria-label="row form" />,
}
it('keeps configuration and installed plugins on the main list and shows eligible official plugins on the secondary page', () => {
const team = '@deepseek-ai/dsh-experimental-agent-team-profile'
renderTab(
{ packages: [
pkg({ name: '@deepseek-ai/dsh-experimental-badge-skill-bundle', official: true, enabled: false }),
pkg({ name: '@deepseek-ai/dsh-experimental-agent-team-profile', installed: false, optional: true, removable: false, enabled: false }),
pkg({ name: '@deepseek-ai/dsh-subagent-claude-code', official: true, availability: 'missing', installed: false, enabled: false }),
pkg({ name: '@deepseek-ai/dsh-subagent-codex', official: true, enabled: false }),
pkg({ name: '@deepseek-ai/dsh-experimental-tool-worktree', official: true, enabled: false }),
pkg({ name: '@deepseek-ai/dsh-experimental-session-search', installed: false, optional: true, enabled: false, listedByDefault: false }),
pkg({ name: '@deepseek-ai/dsh-experimental-ralph-bundle', installed: false, optional: true, enabled: false, listedByDefault: false }),
pkg({ name: '@acme/new-official', official: true }),
pkg(),
] },
{ items: [{ id: 'bash', label: 'Shell' }] },
bodies,
)
const basic = document.querySelector('[data-plugin-group="basic"]') as HTMLElement
const extensions = document.querySelector('[data-plugin-group="extensions"]') as HTMLElement
expect(document.querySelector('[data-plugin-group="more"]')).toBeNull()
expect([...document.querySelectorAll('[data-plugin-group]')].map(group => group.getAttribute('data-plugin-group')))
.toEqual(['basic', 'extensions', 'bundles'])
expect(within(basic).getAllByRole('listitem').map(card => card.getAttribute('data-plugin-item'))).toEqual(['bash'])
expect(within(extensions).getByRole('heading', { name: en.extensionsTitle })).toBeTruthy()
expect(within(extensions).getByRole('button', { name: en.moreAction }).textContent).toBe(en.moreAction)
expect(within(extensions).getAllByRole('listitem').map(card => card.getAttribute('data-plugin-package')))
.toEqual(['@deepseek-ai/dsh-experimental-agent-team-profile', '@acme/new-official'])
expect(document.querySelector('[data-plugin-count]')).toBeNull()
expect(within(basic).getByText('Limits every command.')).toBeTruthy()
expect(within(basic).queryByRole('switch', { name: en.enableToggle.replace('{name}', 'Shell') })).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', team) }))
fireEvent.click(screen.getByRole('button', { name: en.backToList }))
expect(screen.getByRole('heading', { name: en.title, level: 1 })).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
expect(screen.getByRole('heading', { name: en.moreTitle, level: 1 })).toBeTruthy()
expect(screen.queryByRole('heading', { name: en.moreTitle, level: 3 })).toBeNull()
expect([...document.querySelectorAll('[data-plugin-group]')].map(group => group.getAttribute('data-plugin-group')))
.toEqual(['more'])
const more = document.querySelector('[data-plugin-group="more"]') as HTMLElement
expect(within(more).getAllByRole('listitem').map(card => card.getAttribute('data-plugin-package')))
.toEqual([team, '@acme/new-official', '@deepseek-ai/dsh-experimental-tool-worktree', '@deepseek-ai/dsh-experimental-badge-skill-bundle', '@deepseek-ai/dsh-subagent-codex', '@deepseek-ai/dsh-subagent-claude-code'])
expect(document.querySelector('[data-plugin-package="dsh-better-sidebar"]')).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', team) }))
fireEvent.click(screen.getByRole('button', { name: en.backToPackage.replace('{name}', en.moreTitle) }))
expect(screen.getByRole('heading', { name: en.moreTitle, level: 1 })).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.backToList }))
expect(document.querySelector('[data-plugin-group="more"]')).toBeNull()
expect(document.querySelector('[data-plugin-package="dsh-better-sidebar"]')).not.toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'Shell') }))
const detail = document.querySelector('[data-plugin-item-detail="bash"]') as HTMLElement
expect(within(detail).getByRole('heading', { level: 3 }).textContent).toBe('Shell')
expect(within(detail).getByText('Limits every command.')).toBeTruthy()
expect(within(detail).getByRole('form', { name: 'bash form' })).toBeTruthy()
fireEvent.click(within(detail).getByRole('button', { name: en.backToList }))
expect(document.querySelector('[data-plugin-item-detail]')).toBeNull()
expect(screen.getByRole('heading', { name: en.basicTitle })).toBeTruthy()
})
it.each([
'@deepseek-ai/dsh-experimental-session-search',
'@deepseek-ai/dsh-experimental-ralph-bundle',
'@acme/shipped-exploration',
])('omits %s from default lists while retaining explicit profile selections and installations', (name) => {
const entry = pkg({ name, installed: false, optional: true, removable: false, enabled: false, listedByDefault: false })
const other = pkg({ name: '@deepseek-ai/dsh-experimental-session-titles-bundle', installed: false, optional: true, enabled: false })
const { actions, navigation, set, setLanguage } = renderTab({ packages: [other, entry] })
const card = () => document.querySelector(`[data-plugin-package="${name}"]`)
for (const dict of [en, zh]) {
setLanguage(dict)
expect(card()).toBeNull()
fireEvent.click(screen.getByRole('button', { name: dict.moreAction }))
expect(card()).toBeNull()
fireEvent.click(screen.getByRole('button', { name: dict.backToList }))
}
setLanguage(en)
set({ packages: [other, { ...entry, enabled: true }] })
expect(card()).not.toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', name) }))
fireEvent.click(screen.getByRole('switch', { name: en.enableToggle.replace('{name}', name) }))
expect(actions.setEnabled).toHaveBeenCalledExactlyOnceWith(name, false)
set({ packages: [other, entry] })
expect(card()).toBeNull()
expect(document.querySelector('[data-plugin-detail]')).toBeNull()
expect(document.querySelector('[data-plugin-group="more"]')).toBeNull()
act(() => { navigation.actions.setView({ kind: 'more' }) })
set({ packages: [other, { ...entry, installed: true, availability: 'profile' }] })
expect(card()).not.toBeNull()
})
it.each([en, zh])('keeps disabled CUA on the secondary page with experimental labels', (dict) => {
const name = '@deepseek-ai/dsh-cua-bundle'
const entry = (enabled: boolean) => pkg({ name, official: true, enabled, meta: { title: 'Computer Use' } })
const b = renderTab({ packages: [entry(false)] })
b.setLanguage(dict)
expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeNull()
fireEvent.click(screen.getByRole('button', { name: dict.moreAction }))
const card = document.querySelector(`[data-plugin-package="${name}"]`) as HTMLElement
expect(within(card).getByText(dict.statusBeta)).toBeTruthy()
fireEvent.click(within(card).getByRole('button', { name: dict.openDetail.replace('{name}', 'Computer Use') }))
const detail = document.querySelector('[data-plugin-detail]') as HTMLElement
expect(within(detail).getByText(dict.statusBeta)).toBeTruthy()
fireEvent.click(within(detail).getByRole('switch', { name: dict.enableToggle.replace('{name}', 'Computer Use') }))
expect(b.actions.setEnabled).toHaveBeenCalledExactlyOnceWith(name, true)
b.set({ packages: [entry(true)] })
fireEvent.click(screen.getByRole('button', { name: dict.backToPackage.replace('{name}', dict.moreTitle) }))
expect(screen.getByRole('heading', { name: dict.moreTitle, level: 1 })).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: dict.backToList }))
const enabledCard = document.querySelector(`[data-plugin-package="${name}"]`) as HTMLElement
expect(within(enabledCard).getByText(dict.statusBeta)).toBeTruthy()
fireEvent.click(within(enabledCard).getByRole('switch', { name: dict.enableToggle.replace('{name}', 'Computer Use') }))
expect(b.actions.setEnabled).toHaveBeenLastCalledWith(name, false)
b.set({ packages: [entry(false)] })
expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeNull()
fireEvent.click(screen.getByRole('button', { name: dict.moreAction }))
expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeTruthy()
})
it.each([
'@deepseek-ai/dsh-experimental-session-titles-bundle',
'@deepseek-ai/dsh-creation-recommendations',
'@deepseek-ai/dsh-experimental-plugin-recommendations',
])('returns %s details to the secondary page and retains it across React remounts', (name) => {
const b = renderTab({ packages: [pkg({ name, official: true, enabled: false })] })
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
expect(screen.getByRole('heading', { name: en.moreTitle, level: 1 })).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', name) }))
fireEvent.click(screen.getByRole('button', { name: en.backToPackage.replace('{name}', en.moreTitle) }))
expect(screen.getByRole('heading', { name: en.moreTitle, level: 1 })).toBeTruthy()
b.hidePage()
b.showPage()
expect(screen.getByRole('heading', { name: en.moreTitle, level: 1 })).toBeTruthy()
expect(document.querySelector(`[data-plugin-package="${name}"]`)).not.toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.backToList }))
expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeNull()
})
it.each([
'@deepseek-ai/dsh-experimental-tool-worktree',
'@deepseek-ai/dsh-creation-recommendations',
'@deepseek-ai/dsh-experimental-plugin-recommendations',
])('pins enabled %s onto the main list and removes it when disabled', (first) => {
const team = '@deepseek-ai/dsh-experimental-agent-team-profile'
const second = '@deepseek-ai/dsh-experimental-cot-translation-bundle'
const entry = (name: string, enabled: boolean): PackageView => pkg({ name, official: true, enabled })
const b = renderTab({ packages: [entry(second, true), entry(team, true), entry(first, false)] })
const cards = (group: 'extensions' | 'more'): (string | null)[] => {
const list = document.querySelector(`[data-plugin-group="${group}"]`) as HTMLElement
return within(list).getAllByRole('listitem').map(card => card.getAttribute('data-plugin-package'))
}
expect(cards('extensions')).toEqual([team, second])
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
expect(cards('more')).toEqual([team, first, second])
fireEvent.click(screen.getByRole('switch', { name: en.enableToggle.replace('{name}', first) }))
expect(b.actions.setEnabled).toHaveBeenCalledWith(first, true)
b.set({ packages: [entry(second, true), entry(team, true), entry(first, true)] })
expect(cards('more')).toEqual([team, first, second])
fireEvent.click(screen.getByRole('button', { name: en.backToList }))
expect(cards('extensions')).toEqual([team, first, second])
fireEvent.click(screen.getByRole('switch', { name: en.enableToggle.replace('{name}', first) }))
expect(b.actions.setEnabled).toHaveBeenCalledWith(first, false)
b.set({ packages: [entry(second, true), entry(team, true), entry(first, false)] })
expect(cards('extensions')).toEqual([team, second])
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
expect(cards('more')).toEqual([team, first, second])
})
it('returns a held-back bundle opened outside the lists to the list that shows it', () => {
const name = '@deepseek-ai/dsh-experimental-session-titles-bundle'
const b = renderTab({ packages: [pkg({ name, official: true, enabled: false })] })
act(() => { b.navigation.actions.setView({ kind: 'package', name }) })
expect(document.querySelector(`[data-plugin-detail="${name}"]`)).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.backToPackage.replace('{name}', en.moreTitle) }))
expect(screen.getByRole('heading', { name: en.moreTitle, level: 1 })).toBeTruthy()
b.set({ packages: [pkg({ name, official: true, enabled: true })] })
act(() => { b.navigation.actions.setView({ kind: 'package', name }) })
fireEvent.click(screen.getByRole('button', { name: en.backToList }))
expect(screen.getByRole('heading', { name: en.title, level: 1 })).toBeTruthy()
})
it('starts on the main list when the navigation store is recreated', () => {
const first = createNavigationStore().create()
first.actions.setView({ kind: 'more' })
expect(createNavigationStore().create().getSnapshot()).toEqual({ tab: 'installed', view: { kind: 'list' } })
})
it('counts an official plugin as content: the empty line waits for a page with nothing at all', () => {
renderTab({ packages: [] }, { items: [{ id: 'bash', label: 'Shell' }] }, bodies)
expect(screen.queryByText(en.empty)).toBeNull()
expect(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'Shell') })).toBeTruthy()
})
it('gives an official plugin without artwork of its own the default artwork', () => {
renderTab({ packages: [] }, { items: [{ id: 'custom-tool', label: 'Custom' }] })
const card = document.querySelector('[data-plugin-item="custom-tool"]') as HTMLElement
const stops = [...card.querySelectorAll('stop')].map(stop => stop.getAttribute('stop-color'))
expect(stops).toEqual(['#54ECE7', '#658EFF'])
})
it('renders a bundle\'s own configuration on its page, and no configure control on a row without one', () => {
renderTab({ packages: [pkg({ rows: [row()] })] }, { bundles: new Set(['dsh-better-sidebar']) }, bodies)
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') }))
const detail = document.querySelector('[data-plugin-detail]') as HTMLElement
expect(within(detail).getByRole('form', { name: 'sidebar form' })).toBeTruthy()
expect(within(detail).queryByRole('button', { name: en.configureRow.replace('{name}', 'dsh-better-sidebar') })).toBeNull()
})
it.each([false, true])('opens row configuration and returns to the bundle and originating list, from secondary page: %s', (fromMore) => {
const theme = row({ rowId: 'theme', moduleName: 'dsh-better-sidebar/theme', entryId: 'include:theme' as PluginEntryId })
renderTab({ packages: [pkg({ official: fromMore, rows: [row(), theme] })] }, { rows: new Set(['dsh-better-sidebar#sidebar']) }, bodies)
if (fromMore) fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') }))
expect(screen.queryByRole('button', { name: en.configureRow.replace('{name}', 'dsh-better-sidebar/theme') })).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.configureRow.replace('{name}', 'dsh-better-sidebar') }))
const page = document.querySelector('[data-plugin-row-detail="dsh-better-sidebar#sidebar"]') as HTMLElement
expect(within(page).getByRole('heading', { level: 3 }).textContent).toBe('dsh-better-sidebar')
expect(within(page).getByText('dsh-better-sidebar', { selector: 'code' })).toBeTruthy()
expect(within(page).getByText('sidebar', { selector: 'code' })).toBeTruthy()
expect(within(page).getByText('The sidebar row.')).toBeTruthy()
expect(within(page).getByRole('form', { name: 'row form' })).toBeTruthy()
fireEvent.click(within(page).getByRole('button', { name: en.backToPackage.replace('{name}', 'dsh-better-sidebar') }))
expect(document.querySelector('[data-plugin-row-detail]')).toBeNull()
expect(document.querySelector('[data-plugin-detail="dsh-better-sidebar"]')).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: fromMore ? en.backToPackage.replace('{name}', en.moreTitle) : en.backToList }))
expect(screen.getByRole('heading', { name: fromMore ? en.moreTitle : en.title, level: 1 })).toBeTruthy()
})
})
describe('detail contributions', () => {
const subjects: PluginsSubject[] = []
const label = (owner: unknown): string => {
const subject = subjectOf(owner)
if (subject === undefined) return 'none'
subjects.push(subject)
if (subject.kind === 'bundle') return `bundle ${subject.pkg.name}`
if (subject.kind === 'row') return `row ${subject.pkg.name}#${subject.row.rowId}`
return `item ${subject.id}`
}
const bodies: SlotBodies = {
'plugins.item:bash': view => view === 'summary' ? 'Limits every command.' : <form aria-label="bash form" />,
'plugins.row.config:dsh-better-sidebar#sidebar': view => view === 'summary' ? 'The sidebar row.' : <form aria-label="row form" />,
'plugins.detail.actions:': (_view, owner) => <button type="button">{`act ${label(owner)}`}</button>,
'plugins.detail.badge:': (_view, owner) => <span>{`badge ${label(owner)}`}</span>,
'plugins.detail.section:': (_view, owner) => <section aria-label={`section ${label(owner)}`} />,
}
it('renders the contributed actions, badges, and sections on each page, told what the page is about', () => {
renderTab(
{ packages: [pkg({ rows: [row()] })] },
{ items: [{ id: 'bash', label: 'Shell' }], rows: new Set(['dsh-better-sidebar#sidebar']) },
bodies,
)
// The cards carry none of it.
expect(screen.queryByRole('button', { name: /^act / })).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') }))
const detail = document.querySelector('[data-plugin-detail]') as HTMLElement
const act = within(detail).getByRole('button', { name: 'act bundle dsh-better-sidebar' })
expect(within(detail).getByText('badge bundle dsh-better-sidebar')).toBeTruthy()
const section = within(detail).getByRole('region', { name: 'section bundle dsh-better-sidebar' })
// The contributed actions come before the page's own switch; the sections after the rows.
const toggle = within(detail).getByRole('switch', { name: en.enableToggle.replace('{name}', 'dsh-better-sidebar') })
expect(act.compareDocumentPosition(toggle) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
const rows = detail.querySelector('[data-plugin-rows]') as HTMLElement
expect(rows.compareDocumentPosition(section) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy()
// A contribution sees the bundle's facts, not the page's own state.
expect(subjects.at(-1)).toEqual({
kind: 'bundle',
pkg: { name: 'dsh-better-sidebar', version: '0.16.0', installed: true, official: false, availability: 'profile', enabled: true, rows: [{ rowId: 'sidebar', moduleName: 'dsh-better-sidebar', enabled: true }] },
})
fireEvent.click(screen.getByRole('button', { name: en.configureRow.replace('{name}', 'dsh-better-sidebar') }))
const page = document.querySelector('[data-plugin-row-detail]') as HTMLElement
expect(within(page).getByRole('button', { name: 'act row dsh-better-sidebar#sidebar' })).toBeTruthy()
expect(within(page).getByText('badge row dsh-better-sidebar#sidebar')).toBeTruthy()
expect(within(page).getByRole('region', { name: 'section row dsh-better-sidebar#sidebar' })).toBeTruthy()
expect(subjects.at(-1)).toMatchObject({ kind: 'row', row: { rowId: 'sidebar', moduleName: 'dsh-better-sidebar', enabled: true } })
fireEvent.click(within(page).getByRole('button', { name: en.backToPackage.replace('{name}', 'dsh-better-sidebar') }))
fireEvent.click(screen.getByRole('button', { name: en.backToList }))
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'Shell') }))
const item = document.querySelector('[data-plugin-item-detail="bash"]') as HTMLElement
expect(within(item).getByRole('button', { name: 'act item bash' })).toBeTruthy()
expect(within(item).getByText('badge item bash')).toBeTruthy()
expect(within(item).getByRole('region', { name: 'section item bash' })).toBeTruthy()
expect(within(item).getByRole('form', { name: 'bash form' })).toBeTruthy()
})
it('leaves the version out of a bundle the Host reports none for', () => {
const unversioned: PackageView = { name: 'dsh-better-sidebar', installed: true, official: false, availability: 'profile', optional: false, removable: true, updatable: false, enabled: true, rows: [] }
renderTab({ packages: [unversioned] }, {}, bodies)
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') }))
expect(subjects.at(-1)).toEqual({ kind: 'bundle', pkg: { name: 'dsh-better-sidebar', installed: true, official: false, availability: 'profile', enabled: true, rows: [] } })
})
})
it('preserves the full package name and description for a third-party scope', () => {
const name = '@acme/dsh-experimental-agent-team-profile'
const { setLanguage } = renderTab({ packages: [pkg({ name, meta: { description: { en: 'Third-party description.' } } })] })
setLanguage(zh)
fireEvent.click(screen.getByRole('button', { name: zh.openDetail.replace('{name}', name) }))
expect(screen.getByRole('heading', { level: 3 }).textContent).toBe(name)
expect(screen.getByText('Third-party description.')).toBeTruthy()
expect(document.querySelector('[data-plugin-name]')?.textContent).toBe(name)
})
it.each([
{ locale: en, placeholder: 'for example dsh-plugin-whale-pet' },
{ locale: zh, placeholder: '例如 dsh-plugin-whale-pet' },
])('uses the package example in $placeholder and the install guide', ({ locale, placeholder }) => {
const { actions, setLanguage } = renderTab({ install: { ...IDLE_INSTALL, open: true } })
setLanguage(locale)
expect(screen.getByPlaceholderText(placeholder)).toBeTruthy()
expect(screen.getByText(locale.installUpgradeNotice)).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: locale.installGuideToggle }))
expect(screen.getByText('dsh-plugin-whale-pet')).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: locale.installGuideFillAria.replace('{example}', 'dsh-plugin-whale-pet') }))
expect(actions.editInstallSpec).toHaveBeenCalledExactlyOnceWith('dsh-plugin-whale-pet')
})
it.each([
{ example: INSTALL_GIT_EXAMPLE, key: 'installGitTemplateHint' as const },
{ example: INSTALL_PATH_EXAMPLE, key: 'installPathTemplateHint' as const },
])('asks for a real value when the field holds $example, including after a language change', ({ example, key }) => {
const install = { ...IDLE_INSTALL, open: true, spec: example }
const { set, setLanguage } = renderTab({ install })
const hint = screen.getByRole('status')
expect(hint.textContent).toBe(en[key])
expect(screen.getByRole('textbox', { name: en.installSpecLabel }).getAttribute('aria-describedby')).toBe(hint.id)
setLanguage(zh)
expect(screen.getByRole('status').textContent).toBe(zh[key])
expect(screen.getByRole('note').textContent).toContain(zh.installGuideSafety)
set({ install: { ...install, spec: '@actual/plugin' } })
expect(screen.queryByRole('status')).toBeNull()
expect(screen.getByRole('textbox', { name: zh.installSpecLabel }).getAttribute('aria-describedby')).toBeNull()
})
it('opens a guide under the field and drops an example into it', () => {
const { actions } = renderTab({ install: { ...IDLE_INSTALL, open: true } })
expect(screen.queryByText(en.installGuideIdHint)).toBeNull()
const toggle = screen.getByRole('button', { name: en.installGuideToggle })
expect(toggle.getAttribute('aria-expanded')).toBe('false')
fireEvent.click(toggle)
expect(screen.getByRole('button', { name: en.installGuideHide }).getAttribute('aria-expanded')).toBe('true')
expect(screen.getByText(en.installGuideIdHint)).toBeTruthy()
expect(screen.getAllByRole('listitem')).toHaveLength(1)
fireEvent.click(screen.getByRole('button', { name: en.installGuideFillAria.replace('{example}', en.installGuideIdExample) }))
expect(actions.editInstallSpec).toHaveBeenCalledExactlyOnceWith(en.installGuideIdExample)
fireEvent.click(screen.getByRole('button', { name: en.installGuideHide }))
expect(screen.queryByText(en.installGuideIdHint)).toBeNull()
})
it('opens a bundle\'s page with its facts and rows, and uninstalls from it', () => {
const { actions, set } = renderTab({
packages: [pkg({
meta: { description: { en: 'A sidebar.' } },
rows: [row(), row({ rowId: 'theme', moduleName: 'dsh-better-sidebar/theme', entryId: 'include:theme' as PluginEntryId, enabled: false, phase: null })],
})],
})
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') }))
const detail = document.querySelector('[data-plugin-detail="dsh-better-sidebar"]') as HTMLElement
expect(within(detail).getByRole('heading', { level: 3 }).textContent).toBe('dsh-better-sidebar')
// The version sits beside the name as a tag; the crumb only leads back.
expect(within(detail).getByText('v0.16.0')).toBeTruthy()
expect(document.querySelector('[data-plugin-name]')?.textContent).toBe('dsh-better-sidebar')
expect(within(detail).getByRole('button', { name: en.backToList }).textContent).toBe(en.crumbRoot)
expect(within(detail).getByText('A sidebar.')).toBeTruthy()
// The rows, in order, with their state and their module.
const rows = within(detail).getAllByRole('listitem').filter(item => item.hasAttribute('data-plugin-row'))
expect(rows.map(item => item.getAttribute('data-plugin-row'))).toEqual(['include:sidebar', 'include:theme'])
expect(rows[1]?.getAttribute('data-state')).toBe('off')
expect(within(detail).getByText(en.partsCountTotal.replace('{count}', '2'), { exact: false })).toBeTruthy()
expect(within(detail).getByRole('switch', { name: en.partToggle.replace('{name}', 'dsh-better-sidebar/theme') })).toBeTruthy()
expect(within(detail).getByText(en.rowPhaseActive)).toBeTruthy()
expect(within(detail).getByText(en.partOff)).toBeTruthy()
fireEvent.click(within(detail).getByRole('button', { name: en.uninstallLabel.replace('{name}', 'dsh-better-sidebar') }))
expect(actions.uninstall).toHaveBeenCalledWith('dsh-better-sidebar')
fireEvent.click(within(detail).getByRole('switch', { name: en.enableToggle.replace('{name}', 'dsh-better-sidebar') }))
expect(actions.setEnabled).toHaveBeenCalledWith('dsh-better-sidebar', false)
// A problem and a protection the Host reports read on the page in the dictionary's words; the page leaves with the crumb.
set({ packages: [pkg({ error: { code: 'operation-error', diagnostic: 'unreadable' }, readOnlyReason: 'management-required' })] })
expect(within(detail).getByText(`${en.reasonLabel}: unreadable`)).toBeTruthy()
expect(within(detail).getByText(en.reasonManagementRequired)).toBeTruthy()
expect(within(detail).getByRole('button', { name: en.uninstallLabel.replace('{name}', 'dsh-better-sidebar') })).toHaveProperty('disabled', true)
expect(within(detail).getByText(en.partsEmpty)).toBeTruthy()
set({ packages: [pkg({ error: { code: 'not-bundle' } })] })
expect(within(detail).getByText(`${en.reasonLabel}: ${en.reasonNotBundle}`)).toBeTruthy()
set({ packages: [pkg({ error: { code: 'operation-error' } })] })
expect(within(detail).getByText(`${en.reasonLabel}: ${en.reasonOperationError}`)).toBeTruthy()
// An incompatibility reads from its structured packages in the dictionary's words, one sentence per package.
set({ packages: [pkg({ error: { code: 'incompatible-version', incompatible: [INCOMPATIBLE, { ...INCOMPATIBLE, name: 'other' }] } })] })
expect(within(detail).getByText(`${en.reasonLabel}: ${incompatibleText()} ${incompatibleText('other')} ${en.reasonIncompatibleInstalled}`)).toBeTruthy()
set({ packages: [pkg({ error: { code: 'incompatible-version' } })] })
expect(within(detail).getByText(`${en.reasonLabel}: ${en.reasonIncompatibleVersionUnnamed} ${en.reasonIncompatibleInstalled}`)).toBeTruthy()
fireEvent.click(within(detail).getByRole('button', { name: en.backToList }))
expect(document.querySelector('[data-plugin-detail]')).toBeNull()
// A bundle that leaves the list drops back to the cards.
const { version: _version, ...unversioned } = pkg()
set({ packages: [unversioned] })
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') }))
expect(screen.queryByText('No description.')).toBeNull()
expect(screen.queryByText(en.versionTag.replace('{version}', '0.16.0'))).toBeNull()
set({ packages: [] })
expect(document.querySelector('[data-plugin-detail]')).toBeNull()
expect(screen.getByText(en.empty)).toBeTruthy()
})
it('switches the rows of a bundle that is on, filters a long list, and locks what the Host will not address', () => {
const rows = Array.from({ length: 12 }, (_row, index): PackageRow => {
const live = row({
rowId: `row-${String(index)}`, entryId: `include:row-${String(index)}` as PluginEntryId,
...index === 1 ? { readOnlyReason: 'unaddressable' as const } : {},
...index === 3 ? { phase: 'failed' as const } : {},
...index === 4 ? { phase: 'loading' as const } : {},
...index === 5 ? { phase: 'unloading' as const } : {},
})
if (index !== 2) return live
// The third row has no live entry: nothing to switch.
const { entryId: _entryId, ...unmounted } = live
return { ...unmounted, enabled: false, phase: null }
})
const { actions, set } = renderTab({ packages: [pkg({ rows })], busy: [rowKey('include:row-5')] })
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', 'dsh-better-sidebar') }))
const detail = document.querySelector('[data-plugin-detail]') as HTMLElement
const toggle = (id: string): HTMLElement => within(detail.querySelector<HTMLElement>(`[data-plugin-row="${id}"]`)!)
.getByRole('switch', { name: en.partToggle.replace('{name}', 'dsh-better-sidebar') })
expect(within(detail).getByText(`${en.partsCountTotal.replace('{count}', '12')} · ${en.partsCountRunning.replace('{count}', '8')} · ${en.partsCountOff.replace('{count}', '1')} · ${en.partsCountFailed.replace('{count}', '1')}`)).toBeTruthy()
fireEvent.click(toggle('include:row-0'))
expect(actions.setRowEnabled).toHaveBeenCalledWith('include:row-0', false)
// A protected row, a row without a live entry, and a row with a write in flight cannot be switched.
const locked = toggle('include:row-1')
expect(locked).toHaveProperty('disabled', true)
expect(locked.getAttribute('title')).toBe(en.reasonUnaddressable)
expect(toggle('row-2')).toHaveProperty('disabled', true)
fireEvent.click(toggle('row-2'))
expect(actions.setRowEnabled).toHaveBeenCalledTimes(1)
expect(toggle('include:row-5')).toHaveProperty('disabled', true)
expect(within(detail).getByText(en.rowPhaseFailed)).toBeTruthy()
expect(within(detail).getByText(en.rowPhaseLoading)).toBeTruthy()
expect(within(detail).getByText(en.rowPhaseUnloading)).toBeTruthy()
expect(document.querySelector('[data-plugin-row="include:row-3"]')?.getAttribute('data-state')).toBe('failed')
expect(document.querySelector('[data-plugin-row="include:row-4"] [data-state="ongoing"]')).not.toBeNull()
expect(document.querySelector('[data-plugin-row="include:row-5"] [data-state="ongoing"]')).not.toBeNull()
// A long list gets a filter; nothing matching says so.
const filter = within(detail).getByRole('searchbox', { name: en.partsFilter })
fireEvent.change(filter, { target: { value: 'ROW-1' } })
expect(within(detail).getAllByRole('listitem').filter(item => item.hasAttribute('data-plugin-row'))).toHaveLength(3)
fireEvent.change(filter, { target: { value: 'nothing' } })
expect(within(detail).getByText(en.partsFilterEmpty)).toBeTruthy()
fireEvent.change(filter, { target: { value: '' } })
// A bundle that is off shows its rows without switches.
set({ packages: [pkg({ enabled: false, rows: rows.slice(0, 2).map(item => ({ ...item, enabled: false, phase: null })) })] })
expect(within(detail).queryByRole('switch', { name: en.partToggle.replace('{name}', 'dsh-better-sidebar') })).toBeNull()
expect(within(detail).getAllByText(en.partOff)).toHaveLength(2)
// A row without a fiber, on a bundle that is on, reads idle.
set({ packages: [pkg({ rows: [row({ phase: null })] })] })
expect(within(detail).getByText(en.rowStateIdle)).toBeTruthy()
})
describe.each(['package', 'custom registry'] as const)('%s input IME confirmation', (input) => {
beforeEach(() => { vi.useFakeTimers() })
afterEach(() => { cleanup(); vi.useRealTimers() })
const inputName = input === 'package' ? en.installSpecLabel : en.registryCustom
const open: InstallState = {
...IDLE_INSTALL, open: true, spec: 'dsh-new', registryOpen: true,
registry: { kind: 'custom', url: 'https://npm.corp.example/' },
}
const enter = (field: HTMLElement) => {
// oxlint-disable-next-line typescript/no-deprecated -- Exercise native Enter's legacy keyCode in the IME regression.
const event = new KeyboardEvent('keydown', { key: 'Enter', isComposing: false, keyCode: 13, bubbles: true, cancelable: true })
fireEvent(field, event)
expect(event.defaultPrevented).toBe(false)
}
it('guards active composition and the first 10ms after compositionend without native flags', () => {
const { actions } = renderTab({ install: open })
const field = screen.getByRole('textbox', { name: inputName })
fireEvent.compositionStart(field)
enter(field)
expect(actions.runInstall).not.toHaveBeenCalled()
fireEvent.compositionEnd(field)
enter(field)
act(() => { vi.advanceTimersByTime(9) })
enter(field)
expect(actions.runInstall).not.toHaveBeenCalled()
act(() => { vi.advanceTimersByTime(1) })
enter(field)
expect(actions.runInstall).toHaveBeenCalledOnce()
})
it('keeps composition state independent from the other input', () => {
const { actions } = renderTab({ install: open })
const field = screen.getByRole('textbox', { name: inputName })
const other = screen.getByRole('textbox', { name: input === 'package' ? en.registryCustom : en.installSpecLabel })
fireEvent.compositionStart(field)
enter(other)
expect(actions.runInstall).toHaveBeenCalledOnce()
enter(field)
expect(actions.runInstall).toHaveBeenCalledOnce()
})
it.each(['active', 'ended'] as const)('clears %s composition on blur and dialog reopen', (phase) => {
const { actions, set } = renderTab({ install: open })
const field = screen.getByRole('textbox', { name: inputName })
fireEvent.compositionStart(field)
if (phase === 'ended') fireEvent.compositionEnd(field)
fireEvent.blur(field)
enter(field)
expect(actions.runInstall).toHaveBeenCalledOnce()
fireEvent.compositionStart(field)
if (phase === 'ended') fireEvent.compositionEnd(field)
set({ install: { ...open, open: false } })
set({ install: open })
enter(screen.getByRole('textbox', { name: inputName }))
expect(actions.runInstall).toHaveBeenCalledTimes(2)
})
if (input === 'custom registry') {
it('clears composition when the registry menu closes and reopens', () => {
const { actions, set } = renderTab({ install: open })
fireEvent.compositionStart(screen.getByRole('textbox', { name: inputName }))
set({ install: { ...open, registryOpen: false } })
set({ install: open })
enter(screen.getByRole('textbox', { name: inputName }))
expect(actions.runInstall).toHaveBeenCalledOnce()
})
}
it.each([
{ signal: 'isComposing', isComposing: true, keyCode: 13 },
{ signal: 'legacy keyCode 229', isComposing: false, keyCode: 229 },
])('leaves $signal Enter to the IME and installs on the next ordinary Enter', ({ isComposing, keyCode }) => {
const { actions } = renderTab({
install: {
...IDLE_INSTALL, open: true, spec: 'dsh-new', registryOpen: input === 'custom registry',
registry: { kind: 'custom', url: 'https://npm.corp.example/' },
},
})
const field = screen.getByRole('textbox', { name: input === 'package' ? en.installSpecLabel : en.registryCustom })
fireEvent.compositionStart(field)
if (!isComposing) fireEvent.compositionEnd(field)
// oxlint-disable-next-line typescript/no-deprecated -- Exercise the legacy IME signal when isComposing is false.
const confirm = new KeyboardEvent('keydown', { key: 'Enter', isComposing, keyCode, bubbles: true, cancelable: true })
fireEvent(field, confirm)
expect(actions.runInstall).not.toHaveBeenCalled()
expect(confirm.defaultPrevented).toBe(false)
if (isComposing) fireEvent.compositionEnd(field)
act(() => { vi.advanceTimersByTime(10) })
fireEvent.keyDown(field, { key: 'Enter', isComposing: false, keyCode: 13 })
expect(actions.runInstall).toHaveBeenCalledOnce()
})
})
it('takes a spec, checks it, and words what the check refused', () => {
const { actions, set } = renderTab({ install: { ...IDLE_INSTALL, open: true } })
expect(screen.getByText(en.installDescription)).toBeTruthy()
const install = () => screen.getByRole('button', { name: en.installRun })
expect(install()).toHaveProperty('disabled', true)
const field = screen.getByRole('textbox', { name: en.installSpecLabel })
fireEvent.change(field, { target: { value: 'dsh-x' } })
expect(actions.editInstallSpec).toHaveBeenCalledWith('dsh-x')
expect(document.querySelector('[data-terminal]')).toBeNull()
expect(screen.queryByRole('checkbox')).toBeNull()
set({ install: { ...IDLE_INSTALL, open: true, spec: ' dsh-x ' } })
fireEvent.keyDown(screen.getByRole('textbox', { name: en.installSpecLabel }), { key: 'Enter' })
fireEvent.click(install())
expect(actions.runInstall).toHaveBeenCalledTimes(2)
// The check keeps the field and the button inert.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'checking' } })
expect(screen.getByRole('textbox', { name: en.installSpecLabel })).toHaveProperty('disabled', true)
const checking = screen.getByRole('button', { name: en.installChecking })
expect(checking).toHaveProperty('disabled', true)
expect(checking.querySelector('[data-state="ongoing"]')).not.toBeNull()
fireEvent.keyDown(screen.getByRole('textbox', { name: en.installSpecLabel }), { key: 'Enter' })
expect(actions.runInstall).toHaveBeenCalledTimes(2)
// Each refusal reads under the field.
const problems: [string, string][] = [
['invalid-spec', en.installProblemInvalid.replace('{reason}', 'r')],
['already-installed', en.installProblemInstalled],
['shipped', en.installProblemShipped],
['not-found', en.installProblemNotFound],
['not-a-package', en.installProblemNotPackage],
['not-a-bundle', en.installProblemNotBundle.replace('{reason}', 'r')],
['network', en.installProblemNetwork],
['unknown', en.installProblemUnknown.replace('{reason}', 'r')],
]
for (const [problem, sentence] of problems) {
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', inputError: { problem: problem as never, reason: 'r' } } })
expect(screen.getByRole('alert').textContent).toBe(sentence)
expect(screen.getByRole('textbox', { name: en.installSpecLabel }).getAttribute('aria-invalid')).toBe('true')
}
// A check no registry answered names them all.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', inputError: { problem: 'network', reason: 'r', registries: [null, MIRROR] } } })
expect(screen.getByRole('alert').textContent).toBe(en.installProblemNetworkAll.replace('{registries}', `${en.registryDefault}, ${en.registryNpmmirror}`))
fireEvent.click(screen.getByRole('button', { name: en.close }))
expect(actions.closeInstall).toHaveBeenCalledTimes(1)
})
it('shows the subject while installing, folds the pnpm output behind the details, and stops through the Host', () => {
const subject = { spec: 'dsh-x', status: 'accepted', kind: 'registry', name: 'dsh-x', version: '1.4.2', description: 'A sidebar.', bundle: true, registry: null } as const
const run = { jobId: 'j1', command: 'pnpm add dsh-x', cwd: '/home/u/.dsh/profiles/web', output: 'Progress: resolved \x1b[96m1\x1b[39m\n' }
const { actions, set } = renderTab({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'running', subject, runs: [run] } })
expect(screen.getByRole('status').textContent).toBe(en.installingTitle)
expect(screen.getByRole('status').parentElement?.querySelector('[data-state="ongoing"]')).not.toBeNull()
expect(screen.getByText('dsh-x')).toBeTruthy()
expect(screen.getByText('A sidebar.')).toBeTruthy()
expect(screen.getByText(en.installVersion.replace('{version}', '1.4.2'))).toBeTruthy()
expect(screen.queryByRole('textbox')).toBeNull()
// The output stays folded until asked for.
expect(document.querySelector('[data-terminal]')).toBeNull()
const details = screen.getByRole('button', { name: en.installDetailsShow })
expect(details.getAttribute('aria-expanded')).toBe('false')
fireEvent.click(details)
expect(actions.toggleInstallDetails).toHaveBeenCalledTimes(1)
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'running', subject, runs: [run], detailsOpen: true } })
expect(screen.getByRole('button', { name: en.installDetailsHide }).getAttribute('aria-expanded')).toBe('true')
expect(screen.getByText(en.installLocation.replace('{dir}', '/home/u/.dsh/profiles/web'))).toBeTruthy()
// The run streams as a terminal: its command line, its coloured output so far, the running label.
expect(screen.getByText('pnpm add dsh-x')).toBeTruthy()
const terminal = document.querySelector('[data-terminal]') as HTMLElement
expect(terminal.hasAttribute('data-running')).toBe(true)
expect(within(terminal).getByText('1').getAttribute('style')).toContain('--dsw-static-blue-500')
expect(within(terminal).getByText(en.terminalRunning)).toBeTruthy()
// A long log folds its middle behind an expand control, so the dialog keeps its height while pnpm talks.
const lines = Array.from({ length: 15 }, (_line, index) => `line ${String(index + 1)}`).join('\n')
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'running', subject, detailsOpen: true, runs: [{ ...run, output: `${lines}\n` }] } })
expect(screen.queryByText('line 8')).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.terminalExpandAria.replace('{n}', '3') }))
expect(screen.getByText('line 8')).toBeTruthy()
// Before the first chunk there is no location to name.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'running', subject, detailsOpen: true } })
expect(screen.getByText(en.terminalNoOutput)).toBeTruthy()
// Cancel and the back control each ask the Host to stop the run; the close control asks too, and closes once the Host confirms.
fireEvent.click(screen.getByRole('button', { name: en.installCancel }))
fireEvent.click(screen.getByRole('button', { name: en.installCancelAndEdit }))
expect(actions.cancelInstall).toHaveBeenCalledTimes(2)
expect(screen.queryByRole('button', { name: en.close })).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.installCloseCancels }))
expect(actions.closeInstall).toHaveBeenCalledOnce()
})
it('waits with the Host through starting, stopping, and applying, and words an unconfirmed stop', () => {
const subject = { spec: 'slow', status: 'accepted', kind: 'registry', name: 'slow', bundle: true, registry: null } as const
const { actions, set } = renderTab({ install: { ...IDLE_INSTALL, open: true, spec: 'slow', phase: 'starting', subject } })
expect(screen.getByRole('status').textContent).toBe(en.installStarting)
expect(screen.getByRole('button', { name: en.installCancel })).toHaveProperty('disabled', false)
expect(screen.getByRole('button', { name: en.installCancelAndEdit })).toHaveProperty('disabled', false)
fireEvent.click(screen.getByRole('button', { name: en.installCloseCancels }))
expect(actions.closeInstall).toHaveBeenCalledOnce()
set({ install: { ...IDLE_INSTALL, open: true, spec: 'slow', phase: 'running', subject } })
fireEvent.click(screen.getByRole('button', { name: en.installCancel }))
fireEvent.click(screen.getByRole('button', { name: en.installCancelAndEdit }))
expect(actions.cancelInstall).toHaveBeenCalledTimes(2)
// While the Host stops the run the terminal reads as cancelled rather than failed.
set({
install: {
...IDLE_INSTALL, open: true, spec: 'slow', phase: 'cancelling', subject, detailsOpen: true,
runs: [{ jobId: 'j', command: 'pnpm add slow', cwd: '/p', output: '', exitCode: null }],
},
})
expect(screen.getByRole('status').textContent).toBe(en.installCancelling)
expect(screen.getByRole('button', { name: en.installCancelling })).toHaveProperty('disabled', true)
expect(screen.getByRole('button', { name: en.close })).toHaveProperty('disabled', false)
expect(within(document.querySelector('[data-terminal]') as HTMLElement).getByText(en.installCancelledShort)).toBeTruthy()
set({ install: { ...IDLE_INSTALL, open: true, spec: 'slow', phase: 'applying', subject } })
expect(screen.getByRole('status').textContent).toBe(en.installApplying)
expect(screen.getByRole('button', { name: en.installCancel })).toHaveProperty('disabled', true)
expect(screen.getByRole('button', { name: en.close })).toHaveProperty('disabled', false)
// A stop the Host could not confirm says so over the running screen.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'slow', phase: 'running', subject, failure: { reason: 'offline', uncertainty: 'cancellation' } } })
expect(screen.getByRole('alert').textContent).toContain('offline')
expect(screen.getByRole('button', { name: en.installCancel })).toHaveProperty('disabled', false)
})
it('offers to enable what a finished install added, and says when it waits for a restart', () => {
const subject = { spec: '/plugins/dsh-x', status: 'accepted', kind: 'path', name: 'dsh-x', bundle: true, registry: null } as const
const { actions, set } = renderTab({
install: {
...IDLE_INSTALL,
open: true,
spec: '/plugins/dsh-x',
phase: 'done',
subject,
runs: [{ jobId: 'j1', command: 'pnpm add /plugins/dsh-x', cwd: '/p', output: 'Done in 1s\n', exitCode: 0 }],
installed: 'dsh-x',
},
})
expect(screen.getByText(en.installedTitle)).toBeTruthy()
expect(screen.getByText(en.installedTitle).parentElement?.querySelector('[data-state="done"]')).not.toBeNull()
// A path without a description reads by its kind.
expect(screen.getByText('dsh-x')).toBeTruthy()
expect(screen.getByText(en.installSubjectPath)).toBeTruthy()
expect(screen.queryByText(en.installDoneRestart)).toBeNull()
// No way back to the spec from here; enabling is the one action.
expect(screen.queryByRole('button', { name: en.installEditAria })).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.installEnableNow }))
expect(actions.enableInstalled).toHaveBeenCalledTimes(1)
set({ install: { ...IDLE_INSTALL, open: true, spec: '/plugins/dsh-x', phase: 'done', subject, installed: 'dsh-x', restartRequired: true, restartReason: 'host-module', enabling: true } })
expect(screen.getByRole('button', { name: en.installEnableNow })).toHaveProperty('disabled', true)
expect(screen.getByText(en.installDoneRestart.replace('{reason}', en.restartReasonHostModule))).toBeTruthy()
// A run that named no bundle leaves only Done.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'done', subject: { spec: 'dsh-x', status: 'accepted', kind: 'registry', name: 'dsh-x', bundle: true, registry: null } } })
expect(screen.getByText(en.installDoneNothing)).toBeTruthy()
expect(screen.queryByRole('button', { name: en.installEnableNow })).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.installClose }))
expect(actions.closeInstall).toHaveBeenCalledTimes(1)
})
it.each([en, zh])('offers an existing bundled plugin without claiming a download or installation', (dict) => {
const name = '@acme/shipped-exploration'
const { actions, setLanguage } = renderTab({ install: {
...IDLE_INSTALL, open: true, spec: name, phase: 'done', installed: name, installedVersion: '1.0.0',
subject: { status: 'accepted', spec: name, name, kind: 'registry', registry: null, bundle: true, bundled: true, version: '1.0.0' },
} })
setLanguage(dict)
expect(screen.getByText(dict.installBundledTitle)).toBeTruthy()
expect(screen.getByText(dict.installBundledDescription)).toBeTruthy()
expect(screen.queryByText(dict.installedTitle)).toBeNull()
expect(screen.queryByRole('button', { name: dict.installDetailsShow })).toBeNull()
fireEvent.click(screen.getByRole('button', { name: dict.installEnableNow }))
expect(actions.enableInstalled).toHaveBeenCalledExactlyOnceWith()
})
it('asks to allow the scripts a blocked install left pending, retries with them, and says what was allowed', () => {
const subject = { spec: 'dsh-x', status: 'accepted', kind: 'registry', name: 'dsh-x', bundle: true, registry: null } as const
const { actions, set } = renderTab({
install: {
...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'failed', subject,
failure: { reason: 'ERR', kind: 'build-blocked', pendingBuilds: ['native', '@scope/other'] },
},
})
expect(screen.getByText(en.installFailureBuildBlocked)).toBeTruthy()
const group = screen.getByRole('group', { name: en.installApprovalTitle })
expect(within(group).getByText('native')).toBeTruthy()
expect(within(group).getByText('@scope/other')).toBeTruthy()
expect(within(group).getByText(en.installApprovalCaution)).toBeTruthy()
// Plain retry would fail the same way, so only the approval is offered.
expect(screen.queryByRole('button', { name: en.installRetry })).toBeNull()
fireEvent.click(within(group).getByRole('button', { name: en.installApproveAndRetry }))
expect(actions.approveBuildsAndRetry).toHaveBeenCalledTimes(1)
// Without the pending names the failure reads as the manual instruction, and plain retry is back.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'failed', subject, failure: { reason: 'ERR', kind: 'build-blocked' } } })
expect(screen.getByText(en.installFailureBuildBlockedManual)).toBeTruthy()
expect(screen.queryByRole('group', { name: en.installApprovalTitle })).toBeNull()
expect(screen.getByRole('button', { name: en.installRetry })).toBeTruthy()
// The installed screen says which scripts were allowed.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'done', subject, installed: 'dsh-x', approvedBuilds: ['native'] } })
expect(screen.getByText(en.installDoneApproved.replace('{names}', 'native'))).toBeTruthy()
})
it('names the exact spec when pnpm installed another version than the one inspected', () => {
const subject = { spec: 'dsh-x', status: 'accepted', kind: 'registry', name: 'dsh-x', version: '1.4.2', bundle: true, registry: null } as const
const other = en.installDoneOtherVersion
.replace('{installed}', '1.4.1').replaceAll('{version}', '1.4.2').replace('{exact}', '[email protected]')
const done = { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'done', installed: 'dsh-x' } as const
const { set } = renderTab({ install: { ...done, subject, installedVersion: '1.4.1' } })
expect(screen.getByText(other)).toBeTruthy()
// The subject card shows the version pnpm installed.
expect(screen.getByText(en.installVersion.replace('{version}', '1.4.1'))).toBeTruthy()
// A run that fell back to another registry may have received another release, so nothing is claimed.
set({ install: { ...done, subject, installedVersion: '1.4.1', attempts: { registries: [null, 'https://mirror.example/'], total: 2 } } })
expect(screen.queryByText(other)).toBeNull()
set({ install: { ...done, subject, installedVersion: '1.4.1', attempts: { registries: [null], total: 2 } } })
expect(screen.getByText(other)).toBeTruthy()
const { name: _name, ...unnamed } = subject
set({ install: { ...done, subject: unnamed, installedVersion: '1.4.1' } })
expect(screen.queryByText(other)).toBeNull()
set({ install: { ...done, subject, installedVersion: '1.4.2' } })
expect(screen.queryByText(other)).toBeNull()
set({ install: { ...done, subject: { ...subject, kind: 'path' }, installedVersion: '1.4.1' } })
expect(screen.queryByText(other)).toBeNull()
})
it('words a failed install by its kind, else in the Host\'s words, and retries it', () => {
const subject = { spec: 'github:a/b', status: 'accepted', kind: 'git', bundle: null, registry: null, host: 'github.com' } as const
const { actions, set } = renderTab({
install: {
...IDLE_INSTALL,
open: true,
spec: 'github:a/b',
phase: 'failed',
subject,
detailsOpen: true,
runs: [{ jobId: 'j1', command: 'pnpm add github:a/b', cwd: '/p', output: 'ERR\n', exitCode: 1 }],
failure: { reason: 'ERR\n', kind: 'network', failedAt: 'spec-host' },
},
})
expect(screen.getByRole('alert').textContent).toBe(en.installFailedTitle)
// A git spec the Host could not reach reads by its own host: no registry stands in for it, so none is offered.
expect(screen.getByText(en.installFailureNetworkHost.replace('{host}', 'github.com'))).toBeTruthy()
expect(screen.queryByRole('button', { name: en.installChangeRegistry })).toBeNull()
// A git spec without a manifest reads by its address and kind.
expect(screen.getByText('github:a/b')).toBeTruthy()
expect(screen.getByText(en.installSubjectGit)).toBeTruthy()
expect(screen.getByText(en.terminalExitCode.replace('{code}', '1'))).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.installRetry }))
expect(actions.runInstall).toHaveBeenCalledTimes(1)
fireEvent.click(screen.getByRole('button', { name: en.installEditAria }))
expect(actions.cancelInstall).toHaveBeenCalledTimes(1)
const kinds: [string, string][] = [
['pnpm-missing', en.installFailurePnpmMissing], ['timeout', en.installFailureTimeout],
['not-found', en.installFailureNotFound], ['no-matching-version', en.installFailureNoMatchingVersion],
['disk-full', en.installFailureDiskFull], ['permission', en.installFailurePermission],
['build-blocked', en.installFailureBuildBlockedManual], ['integrity', en.installFailureIntegrity], ['unknown', en.installFailureGeneric],
]
for (const [kind, sentence] of kinds) {
set({ install: { ...IDLE_INSTALL, open: true, spec: 'x', phase: 'failed', failure: { reason: 'r', kind: kind as never } } })
expect(screen.getByText(sentence)).toBeTruthy()
}
// A failure without a kind reads by its code, else in the Host's words; without words, or without a failure at all, generically.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'x', phase: 'failed', failure: { reason: '', code: 'not-bundle' } } })
expect(screen.getByText(en.reasonNotBundle)).toBeTruthy()
set({ install: { ...IDLE_INSTALL, open: true, spec: 'x', phase: 'failed', failure: { reason: 'ERR_PNPM_ADDING_TO_ROOT', code: 'operation-error' } } })
expect(screen.getByText('ERR_PNPM_ADDING_TO_ROOT')).toBeTruthy()
// A compatibility refusal outranks the kind pnpm's exit was classified as.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'x', phase: 'failed',
failure: { reason: '', code: 'incompatible-version', incompatible: [INCOMPATIBLE], kind: 'unknown' } } })
expect(screen.getByText(`${incompatibleText()} ${en.reasonIncompatibleInstall}`)).toBeTruthy()
set({ install: { ...IDLE_INSTALL, open: true, spec: 'x', phase: 'failed', failure: { reason: '', code: 'incompatible-version' } } })
expect(screen.getByText(`${en.reasonIncompatibleVersionUnnamed} ${en.reasonIncompatibleInstall}`)).toBeTruthy()
set({ install: { ...IDLE_INSTALL, open: true, spec: 'x', phase: 'failed', failure: { reason: 'the transport said so' } } })
expect(screen.getByText('the transport said so')).toBeTruthy()
set({ install: { ...IDLE_INSTALL, open: true, spec: 'x', phase: 'failed', failure: { reason: '' } } })
expect(screen.getByText(en.installFailureGeneric)).toBeTruthy()
set({ install: { ...IDLE_INSTALL, open: true, spec: 'x', phase: 'failed', failure: null } })
expect(screen.getByText(en.installFailureGeneric)).toBeTruthy()
// A tarball spec reads by its kind too.
set({ install: { ...IDLE_INSTALL, open: true, spec: '/p/x.tgz', phase: 'failed', subject: { spec: '/p/x.tgz', status: 'accepted', kind: 'tarball', bundle: null, registry: null }, failure: null } })
expect(screen.getByText(en.installSubjectTarball)).toBeTruthy()
})
it.each(['network', 'timeout'] as const)('shows GitHub recovery only after a %s failure and returns to package input', (kind) => {
const subject = { spec: 'github:a/b', status: 'accepted', kind: 'git', bundle: null, registry: null, host: 'github.com' } as const
const failed: InstallState = {
...IDLE_INSTALL, open: true, spec: subject.spec, registries: REGISTRIES, phase: 'failed', subject,
failure: { reason: 'GitHub connection failed', kind, failedAt: 'spec-host' },
}
const { actions, set, setLanguage } = renderTab({ install: { ...failed, phase: 'idle', failure: null } })
expect(screen.queryByText(en.installGithubFailedTitle)).toBeNull()
set({ install: failed })
const dialog = screen.getByRole('dialog', { name: kind === 'timeout' ? en.installGithubTimeoutTitle : en.installGithubFailedTitle })
expect(within(dialog).getByText(en.installGithubFailedDescription)).toBeTruthy()
expect(screen.queryByRole('button', { name: en.installRetry })).toBeNull()
fireEvent.click(within(dialog).getByRole('button', { name: en.installUseGithubMirror }))
expect(actions.useGithubMirror).toHaveBeenCalledOnce()
expect(actions.runInstall).not.toHaveBeenCalled()
fireEvent.click(within(dialog).getByRole('button', { name: en.cancel }))
expect(actions.closeInstall).toHaveBeenCalledOnce()
setLanguage(zh)
expect(screen.getByRole('dialog', { name: kind === 'timeout' ? '连接 GitHub 超时' : '无法访问 GitHub' })).toBeTruthy()
expect(screen.getByText('请尝试其他安装来源。')).toBeTruthy()
set({ install: { ...IDLE_INSTALL, open: true, mirrorRecovery: true, registries: REGISTRIES, registry: { kind: 'offered', registry: MIRROR } } })
const form = within(screen.getByRole('dialog', { name: zh.installTitle }))
expect(form.queryByText(zh.installDescription)).toBeNull()
expect(form.queryByRole('textbox', { name: zh.installSpecLabel })).toBeNull()
const input = screen.getByRole('textbox', { name: zh.installPackageLabel })
expect(input).toHaveProperty('value', '')
expect(document.activeElement).toBe(input)
expect(screen.getByRole('button', { name: zh.installRun })).toHaveProperty('disabled', true)
expect(form.getAllByRole('button')).toEqual([
form.getByRole('button', { name: zh.close }),
form.getByRole('button', { name: zh.installGuideToggle }),
form.getByRole('button', { name: '安装源 中国大陆镜像源' }),
form.getByRole('button', { name: zh.installRun }),
])
})
it('offers another way instead of a mirror the failed GitHub install already asked', () => {
const subject = { spec: 'github:a/b', status: 'accepted', kind: 'git', bundle: null, registry: null, host: 'github.com' } as const
const failed: InstallState = {
...IDLE_INSTALL, open: true, spec: subject.spec, registries: REGISTRIES, registry: { kind: 'offered', registry: MIRROR },
phase: 'failed', subject, failure: { reason: 'GitHub connection timed out', kind: 'timeout', failedAt: 'spec-host' },
}
const { actions, set, setLanguage } = renderTab({ install: failed })
// The mirror picked from the options, also while pnpm's own configuration is unread, typed as an address, or named
// by pnpm's own configuration.
for (const patch of [
{},
{ registries: { ...REGISTRIES, resolved: null } },
{ registry: { kind: 'custom', url: ' https://registry.npmmirror.com ' } },
{ registries: { ...REGISTRIES, resolved: MIRROR }, registry: { kind: 'offered', registry: null } },
] as const) {
set({ install: { ...failed, ...patch } })
const dialog = within(screen.getByRole('dialog', { name: en.installGithubTimeoutTitle }))
expect(dialog.getByText(en.installGithubFailedDescription)).toBeTruthy()
expect(dialog.getAllByRole('button')).toEqual([
dialog.getByRole('button', { name: en.close }),
dialog.getByRole('button', { name: en.cancel }),
dialog.getByRole('button', { name: en.installTryAnotherWay }),
])
}
fireEvent.click(screen.getByRole('button', { name: en.installTryAnotherWay }))
expect(actions.useGithubMirror).toHaveBeenCalledOnce()
expect(actions.runInstall).not.toHaveBeenCalled()
// The form it returns to opens the guide to the package-name example.
set({ install: { ...IDLE_INSTALL, open: true, mirrorRecovery: true, registries: REGISTRIES, registry: failed.registry } })
expect(screen.getByRole('button', { name: en.installGuideHide }).getAttribute('aria-expanded')).toBe('true')
expect(screen.getByText(en.installGuideIdHint)).toBeTruthy()
// A typed address other than the mirror can still switch to it.
set({ install: { ...failed, registry: { kind: 'custom', url: 'npm.corp' } } })
expect(screen.getByRole('button', { name: en.installUseGithubMirror })).toBeTruthy()
setLanguage(zh)
set({ install: failed })
expect(screen.getByRole('button', { name: '试试其他方式' })).toBeTruthy()
expect(screen.queryByRole('button', { name: '改用国内镜像' })).toBeNull()
})
it('keeps the ordinary failure view for registry errors, other hosts, and unavailable mirrors', () => {
const failed: InstallState = {
...IDLE_INSTALL, open: true, registries: REGISTRIES, phase: 'failed',
subject: { spec: 'github:a/b', status: 'accepted', kind: 'git', bundle: null, registry: null, host: 'github.com' },
failure: { reason: 'failed', kind: 'network', failedAt: 'registry' },
}
const { set } = renderTab({ install: failed })
expect(screen.getByRole('button', { name: en.installChangeRegistry })).toBeTruthy()
for (const patch of [
{ subject: { ...failed.subject!, host: 'gitlab.com' } },
{ subject: null },
{ registries: { ...REGISTRIES, fallbackRegistries: [] } },
{ registries: null },
{ failure: { reason: 'not found', kind: 'not-found' as const, failedAt: 'spec-host' as const } },
]) {
set({ install: { ...failed, failure: { reason: 'failed', kind: 'network', failedAt: 'spec-host' }, ...patch } })
expect(screen.queryByRole('dialog', { name: en.installGithubFailedTitle })).toBeNull()
expect(screen.getByRole('button', { name: en.installRetry })).toBeTruthy()
}
})
it('selects the custom registry from its whole area and focuses the address without clearing it', () => {
const open = { ...IDLE_INSTALL, open: true, registryOpen: true, registries: REGISTRIES }
const { actions, set } = renderTab({ install: open })
const field = screen.getByRole('textbox', { name: en.registryCustom })
const label = screen.getByRole('radio', { name: en.registryCustom }).parentElement!
const area = label.parentElement!
expect(field).toHaveProperty('disabled', false)
for (const target of [area, label, screen.getByText(en.registryCustomHint)]) {
field.blur()
set({ install: open })
actions.chooseRegistry.mockClear()
fireEvent.pointerDown(target)
fireEvent.click(target)
expect(actions.chooseRegistry).toHaveBeenCalledExactlyOnceWith({ kind: 'custom', url: '' })
expect(document.activeElement).toBe(field)
}
set({ install: { ...open, registry: { kind: 'custom', url: 'https://npm.corp/' } } })
field.blur()
actions.chooseRegistry.mockClear()
field.focus()
fireEvent.click(field)
fireEvent.click(area)
expect(document.activeElement).toBe(field)
expect(field).toHaveProperty('value', 'https://npm.corp/')
expect(actions.chooseRegistry).not.toHaveBeenCalled()
field.blur()
fireEvent.click(screen.getByRole('radio', { name: OFFICIAL_OPTION }))
set({ install: open })
expect(field).toHaveProperty('value', 'https://npm.corp/')
fireEvent.click(screen.getByRole('radio', { name: MIRROR_OPTION }))
set({ install: { ...open, registry: { kind: 'offered', registry: MIRROR } } })
fireEvent.click(area)
expect(actions.chooseRegistry).toHaveBeenLastCalledWith({ kind: 'custom', url: 'https://npm.corp/' })
expect(document.activeElement).toBe(field)
})
it('keeps the selected registry on keyboard focus and selects custom through its radio', () => {
const open = { ...IDLE_INSTALL, open: true, registryOpen: true, registries: REGISTRIES }
const { actions } = renderTab({ install: open })
const field = screen.getByRole('textbox', { name: en.registryCustom })
const official = screen.getByRole('radio', { name: OFFICIAL_OPTION })
official.focus()
fireEvent.keyDown(official, { key: 'Tab' })
expect(document.activeElement).toBe(field)
expect(official).toHaveProperty('checked', true)
expect(actions.chooseRegistry).not.toHaveBeenCalled()
fireEvent.keyDown(field, { key: 'Tab', shiftKey: true })
expect(document.activeElement).toBe(official)
fireEvent.keyDown(official, { key: 'Tab', shiftKey: true })
const toggle = screen.getByRole('button', { name: `${en.registryToggle} ${en.registryOfficial}` })
expect(document.activeElement).toBe(toggle)
expect(actions.toggleRegistryOptions).toHaveBeenCalledOnce()
actions.toggleRegistryOptions.mockClear()
field.focus()
fireEvent.keyDown(field, { key: 'Tab' })
expect(document.activeElement).toBe(toggle)
expect(actions.toggleRegistryOptions).toHaveBeenCalledOnce()
const custom = screen.getByRole('radio', { name: en.registryCustom })
custom.focus()
fireEvent.keyDown(custom, { key: ' ' })
fireEvent.click(custom)
expect(actions.chooseRegistry).toHaveBeenCalledExactlyOnceWith({ kind: 'custom', url: '' })
expect(document.activeElement).toBe(field)
})
it('focuses an invalid custom address when submission opens its registry menu', () => {
const open = { ...IDLE_INSTALL, open: true, spec: 'dsh-new', registry: { kind: 'custom' as const, url: 'invalid' } }
const { actions, set } = renderTab({ install: open })
fireEvent.click(screen.getByRole('button', { name: en.installRun }))
expect(actions.runInstall).toHaveBeenCalledOnce()
set({ install: { ...open, registryOpen: true, registryError: true } })
const field = screen.getByRole('textbox', { name: en.registryCustom })
expect(document.activeElement).toBe(field)
expect(field.getAttribute('aria-invalid')).toBe('true')
expect(screen.getByRole('alert').textContent).toBe(en.registryCustomInvalid)
fireEvent.keyDown(field, { key: 'Escape' })
expect(actions.toggleRegistryOptions).toHaveBeenCalledOnce()
expect(actions.closeInstall).not.toHaveBeenCalled()
})
it('offers the registries under the spec, folded by default, and picks or types one', () => {
const open = { ...IDLE_INSTALL, open: true, registries: REGISTRIES }
const { actions, set } = renderTab({ install: open })
// Folded, the toggle names the registry the install asks first, by name alone.
const toggle = screen.getByRole('button', { name: `${en.registryToggle} ${en.registryOfficial}` })
expect(toggle.getAttribute('aria-expanded')).toBe('false')
expect(screen.queryByRole('radio')).toBeNull()
fireEvent.click(toggle)
expect(actions.toggleRegistryOptions).toHaveBeenCalledTimes(1)
set({ install: { ...open, registryOpen: true } })
expect(screen.getByRole('button', { name: `${en.registryToggle} ${en.registryOfficial}` }).getAttribute('aria-expanded')).toBe('true')
const radios = screen.getAllByRole('radio')
expect(radios).toHaveLength(3)
expect(radios[0]).toHaveProperty('checked', true)
// The options carry the host each names; the control does not.
expect(radios.map(radio => radio.parentElement?.textContent)).toEqual([OFFICIAL_OPTION, MIRROR_OPTION, en.registryCustom])
// The options float from the toggle, so the dialog card itself does not grow.
expect(screen.getByRole('dialog').contains(screen.getByRole('group', { name: en.registryLegend }))).toBe(false)
// Escape folds the options without reaching the dialog; a pointer outside them folds them too.
fireEvent.keyDown(document.body, { key: 'Escape' })
expect(actions.toggleRegistryOptions).toHaveBeenCalledTimes(2)
expect(actions.closeInstall).not.toHaveBeenCalled()
fireEvent.pointerDown(document.body)
expect(actions.toggleRegistryOptions).toHaveBeenCalledTimes(3)
fireEvent.pointerDown(screen.getByRole('group', { name: en.registryLegend }))
expect(actions.toggleRegistryOptions).toHaveBeenCalledTimes(3)
fireEvent.click(screen.getByRole('radio', { name: MIRROR_OPTION }))
expect(actions.chooseRegistry).toHaveBeenLastCalledWith({ kind: 'offered', registry: MIRROR })
set({ install: { ...open, registryOpen: true, registry: { kind: 'offered', registry: MIRROR } } })
expect(screen.getByRole('button', { name: `${en.registryToggle} ${en.registryNpmmirror}` })).toBeTruthy()
// A typed registry: the radio picks it, the field carries it, and it is asked alone.
fireEvent.click(screen.getByRole('radio', { name: new RegExp(en.registryCustom) }))
expect(actions.chooseRegistry).toHaveBeenLastCalledWith({ kind: 'custom', url: '' })
set({ install: { ...open, registryOpen: true, registry: { kind: 'custom', url: 'npm.corp' }, registryError: true } })
const field = screen.getByRole('textbox', { name: en.registryCustom })
expect(field).toHaveProperty('value', 'npm.corp')
expect(field.getAttribute('aria-invalid')).toBe('true')
expect(screen.getByRole('alert').textContent).toBe(en.registryCustomInvalid)
fireEvent.change(field, { target: { value: 'https://npm.corp/' } })
expect(actions.chooseRegistry).toHaveBeenLastCalledWith({ kind: 'custom', url: 'https://npm.corp/' })
expect(screen.getByRole('button', { name: `${en.registryToggle} ${en.registryCustom}` })).toBeTruthy()
// Enter in the field installs, once there is a spec.
fireEvent.keyDown(field, { key: 'Enter' })
expect(actions.runInstall).not.toHaveBeenCalled()
set({ install: { ...open, spec: 'dsh-x', registryOpen: true, registry: { kind: 'custom', url: 'https://npm.corp/' } } })
fireEvent.keyDown(screen.getByRole('textbox', { name: en.registryCustom }), { key: 'Enter' })
expect(actions.runInstall).toHaveBeenCalledTimes(1)
// A remembered registry that does not parse as a URL reads as written.
set({ install: { ...open, registry: { kind: 'offered', registry: 'garbage' } } })
expect(screen.getByRole('button', { name: `${en.registryToggle} garbage` })).toBeTruthy()
// A registry the Host configured that the dictionary does not know reads by its host, listed first.
const corporate = { registry: 'https://npm.corp.example/', fallbackRegistries: [], resolved: OFFICIAL }
set({ install: { ...open, registryOpen: true, registries: corporate, registry: { kind: 'offered', registry: 'https://npm.corp.example/' } } })
expect(screen.getAllByRole('radio').map(radio => radio.parentElement?.textContent)).toEqual(['npm.corp.example', OFFICIAL_OPTION, en.registryCustom])
// pnpm's own configuration naming another registry reads by that host, never as npm's own.
set({ install: { ...open, registryOpen: true, registries: { ...REGISTRIES, resolved: 'https://npm.corp.example/' } } })
expect(screen.getByRole('radio', { name: 'npm.corp.example' })).toBeTruthy()
expect(screen.getByRole('button', { name: `${en.registryToggle} npm.corp.example` })).toBeTruthy()
// One that names the mirror reads by the mirror's name, and the mirror the Host offers is not listed twice.
set({ install: { ...open, registryOpen: true, registries: { ...REGISTRIES, resolved: MIRROR } } })
expect(screen.getByRole('button', { name: `${en.registryToggle} ${en.registryNpmmirror}` })).toBeTruthy()
const mirrorLabels = screen.getAllByRole('radio').map(radio => radio.parentElement?.textContent)
expect(mirrorLabels).toEqual([MIRROR_OPTION, en.registryCustom])
expect(new Set(mirrorLabels).size).toBe(mirrorLabels.length)
// A configuration the Host could not read keeps the neutral default name.
set({ install: { ...open, registryOpen: true, registries: { ...REGISTRIES, resolved: null } } })
expect(screen.getByRole('button', { name: `${en.registryToggle} ${en.registryDefault}` })).toBeTruthy()
// Before the Host answers, only pnpm's own is offered, under the neutral name its unread configuration keeps.
set({ install: { ...open, registryOpen: true, registries: null } })
expect(screen.getAllByRole('radio')).toHaveLength(2)
expect(screen.getByRole('radio', { name: UNREAD_OPTION })).toBeTruthy()
})
it('names the registry each attempt asks while installing, badges each run, and says when every registry failed', () => {
const subject = { spec: 'dsh-x', status: 'accepted', kind: 'registry', name: 'dsh-x', bundle: true, registry: null } as const
const runs = [
{ jobId: 'j1', command: 'pnpm add dsh-x', cwd: '/p', output: 'ERR\n', exitCode: 1 },
{ jobId: 'j2', command: `pnpm add dsh-x --registry=${MIRROR}`, cwd: '/p', output: 'Progress\n' },
]
const { actions, set } = renderTab({
install: {
...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'running', subject, runs, detailsOpen: true, attempts: { registries: [null, MIRROR], total: 2 },
},
})
expect(screen.getByRole('status').textContent).toBe(en.installingTitle)
expect(screen.getByText(en.installAttempt
.replace('{previous}', en.registryDefault).replace('{registry}', en.registryNpmmirror).replace('{index}', '2').replace('{total}', '2'))).toBeTruthy()
expect(screen.getByText(en.installAttemptBadge.replace('{index}', '1').replace('{registry}', en.registryDefault))).toBeTruthy()
expect(screen.getByText(en.installAttemptBadge.replace('{index}', '2').replace('{registry}', en.registryNpmmirror))).toBeTruthy()
// The first attempt says nothing about a registry before it; a single run carries no badge.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'running', subject, runs: [runs[0] as never], detailsOpen: true, attempts: { registries: [null], total: 2 } } })
expect(screen.queryByText(en.installAttemptBadge.replace('{index}', '1').replace('{registry}', en.registryDefault))).toBeNull()
expect(screen.getByRole('status').parentElement?.textContent).toBe(en.installingTitle)
// Every registry failed: the failure names them all, and the registries can be changed from here.
set({
install: {
...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'failed', subject, runs,
attempts: { registries: [null, MIRROR], total: 2 }, failure: { reason: 'ERR', kind: 'network', failedAt: 'registry' },
},
})
expect(screen.getByText(en.installFailureNetworkAll.replace('{registries}', `${en.registryDefault}, ${en.registryNpmmirror}`))).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.installChangeRegistry }))
expect(actions.changeRegistry).toHaveBeenCalledTimes(1)
// One registry that failed reads as the plain network failure; a stale copy on it still offers another registry.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'failed', subject, attempts: { registries: [null], total: 1 }, failure: { reason: 'ERR', kind: 'network', failedAt: 'registry' } } })
expect(screen.getByText(en.installFailureNetwork)).toBeTruthy()
expect(screen.getByRole('button', { name: en.installChangeRegistry })).toBeTruthy()
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'failed', subject, attempts: { registries: [null], total: 1 }, failure: { reason: 'ERR', kind: 'not-found', failedAt: 'registry' } } })
expect(screen.getByRole('button', { name: en.installChangeRegistry })).toBeTruthy()
// A failure the Host laid at neither offers no other registry.
set({ install: { ...IDLE_INSTALL, open: true, spec: 'dsh-x', phase: 'failed', subject, attempts: { registries: [null], total: 1 }, failure: { reason: 'ERR', kind: 'disk-full' } } })
expect(screen.queryByRole('button', { name: en.installChangeRegistry })).toBeNull()
})
it('scrolls to and marks the package an install enabled, then lets the mark go', () => {
vi.useFakeTimers()
const scrollIntoView = vi.fn()
const descriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'scrollIntoView')
Object.defineProperty(Element.prototype, 'scrollIntoView', { value: scrollIntoView, configurable: true })
try {
const { actions, set } = renderTab({ packages: [pkg(), pkg({ name: '@deepseek-ai/dsh-experimental-session-titles-bundle', official: true })] })
// A package the list does not show has nothing to scroll to; the mark still times out.
set({ highlight: 'missing' })
expect(scrollIntoView).not.toHaveBeenCalled()
act(() => { vi.advanceTimersByTime(2_400) })
expect(actions.clearHighlight).toHaveBeenCalledTimes(1)
set({ highlight: 'dsh-better-sidebar' })
expect(screen.getByRole('heading', { name: en.title, level: 1 })).toBeTruthy()
expect(document.querySelector('[data-plugin-package="dsh-better-sidebar"]')?.hasAttribute('data-plugin-highlight')).toBe(true)
expect(scrollIntoView).toHaveBeenCalledWith({ block: 'center', behavior: 'smooth' })
act(() => { vi.advanceTimersByTime(2_400) })
expect(actions.clearHighlight).toHaveBeenCalledTimes(2)
set({ highlight: null })
expect(document.querySelector('[data-plugin-package="dsh-better-sidebar"]')?.hasAttribute('data-plugin-highlight')).toBe(false)
} finally {
if (descriptor === undefined) delete (Element.prototype as { scrollIntoView?: unknown }).scrollIntoView
else Object.defineProperty(Element.prototype, 'scrollIntoView', descriptor)
vi.useRealTimers()
}
})
it('highlights only the visible list and does not extend the mark when navigating or refreshing', () => {
vi.useFakeTimers()
const name = '@deepseek-ai/dsh-experimental-session-titles-bundle'
const scrollIntoView = vi.fn()
const descriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'scrollIntoView')
Object.defineProperty(Element.prototype, 'scrollIntoView', { value: scrollIntoView, configurable: true })
try {
const { actions, set } = renderTab({ packages: [
pkg({ name, official: true, enabled: false }),
pkg({ name: '@deepseek-ai/dsh-subagent-codex', official: true, enabled: false }),
] })
expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeNull()
set({ highlight: name })
expect(screen.getByRole('heading', { name: en.title, level: 1 })).toBeTruthy()
expect(scrollIntoView).not.toHaveBeenCalled()
set({ highlight: null })
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
set({ highlight: name })
expect(screen.getByRole('heading', { name: en.moreTitle, level: 1 })).toBeTruthy()
expect(document.querySelector(`[data-plugin-package="${name}"]`)?.hasAttribute('data-plugin-highlight')).toBe(true)
expect(scrollIntoView).toHaveBeenCalledExactlyOnceWith({ block: 'center', behavior: 'smooth' })
act(() => { vi.advanceTimersByTime(1_600) })
fireEvent.click(screen.getByRole('button', { name: en.backToList }))
expect(screen.getByRole('heading', { name: en.title, level: 1 })).toBeTruthy()
expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeNull()
expect(scrollIntoView).toHaveBeenCalledTimes(1)
set({ packages: [pkg({ name, official: true, enabled: false })] })
act(() => { vi.advanceTimersByTime(799) })
expect(actions.clearHighlight).not.toHaveBeenCalled()
act(() => { vi.advanceTimersByTime(1) })
expect(actions.clearHighlight).toHaveBeenCalledTimes(1)
} finally {
if (descriptor === undefined) delete (Element.prototype as { scrollIntoView?: unknown }).scrollIntoView
else Object.defineProperty(Element.prototype, 'scrollIntoView', descriptor)
vi.useRealTimers()
}
})
it('keeps edited configuration open and preserves explicit detail navigation after remounting with a highlight', () => {
const b = renderTab({ packages: [pkg()] }, { items: [{ id: 'bash', label: 'Shell' }] }, {
'plugins.item:bash': view => view === 'page' ? <input aria-label="Draft setting" defaultValue="original" /> : null,
})
act(() => { b.navigation.actions.setView({ kind: 'item', id: 'bash' }) })
const draft = screen.getByRole('textbox', { name: 'Draft setting' })
fireEvent.change(draft, { target: { value: 'unsaved change' } })
b.set({ highlight: 'dsh-better-sidebar' })
expect(screen.getByRole('textbox', { name: 'Draft setting' })).toBe(draft)
expect(draft).toHaveProperty('value', 'unsaved change')
b.hidePage()
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'dsh-better-sidebar' }) })
b.showPage()
expect(document.querySelector('[data-plugin-detail="dsh-better-sidebar"]')).not.toBeNull()
})
it('marks a card without a scrollIntoView to call', () => {
vi.useFakeTimers()
const descriptor = Object.getOwnPropertyDescriptor(Element.prototype, 'scrollIntoView')
delete (Element.prototype as { scrollIntoView?: unknown }).scrollIntoView
try {
const { set } = renderTab({ packages: [pkg()] })
set({ highlight: 'dsh-better-sidebar' })
expect(document.querySelector('[data-plugin-package="dsh-better-sidebar"]')?.hasAttribute('data-plugin-highlight')).toBe(true)
} finally {
if (descriptor !== undefined) Object.defineProperty(Element.prototype, 'scrollIntoView', descriptor)
vi.useRealTimers()
}
})
it('confirms an uninstall by the package\'s title and runs the action through it', () => {
const { actions, set } = renderTab({
packages: [pkg(), pkg({ name: 'dsh-other' })],
confirm: { action: 'uninstall', packageName: 'dsh-better-sidebar' },
})
expect(screen.getByRole('dialog', { name: en.confirmUninstallTitle.replace('{name}', 'dsh-better-sidebar') })).toBeTruthy()
expect(screen.getByText(en.confirmUninstallDescription)).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.cancel }))
expect(actions.cancelConfirm).toHaveBeenCalledTimes(1)
set({ confirm: { action: 'uninstall', packageName: 'dsh-other' } })
expect(screen.getByRole('dialog', { name: en.confirmUninstallTitle.replace('{name}', 'dsh-other') })).toBeTruthy()
set({ packages: [] })
expect(screen.getByRole('dialog', { name: en.confirmUninstallTitle.replace('{name}', 'dsh-other') })).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.confirmUninstall }))
expect(actions.confirm).toHaveBeenCalledTimes(1)
})
it('words every notice as a toast that dismisses itself', () => {
vi.useFakeTimers()
try {
const { actions, set } = renderTab({ notice: { kind: 'restart', packageName: 'x', reason: 'no-hmr', seq: 1 } })
expect(screen.getByRole('alert').textContent).toContain(en.restartNotice.replace('{reason}', en.restartReasonNoHmr))
// A Host that names no reason still asks for the restart.
set({ notice: { kind: 'restart', packageName: 'x', reason: undefined, seq: 4 } })
expect(screen.getByRole('alert').textContent).toContain(en.restartNotice.replace(' {reason}', ''))
set({ notice: { kind: 'overridden', packageName: 'pkg-1', seq: 2 } })
expect(screen.getByRole('alert').textContent).toContain(en.overriddenNotice.replace('{name}', 'pkg-1'))
set({ notice: { kind: 'cancelled', seq: 3 } })
expect(screen.getByRole('alert').textContent).toContain(en.installCancelled)
// A failure names what was being done; a refusal is worded by its code, a silent one generically.
set({ notice: { kind: 'failed', action: 'enable', reason: 'the tree rejected it', packageName: 'pkg-1', seq: 4 } })
expect(screen.getByRole('alert').textContent).toContain(en.failedEnable.replace('{reason}', 'the tree rejected it'))
set({ notice: { kind: 'failed', action: 'uninstall', code: 'bundle-in-use', reason: '', packageName: 'pkg-1', seq: 5 } })
expect(screen.getByRole('alert').textContent).toContain(en.failedUninstall.replace('{reason}', en.reasonBundleInUse))
set({ notice: { kind: 'failed', action: 'enable', code: 'incompatible-version', incompatible: [INCOMPATIBLE], reason: '', packageName: 'pkg-1', seq: 5 } })
expect(screen.getByRole('alert').textContent).toContain(en.failedEnable.replace('{reason}', `${incompatibleText()} ${en.reasonIncompatibleInstalled}`))
set({ notice: { kind: 'failed', action: 'rowDisable', code: 'operation-error', reason: 'EACCES', packageName: 'pkg-1', seq: 6 } })
expect(screen.getByRole('alert').textContent).toContain(en.failedRowDisable.replace('{reason}', 'EACCES'))
set({ notice: { kind: 'failed', action: 'disable', reason: '', packageName: 'pkg-1', seq: 7 } })
expect(screen.getByRole('alert').textContent).toContain(en.failedDisable.replace('{reason}', en.reasonOperationError))
set({ notice: { kind: 'failed', action: 'rowEnable', reason: 'x', packageName: 'pkg-1', seq: 8 } })
expect(screen.getByRole('alert').textContent).toContain(en.failedRowEnable.replace('{reason}', 'x'))
for (const [index, outcome] of (['done', 'failed', 'unconfirmed', 'applying', 'unknown'] as const).entries()) {
set({ notice: { kind: 'install', outcome, seq: 9 + index } })
const key = ({ done: 'installBackgroundDone', failed: 'installBackgroundFailed', unconfirmed: 'installBackgroundUnconfirmed', applying: 'installBackgroundApplying', unknown: 'installBackgroundUnknown' } as const)[outcome]
expect(screen.getByRole('alert').textContent).toContain(en[key])
}
// No button to press: the toast retires on its own and the store forgets it.
expect(screen.queryByRole('button', { name: /got it/i })).toBeNull()
expect(actions.dismissNotice).not.toHaveBeenCalled()
// The hold grows with the text, up to eight seconds, then the fade.
act(() => { vi.advanceTimersByTime(8_000 + 1_000) })
expect(actions.dismissNotice).toHaveBeenCalledTimes(1)
} finally {
vi.useRealTimers()
}
})
it('distinguishes lost results, pending acceptance, and unknown outcomes in both languages', () => {
const { actions, set, setLanguage } = renderTab()
for (const dict of [en, zh]) {
setLanguage(dict)
set({ install: { ...IDLE_INSTALL, open: true, phase: 'unconfirmed', failure: { reason: 'offline', uncertainty: 'result' } } })
expect(screen.getByRole('alert').textContent).toBe(dict.installResultUnconfirmed.replace('{reason}', 'offline'))
fireEvent.click(screen.getByRole('button', { name: dict.installReconcile }))
expect(screen.getByRole('button', { name: dict.installCancelAndEdit }).textContent).toBe(dict.installCancelAndEdit)
set({ install: { ...IDLE_INSTALL, open: true, phase: 'unconfirmed', failure: { reason: '', uncertainty: 'acceptance' } } })
expect(screen.getByRole('alert').textContent).toBe(dict.installAwaitingAcceptance)
expect(screen.queryByRole('button', { name: dict.installReconcile })).toBeNull()
set({ install: { ...IDLE_INSTALL, open: true, phase: 'applying', failure: { reason: 'offline', uncertainty: 'cancellation' } } })
expect(screen.getByRole('alert').textContent).toBe(dict.installApplyingCancellationError.replace('{reason}', 'offline'))
expect(screen.getByRole('button', { name: dict.installCancel })).toHaveProperty('disabled', true)
set({ install: { ...IDLE_INSTALL, open: true, phase: 'unknown' } })
expect(screen.getByRole('status').textContent).toBe(dict.installUnknownTitle)
expect(screen.getByText(dict.installUnknownDescription)).toBeTruthy()
expect(screen.queryByRole('button', { name: dict.installCancel })).toBeNull()
fireEvent.click(screen.getByRole('button', { name: dict.installEditAria }))
}
expect(actions.reconcileInstall).toHaveBeenCalledTimes(2)
expect(actions.cancelInstall).toHaveBeenCalledTimes(2)
})
it('offers the retained installation and keeps its uncertain state cancellable', () => {
const { actions } = renderTab({ install: {
...IDLE_INSTALL, open: true, phase: 'unconfirmed', requestId: 'pending-install' as PluginInstallRequestId,
failure: { reason: 'offline', uncertainty: 'cancellation' },
} })
expect(screen.queryByRole('button', { name: en.chooseAddMethod })).toBeNull()
expect(screen.queryByRole('menu')).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.installViewTask }))
expect(actions.openInstall).toHaveBeenCalledOnce()
expect(screen.getByRole('status').textContent).toBe(en.installUnconfirmedTitle)
fireEvent.click(screen.getByRole('button', { name: en.installCancel }))
expect(actions.cancelInstall).toHaveBeenCalledOnce()
fireEvent.click(screen.getByRole('button', { name: en.installCloseCancels }))
expect(actions.closeInstall).toHaveBeenCalledOnce()
})
})
it.each([false, true])('shows progress only for the changing bundle and clears it when settled (enabled=%s)', (enabled) => {
const name = 'dsh-better-sidebar'
const { set, setLanguage, actions } = renderTab({ packages: [pkg({ enabled }), pkg({ name: 'dsh-other' })] })
const toggle = screen.getByRole('switch', { name: `Enable ${name}` })
const status = within(toggle.parentElement!).getByRole('status')
expect(status.textContent).toBe('')
fireEvent.click(toggle)
expect(actions.setEnabled).toHaveBeenCalledExactlyOnceWith(name, !enabled)
set({ busy: [name] })
for (const dict of [en, zh]) {
setLanguage(dict)
expect(status.textContent).toBe(dict.applyingChange.replace('{name}', name))
expect(status.querySelector('[data-state="ongoing"]')).not.toBeNull()
}
expect(toggle).toHaveProperty('disabled', true)
fireEvent.click(toggle)
expect(actions.setEnabled).toHaveBeenCalledOnce()
expect(screen.getByRole('switch', { name: '启用 dsh-other' })).toHaveProperty('disabled', false)
set({ busy: [] })
expect(within(toggle.parentElement!).getByRole('status')).toBe(status)
expect(status.textContent).toBe('')
expect(toggle).toHaveProperty('disabled', false)
})
it.each([false, true])('opens activation guidance details and returns to the originating list (experimental: %s)', (fromMore) => {
const name = fromMore ? '@deepseek-ai/dsh-experimental-session-titles-bundle' : 'dsh-better-sidebar'
const bundle = pkg({ name, official: fromMore })
const { set, actions } = renderTab({ packages: [{ ...bundle, enabled: false }] }, { bundles: new Set([name]) }, {
[`plugins.bundle.activation:${name}`]: (_view, owner) => <button onClick={(owner as PluginActivationOwnerProps).onOpenDetails}>Go to setup</button>,
[`plugins.bundle.config:${name}`]: () => <div>Bundle setup</div>,
})
if (fromMore) fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
expect(screen.queryByText('Go to setup')).toBeNull()
set({ packages: [bundle] })
expect(screen.queryByText('Go to setup')).toBeNull()
set({ packages: [{ ...bundle, enabled: false }] })
fireEvent.click(screen.getByRole('switch'))
expect(actions.setEnabled).toHaveBeenCalledWith(name, true)
expect(screen.queryByText('Go to setup')).toBeNull()
set({ packages: [bundle], busy: [name] })
expect(screen.queryByText('Go to setup')).toBeNull()
set({ busy: [] })
fireEvent.click(screen.getByText('Go to setup'))
expect(screen.getByText('Bundle setup')).toBeTruthy()
expect(screen.queryByText('Go to setup')).toBeNull()
fireEvent.click(screen.getByRole('button', { name: fromMore ? en.backToPackage.replace('{name}', en.moreTitle) : en.backToList }))
expect(screen.getByRole('heading', { name: fromMore ? en.moreTitle : en.title, level: 1 })).toBeTruthy()
})
it('dismisses activation guidance until the user enables the bundle again', () => {
const name = 'dsh-better-sidebar'
const { set } = renderTab({ packages: [pkg({ enabled: false })] }, {}, {
[`plugins.bundle.activation:${name}`]: (_view, owner) => <button onClick={(owner as PluginActivationOwnerProps).onDismiss}>Later</button>,
})
fireEvent.click(screen.getByRole('switch'))
set({ packages: [pkg()] })
fireEvent.click(screen.getByText('Later'))
set({ packages: [pkg()] })
expect(screen.queryByText('Later')).toBeNull()
fireEvent.click(screen.getByRole('switch'))
set({ packages: [pkg({ enabled: false })] })
fireEvent.click(screen.getByRole('switch'))
set({ packages: [pkg()] })
expect(screen.getByText('Later')).toBeTruthy()
})
it('keeps activation guidance with the Installed list when a contributed tab enables the bundle', () => {
const name = 'dsh-better-sidebar'
const subject = { spec: `${name}@0.16.0`, status: 'accepted', kind: 'registry', name, bundle: true, registry: null } as const
const { set } = renderTab({
install: { ...IDLE_INSTALL, open: true, phase: 'done', subject, installed: name },
}, { bundles: new Set([name]) }, {
[`plugins.bundle.activation:${name}`]: () => <div>Enable guidance</div>,
}, {}, [{ id: 'discover', label: 'Discover' }])
fireEvent.click(screen.getByRole('button', { name: en.installEnableNow }))
set({ install: IDLE_INSTALL, packages: [pkg()] })
expect(screen.getByText('Enable guidance')).toBeTruthy()
// A contributed tab shows its own content, not guidance for the Installed list.
fireEvent.click(screen.getByRole('tab', { name: 'Discover' }))
expect(screen.queryByText('Enable guidance')).toBeNull()
fireEvent.click(screen.getByRole('tab', { name: en.installedTab }))
expect(screen.getByText('Enable guidance')).toBeTruthy()
})
it('supplies the accepted entry values and atomic mutation action to a custom plugin page', () => {
const mutate = vi.fn(async () => true)
const form: ConfigPageForm = {
state: { status: 'ready', value: { count: 2 }, base: {}, user: {}, revision: 7, writable: true }, mutate,
}
renderTab({}, { items: [{ id: 'custom', label: 'Custom' }] }, {
'plugins.item:custom': (view, _owner, supplied) => view === 'summary' ? 'Custom summary'
: <button onClick={() => { void supplied!.mutate([{ op: 'set', path: ['count'], value: 3 }], supplied!.state.revision) }}>Save custom</button>,
}, { custom: form })
fireEvent.click(screen.getByText('Custom'))
fireEvent.click(screen.getByText('Save custom'))
expect(mutate).toHaveBeenCalledWith([{ op: 'set', path: ['count'], value: 3 }], 7)
})
describe('on-demand Official bundles', () => {
const name = '@deepseek-ai/dsh-subagent-codex'
const title = 'Codex subagent'
const catalog = (): PackageView => ({ name, meta: { title }, official: true, availability: 'missing', installed: false,
optional: false, removable: false, updatable: false, enabled: false, rows: [],
installTarget: { spec: `${name}@2.0.0`, version: '2.0.0' },
})
it('keeps one card on the secondary page before installation, after installation, and after removal', () => {
const entry = catalog()
const { actions, set } = renderTab({ packages: [entry] })
expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
const assertOfficial = () => {
expect(document.querySelectorAll(`[data-plugin-package="${name}"]`)).toHaveLength(1)
expect(document.querySelector('[data-plugin-group="more"]')?.contains(document.querySelector(`[data-plugin-package="${name}"]`))).toBe(true)
expect(document.querySelector('[data-plugin-group="bundles"]')).toBeNull()
}
assertOfficial()
expect(screen.getByText(en.statusNotInstalled)).toBeTruthy()
fireEvent.click(screen.getByRole('switch', { name: en.enableToggle.replace('{name}', title) }))
expect(actions.setEnabled).toHaveBeenCalledWith(name, true)
set({ packages: [{ ...entry, installed: true, availability: 'profile', enabled: true, removable: true, version: '2.0.0' }] })
assertOfficial()
set({ packages: [entry] })
assertOfficial()
})
it.each(['checking', 'starting'] as const)('keeps the catalog switch busy while its installation is %s', (phase) => {
const entry = catalog()
renderTab({ packages: [entry], install: { ...IDLE_INSTALL, phase, subject: {
status: 'accepted', kind: 'registry', name, version: '2.0.0', spec: `${name}@2.0.0`, registry: null,
bundle: true, selection: true, saveExact: true,
} } })
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
expect(screen.getByRole('switch', { name: en.enableToggle.replace('{name}', title) }).hasAttribute('disabled')).toBe(true)
})
it('offers an explicit update and displays installed and target versions', () => {
const entry = { ...catalog(), installed: true, availability: 'profile' as const, removable: true, version: '1.0.0' }
const { actions } = renderTab({ packages: [entry] })
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
expect(screen.getByText(en.statusUpdateAvailable)).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', title) }))
const detail = document.querySelector('[data-plugin-detail]') as HTMLElement
expect(within(detail).getByText('1.0.0')).toBeTruthy()
expect(within(detail).getByText('2.0.0')).toBeTruthy()
fireEvent.click(within(detail).getByRole('button', { name: en.updateLabel.replace('{name}', title) }))
expect(actions.update).toHaveBeenCalledWith(name)
})
it.each([undefined, `${name}@1.0.0`])('retains dependency source=%s beside unavailable status and the update action', (source) => {
renderTab({ packages: [{ ...catalog(), ...source === undefined ? {} : { source }, installed: true, removable: true, error: { code: 'operation-error', diagnostic: 'Unreadable package files' } }] })
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
expect(screen.getByText(en.statusMissingFiles)).toBeTruthy()
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', title) }))
const detail = document.querySelector('[data-plugin-detail]') as HTMLElement
expect(within(detail).queryByText(en.statusNotInstalled)).toBeNull()
if (source !== undefined) expect(within(detail).getByText(source).tagName).toBe('CODE')
expect(within(detail).queryAllByText(en.statusMissingFiles)).not.toEqual([])
expect(within(detail).getByRole('button', { name: en.updateLabel.replace('{name}', title) })).toBeTruthy()
})
it.each([
['link:/current-checkout/codex', `${name}@2.0.0`, true],
['link:/current-checkout/codex', 'link:/other-checkout/codex', true],
['link:/current-checkout/codex', 'link:/current-checkout/codex', false],
[`${name}@2.0.0`, 'link:/current-checkout/codex', true],
[`${name}@2.0.0`, `${name}@^2.0.0`, false],
[`${name}@2.0.0`, undefined, false],
] as const)('offers source replacement from %s with recorded source %s: %s', (spec, source, expected) => {
const entry: PackageView = { ...catalog(), installed: true, availability: 'profile', removable: true, version: '2.0.0',
installTarget: { spec, version: '2.0.0' }, ...source === undefined ? {} : { source } }
renderTab({ packages: [entry] })
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
expect(screen.queryByText(en.statusUpdateAvailable) !== null).toBe(expected)
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', title) }))
const detail = document.querySelector('[data-plugin-detail]') as HTMLElement
expect(within(detail).getByText(en.sourceTargetSpec)).toBeTruthy()
expect(within(detail).getAllByText(spec).every(element => element.tagName === 'CODE')).toBe(true)
expect(within(detail).queryByRole('button', { name: en.updateLabel.replace('{name}', title) }) !== null).toBe(expected)
})
it('allows Off while an enabled source bundle has no usable installation target', () => {
const { installTarget: _target, ...entry } = catalog()
const { actions, set } = renderTab({ packages: [{ ...entry, enabled: true,
error: { code: 'operation-error', diagnostic: 'Source package is not built' } }] })
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
const toggle = screen.getByRole('switch', { name: en.enableToggle.replace('{name}', title) })
expect(toggle.hasAttribute('disabled')).toBe(false)
fireEvent.click(toggle)
expect(actions.setEnabled).toHaveBeenCalledWith(name, false)
set({ packages: [{ ...entry, error: { code: 'operation-error', diagnostic: 'Source package is not built' } }] })
expect(screen.getByRole('switch', { name: en.enableToggle.replace('{name}', title) }).hasAttribute('disabled')).toBe(true)
})
it('does not present an absent catalog package as built in or as an empty loaded component list', () => {
renderTab({ packages: [catalog()] })
fireEvent.click(screen.getByRole('button', { name: en.moreAction }))
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', title) }))
const detail = document.querySelector('[data-plugin-detail]') as HTMLElement
expect(within(detail).queryByText(en.sourceBuiltIn)).toBeNull()
expect(within(detail).queryByRole('button', { name: en.uninstallLabel.replace('{name}', title) })).toBeNull()
expect(detail.querySelector('[data-plugin-rows]')).toBeNull()
})
})
it('keeps bundle configuration mounted while its operation is pending', () => {
const name = 'dsh-better-sidebar'
const { set } = renderTab({ packages: [pkg()] }, { bundles: new Set([name]) }, {
[`plugins.bundle.config:${name}`]: () => <div data-testid="bundle-config">Configuration</div>,
})
fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', name) }))
const panel = screen.getByTestId('bundle-config')
expect(panel.textContent).toBe('Configuration')
set({ busy: [name] })
expect(screen.getByTestId('bundle-config')).toBe(panel)
expect(panel.textContent).toBe('Configuration')
set({ busy: [] })
expect(panel.textContent).toBe('Configuration')
})
describe('registry versions and updates', () => {
const MIRROR = 'https://registry.npmmirror.com/'
const updatable = (): PackageView => pkg({ updatable: true, version: '0.16.0', source: '[email protected]' })
const runtime = '0.8.3'
const listed = (versions: PluginVersion[], attempts: RegistryAttempt[] = [{ registry: null }]): VersionCheck => ({
phase: 'done', checkedAt: 0,
list: { status: 'listed', installed: '0.16.0', registry: attempts.at(-1)?.registry ?? null, attempts, versions, omitted: 0 },
})
const v = (version: string, more: Partial<PluginVersion> = {}): PluginVersion => ({
version, tags: [], prerelease: false, bundle: true, ...more,
})
const incompatible = (version: string) => ({ name: 'dsh-better-sidebar', version, runtimeVersion: runtime, requirements: { '@deepseek-ai/dsh': '>=0.9.0' } })
const detail = (): HTMLElement => document.querySelector('[data-plugin-detail]') as HTMLElement
const section = (): HTMLElement => document.querySelector('[data-plugin-versions]') as HTMLElement
const open = (navigation: ReturnType<typeof renderTab>['navigation']) => {
act(() => { navigation.actions.setView({ kind: 'package', name: 'dsh-better-sidebar' }) })
}
it('checks once on opening, offers the newest installable version, and lists newer versions when unfolded', () => {
const versions = [
v('2.1.0-beta.1', { prerelease: true, tags: ['next'] }),
v('2.0.0', { tags: ['latest'], incompatible: incompatible('2.0.0'), publishedAt: '2026-10-10T00:00:00.000Z' }),
v('1.4.0', { publishedAt: '2026-09-28T08:00:00.000Z' }),
v('1.3.2', { deprecated: 'use 1.4.0' }),
v('1.3.1', { bundle: false }),
]
const check = listed(versions, [{ registry: null, problem: 'network', reason: 'ETIMEDOUT' }, { registry: MIRROR }])
const { actions, navigation } = renderTab({ packages: [updatable()], versions: { 'dsh-better-sidebar': check } })
expect(screen.getByText(en.statusUpdateAvailable)).toBeTruthy()
open(navigation)
expect(actions.ensureVersions).toHaveBeenCalledWith('dsh-better-sidebar')
expect(actions.ensureVersions).toHaveBeenCalledOnce()
expect(within(section()).getByText('Update to 1.4.0 available')).toBeTruthy()
expect(within(section()).getByText('Published 2026-09-28')).toBeTruthy()
fireEvent.click(within(detail()).getAllByRole('button', { name: 'Update to 1.4.0' })[0]!)
expect(actions.openUpdate).toHaveBeenCalledWith('dsh-better-sidebar', '1.4.0')
const summary = within(section()).getByRole('button', { name: /Update to 1\.4\.0 available/ })
expect(summary.getAttribute('aria-expanded')).toBe('false')
fireEvent.click(summary)
expect(summary.getAttribute('aria-expanded')).toBe('true')
const rows = [...section().querySelectorAll('[data-plugin-version]')].map(row => row.getAttribute('data-plugin-version'))
expect(rows).toEqual(['2.0.0', '1.4.0', '1.3.2', '1.3.1', '0.16.0'])
const row = (version: string) => section().querySelector(`[data-plugin-version="${version}"]`) as HTMLElement
expect(within(row('2.0.0')).getByText('Needs @deepseek-ai/dsh >=0.9.0')).toBeTruthy()
expect(within(row('2.0.0')).queryByRole('button')).toBeNull()
expect(within(row('1.3.1')).getByText(en.versionsNotBundle)).toBeTruthy()
expect(within(row('1.3.2')).getByText('use 1.4.0')).toBeTruthy()
expect(within(row('0.16.0')).getByText(en.versionsInstalledTag)).toBeTruthy()
fireEvent.click(within(row('1.3.2')).getByRole('button', { name: 'Update to 1.3.2' }))
expect(actions.openUpdate).toHaveBeenLastCalledWith('dsh-better-sidebar', '1.3.2')
// The registry that answered, marked as a fallback after the first one failed.
expect(within(section()).getByText(en.registryNpmmirror)).toBeTruthy()
expect(within(section()).getByText(en.versionsFallbackTag)).toBeTruthy()
fireEvent.click(within(section()).getByRole('checkbox', { name: en.versionsShowPrerelease }))
expect(row('2.1.0-beta.1')).toBeTruthy()
fireEvent.click(within(section()).getByRole('button', { name: en.versionsRecheck }))
expect(actions.checkVersions).toHaveBeenCalledWith('dsh-better-sidebar')
fireEvent.click(summary)
expect(row('1.4.0')).toBeNull()
})
it.each([
['checking', { phase: 'checking' }, en.versionsChecking],
['up to date', listed([]), en.versionsLatest],
['blocked', listed([v('2.0.0', { incompatible: incompatible('2.0.0') })]), en.versionsBlocked],
['not installable', listed([v('1.0.0', { deprecated: 'old' })]), en.versionsNoneInstallable],
] as const)('summarizes a %s check', (_name, check, title) => {
const { navigation } = renderTab({ packages: [updatable()], versions: { 'dsh-better-sidebar': check } })
open(navigation)
expect(within(section()).getByText(title)).toBeTruthy()
expect(within(detail()).queryByRole('button', { name: /^Update to / })).toBeNull()
if (title === en.versionsBlocked) expect(within(section()).getByText(`2.0.0 needs @deepseek-ai/dsh >=0.9.0; this DSH is ${runtime}`)).toBeTruthy()
})
it('reports a refused check with the registries asked and a retry', () => {
const attempts: RegistryAttempt[] = [
{ registry: null, problem: 'network', reason: 'pnpm view timed out after 20000ms' },
{ registry: MIRROR, problem: 'network', reason: 'getaddrinfo ENOTFOUND registry.npmmirror.com' },
]
const { actions, navigation } = renderTab({ packages: [updatable()], versions: {
'dsh-better-sidebar': { phase: 'done', checkedAt: 0, list: { status: 'refused', problem: 'network', reason: 'down', attempts } },
} })
open(navigation)
expect(within(section()).getByText(en.versionsFailedNetwork)).toBeTruthy()
expect(within(section()).getByText(`${en.registryNpmmirror} ${en.versionsProblemNetwork}`)).toBeTruthy()
expect(within(section()).queryByRole('button', { name: en.versionsRecheck })).toBeNull()
fireEvent.click(within(section()).getByRole('button', { name: en.versionsDetails }))
expect(within(section()).getByText('getaddrinfo ENOTFOUND registry.npmmirror.com')).toBeTruthy()
expect(within(section()).getByText(en.versionsPrimaryTag)).toBeTruthy()
fireEvent.click(within(section()).getByRole('button', { name: en.retry }))
expect(actions.checkVersions).toHaveBeenCalledWith('dsh-better-sidebar')
})
it('reports a check the transport lost by its words, and lists a single registry\'s answer without a fallback mark', () => {
const { navigation, set } = renderTab({ packages: [updatable()], versions: {
'dsh-better-sidebar': { phase: 'done', checkedAt: 0, list: { status: 'refused', problem: 'unknown', reason: 'socket closed', attempts: [] } },
} })
open(navigation)
expect(within(section()).getByText(en.versionsFailedOther)).toBeTruthy()
expect(within(section()).getByText('socket closed')).toBeTruthy()
expect(within(section()).queryByRole('button', { name: en.versionsDetails })).toBeNull()
// A refused list can name a registry that answered before a later lookup failed.
set({ versions: { 'dsh-better-sidebar': { phase: 'done', checkedAt: 0, list: { status: 'refused', problem: 'unknown', reason: 'boom', attempts: [{ registry: null }] } } } })
fireEvent.click(within(section()).getByRole('button', { name: en.versionsDetails }))
expect(within(section()).getByText(en.registryDefault)).toBeTruthy()
expect(within(section()).queryByText(en.versionsProblemUnknown)).toBeNull()
set({ versions: { 'dsh-better-sidebar': { ...listed([v('0.17.0')]), list: { ...(listed([v('0.17.0')]) as Extract<VersionCheck, { phase: 'done' }>).list, omitted: 3 } } as VersionCheck } })
expect(within(section()).queryByText(/^Published/)).toBeNull()
// The section stays unfolded across the new answer.
expect(within(section()).getByRole('button', { name: /Update to 0\.17\.0 available/ }).getAttribute('aria-expanded')).toBe('true')
expect(within(section()).getByText('3 more versions newer than the installed one are not listed')).toBeTruthy()
expect(within(section()).queryByText(en.versionsFallbackTag)).toBeNull()
expect(within(section()).queryByRole('checkbox')).toBeNull()
})
it('shows no Versions section for a package that is not updatable', () => {
const { actions, navigation } = renderTab({ packages: [pkg({ source: 'link:/dev/dsh-better-sidebar' })] })
open(navigation)
expect(section()).toBeNull()
expect(actions.ensureVersions).not.toHaveBeenCalled()
})
it('says in the toolbar what the version checks found', () => {
const { set } = renderTab({ packages: [updatable(), pkg({ name: 'dsh-linked' })] })
set({ versions: { 'dsh-better-sidebar': { phase: 'checking' } } })
expect(screen.queryByText(en.updatesNone)).toBeNull()
set({ versions: { 'dsh-better-sidebar': listed([v('1.0.0')]) } })
expect(screen.getByText('Updates available: 1')).toBeTruthy()
set({ versions: { 'dsh-better-sidebar': listed([]) } })
expect(screen.getByText(en.updatesNone)).toBeTruthy()
set({ versions: { 'dsh-better-sidebar': { phase: 'done', checkedAt: 0, list: { status: 'refused', problem: 'unknown', reason: 'x', attempts: [] } } } })
expect(screen.getByText(en.updatesFailed)).toBeTruthy()
})
const base: UpdateState = { packageName: 'dsh-better-sidebar', from: '1.3.0', to: '1.4.0', registry: MIRROR, phase: 'confirm', steps: [], result: null, outputOpen: false }
const change = (application: ChangeResult['application'], more: Partial<ChangeResult> = {}): ChangeResult => ({
changed: true, application, stage: 'install', target: 'dsh-better-sidebar', ...more,
})
const dialog = (): HTMLElement => screen.getByRole('dialog')
it('confirms an update with its versions and registry, and runs it', () => {
const { actions } = renderTab({ packages: [updatable()], update: base })
expect(within(dialog()).getByText('1.3.0')).toBeTruthy()
expect(within(dialog()).getByText(en.registryNpmmirror)).toBeTruthy()
expect(within(dialog()).getByText(en.updateDescription.replaceAll('{from}', '1.3.0').replace('{to}', '1.4.0'))).toBeTruthy()
fireEvent.click(within(dialog()).getByRole('button', { name: en.update }))
expect(actions.runUpdate).toHaveBeenCalledOnce()
fireEvent.click(within(dialog()).getByRole('button', { name: en.cancel }))
expect(actions.closeUpdate).toHaveBeenCalledOnce()
})
it('names a package that left the list by its package name', () => {
renderTab({ packages: [], update: base })
expect(within(dialog()).getByText(en.updateTitle.replace('{name}', 'dsh-better-sidebar'))).toBeTruthy()
})
it('follows the running steps and keeps the dialog open', () => {
const { actions } = renderTab({ packages: [updatable()], update: { ...base, phase: 'running', steps: ['checking', 'removing'] } })
const steps = [...dialog().querySelectorAll('li')].map(item => [item.getAttribute('data-state'), item.textContent])
expect(steps).toEqual([['done', 'Check 1.4.0'], ['running', 'Stop and remove 1.3.0']])
expect(within(dialog()).getByRole<HTMLButtonElement>('button', { name: en.updateRunning }).disabled).toBe(true)
fireEvent.keyDown(document, { key: 'Escape' })
expect(actions.closeUpdate).not.toHaveBeenCalled()
})
it.each([
['applied', { step: 'install', result: change('applied') }, 'Updated to 1.4.0', [['done', 'Check 1.4.0'], ['done', `Install 1.4.0 from ${en.registryNpmmirror}`]]],
['restart', { step: 'install', result: change('restart-required', { restartReason: 'host-module' }) }, '1.4.0 installed; it takes effect after a restart', [['done', 'Check 1.4.0'], ['done', `Install 1.4.0 from ${en.registryNpmmirror}`]]],
['refused', { step: 'check', result: change('failed', { error: { code: 'incompatible-version' } }) }, 'Cannot update to 1.4.0', [['failed', 'Check 1.4.0']]],
['removal failure', { step: 'remove', result: change('failed', { stage: 'remove', error: { code: 'operation-error', diagnostic: 'EBUSY' } }) }, 'Could not remove 1.3.0', [['done', 'Check 1.4.0'], ['failed', 'Stop and remove 1.3.0']]],
['restored', {
step: 'install', result: change('failed', { packageResult: { exitCode: 1, output: '\nERR_PNPM_FETCH_404 Not Found', truncated: false, logPath: '/l', kind: 'not-found' } }),
restore: change('applied'),
}, 'Update failed; restored 1.3.0', [['done', 'Check 1.4.0'], ['failed', `Install 1.4.0 from ${en.registryNpmmirror}`], ['done', 'Install 1.3.0 again']]],
['kept', { step: 'install', result: change('failed', {
error: { code: 'operation-error', diagnostic: 'ETARGET' }, packageResult: { exitCode: 1, output: 'ETARGET', truncated: false, logPath: '/l' },
}) }, 'Could not update to 1.4.0', [['done', 'Check 1.4.0'], ['failed', `Install 1.4.0 from ${en.registryNpmmirror}`]]],
['broken', { step: 'install', result: change('cancelled'), restore: change('failed', { packageResult: { exitCode: 1, output: 'gone', truncated: false, logPath: '/l' } }) },
en.updateBroken, [['done', 'Check 1.4.0'], ['failed', `Install 1.4.0 from ${en.registryNpmmirror}`], ['failed', 'Install 1.3.0 again']]],
] as const)('reports a %s outcome with the steps its answer implies', (_name, answer, title, steps) => {
const result = { from: '1.3.0', to: '1.4.0', ...answer } as PluginUpdateResult
const { actions } = renderTab({ packages: [updatable()], update: { ...base, phase: 'done', result } })
expect(within(dialog()).getByText(title)).toBeTruthy()
expect([...dialog().querySelectorAll('li')].map(item => [item.getAttribute('data-state'), item.textContent])).toEqual(steps)
const output = within(dialog()).queryByRole('button', { name: en.updateOutput })
expect(output !== null).toBe(_name === 'restored' || _name === 'broken' || _name === 'kept')
if (_name === 'kept') expect(within(dialog()).getByText('1.3.0 was not removed and stays in use. ETARGET')).toBeTruthy()
if (output !== null) {
fireEvent.click(output)
expect(actions.toggleUpdateOutput).toHaveBeenCalledOnce()
}
fireEvent.click(within(dialog()).getByRole('button', { name: en.updateFinish }))
expect(actions.closeUpdate).toHaveBeenCalledOnce()
fireEvent.keyDown(document, { key: 'Escape' })
expect(actions.closeUpdate).toHaveBeenCalledTimes(2)
})
it('lists the steps the answer implies beside the announced ones', () => {
renderTab({ packages: [updatable()], update: {
...base, phase: 'done', steps: ['checking', 'removing'],
result: { from: '1.3.0', to: '1.4.0', step: 'install', result: change('failed'), restore: change('applied') },
} })
expect([...dialog().querySelectorAll('li')].map(item => item.getAttribute('data-state'))).toEqual(['done', 'done', 'failed', 'done'])
})
it('shows a failed run\'s output once unfolded and an unknown outcome after a lost answer', () => {
const failedRun = change('failed', { packageResult: { exitCode: 1, output: 'ERR_PNPM_FETCH_404', truncated: false, logPath: '/l' } })
const { set } = renderTab({ packages: [updatable()], update: {
...base, phase: 'done', outputOpen: true, steps: ['checking', 'removing', 'installing', 'restoring'],
result: { from: '1.3.0', to: '1.4.0', step: 'install', result: failedRun, restore: change('restart-required') },
} })
expect(within(dialog()).getByText('ERR_PNPM_FETCH_404').tagName).toBe('PRE')
set({ update: { ...base, phase: 'done', steps: ['checking', 'removing'], lost: 'socket closed' } })
expect(within(dialog()).getByText(en.updateLost)).toBeTruthy()
expect([...dialog().querySelectorAll('li')].map(item => item.getAttribute('data-state'))).toEqual(['done', 'unknown'])
})
})