Files
deepseek-harness/packages/client/ui-plugin-manager/tests/components.client.spec.tsx
T
ZiyaandZiyaZhang d6d51be4cb feat(experimental): 把个性化页面和插件推荐合并为实验性插件「个性化推荐」 (#7034)
* feat(experimental): ship the two card experiments as one Personalized recommendations bundle

@deepseek-ai/dsh-personalized-recommendations inserts the action card
service, its browser half, Personalized pages and Plugin recommendations
with their card bodies under the existing row ids. As the only layer that
declares those ids, the Plugins page addresses and switches each row,
including the two shared card rows that were read-only while two bundles
inserted them.

The two experiment packages stop being bundles; the new bundle takes their
place in the official lists and the dsh installation, with the personalized
pages icon, localized title and description, and a feature preview of both
cards. The two feature rows describe what they do on the bundle page.

* fix(experimental): drop the model reason line from plugin recommendation cards

The reason follows the language of the user's question while the catalog
description below it follows the interface language, so the two lines could
differ in language and largely repeat each other; the answer already gives
the reason. The card keeps the catalog title and description, both in the
interface language, as its details target.

* test(web): refresh the card fixtures for the Personalized recommendations bundle

Both card replays enable the new bundle, so each Session lists both card
skills, and each skill's catalog lists Personalized recommendations in place
of the two former bundles. The plugin card goldens lose the reason line.

---------

Co-authored-by: ZiyaZhang <199893125+ZiyaZhang@users.noreply.github.com>
2026-10-23 07:55:49 +00:00

2879 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: 'dsh-a@1.0.0', 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: 'dsh-a@1.0.0', 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: 'dsh-a@1.0.0', 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'), 'dsh-a@1.0.0'])
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: 'dsh-a@1.0.0', 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: 'dsh-a@1.0.0', 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-personalized-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-personalized-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}', 'dsh-x@1.4.2')
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: 'dsh-better-sidebar@0.16.0' })
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'])
})
})