@@ -9,15 +9,10 @@ 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 ( ) ;
@@ -181,15 +176,12 @@ function projection(selected: PluginWorkspaceItem | null = null): PluginWorkspac
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 ) ,
@@ -248,15 +240,21 @@ function preparePluginsRouteState() {
}
describe ( 'PluginsView' , ( ) = > {
it ( 'renders as project-configuration drawer content without a duplicate standalone header ' , ( ) = > {
it ( 'renders a direct Skill-like list in the project drawer without filters or refresh ' , ( ) = > {
render ( < MemoryRouter > < PluginsView { ...props ( ) } embedded / > < / MemoryRouter > ) ;
expect ( screen . getByTestId ( 'plugins-page' ) ) . toBeVisible ( ) ;
expect ( screen . getByRole ( 'button' , { name : '刷新全部来源' } ) ) . toBeVisible ( ) ;
expect ( screen . queryByRole ( 'heading' , { name : '插件' , level : 1 } ) ) . not . toBeInTheDocument ( ) ;
expect ( screen . getByTestId ( 'plugin-list' ) ) . toHaveClass ( 'space-y-3' ) ;
expect ( screen . queryByRole ( 'button' , { name : /刷新/ } ) ) . not . toBeInTheDocument ( ) ;
expect ( screen . queryByRole ( 'searchbox' ) ) . not . toBeInTheDocument ( ) ;
expect ( screen . queryByRole ( 'combobox' ) ) . not . toBeInTheDocument ( ) ;
expect ( screen . getByRole ( 'button' , { name : '查看开发数据服务详情' } ) ) . toBeVisible ( ) ;
expect ( screen . getByRole ( 'button' , { name : '开发数据服务:已添加到项目' } ) ) . toBeDisabled ( ) ;
expect ( screen . getByRole ( 'button' , { name : 'Pi Web Search: 已安装' } ) ) . toBeDisabled ( ) ;
} ) ;
it ( 'keeps the production URL, scope control, and fallback notice aligned across in-place change s' , async ( ) = > {
it ( 'keeps the production URL and fallback notice aligned without exposing query control s' , async ( ) = > {
preparePluginsRouteState ( ) ;
render (
< MemoryRouter initialEntries = { [ '/project-config/plugins?scope=project' ] } >
@@ -267,7 +265,7 @@ describe('PluginsView', () => {
await waitFor ( ( ) = > expect ( screen . getByTestId ( 'plugins-location' ) ) . toHaveTextContent (
'/project-config/plugins?scope=all&source=all&state=all' ,
) ) ;
expect ( screen . get ByLabelText( '范围' ) ) . toHaveValue ( 'all' ) ;
expect ( screen . query ByLabelText( '范围' ) ) . not . toBeInTheDocument ( ) ;
expect ( screen . getByText ( '当前没有打开的项目,已显示全部插件。' ) ) . toBeVisible ( ) ;
const activeProject = {
@@ -282,23 +280,23 @@ describe('PluginsView', () => {
await waitFor ( ( ) = > expect ( screen . getByTestId ( 'plugins-location' ) ) . toHaveTextContent (
'/project-config/plugins?scope=project&source=all&state=all' ,
) ) ;
expect ( screen . get ByLabelText( '范围' ) ) . toHaveValue ( 'project' ) ;
expect ( screen . query ByLabelText( '范围' ) ) . not . toBeInTheDocument ( ) ;
fireEvent . click ( screen . getByRole ( 'button' , { name : '打开非法插件地址' } ) ) ;
await waitFor ( ( ) = > expect ( screen . getByTestId ( 'plugins-location' ) ) . toHaveTextContent (
'/project-config/plugins?scope=project&source=all&state=all' ,
) ) ;
expect ( screen . get ByLabelText( '范围' ) ) . toHaveValue ( 'project' ) ;
expect ( screen . query ByLabelText( '范围' ) ) . not . toBeInTheDocument ( ) ;
act ( ( ) = > codingWorkspaceStore . setState ( { activeProjectId : null , activeProject : null } ) ) ;
await waitFor ( ( ) = > expect ( screen . getByTestId ( 'plugins-location' ) ) . toHaveTextContent (
'/project-config/plugins?scope=all&source=all&state=all' ,
) ) ;
expect ( screen . get ByLabelText( '范围' ) ) . toHaveValue ( 'all' ) ;
expect ( screen . query ByLabelText( '范围' ) ) . not . toBeInTheDocument ( ) ;
expect ( screen . getByText ( '当前没有打开的项目,已显示全部插件。' ) ) . toBeVisible ( ) ;
} ) ;
it ( 'keeps old device and project rows visible and labels each affected snapshot cached after refresh errors ' , async ( ) = > {
it ( 'keeps available rows visible when individual sources fail to load ' , async ( ) = > {
const activeProject = {
id : 'project-a' ,
name : 'Project A' ,
@@ -349,10 +347,10 @@ describe('PluginsView', () => {
render ( < MemoryRouter initialEntries = { [ '/project-config/plugins?scope=project&source=all&state=all' ] } > < Plugins / > < / MemoryRouter > ) ;
for ( const name of [ '开发数据服务' , 'Pi Web Search' , 'makelore.retained' ] ) {
const heading = await screen . findByRole ( 'heading' , { name } ) ;
expect ( heading . closest ( 'article' ) ) . toHaveTextContent ( '缓存' ) ;
expect ( await screen . findByRole ( 'heading' , { name } ) ) . toBeVisible ( ) ;
}
expect ( screen . getAllByRole ( 'alert' ) ) . toHaveLength ( 2 ) ;
expect ( screen . queryByText ( '缓存' ) ) . not . toBeInTheDocument ( ) ;
} ) ;
it ( 'attempts a rejected official detail load once per open and retries only after close and reopen' , async ( ) = > {
@@ -416,7 +414,7 @@ describe('PluginsView', () => {
await Promise . resolve ( ) ;
} ) ;
expect ( loadDetail ) . toHaveBeenCalledTimes ( 1 ) ;
expect ( screen . getByRole ( 'alert' , { hidden : true } ) ) . toHaveTextContent ( '插件详情刷新 失败。 detail offline' ) ;
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 ( ) ) ;
@@ -424,7 +422,7 @@ describe('PluginsView', () => {
await waitFor ( ( ) = > expect ( loadDetail ) . toHaveBeenCalledTimes ( 2 ) ) ;
} ) ;
it ( 'renders the unified workspace and exposes URL-backed filter change s' , ( ) = > {
it ( 'renders the capability-first list with details and contextual action s' , ( ) = > {
const view = props ( ) ;
view . sourceErrors = [
{ source : 'catalog' , message : 'catalog offline' } ,
@@ -433,27 +431,15 @@ describe('PluginsView', () => {
render ( < MemoryRouter > < PluginsView { ...view } / > < / MemoryRouter > ) ;
expect ( screen . getByRole ( 'heading' , { name : '插件' } ) ) . toBeVisible ( ) ;
expect ( screen . getByText ( /MakeLore 运营发布官方插件/ ) ) . toBeVisible ( ) ;
expect ( screen . getByText ( /获取与项目启用彼此独立.*需要定向生效的插件还可分配伙伴/ ) ) . 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 ( '本机全局已启用' ) ) . toBeV isi ble ( ) ;
expect ( screen . getByText ( '随 MakeLore 提供' ) ) . toBeVisible ( ) ;
expect ( screen . getByText ( '随项目启用' ) ) . toBeVisible ( ) ;
expect ( screen . getByRole ( 'button ' , { name : '查看开发数据服务详情 ' } ) ) . toBeVisible ( ) ;
expect ( screen . getByRole ( 'button' , { name : '开发数据服务:已添加到项目' } ) ) . toBeD isa bled ( ) ;
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' } ) ) ;
expect ( screen . queryByRole ( 'button ' , { name : /刷新/ } ) ) . not . toBeInTheDocument ( ) ;
expect ( screen . queryByRole ( 'searchbox' ) ) . not . toBeInTheDocument ( ) ;
expect ( screen . queryByRole ( 'combobox' ) ) . not . toBeInTheDocument ( ) ;
} ) ;
it ( 'labels an unacquired code-owned bundled plugin as supplied by MakeLore instead of not downloaded' , ( ) = > {
@@ -491,12 +477,11 @@ describe('PluginsView', () => {
render ( < MemoryRouter > < PluginsView { ...view } / > < / MemoryRouter > ) ;
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 ( ) ;
expect ( screen . getByRole ( 'button' , { name : 'MakeLore 项目脚手架:随应用提供' , hidden : true } ) ) . toBeDisabled ( ) ;
const dialog = screen . getByRole ( 'dialog' , { name : 'MakeLore 项目脚手架' } ) ;
expect ( dialog ) . toHaveTextContent ( '此插件随应用提供,添加到项目后即可使用。' ) ;
expect ( dialog ) . not . toHaveTextContent ( '未下载官方包' ) ;
expect ( dialog ) . not . toHaveTextContent ( '生效范围' ) ;
} ) ;
it ( 'shows one dialog detail surface, confirms destructive commands, and embeds Data Service settings' , ( ) = > {
@@ -505,26 +490,25 @@ describe('PluginsView', () => {
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 ( '写入文档 ' ) ;
expect ( dialog ) . toHaveTextContent ( '写入项目数据 ' ) ;
expect ( dialog ) . not . toHaveTextContent( '读取项目名称 ' ) ;
expect ( dialog ) . not . toHaveTextContent( 'data-service.documents ' ) ;
expect ( dialog ) . not . toHaveTextContent( '按平台包含 ' ) ;
expect ( dialog ) . not . toHaveTextContent ( '版本 1.0.0 ' ) ;
expect ( dialog ) . toHaveTextContent ( '使用设置' ) ;
expect ( dialog ) . toHaveTextContent ( '创建开发数据空间' ) ;
fireEvent . click ( screen . getByRole ( 'button' , { name : '从当前项目禁用 开发数据服务' } ) ) ;
fireEvent . click ( screen . getByRole ( 'button' , { name : '从项目移除 开发数据服务' } ) ) ;
expect ( view . onCommand ) . not . toHaveBeenCalled ( ) ;
expect ( screen . getByRole ( 'alertdialog' ) ) . toHaveTextContent ( 'Project A' ) ;
fireEvent . click ( screen . getByRole ( 'button' , { name : '确认从当前项目禁用 ' } ) ) ;
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 extension s' , ( ) = > {
it ( 'describes local plugins by capability without exposing implementation detail s' , ( ) = > {
const scriptSkill : PluginWorkspaceItem = {
. . . localItem ,
key : 'local:makelore-project-scaffold' ,
@@ -547,11 +531,13 @@ describe('PluginsView', () => {
render ( < MemoryRouter > < PluginsView { ...props ( scriptSkill ) } / > < / MemoryRouter > ) ;
const dialog = screen . getByRole ( 'dialog' , { name : 'MakeLore 项目初始化' } ) ;
expect ( dialog ) . toHaveTextContent ( '包含可执行 Skill 脚本 ' ) ;
expect ( dialog ) . not . toHaveTextContent ( '包含可执行 Pi extension ' ) ;
expect ( dialog ) . toHaveTextContent ( '为智能体提供可在本机使用的扩展能力。 ' ) ;
expect ( dialog ) . not . toHaveTextContent ( 'Skill 脚本 ' ) ;
expect ( dialog ) . not . toHaveTextContent ( 'Pi extension' ) ;
expect ( dialog ) . not . toHaveTextContent ( '完整桌面权限' ) ;
} ) ;
it ( 'shows cached, suspended, retired, and retained-device reason copy wi th an explicit sign-in action' , ( ) = > {
it ( 'shows a friendly unavailable message and keeps the applicable sign-in action' , ( ) = > {
const unavailableItem : PluginWorkspaceItem = {
. . . officialItem ,
title : 'Notes' ,
@@ -580,19 +566,21 @@ describe('PluginsView', () => {
render ( < MemoryRouter > < PluginsView { ...view } / > < / MemoryRouter > ) ;
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 ( '旧设备版本已保留:当前客户端与新版本不兼容 ' ) ;
fireEven t. click ( screen . getByRole ( 'button' , { name : '登录后免费获取Notes' } ) ) ;
expect ( dialog ) . toHaveTextContent ( '这项能力目前暂不可用 ' ) ;
expect ( dialog ) . not . toHaveTextContent( '缓存 ' ) ;
expect ( dialog ) . not . toHaveTextContent( '已暂停 ' ) ;
expect ( dialog ) . not . toHaveTextContent( '已退役 ' ) ;
expect ( dialog ) . not . toHaveTextContent( '1.0.0 ' ) ;
expec t( dialog ) . not . toHaveTextContent ( '当前客户端与新版本不兼容' ) ;
fireEvent . click ( screen . getByRole ( 'button' , { name : '登录后添加Notes' } ) ) ;
expect ( view . onCommand ) . toHaveBeenCalledWith ( { kind : 'sign_in' } ) ;
} ) ;
it ( 'show s Marketplace detail operations and server-owned pricing without an active project ' , ( ) = > {
it ( 'keep s Marketplace details capability-focused without exposing operation or pricing fields ' , ( ) = > {
const detailItem : PluginWorkspaceItem = {
. . . officialItem ,
title : 'Notes' ,
summary : '记录、整理并回顾项目笔记。' ,
projectState : 'not_applicable' ,
assignedAgentIds : [ ] ,
assignedAgentNames : [ ] ,
@@ -632,54 +620,60 @@ describe('PluginsView', () => {
render ( < MemoryRouter > < PluginsView { ...view } / > < / MemoryRouter > ) ;
const dialog = screen . getByRole ( 'dialog' , { name : 'Notes' } ) ;
expect ( dialog ) . toHaveTextContent ( 'notes ' ) ;
expect ( dialog ) . toHaveTextContent ( 'wri te' ) ;
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' ) ;
expect ( dialog ) . toHaveTextContent ( '记录、整理并回顾项目笔记。 ' ) ;
expect ( dialog ) . not . toHaveTextContent( 'no tes ' ) ;
expect ( dialog ) . not . toHaveTextContent( 'write ' ) ;
expect ( dialog ) . not . toHaveTextContent( '每次由服务端结算 ' ) ;
expect ( dialog ) . not . toHaveTextContent( 'request ' ) ;
expect ( dialog ) . not . toHaveTextContent( 'Token Point' ) ;
expect ( dialog ) . not . toHaveTextContent( 'pricing-7 ' ) ;
} ) ;
it . each ( [
[ 'stale' , '当前项目策略使用缓存,所示能力与价格可能不是最新状态。' ],
[ 'unavailable' , '当前项目策略不可用,无法确认最新能力与价格。' ],
] as const ) ( 'shows %s project policy status beside policy-owned capability and pricing ' , ( projectPolicyStatus , copy ) = > {
[ 'stale' ] ,
[ 'unavailable' ] ,
] as const ) ( 'does not expose the %s policy implementation state in details ' , ( projectPolicyStatus ) = > {
const policyItem : PluginWorkspaceItem = { . . . officialItem , projectPolicyStatus } ;
render ( < MemoryRouter > < PluginsView { ...props ( policyItem ) } / > < / MemoryRouter > ) ;
expect ( screen . getByRole ( 'dialog' , { name : '开发数据服务' } ) ) . toHaveTextContent ( copy ) ;
} ) ;
it ( 'does not mark current project policy as stale or unavailable' , ( ) = > {
render ( < MemoryRouter > < PluginsView { ...props ( officialItem ) } / > < / MemoryRouter > ) ;
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 ' , ( ) = > {
it ( 'deduplicates human-facing capability descriptions and hides their technical identity ' , ( ) = > {
const capabilityTool = {
name : 'notes_write' ,
label : '整理笔记' ,
description : '整理并保存项目笔记。' ,
mutation : 'write' as const ,
permissions : [ 'project.notes.write' ] ,
} ;
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 ,
id : 'notes.internal ' ,
operations : [
{
id : 'write_internal' ,
billing : {
mode : 'platform_metered ' as const ,
availability : 'available' as const ,
notice : '当前项目策略价格 ' ,
ratePoints : '7' ,
} ,
tool : capabilityTool ,
} ,
tool : null ,
} ] ,
{
id : 'write_duplicate' ,
billing : {
mode : 'included' as const ,
availability : 'available' as const ,
notice : '目录包含读取' ,
} ,
tool : capabilityTool ,
} ,
] ,
} ] ,
} ;
const detailItem : PluginWorkspaceItem = {
@@ -724,20 +718,21 @@ describe('PluginsView', () => {
render ( < MemoryRouter > < PluginsView { ...props ( detailItem ) } / > < / MemoryRouter > ) ;
const dialog = screen . getByRole ( 'dialog' , { name : 'Notes' } ) ;
expect ( screen . getAllByText ( 'write ' ) ) . toHaveLength ( 1 ) ;
expect ( dialog ) . toHaveTextContent ( 'read' ) ;
expect ( dialog ) . toHaveTextContent ( '当前项目策略价格 ' ) ;
expect ( screen . getAllByText ( '整理笔记 ' ) ) . toHaveLength ( 1 ) ;
expect ( screen . getAllByText ( '整理并保存项目笔记。' ) ) . toHaveLength ( 1 ) ;
expect ( dialog ) . not . toHaveTextContent( 'notes.internal ' ) ;
expect ( dialog ) . not . toHaveTextContent ( 'write_internal' ) ;
expect ( dialog ) . not . toHaveTextContent ( 'project.notes.write' ) ;
expect ( dialog ) . not . toHaveTextContent ( '当前项目策略价格' ) ;
expect ( dialog ) . not . toHaveTextContent ( '目录价格' ) ;
expect ( dialog ) . toHaveTextContent ( '每单位 Token Point: 7 ' ) ;
expect ( dialog ) . toHaveTextContent ( '价格版本: 9' ) ;
expect ( dialog ) . toHaveTextContent ( '目录包含读取' ) ;
expect ( dialog ) . not . toHaveTextContent( 'Token Point' ) ;
} ) ;
it ( 'dispatches non-destructive detail commands directly and closes from the dialog control' , ( ) = > {
const view = props ( officialItem ) ;
render ( < MemoryRouter > < PluginsView { ...view } / > < / MemoryRouter > ) ;
fireEvent . click ( screen . getByRole ( 'button' , { name : '查看 开发数据服务插件 设置' } ) ) ;
fireEvent . click ( screen . getByRole ( 'button' , { name : '打开 开发数据服务设置' } ) ) ;
expect ( view . onCommand ) . toHaveBeenCalledWith ( {
kind : 'open_settings' , projectId : 'project-a' , pluginId : 'makelore.data-service' ,
} ) ;
@@ -749,7 +744,7 @@ describe('PluginsView', () => {
const viewA = props ( officialItem ) ;
const { rerender } = render ( < MemoryRouter > < PluginsView { ...viewA } / > < / MemoryRouter > ) ;
fireEvent . click ( screen . getByRole ( 'button' , { name : '从当前项目禁用 开发数据服务' } ) ) ;
fireEvent . click ( screen . getByRole ( 'button' , { name : '从项目移除 开发数据服务' } ) ) ;
expect ( screen . getByRole ( 'alertdialog' ) ) . toHaveTextContent ( 'Project A' ) ;
const projectBItem : PluginWorkspaceItem = {