import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { useState } from 'react'; import { MemoryRouter, useLocation, useNavigate } from 'react-router-dom'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { Plugins, PluginsView } from '@/pages/Plugins'; import { useAuthStore } from '@/stores/auth'; import { codingPluginsStore } from '@/stores/coding-plugins'; import { codingWorkspaceStore } from '@/stores/coding-workspace'; import { devicePackageStore } from '@/stores/device-packages'; import { pluginMarketplaceStore } from '@/stores/plugin-marketplace'; import type { PluginWorkspaceFilters, PluginWorkspaceItem, PluginWorkspaceProjection, } from '@/pages/Plugins/plugin-workspace-model'; const filters: PluginWorkspaceFilters = { scope: 'project', source: 'all', state: 'all', search: '', selectedKey: null, }; const initialAuthState = useAuthStore.getInitialState(); const initialCodingPluginsState = codingPluginsStore.getInitialState(); const initialCodingWorkspaceState = codingWorkspaceStore.getInitialState(); const initialDevicePackageState = devicePackageStore.getInitialState(); const initialMarketplaceState = pluginMarketplaceStore.getInitialState(); afterEach(() => { vi.restoreAllMocks(); act(() => { useAuthStore.setState(initialAuthState, true); codingPluginsStore.setState(initialCodingPluginsState, true); codingWorkspaceStore.setState(initialCodingWorkspaceState, true); devicePackageStore.setState(initialDevicePackageState, true); pluginMarketplaceStore.setState(initialMarketplaceState, true); }); }); const projectPlugin = { id: 'makelore.data-service', version: '1.0.0', contractVersion: 1, requiresBackend: true, displayName: '开发数据服务', description: '为当前项目提供 JSON 数据。', enabled: true, state: 'configuration_required' as const, backend: { status: 'unconfigured' as const }, skills: [{ id: 'data-service', assignedAgentIds: ['agent-a'] }], capabilities: [{ id: 'data-service.documents', operations: [{ id: 'put_document', billing: { mode: 'included' as const, availability: 'available' as const, notice: 'Fixed quotas apply' }, tool: { name: 'data_service_put_document', label: '写入文档', description: '写入项目数据', mutation: 'write' as const, permissions: ['project.data.write'], }, }], }], settingsSurface: 'data-service', }; const officialItem: PluginWorkspaceItem = { key: 'official:makelore.data-service', source: 'official', pluginId: 'makelore.data-service', packageId: null, title: '开发数据服务', summary: '为当前项目提供 JSON 数据。', category: '系统', tags: ['data'], publisher: 'MakeLore', version: '1.0.0', delivery: 'system_included', projectState: 'enabled', projectPolicyStatus: 'current', localEnabled: null, assignedAgentIds: ['agent-a'], assignedAgentNames: ['小明'], billing: 'included', updateAvailable: false, unavailable: false, stale: false, suspended: false, retired: false, deviceReason: null, commands: [ { kind: 'disable_project', projectId: 'project-a', pluginId: 'makelore.data-service' }, { kind: 'open_settings', projectId: 'project-a', pluginId: 'makelore.data-service' }, ], official: { catalog: null, library: null, installation: null, project: projectPlugin, detail: { pluginId: 'makelore.data-service', title: '开发数据服务', summary: '为当前项目提供 JSON 数据。', category: '系统', tags: ['data'], providerDisplayName: 'MakeLore', runtimeKind: 'bundled_typed', runtimeStatus: 'enabled', acquisition: 'system_included', usageBilling: 'included', includedOperationCount: 1, meteredOperationCount: 0, descriptionMarkdown: '详细的数据服务介绍。', permissions: ['读取项目名称'], operations: [], stableVersion: '1.0.0', betaVersion: null, stableRelease: { releaseId: 'data-1', version: '1.0.0' }, betaRelease: null, etag: 'data-1', pricingVersionId: null, stale: false, fetchedAt: 1, }, }, local: null, }; const localItem: PluginWorkspaceItem = { key: 'local:pi-web-search', source: 'local', pluginId: 'pi-web-search', packageId: 'pi-web-search', title: 'Pi Web Search', summary: 'npm:pi-web-search', category: null, tags: ['search'], publisher: null, version: '1.2.3', delivery: 'local_installed', projectState: 'not_applicable', projectPolicyStatus: null, localEnabled: true, assignedAgentIds: [], assignedAgentNames: [], billing: 'none', updateAvailable: false, unavailable: false, stale: false, suspended: false, retired: false, deviceReason: null, commands: [ { kind: 'disable_local', packageId: 'pi-web-search' }, { kind: 'remove_local', packageId: 'pi-web-search' }, ], official: null, local: { schemaVersion: 1, packageId: 'pi-web-search', displayName: 'Pi Web Search', resolvedVersion: '1.2.3', source: { kind: 'npm', requested: 'npm:pi-web-search', resolved: 'npm:pi-web-search@1.2.3' }, kind: 'mixed', skillEntries: [{ id: 'search', entryPath: 'skills/search/SKILL.md' }], extensionEntries: ['extensions/search.ts'], enabled: true, confirmedExecutableCode: true, installedAt: '2026-09-03T00:00:00Z', }, }; function projection(selected: PluginWorkspaceItem | null = null): PluginWorkspaceProjection { return { items: [officialItem, localItem], selected, counts: { all: 2, available: 0, mine: 2, enabled: 2, update: 0, unavailable: 0 }, notices: [], }; } function props(selected: PluginWorkspaceItem | null = null) { return { projection: projection(selected), filters: { ...filters, selectedKey: selected?.key ?? null }, activeProjectId: 'project-a', activeProjectName: 'Project A', sourceErrors: [], sourceLoading: [], dataService: null, dataServicePending: {}, onFiltersChange: vi.fn(), onRefresh: vi.fn(), onSelect: vi.fn(), onCommand: vi.fn(), isCommandPending: vi.fn().mockReturnValue(false), onConfigureDataService: vi.fn(), onResetDataService: vi.fn(), onRemoveDataServiceCollection: vi.fn(), onRemoveDataServiceProject: vi.fn(), }; } function PluginsRouteHarness() { const location = useLocation(); const navigate = useNavigate(); return ( <>
{location.pathname}{location.search}
); } function preparePluginsRouteState() { useAuthStore.setState({ user: null }); codingWorkspaceStore.setState({ activeProjectId: null, activeProject: null, config: null, loadState: 'ready', }); codingPluginsStore.setState({ projection: null, loadState: 'ready', error: null }); devicePackageStore.setState({ index: { schemaVersion: 1, generation: 1, packages: [] }, state: 'ready', error: null, }); pluginMarketplaceStore.setState({ accountKey: null, catalog: null, catalogState: 'ready', catalogError: null, library: null, libraryState: 'idle', libraryError: null, installations: {}, details: {}, detailState: {}, }); vi.spyOn(pluginMarketplaceStore.getState(), 'loadCatalog').mockResolvedValue(undefined); vi.spyOn(devicePackageStore.getState(), 'load').mockResolvedValue(undefined); vi.spyOn(codingPluginsStore.getState(), 'load').mockResolvedValue(undefined); } describe('PluginsView', () => { it('keeps the production URL, scope control, and fallback notice aligned across in-place changes', async () => { preparePluginsRouteState(); render( , ); await waitFor(() => expect(screen.getByTestId('plugins-location')).toHaveTextContent( '/plugins?scope=all&source=all&state=all', )); expect(screen.getByLabelText('范围')).toHaveValue('all'); expect(screen.getByText('当前没有打开的项目,已显示全部插件。')).toBeVisible(); const activeProject = { id: 'project-a', name: 'Project A', createdAt: '2026-09-03T00:00:00Z', updatedAt: '2026-09-03T00:00:00Z', lastOpenedAt: '2026-09-03T00:00:00Z', }; act(() => codingWorkspaceStore.setState({ activeProjectId: activeProject.id, activeProject })); fireEvent.click(screen.getByRole('button', { name: '打开裸插件地址' })); await waitFor(() => expect(screen.getByTestId('plugins-location')).toHaveTextContent( '/plugins?scope=project&source=all&state=all', )); expect(screen.getByLabelText('范围')).toHaveValue('project'); fireEvent.click(screen.getByRole('button', { name: '打开非法插件地址' })); await waitFor(() => expect(screen.getByTestId('plugins-location')).toHaveTextContent( '/plugins?scope=project&source=all&state=all', )); expect(screen.getByLabelText('范围')).toHaveValue('project'); act(() => codingWorkspaceStore.setState({ activeProjectId: null, activeProject: null })); await waitFor(() => expect(screen.getByTestId('plugins-location')).toHaveTextContent( '/plugins?scope=all&source=all&state=all', )); expect(screen.getByLabelText('范围')).toHaveValue('all'); expect(screen.getByText('当前没有打开的项目,已显示全部插件。')).toBeVisible(); }); it('keeps old device and project rows visible and labels each affected snapshot cached after refresh errors', async () => { const activeProject = { id: 'project-a', name: 'Project A', createdAt: '2026-09-03T00:00:00Z', updatedAt: '2026-09-03T00:00:00Z', lastOpenedAt: '2026-09-03T00:00:00Z', }; useAuthStore.setState({ user: null }); codingWorkspaceStore.setState({ activeProjectId: activeProject.id, activeProject, config: null, loadState: 'ready', }); codingPluginsStore.setState({ projectId: activeProject.id, projection: { schemaVersion: 1, project: { localProjectId: activeProject.id, durableProjectId: 'durable-a' }, policyStatus: 'current', unknownPluginIds: ['makelore.retained'], items: [projectPlugin], }, loadState: 'error', error: 'project refresh failed', }); devicePackageStore.setState({ index: { schemaVersion: 1, generation: 8, packages: [localItem.local!] }, state: 'error', error: 'device refresh failed', }); pluginMarketplaceStore.setState({ accountKey: null, catalog: null, catalogState: 'ready', catalogError: null, library: null, libraryState: 'idle', libraryError: null, installations: {}, details: {}, detailState: {}, }); vi.spyOn(pluginMarketplaceStore.getState(), 'loadCatalog').mockResolvedValue(undefined); vi.spyOn(devicePackageStore.getState(), 'load').mockResolvedValue(undefined); vi.spyOn(codingPluginsStore.getState(), 'load').mockResolvedValue(undefined); render(); for (const name of ['开发数据服务', 'Pi Web Search', 'makelore.retained']) { const heading = await screen.findByRole('heading', { name }); expect(heading.closest('article')).toHaveTextContent('缓存'); } expect(screen.getAllByRole('alert')).toHaveLength(2); }); it('attempts a rejected official detail load once per open and retries only after close and reopen', async () => { preparePluginsRouteState(); pluginMarketplaceStore.setState({ catalog: { items: [{ pluginId: 'makelore.notes', title: 'Notes', summary: 'Write project notes', category: 'productivity', tags: ['notes'], providerDisplayName: 'MakeLore', runtimeKind: 'skill_only', runtimeStatus: 'enabled', acquisition: 'free', usageBilling: 'token_point', includedOperationCount: 0, meteredOperationCount: 1, stableVersion: '2.0.0', betaVersion: null, }], nextCursor: null, total: 1, catalogGeneration: 1, etag: 'catalog-1', pricingVersionId: 'pricing-1', stale: false, fetchedAt: 1, }, }); let rejectFirstAttempt: (reason?: unknown) => void = () => undefined; const firstAttempt = new Promise((_resolve, reject) => { rejectFirstAttempt = reject; }); const loadDetail = vi.spyOn(pluginMarketplaceStore.getState(), 'loadDetail') .mockImplementationOnce(async () => await firstAttempt) .mockResolvedValue(undefined); render( , ); await waitFor(() => expect(loadDetail).toHaveBeenCalledTimes(1)); await act(async () => { pluginMarketplaceStore.setState((state) => ({ detailState: { ...state.detailState, 'makelore.notes': 'error' }, catalogError: 'detail offline', })); rejectFirstAttempt(new Error('detail offline')); await Promise.resolve(); }); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(loadDetail).toHaveBeenCalledTimes(1); expect(screen.getByRole('alert', { hidden: true })).toHaveTextContent('插件详情刷新失败。 detail offline'); fireEvent.click(screen.getByRole('button', { name: '关闭插件详情' })); await waitFor(() => expect(screen.queryByRole('dialog', { name: 'Notes' })).not.toBeInTheDocument()); fireEvent.click(screen.getByRole('button', { name: '查看Notes详情' })); await waitFor(() => expect(loadDetail).toHaveBeenCalledTimes(2)); }); it('renders the unified workspace and exposes URL-backed filter changes', () => { const view = props(); view.sourceErrors = [ { source: 'catalog', message: 'catalog offline' }, { source: 'project', message: 'project unavailable' }, ]; render(); expect(screen.getByRole('heading', { name: '插件' })).toBeVisible(); expect(screen.getByText(/MakeLore 运营发布官方插件/)).toBeVisible(); expect(screen.getByText(/获取与项目启用彼此独立.*需要定向生效的插件还可分配伙伴/)).toBeVisible(); expect(screen.getByText('开发数据服务')).toBeVisible(); expect(screen.getByText('Pi Web Search')).toBeVisible(); expect(screen.getByRole('heading', { name: '本机全局生效' })).toBeVisible(); expect(screen.getByText('本机全局已启用')).toBeVisible(); expect(screen.getByText('随 MakeLore 提供')).toBeVisible(); expect(screen.getByText('随项目启用')).toBeVisible(); expect(screen.getAllByRole('alert')).toHaveLength(2); expect(screen.queryByRole('textbox', { name: /包|路径|source/i })).not.toBeInTheDocument(); expect(screen.getByText(/通过对话安装/)).toBeVisible(); fireEvent.change(screen.getByLabelText('范围'), { target: { value: 'all' } }); expect(view.onFiltersChange).toHaveBeenCalledWith(expect.objectContaining({ scope: 'all' })); fireEvent.change(screen.getByLabelText('来源'), { target: { value: 'local' } }); expect(view.onFiltersChange).toHaveBeenCalledWith(expect.objectContaining({ source: 'local' })); fireEvent.change(screen.getByLabelText('状态'), { target: { value: 'enabled' } }); expect(view.onFiltersChange).toHaveBeenCalledWith(expect.objectContaining({ state: 'enabled' })); fireEvent.change(screen.getByRole('searchbox', { name: '搜索插件' }), { target: { value: 'web' } }); fireEvent.submit(screen.getByRole('search')); expect(view.onFiltersChange).toHaveBeenCalledWith(expect.objectContaining({ search: 'web' })); }); it('labels an unacquired code-owned bundled plugin as supplied by MakeLore instead of not downloaded', () => { const scaffoldItem: PluginWorkspaceItem = { ...officialItem, key: 'official:makelore.project-scaffold', pluginId: 'makelore.project-scaffold', title: 'MakeLore 项目脚手架', delivery: 'not_acquired', projectState: 'disabled', assignedAgentIds: [], assignedAgentNames: [], commands: [], official: { catalog: null, detail: null, library: null, installation: null, project: { ...projectPlugin, id: 'makelore.project-scaffold', displayName: 'MakeLore 项目脚手架', enabled: false, state: 'disabled', }, }, }; const view = props(scaffoldItem); view.projection = { items: [scaffoldItem], selected: scaffoldItem, counts: { all: 1, available: 0, mine: 0, enabled: 0, update: 0, unavailable: 0 }, notices: [], }; render(); expect(screen.getAllByText('随应用提供')).toHaveLength(2); expect(screen.queryByText('未下载官方包')).not.toBeInTheDocument(); expect(screen.getByText('随项目启用')).toBeVisible(); expect(screen.getByRole('heading', { name: '生效范围' })).toBeVisible(); expect(screen.getByText(/启用当前项目后.*无需单独分配/)).toBeVisible(); expect(screen.queryByRole('heading', { name: '伙伴分配' })).not.toBeInTheDocument(); }); it('shows one dialog detail surface, confirms destructive commands, and embeds Data Service settings', () => { const view = props(officialItem); render(); const dialog = screen.getByRole('dialog', { name: '开发数据服务' }); expect(dialog).toHaveTextContent('详细的数据服务介绍。'); expect(dialog).toHaveTextContent('读取项目名称'); expect(dialog).toHaveTextContent('data-service.documents'); expect(dialog).toHaveTextContent('按平台包含'); expect(dialog).toHaveTextContent('生效范围'); expect(dialog).toHaveTextContent('无需单独分配'); expect(dialog).not.toHaveTextContent('小明'); expect(screen.queryByRole('button', { name: '分配开发数据服务给伙伴' })).not.toBeInTheDocument(); expect(dialog).toHaveTextContent('插件专属设置'); expect(dialog).toHaveTextContent('创建开发数据空间'); fireEvent.click(screen.getByRole('button', { name: '从当前项目禁用开发数据服务' })); expect(view.onCommand).not.toHaveBeenCalled(); expect(screen.getByRole('alertdialog')).toHaveTextContent('Project A'); fireEvent.click(screen.getByRole('button', { name: '确认从当前项目禁用' })); expect(view.onCommand).toHaveBeenCalledWith({ kind: 'disable_project', projectId: 'project-a', pluginId: 'makelore.data-service', }); }); it('describes executable Skill scripts without calling them Pi extensions', () => { const scriptSkill: PluginWorkspaceItem = { ...localItem, key: 'local:makelore-project-scaffold', pluginId: 'makelore-project-scaffold', packageId: 'makelore-project-scaffold', title: 'MakeLore 项目初始化', local: { ...localItem.local!, packageId: 'makelore-project-scaffold', displayName: 'MakeLore 项目初始化', kind: 'skill-only', skillEntries: [{ id: 'makelore-project-scaffold', entryPath: 'skills/makelore-project-scaffold/SKILL.md', }], extensionEntries: [], }, }; render(); const dialog = screen.getByRole('dialog', { name: 'MakeLore 项目初始化' }); expect(dialog).toHaveTextContent('包含可执行 Skill 脚本'); expect(dialog).not.toHaveTextContent('包含可执行 Pi extension'); }); it('shows cached, suspended, retired, and retained-device reason copy with an explicit sign-in action', () => { const unavailableItem: PluginWorkspaceItem = { ...officialItem, title: 'Notes', delivery: 'account_unknown', projectState: 'not_applicable', version: '1.0.0', stale: true, unavailable: true, suspended: true, retired: true, deviceReason: '当前客户端与新版本不兼容', commands: [{ kind: 'sign_in' }], official: { ...officialItem.official!, installation: { status: 'unavailable', pluginId: 'makelore.notes', version: '1.0.0', channel: 'stable', reason: '当前客户端与新版本不兼容', }, project: null, }, }; const view = props(unavailableItem); render(); const dialog = screen.getByRole('dialog', { name: 'Notes' }); expect(dialog).toHaveTextContent('缓存'); expect(dialog).toHaveTextContent('已暂停'); expect(dialog).toHaveTextContent('已退役'); expect(dialog).toHaveTextContent('官方包 1.0.0'); expect(dialog).toHaveTextContent('旧设备版本已保留:当前客户端与新版本不兼容'); fireEvent.click(screen.getByRole('button', { name: '登录后免费获取Notes' })); expect(view.onCommand).toHaveBeenCalledWith({ kind: 'sign_in' }); }); it('shows Marketplace detail operations and server-owned pricing without an active project', () => { const detailItem: PluginWorkspaceItem = { ...officialItem, title: 'Notes', projectState: 'not_applicable', assignedAgentIds: [], assignedAgentNames: [], official: { ...officialItem.official!, project: null, detail: { ...officialItem.official!.detail!, pluginId: 'makelore.notes', title: 'Notes', operations: [{ capabilityId: 'notes', operation: 'write', executionMode: 'job', billing: { mode: 'platform_metered', status: null, notice: '每次由服务端结算', entitlementScope: 'account', unitName: 'request', unitSize: 2, ratePoints: '3', minimumChargePoints: '1', roundingMode: 'ceil', pricingVersion: 'pricing-7', }, enabled: true, }], }, }, }; const view = { ...props(detailItem), activeProjectId: null, activeProjectName: null, }; render(); const dialog = screen.getByRole('dialog', { name: 'Notes' }); expect(dialog).toHaveTextContent('notes'); expect(dialog).toHaveTextContent('write'); expect(dialog).toHaveTextContent('每次由服务端结算'); expect(dialog).toHaveTextContent('计费单位:request'); expect(dialog).toHaveTextContent('单位大小:2'); expect(dialog).toHaveTextContent('每单位 Token Point:3'); expect(dialog).toHaveTextContent('最低扣点:1'); expect(dialog).toHaveTextContent('价格版本:pricing-7'); }); it.each([ ['stale', '当前项目策略使用缓存,所示能力与价格可能不是最新状态。'], ['unavailable', '当前项目策略不可用,无法确认最新能力与价格。'], ] as const)('shows %s project policy status beside policy-owned capability and pricing', (projectPolicyStatus, copy) => { const policyItem: PluginWorkspaceItem = { ...officialItem, projectPolicyStatus }; render(); expect(screen.getByRole('dialog', { name: '开发数据服务' })).toHaveTextContent(copy); }); it('does not mark current project policy as stale or unavailable', () => { render(); const dialog = screen.getByRole('dialog', { name: '开发数据服务' }); expect(dialog).not.toHaveTextContent('当前项目策略使用缓存'); expect(dialog).not.toHaveTextContent('当前项目策略不可用'); }); it('deduplicates operation identity while preferring the current project policy projection', () => { const projectPolicy = { ...projectPlugin, capabilities: [{ id: 'notes', operations: [{ id: 'write', billing: { mode: 'platform_metered' as const, availability: 'available' as const, notice: '当前项目策略价格', unitName: 'project-request', unitSize: 1, ratePoints: '7', minimumChargePoints: '2', pricingVersion: 9, roundingMode: 'ceil' as const, }, tool: null, }], }], }; const detailItem: PluginWorkspaceItem = { ...officialItem, title: 'Notes', official: { ...officialItem.official!, project: projectPolicy, detail: { ...officialItem.official!.detail!, pluginId: 'makelore.notes', title: 'Notes', operations: [ { capabilityId: 'notes', operation: 'write', executionMode: 'synchronous', billing: { mode: 'platform_metered', status: null, notice: '目录价格', entitlementScope: null, unitName: 'catalog-request', unitSize: 1, ratePoints: '3', minimumChargePoints: null, roundingMode: 'ceil', pricingVersion: 'pricing-3', }, enabled: true, }, { capabilityId: 'notes', operation: 'read', executionMode: 'synchronous', billing: { mode: 'included', status: null, notice: '目录包含读取', entitlementScope: null, unitName: null, unitSize: null, ratePoints: null, minimumChargePoints: null, roundingMode: null, pricingVersion: 'pricing-3', }, enabled: true, }, ], }, }, }; render(); const dialog = screen.getByRole('dialog', { name: 'Notes' }); expect(screen.getAllByText('write')).toHaveLength(1); expect(dialog).toHaveTextContent('read'); expect(dialog).toHaveTextContent('当前项目策略价格'); expect(dialog).not.toHaveTextContent('目录价格'); expect(dialog).toHaveTextContent('每单位 Token Point:7'); expect(dialog).toHaveTextContent('价格版本:9'); expect(dialog).toHaveTextContent('目录包含读取'); }); it('dispatches non-destructive detail commands directly and closes from the dialog control', () => { const view = props(officialItem); render(); fireEvent.click(screen.getByRole('button', { name: '查看开发数据服务插件设置' })); expect(view.onCommand).toHaveBeenCalledWith({ kind: 'open_settings', projectId: 'project-a', pluginId: 'makelore.data-service', }); fireEvent.click(screen.getByRole('button', { name: '关闭插件详情' })); expect(view.onSelect).toHaveBeenCalledWith(null); }); it('clears a destructive confirmation when the active project changes', () => { const viewA = props(officialItem); const { rerender } = render(); fireEvent.click(screen.getByRole('button', { name: '从当前项目禁用开发数据服务' })); expect(screen.getByRole('alertdialog')).toHaveTextContent('Project A'); const projectBItem: PluginWorkspaceItem = { ...officialItem, commands: [ { kind: 'disable_project', projectId: 'project-b', pluginId: 'makelore.data-service' }, ], }; const viewB = { ...props(projectBItem), activeProjectId: 'project-b', activeProjectName: 'Project B', }; rerender(); expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument(); expect(viewA.onCommand).not.toHaveBeenCalled(); expect(viewB.onCommand).not.toHaveBeenCalled(); }); it('closes the detail with Escape and returns focus to the originating card', async () => { function Harness() { const [selected, setSelected] = useState(null); const view = props(selected); view.onSelect = vi.fn((key: PluginWorkspaceItem['key'] | null) => { setSelected(key === officialItem.key ? officialItem : null); }); return ; } render(); const trigger = screen.getByRole('button', { name: '查看开发数据服务详情' }); fireEvent.click(trigger); const dialog = screen.getByRole('dialog', { name: '开发数据服务' }); expect(dialog).toBeVisible(); await waitFor(() => expect(dialog.contains(document.activeElement)).toBe(true)); fireEvent.keyDown(dialog, { key: 'Escape' }); await waitFor(() => expect(screen.queryByRole('dialog', { name: '开发数据服务' })).not.toBeInTheDocument()); await waitFor(() => expect(trigger).toHaveFocus()); }); });