mirror of
https://github.com/langgenius/dify.git
synced 2026-07-23 20:18:40 +08:00
Co-authored-by: hjlarry <hjlarry@163.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
1116 lines
38 KiB
TypeScript
1116 lines
38 KiB
TypeScript
import { screen, waitFor, within } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { STEP_BY_STEP_TOUR_TARGETS } from '@/app/components/step-by-step-tour/target-registry'
|
|
import { renderWithNuqs } from '@/test/nuqs-testing'
|
|
import IntegrationsPage from '../page'
|
|
|
|
const { mockRouterPush, mockWindowOpen } = vi.hoisted(() => ({
|
|
mockRouterPush: vi.fn(),
|
|
mockWindowOpen: vi.fn(),
|
|
}))
|
|
|
|
const mockConsoleState = vi.hoisted(() => ({
|
|
workspacePermissionKeys: ['tool.manage', 'mcp.manage'] as string[],
|
|
}))
|
|
|
|
const {
|
|
mockCanManagement,
|
|
mockCanDebugger,
|
|
mockCanSetPermissions,
|
|
mockIsPermissionLoading,
|
|
mockReferenceSetting,
|
|
mockSetReferenceSettings,
|
|
} = vi.hoisted(() => ({
|
|
mockCanManagement: vi.fn(() => true),
|
|
mockCanDebugger: vi.fn(() => true),
|
|
mockCanSetPermissions: vi.fn(() => true),
|
|
mockIsPermissionLoading: vi.fn(() => false),
|
|
mockReferenceSetting: vi.fn(() => ({
|
|
permission: {
|
|
install_permission: 'everyone',
|
|
debug_permission: 'admins',
|
|
},
|
|
auto_upgrade: {
|
|
strategy_setting: 'fix_only',
|
|
upgrade_time_of_day: 0,
|
|
upgrade_mode: 'all',
|
|
exclude_plugins: [],
|
|
include_plugins: [],
|
|
},
|
|
})),
|
|
mockSetReferenceSettings: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/next/navigation', () => ({
|
|
useRouter: () => ({
|
|
push: mockRouterPush,
|
|
}),
|
|
}))
|
|
|
|
vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
|
|
usePluginSettingsAccess: () => ({
|
|
permission: mockReferenceSetting().permission,
|
|
canInstallPlugin: mockCanManagement(),
|
|
canDeletePlugin: true,
|
|
canManagement: mockCanManagement(),
|
|
canDebugger: mockCanDebugger(),
|
|
canSetPermissions: mockCanSetPermissions(),
|
|
canSetPluginPreferences: mockCanSetPermissions(),
|
|
canUpdatePlugin: true,
|
|
isPermissionLoading: mockIsPermissionLoading(),
|
|
setPluginPermissionSettings: mockSetReferenceSettings,
|
|
}),
|
|
default: () => ({
|
|
referenceSetting: mockReferenceSetting(),
|
|
canInstallPlugin: mockCanManagement(),
|
|
canDeletePlugin: true,
|
|
canManagement: mockCanManagement(),
|
|
canDebugger: mockCanDebugger(),
|
|
canSetPermissions: mockCanSetPermissions(),
|
|
canSetPluginPreferences: mockCanSetPermissions(),
|
|
canUpdatePlugin: true,
|
|
isPermissionLoading: mockIsPermissionLoading(),
|
|
setReferenceSettings: mockSetReferenceSettings,
|
|
}),
|
|
}))
|
|
|
|
vi.mock('@/app/components/plugins/plugin-page/debug-info', () => ({
|
|
__esModule: true,
|
|
default: ({
|
|
triggerClassName,
|
|
triggerContent,
|
|
}: {
|
|
triggerClassName?: string
|
|
triggerContent?: React.ReactNode
|
|
}) => (
|
|
<button type="button" aria-label="plugin debug" className={triggerClassName}>
|
|
{triggerContent ?? 'debug'}
|
|
</button>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/plugins/reference-setting-modal', () => ({
|
|
__esModule: true,
|
|
default: ({ onHide }: { onHide: () => void }) => (
|
|
<div data-testid="reference-setting-modal">
|
|
<button type="button" onClick={onHide}>
|
|
close
|
|
</button>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/header/account-setting/update-setting-dialog', () => ({
|
|
default: () => (
|
|
<button type="button" aria-label="plugin.autoUpdate.autoUpdate">
|
|
plugin.autoUpdate.autoUpdate
|
|
</button>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/plugins/plugin-page/install-plugin-dropdown', () => ({
|
|
__esModule: true,
|
|
default: ({
|
|
disabled,
|
|
onSwitchToMarketplaceTab,
|
|
showTriggerArrow,
|
|
triggerClassName,
|
|
}: {
|
|
disabled?: boolean
|
|
onSwitchToMarketplaceTab: () => void
|
|
showTriggerArrow?: boolean
|
|
triggerClassName?: string
|
|
}) => (
|
|
<button
|
|
type="button"
|
|
aria-label="plugin install"
|
|
className={triggerClassName}
|
|
data-show-trigger-arrow={String(showTriggerArrow)}
|
|
disabled={disabled}
|
|
onClick={onSwitchToMarketplaceTab}
|
|
>
|
|
install dropdown
|
|
</button>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/plugins/plugin-page/plugin-tasks', () => ({
|
|
__esModule: true,
|
|
default: () => (
|
|
<button type="button" aria-label="plugin tasks">
|
|
tasks
|
|
</button>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/plugins/install-plugin/install-from-marketplace-query', () => ({
|
|
__esModule: true,
|
|
default: ({ installContextCategory }: { installContextCategory?: string }) => (
|
|
<div
|
|
data-testid="install-from-marketplace-query"
|
|
data-install-context-category={installContextCategory}
|
|
/>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/header/account-setting/model-provider-page', () => ({
|
|
__esModule: true,
|
|
default: ({
|
|
layout,
|
|
onOpenMarketplace,
|
|
onSearchTextChange,
|
|
searchText,
|
|
}: {
|
|
layout?: (parts: { body: React.ReactNode; toolbar: React.ReactNode }) => React.ReactNode
|
|
onOpenMarketplace?: () => void
|
|
onSearchTextChange?: (value: string) => void
|
|
searchText: string
|
|
}) => {
|
|
const toolbar = (
|
|
<div data-testid="model-provider-toolbar">
|
|
<input
|
|
aria-label="search"
|
|
value={searchText}
|
|
onChange={(event) => onSearchTextChange?.(event.target.value)}
|
|
/>
|
|
</div>
|
|
)
|
|
const body = (
|
|
<div data-testid="model-provider-page">
|
|
<button type="button" aria-label="model provider marketplace" onClick={onOpenMarketplace}>
|
|
marketplace
|
|
</button>
|
|
</div>
|
|
)
|
|
|
|
if (layout) return layout({ body, toolbar })
|
|
|
|
return (
|
|
<div data-testid="model-provider-page">
|
|
<input
|
|
aria-label="search"
|
|
value={searchText}
|
|
onChange={(event) => onSearchTextChange?.(event.target.value)}
|
|
/>
|
|
</div>
|
|
)
|
|
},
|
|
}))
|
|
|
|
vi.mock('@/app/components/header/account-setting/data-source-page-new', () => ({
|
|
__esModule: true,
|
|
default: ({
|
|
layout,
|
|
onOpenMarketplace,
|
|
}: {
|
|
layout?: (parts: { body: React.ReactNode; toolbar: React.ReactNode }) => React.ReactNode
|
|
onOpenMarketplace?: () => void
|
|
}) => {
|
|
const toolbar = <div data-testid="data-source-toolbar" />
|
|
const body = (
|
|
<div data-testid="data-source-page">
|
|
<button type="button" aria-label="data source marketplace" onClick={onOpenMarketplace}>
|
|
marketplace
|
|
</button>
|
|
</div>
|
|
)
|
|
|
|
if (layout) return layout({ body, toolbar })
|
|
|
|
return body
|
|
},
|
|
}))
|
|
|
|
vi.mock('@/app/components/header/account-setting/api-based-extension-page', () => ({
|
|
__esModule: true,
|
|
ApiBasedExtensionPage: ({
|
|
layout,
|
|
}: {
|
|
layout?: (parts: { body: React.ReactNode; toolbar: React.ReactNode }) => React.ReactNode
|
|
}) => {
|
|
const toolbar = <div data-testid="api-extension-toolbar" />
|
|
const body = <div data-testid="api-extension-page" />
|
|
|
|
if (layout) return layout({ body, toolbar })
|
|
|
|
return body
|
|
},
|
|
}))
|
|
|
|
vi.mock('../tool-provider-list', async () => {
|
|
const { useState } = await vi.importActual<typeof import('react')>('react')
|
|
|
|
const MockProviderList = ({
|
|
category,
|
|
layout,
|
|
}: {
|
|
category?: string
|
|
layout?: (parts: { body: React.ReactNode; toolbar: React.ReactNode }) => React.ReactNode
|
|
}) => {
|
|
const [mountedCategory] = useState(category)
|
|
const toolbar = <div data-testid="tool-provider-toolbar" />
|
|
const body = (
|
|
<div data-testid="tool-provider-list" data-mounted-category={mountedCategory}>
|
|
{category}
|
|
</div>
|
|
)
|
|
|
|
if (layout) return layout({ body, toolbar })
|
|
|
|
return body
|
|
}
|
|
|
|
return {
|
|
__esModule: true,
|
|
default: MockProviderList,
|
|
}
|
|
})
|
|
|
|
vi.mock('../plugin-category-page', () => ({
|
|
__esModule: true,
|
|
default: ({
|
|
canInstall,
|
|
category,
|
|
layout,
|
|
onSwitchToMarketplace,
|
|
toolbarAction,
|
|
}: {
|
|
canInstall?: boolean
|
|
category: string
|
|
layout?: (parts: { body: React.ReactNode; toolbar: React.ReactNode }) => React.ReactNode
|
|
onSwitchToMarketplace?: () => void
|
|
toolbarAction?: React.ReactNode
|
|
}) => {
|
|
const toolbar = <div data-testid="plugin-category-toolbar">{toolbarAction}</div>
|
|
const body = (
|
|
<div
|
|
data-can-install={canInstall ? 'true' : 'false'}
|
|
data-testid={`plugin-category-${category}`}
|
|
>
|
|
<button type="button" aria-label="empty marketplace" onClick={onSwitchToMarketplace}>
|
|
marketplace
|
|
</button>
|
|
</div>
|
|
)
|
|
|
|
if (layout) return layout({ body, toolbar })
|
|
|
|
return (
|
|
<>
|
|
{toolbar}
|
|
{body}
|
|
</>
|
|
)
|
|
},
|
|
}))
|
|
|
|
const renderIntegrationsPage = (
|
|
searchParams?: Record<string, string>,
|
|
sectionOrProps?:
|
|
| React.ComponentProps<typeof IntegrationsPage>['section']
|
|
| Partial<React.ComponentProps<typeof IntegrationsPage>>,
|
|
) => {
|
|
const props = typeof sectionOrProps === 'string' ? { section: sectionOrProps } : sectionOrProps
|
|
|
|
return renderWithNuqs(<IntegrationsPage {...props} />, { searchParams })
|
|
}
|
|
|
|
describe('IntegrationsPage', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
vi.stubGlobal('open', mockWindowOpen)
|
|
mockCanManagement.mockReturnValue(true)
|
|
mockCanDebugger.mockReturnValue(true)
|
|
mockCanSetPermissions.mockReturnValue(true)
|
|
mockIsPermissionLoading.mockReturnValue(false)
|
|
mockConsoleState.workspacePermissionKeys = ['tool.manage', 'mcp.manage']
|
|
mockReferenceSetting.mockReturnValue({
|
|
permission: {
|
|
install_permission: 'everyone',
|
|
debug_permission: 'admins',
|
|
},
|
|
auto_upgrade: {
|
|
strategy_setting: 'fix_only',
|
|
upgrade_time_of_day: 0,
|
|
upgrade_mode: 'all',
|
|
exclude_plugins: [],
|
|
include_plugins: [],
|
|
},
|
|
})
|
|
})
|
|
|
|
it('defaults to the model provider section when no query is provided', () => {
|
|
const { container } = renderIntegrationsPage()
|
|
|
|
expect(screen.getByTestId('model-provider-page')).toBeInTheDocument()
|
|
expect(screen.getAllByText('common.settings.provider')).toHaveLength(2)
|
|
expect(container.firstElementChild).toHaveClass('bg-components-panel-bg')
|
|
expect(container.querySelector('aside')).toHaveClass('bg-components-panel-bg')
|
|
})
|
|
|
|
it('renders the model provider section from the section query', () => {
|
|
renderIntegrationsPage({ section: 'provider' })
|
|
|
|
expect(screen.getByTestId('model-provider-page')).toBeInTheDocument()
|
|
expect(screen.getByTestId('install-from-marketplace-query')).toHaveAttribute(
|
|
'data-install-context-category',
|
|
'model',
|
|
)
|
|
expect(
|
|
screen.getByTestId('model-provider-toolbar').closest('[class*="max-w-[1600px]"]'),
|
|
).toHaveClass('px-6', 'pt-3', 'pb-2')
|
|
expect(
|
|
within(screen.getByTestId('model-provider-toolbar').closest('section')!).getByText(
|
|
'common.settings.provider',
|
|
),
|
|
).toHaveClass('title-2xl-semi-bold')
|
|
expect(screen.getByTestId('model-provider-page').parentElement).toHaveClass(
|
|
'max-w-[1600px]',
|
|
'px-6',
|
|
)
|
|
expect(screen.getByTestId('model-provider-page').parentElement).not.toHaveClass('pt-2')
|
|
expect(screen.getAllByText('common.settings.provider')).toHaveLength(2)
|
|
expect(screen.getByRole('link', { name: 'common.settings.provider' })).toHaveAttribute(
|
|
'aria-current',
|
|
'page',
|
|
)
|
|
expect(screen.getByRole('link', { name: 'common.settings.dataSource' })).not.toHaveAttribute(
|
|
'aria-current',
|
|
)
|
|
expect(screen.getByRole('textbox', { name: 'search' })).toBeInTheDocument()
|
|
})
|
|
|
|
it('orders sidebar items to match the integrations setting menu', () => {
|
|
renderIntegrationsPage({ section: 'provider' })
|
|
|
|
const navText = screen.getByRole('navigation').textContent ?? ''
|
|
|
|
expect(navText.indexOf('common.settings.provider')).toBeLessThan(
|
|
navText.indexOf('common.menus.tools'),
|
|
)
|
|
expect(navText.indexOf('common.menus.tools')).toBeLessThan(
|
|
navText.indexOf('common.settings.dataSource'),
|
|
)
|
|
expect(navText.indexOf('common.settings.dataSource')).toBeLessThan(
|
|
navText.indexOf('plugin.categorySingle.trigger'),
|
|
)
|
|
expect(navText.indexOf('plugin.categorySingle.trigger')).toBeLessThan(
|
|
navText.indexOf('plugin.categorySingle.agent'),
|
|
)
|
|
expect(navText.indexOf('plugin.categorySingle.agent')).toBeLessThan(
|
|
navText.indexOf('plugin.categorySingle.extension'),
|
|
)
|
|
expect(navText.indexOf('plugin.categorySingle.extension')).toBeLessThan(
|
|
navText.indexOf('common.settings.customEndpoint'),
|
|
)
|
|
})
|
|
|
|
it('anchors step-by-step tour targets inside stable sidebar rows', () => {
|
|
renderIntegrationsPage({ section: 'mcp' })
|
|
|
|
const targetRows = [
|
|
{
|
|
label: 'common.settings.provider',
|
|
target: STEP_BY_STEP_TOUR_TARGETS.integrationModelProviderNav,
|
|
},
|
|
{
|
|
label: 'common.toolsPage.toolPlugin',
|
|
target: STEP_BY_STEP_TOUR_TARGETS.integrationToolPluginNav,
|
|
},
|
|
{
|
|
label: 'MCP',
|
|
target: STEP_BY_STEP_TOUR_TARGETS.integrationMcpNav,
|
|
},
|
|
{
|
|
label: 'common.settings.dataSource',
|
|
target: STEP_BY_STEP_TOUR_TARGETS.integrationDataSourceNav,
|
|
},
|
|
{
|
|
label: 'plugin.categorySingle.trigger',
|
|
target: STEP_BY_STEP_TOUR_TARGETS.integrationTriggerNav,
|
|
},
|
|
]
|
|
|
|
targetRows.forEach(({ label, target }) => {
|
|
const row = screen.getByRole('link', { name: label })
|
|
const targetAnchor = row.querySelector(`[data-step-by-step-tour-target="${target}"]`)
|
|
|
|
expect(row).not.toHaveAttribute('data-step-by-step-tour-target')
|
|
expect(row).toHaveClass('relative')
|
|
expect(targetAnchor).toBeInTheDocument()
|
|
expect(targetAnchor).toHaveClass('absolute', 'inset-y-1', 'left-0', 'right-0')
|
|
})
|
|
})
|
|
|
|
it('keeps sidebar item icons outlined when the item is active', () => {
|
|
const providerView = renderIntegrationsPage({ section: 'provider' })
|
|
|
|
expect(
|
|
screen
|
|
.getByRole('link', { name: 'common.settings.dataSource' })
|
|
.querySelector('.i-ri-database-2-line'),
|
|
).toBeInTheDocument()
|
|
|
|
providerView.rerender(<IntegrationsPage section="data-source" />)
|
|
|
|
expect(
|
|
screen
|
|
.getByRole('link', { name: 'common.settings.dataSource' })
|
|
.querySelector('.i-ri-database-2-line'),
|
|
).toBeInTheDocument()
|
|
expect(
|
|
screen
|
|
.getByRole('link', { name: 'common.settings.dataSource' })
|
|
.querySelector('.i-ri-database-2-fill'),
|
|
).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('renders plugin category sections from the section query', () => {
|
|
const toolView = renderIntegrationsPage({ section: 'builtin' })
|
|
|
|
expect(screen.getByTestId('plugin-category-tool')).toBeInTheDocument()
|
|
expect(screen.getByTestId('plugin-category-tool').parentElement).toHaveClass(
|
|
'flex',
|
|
'flex-col',
|
|
'overflow-hidden',
|
|
)
|
|
expect(screen.getByRole('link', { name: 'common.toolsPage.toolPlugin' })).toHaveAttribute(
|
|
'href',
|
|
'/integrations/tools/built-in',
|
|
)
|
|
|
|
toolView.unmount()
|
|
const triggerView = renderIntegrationsPage({ section: 'trigger' })
|
|
|
|
expect(screen.getByTestId('plugin-category-trigger')).toBeInTheDocument()
|
|
expect(screen.getByTestId('plugin-category-trigger').parentElement).toHaveClass(
|
|
'flex',
|
|
'flex-col',
|
|
'overflow-hidden',
|
|
)
|
|
expect(screen.getByRole('link', { name: 'plugin.categorySingle.trigger' })).toHaveAttribute(
|
|
'href',
|
|
'/integrations/trigger',
|
|
)
|
|
|
|
triggerView.unmount()
|
|
const agentStrategyView = renderIntegrationsPage({ section: 'agent-strategy' })
|
|
|
|
expect(screen.getByTestId('plugin-category-agent-strategy')).toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: 'plugin.categorySingle.agent' })).toHaveAttribute(
|
|
'href',
|
|
'/integrations/agent-strategy',
|
|
)
|
|
|
|
agentStrategyView.unmount()
|
|
renderIntegrationsPage({ section: 'extension' })
|
|
|
|
expect(screen.getByTestId('plugin-category-extension')).toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: 'plugin.categorySingle.extension' })).toHaveAttribute(
|
|
'href',
|
|
'/integrations/extension',
|
|
)
|
|
})
|
|
|
|
it.each([
|
|
['provider', 'model provider marketplace', '/plugins/model'],
|
|
['data-source', 'data source marketplace', '/plugins/datasource'],
|
|
['extension', 'empty marketplace', '/plugins/extension'],
|
|
] as const)(
|
|
'opens the %s marketplace path from integrations',
|
|
async (section, buttonName, marketplacePath) => {
|
|
const user = userEvent.setup()
|
|
renderIntegrationsPage({ section })
|
|
|
|
await user.click(screen.getByRole('button', { name: buttonName }))
|
|
|
|
expect(mockWindowOpen).toHaveBeenCalledWith(
|
|
expect.stringContaining(`${marketplacePath}?source=`),
|
|
'_blank',
|
|
'noopener,noreferrer',
|
|
)
|
|
expect(mockRouterPush).not.toHaveBeenCalled()
|
|
},
|
|
)
|
|
|
|
it('passes marketplace platform paths to external marketplace callbacks', async () => {
|
|
const user = userEvent.setup()
|
|
const onSwitchToMarketplace = vi.fn()
|
|
renderIntegrationsPage({ section: 'trigger' }, { onSwitchToMarketplace })
|
|
|
|
await user.click(screen.getByRole('button', { name: 'empty marketplace' }))
|
|
|
|
expect(onSwitchToMarketplace).toHaveBeenCalledWith('/plugins/trigger')
|
|
expect(mockRouterPush).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('renders migrated legacy setting sections', () => {
|
|
const { unmount } = renderIntegrationsPage({ section: 'data-source' })
|
|
|
|
expect(screen.getByTestId('data-source-page')).toBeInTheDocument()
|
|
expect(screen.getByTestId('install-from-marketplace-query')).toHaveAttribute(
|
|
'data-install-context-category',
|
|
'datasource',
|
|
)
|
|
expect(screen.getByRole('button', { name: 'plugin debug' })).toHaveTextContent(
|
|
'plugin.debugInfo.title',
|
|
)
|
|
|
|
unmount()
|
|
renderIntegrationsPage({ section: 'custom-endpoint' })
|
|
|
|
expect(screen.getByTestId('api-extension-page')).toBeInTheDocument()
|
|
expect(screen.queryByText('plugin.autoUpdate.autoUpdate')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('hides the plugin debug action when debug permission is unavailable', () => {
|
|
mockCanDebugger.mockReturnValue(false)
|
|
|
|
renderIntegrationsPage({ section: 'data-source' })
|
|
|
|
expect(
|
|
screen.queryByLabelText('plugin.privilege.noDebugPermissionTooltip'),
|
|
).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('button', { name: 'plugin debug' })).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('renders existing pages from route sections', () => {
|
|
const modelProviderView = renderIntegrationsPage(undefined, 'provider')
|
|
|
|
expect(screen.getByTestId('model-provider-page')).toBeInTheDocument()
|
|
|
|
modelProviderView.unmount()
|
|
const mcpView = renderIntegrationsPage(undefined, 'mcp')
|
|
|
|
expect(screen.getByTestId('tool-provider-list')).toHaveTextContent('mcp')
|
|
expect(screen.getByTestId('tool-provider-list').parentElement).toHaveClass(
|
|
'flex',
|
|
'flex-col',
|
|
'overflow-hidden',
|
|
)
|
|
|
|
mcpView.unmount()
|
|
renderIntegrationsPage(undefined, 'data-source')
|
|
|
|
expect(screen.getByTestId('data-source-page')).toBeInTheDocument()
|
|
})
|
|
|
|
it('renders the MCP route as read-only without mcp.manage', () => {
|
|
mockConsoleState.workspacePermissionKeys = ['tool.manage']
|
|
|
|
renderIntegrationsPage(undefined, 'mcp')
|
|
|
|
expect(screen.getByTestId('tool-provider-list')).toHaveTextContent('mcp')
|
|
})
|
|
|
|
it.each(['custom-tool', 'workflow-tool'] as const)(
|
|
'renders the %s route as read-only without tool.manage',
|
|
(section) => {
|
|
mockConsoleState.workspacePermissionKeys = ['mcp.manage']
|
|
|
|
renderIntegrationsPage(undefined, section)
|
|
|
|
expect(screen.getByTestId('tool-provider-list')).toBeInTheDocument()
|
|
},
|
|
)
|
|
|
|
it('remounts the tools section content when the route section changes', () => {
|
|
const view = renderIntegrationsPage(undefined, 'builtin')
|
|
|
|
expect(screen.getByTestId('plugin-category-tool')).toBeInTheDocument()
|
|
|
|
view.rerender(<IntegrationsPage section="mcp" />)
|
|
|
|
expect(screen.getByTestId('tool-provider-list')).toHaveAttribute('data-mounted-category', 'mcp')
|
|
})
|
|
|
|
it('keeps existing category-only tools URLs functional', () => {
|
|
renderIntegrationsPage({ category: 'mcp' })
|
|
|
|
expect(screen.getByTestId('tool-provider-list')).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: 'common.menus.tools' })).not.toHaveClass(
|
|
'bg-state-base-active',
|
|
)
|
|
expect(screen.getByRole('button', { name: 'common.menus.tools' })).not.toHaveAttribute(
|
|
'aria-current',
|
|
)
|
|
expect(screen.getByRole('link', { name: 'common.toolsPage.toolPlugin' })).toHaveAttribute(
|
|
'href',
|
|
'/integrations/tools/built-in',
|
|
)
|
|
expect(screen.getByRole('link', { name: 'common.toolsPage.toolPlugin' })).toHaveClass('pl-8')
|
|
expect(
|
|
screen
|
|
.getByRole('link', { name: 'common.toolsPage.toolPlugin' })
|
|
.querySelector('.i-custom-vender-integrations-tools'),
|
|
).toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: 'MCP' })).toHaveAttribute(
|
|
'href',
|
|
'/integrations/tools/mcp',
|
|
)
|
|
expect(screen.getByRole('link', { name: 'MCP' })).toHaveClass('bg-state-base-active')
|
|
expect(screen.getByRole('link', { name: 'MCP' })).toHaveAttribute('aria-current', 'page')
|
|
expect(screen.getByRole('link', { name: 'common.settings.swaggerAPIAsTool' })).toHaveAttribute(
|
|
'href',
|
|
'/integrations/tools/api',
|
|
)
|
|
expect(screen.getByRole('link', { name: 'workflow.common.workflowAsTool' })).toHaveAttribute(
|
|
'href',
|
|
'/integrations/tools/workflow',
|
|
)
|
|
const workflowToolIcon = screen
|
|
.getByRole('link', { name: 'workflow.common.workflowAsTool' })
|
|
.querySelector('.i-custom-vender-integrations-workflow-as-tool')
|
|
expect(workflowToolIcon).toBeInTheDocument()
|
|
expect(workflowToolIcon).toHaveClass('size-4')
|
|
expect(
|
|
screen
|
|
.getByRole('link', { name: 'workflow.common.workflowAsTool' })
|
|
.querySelector('.i-ri-node-tree'),
|
|
).not.toBeInTheDocument()
|
|
expect(
|
|
screen
|
|
.getByRole('link', { name: 'workflow.common.workflowAsTool' })
|
|
.compareDocumentPosition(
|
|
screen.getByRole('link', { name: 'common.settings.swaggerAPIAsTool' }),
|
|
),
|
|
).toBe(Node.DOCUMENT_POSITION_FOLLOWING)
|
|
})
|
|
|
|
it('toggles the tools submenu from the keyboard without other nav items closing it', async () => {
|
|
const user = userEvent.setup()
|
|
const onSectionChange = vi.fn()
|
|
renderWithNuqs(<IntegrationsPage section="provider" onSectionChange={onSectionChange} />)
|
|
|
|
expect(screen.getByRole('button', { name: 'common.settings.provider' })).toHaveClass(
|
|
'bg-state-base-active',
|
|
)
|
|
|
|
const toolsButton = screen.getByRole('button', { name: 'common.menus.tools' })
|
|
|
|
expect(toolsButton).toHaveAttribute('aria-expanded', 'false')
|
|
expect(screen.queryByRole('button', { name: 'MCP' })).not.toBeInTheDocument()
|
|
|
|
toolsButton.focus()
|
|
await user.keyboard('{Enter}')
|
|
|
|
expect(onSectionChange).toHaveBeenCalledWith('builtin')
|
|
expect(toolsButton).toHaveAttribute('aria-expanded', 'true')
|
|
expect(screen.getByRole('button', { name: 'common.toolsPage.toolPlugin' })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: 'MCP' })).toBeInTheDocument()
|
|
|
|
await user.click(screen.getByRole('button', { name: 'common.settings.provider' }))
|
|
|
|
expect(onSectionChange).toHaveBeenCalledWith('provider')
|
|
expect(toolsButton).toHaveAttribute('aria-expanded', 'true')
|
|
expect(screen.getByRole('button', { name: 'MCP' })).toBeInTheDocument()
|
|
|
|
toolsButton.focus()
|
|
await user.keyboard(' ')
|
|
|
|
expect(toolsButton).toHaveAttribute('aria-expanded', 'false')
|
|
expect(screen.queryByRole('button', { name: 'MCP' })).not.toBeInTheDocument()
|
|
expect(onSectionChange).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('keeps custom, workflow, and MCP tool entries visible without manage permissions', async () => {
|
|
const user = userEvent.setup()
|
|
mockConsoleState.workspacePermissionKeys = ['mcp.manage']
|
|
renderIntegrationsPage(undefined, { section: 'provider', onSectionChange: vi.fn() })
|
|
|
|
await user.click(screen.getByRole('button', { name: 'common.menus.tools' }))
|
|
|
|
expect(screen.getByRole('button', { name: 'common.toolsPage.toolPlugin' })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: 'MCP' })).toBeInTheDocument()
|
|
expect(
|
|
screen.getByRole('button', { name: 'workflow.common.workflowAsTool' }),
|
|
).toBeInTheDocument()
|
|
expect(
|
|
screen.getByRole('button', { name: 'common.settings.swaggerAPIAsTool' }),
|
|
).toBeInTheDocument()
|
|
})
|
|
|
|
it('opens tools to the tools plugin page when the parent tools nav is clicked', async () => {
|
|
const user = userEvent.setup()
|
|
renderIntegrationsPage(undefined, 'provider')
|
|
|
|
await user.click(screen.getByRole('button', { name: 'common.menus.tools' }))
|
|
|
|
expect(mockRouterPush).toHaveBeenCalledWith('/integrations/tools/built-in')
|
|
})
|
|
|
|
it('keeps the tools disclosure independent from route section changes', async () => {
|
|
const user = userEvent.setup()
|
|
const view = renderIntegrationsPage(undefined, 'mcp')
|
|
|
|
expect(screen.getByTestId('tool-provider-list')).toHaveAttribute('data-mounted-category', 'mcp')
|
|
expect(screen.getByRole('button', { name: 'common.menus.tools' })).toHaveAttribute(
|
|
'aria-expanded',
|
|
'true',
|
|
)
|
|
expect(screen.getByRole('link', { name: 'common.toolsPage.toolPlugin' })).toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: 'MCP' })).toBeInTheDocument()
|
|
|
|
await user.click(screen.getByRole('button', { name: 'common.menus.tools' }))
|
|
|
|
expect(screen.getByTestId('tool-provider-list')).toHaveAttribute('data-mounted-category', 'mcp')
|
|
expect(screen.getByRole('button', { name: 'common.menus.tools' })).toHaveAttribute(
|
|
'aria-expanded',
|
|
'false',
|
|
)
|
|
expect(
|
|
screen.queryByRole('link', { name: 'common.toolsPage.toolPlugin' }),
|
|
).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('link', { name: 'MCP' })).not.toBeInTheDocument()
|
|
|
|
view.rerender(<IntegrationsPage section="provider" />)
|
|
|
|
expect(screen.getByTestId('model-provider-page')).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: 'common.menus.tools' })).toHaveAttribute(
|
|
'aria-expanded',
|
|
'false',
|
|
)
|
|
expect(
|
|
screen.queryByRole('link', { name: 'common.toolsPage.toolPlugin' }),
|
|
).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('link', { name: 'MCP' })).not.toBeInTheDocument()
|
|
|
|
view.rerender(<IntegrationsPage section="mcp" />)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole('button', { name: 'common.menus.tools' })).toHaveAttribute(
|
|
'aria-expanded',
|
|
'true',
|
|
)
|
|
})
|
|
expect(screen.getByRole('link', { name: 'MCP' })).toHaveClass('bg-state-base-active')
|
|
})
|
|
|
|
it('renders the tools header for tool sections', () => {
|
|
renderIntegrationsPage({ section: 'builtin' })
|
|
|
|
expect(screen.getAllByText('common.toolsPage.toolPlugin')).toHaveLength(2)
|
|
expect(screen.getByText('common.toolsPage.description')).toBeInTheDocument()
|
|
expect(
|
|
screen.getByText('common.toolsPage.description').closest('[class*="max-w-[1600px]"]'),
|
|
).toHaveClass('px-6')
|
|
expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute(
|
|
'href',
|
|
'https://docs.dify.ai/en/self-host/use-dify/workspace/tools',
|
|
)
|
|
})
|
|
|
|
it('aligns model provider headers to the unified content frame', () => {
|
|
renderIntegrationsPage({ section: 'provider' })
|
|
|
|
const description = screen.getByText('common.modelProvider.pageDesc')
|
|
expect(description.closest('[class*="max-w-[1600px]"]')).toHaveClass('px-6')
|
|
expect(
|
|
screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i }),
|
|
).toBeInTheDocument()
|
|
})
|
|
|
|
it('aligns plugin category headers to the unified content frame', () => {
|
|
renderIntegrationsPage({ section: 'trigger' })
|
|
|
|
expect(
|
|
screen.getByText('common.triggerPage.description').closest('[class*="max-w-[1600px]"]'),
|
|
).toHaveClass('px-6')
|
|
})
|
|
|
|
it('renders the mcp header for the mcp section', () => {
|
|
renderIntegrationsPage({ section: 'mcp' })
|
|
|
|
expect(screen.getAllByText('MCP')).toHaveLength(2)
|
|
expect(screen.getByText('common.mcpPage.description')).toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute(
|
|
'href',
|
|
'https://docs.dify.ai/en/self-host/use-dify/workspace/tools#mcp',
|
|
)
|
|
expect(screen.queryByText('common.toolsPage.description')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('renders the custom tool header for the custom tool section', () => {
|
|
renderIntegrationsPage({ section: 'custom-tool' })
|
|
|
|
expect(screen.getAllByText('common.settings.swaggerAPIAsTool')).toHaveLength(2)
|
|
expect(screen.getByText('common.swaggerAPIAsToolPage.description')).toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute(
|
|
'href',
|
|
'https://docs.dify.ai/en/self-host/use-dify/workspace/tools#swagger-api',
|
|
)
|
|
expect(screen.queryByText('common.toolsPage.description')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it.each([
|
|
[
|
|
'data-source',
|
|
'common.settings.dataSource',
|
|
'common.dataSourcePage.description',
|
|
'https://docs.dify.ai/en/develop-plugin/dev-guides-and-walkthroughs/datasource-plugin#data-source-plugin-types',
|
|
],
|
|
] as const)('renders the %s header with a docs link', (section, title, description, href) => {
|
|
renderIntegrationsPage({ section })
|
|
|
|
expect(screen.getAllByText(title)).toHaveLength(2)
|
|
expect(screen.getByText(description)).toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute(
|
|
'href',
|
|
href,
|
|
)
|
|
expect(screen.queryByText('common.toolsPage.description')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('renders the custom endpoint header with toolbar and docs link', () => {
|
|
renderIntegrationsPage({ section: 'custom-endpoint' })
|
|
|
|
expect(screen.getAllByText('common.settings.customEndpoint')).toHaveLength(2)
|
|
expect(screen.getByText('common.apiBasedExtensionPage.description')).toBeInTheDocument()
|
|
expect(screen.getByTestId('api-extension-toolbar')).toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute(
|
|
'href',
|
|
'https://docs.dify.ai/en/develop-plugin/dev-guides-and-walkthroughs/endpoint',
|
|
)
|
|
expect(screen.queryByText('common.toolsPage.description')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it.each([
|
|
[
|
|
'trigger',
|
|
'plugin.categorySingle.trigger',
|
|
'common.triggerPage.description',
|
|
'https://docs.dify.ai/en/develop-plugin/dev-guides-and-walkthroughs/trigger-plugin',
|
|
],
|
|
[
|
|
'extension',
|
|
'plugin.categorySingle.extension',
|
|
'common.extensionPage.description',
|
|
'https://docs.dify.ai/en/self-host/use-dify/workspace/api-extension/api-extension',
|
|
],
|
|
[
|
|
'agent-strategy',
|
|
'plugin.categorySingle.agent',
|
|
'common.agentStrategyPage.description',
|
|
'https://docs.dify.ai/en/develop-plugin/dev-guides-and-walkthroughs/agent-strategy-plugin',
|
|
],
|
|
] as const)('renders the %s header with a docs link', (section, title, description, href) => {
|
|
renderIntegrationsPage({ section })
|
|
|
|
expect(screen.getAllByText(title)).toHaveLength(2)
|
|
expect(screen.getByText(description)).toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute(
|
|
'href',
|
|
href,
|
|
)
|
|
expect(screen.queryByText('common.toolsPage.description')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('renders the workflow as tool header with a docs link', () => {
|
|
renderIntegrationsPage({ section: 'workflow-tool' })
|
|
|
|
expect(screen.getAllByText('workflow.common.workflowAsTool')).toHaveLength(2)
|
|
expect(screen.getByText('common.workflowAsToolPage.description')).toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute(
|
|
'href',
|
|
'https://docs.dify.ai/en/self-host/use-dify/workspace/tools#workflow',
|
|
)
|
|
expect(screen.queryByText('common.toolsPage.description')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it.each([
|
|
['builtin', 'common.toolsPage.description'],
|
|
['mcp', 'common.mcpPage.description'],
|
|
['custom-tool', 'common.swaggerAPIAsToolPage.description'],
|
|
['workflow-tool', 'common.workflowAsToolPage.description'],
|
|
['custom-endpoint', 'common.apiBasedExtensionPage.description'],
|
|
['data-source', 'common.dataSourcePage.description'],
|
|
['trigger', 'common.triggerPage.description'],
|
|
['extension', 'common.extensionPage.description'],
|
|
['agent-strategy', 'common.agentStrategyPage.description'],
|
|
] as const)('renders an unbordered header for %s', (section, description) => {
|
|
renderIntegrationsPage({ section })
|
|
|
|
expect(
|
|
screen.getByText(description).parentElement?.parentElement?.parentElement,
|
|
).not.toHaveClass('border-b', 'border-divider-subtle')
|
|
})
|
|
|
|
it.each(['builtin', 'trigger', 'extension', 'agent-strategy'] as const)(
|
|
'renders plugin update settings action in the category toolbar for %s',
|
|
(section) => {
|
|
renderIntegrationsPage({ section })
|
|
|
|
expect(
|
|
screen.getByRole('button', { name: 'plugin.autoUpdate.autoUpdate' }),
|
|
).toBeInTheDocument()
|
|
},
|
|
)
|
|
|
|
it('opens the integrations marketplace path from the install dropdown marketplace action', async () => {
|
|
const user = userEvent.setup()
|
|
renderIntegrationsPage({ section: 'builtin' })
|
|
|
|
await user.click(screen.getByRole('button', { name: 'plugin install' }))
|
|
|
|
expect(mockWindowOpen).toHaveBeenCalledWith(
|
|
expect.stringContaining('/plugins/tool?source='),
|
|
'_blank',
|
|
'noopener,noreferrer',
|
|
)
|
|
expect(mockRouterPush).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('hides the install action and category installs when install permission is unavailable', () => {
|
|
mockCanManagement.mockReturnValue(false)
|
|
|
|
renderIntegrationsPage({ section: 'trigger' })
|
|
|
|
expect(
|
|
screen.queryByLabelText('plugin.privilege.noInstallPermissionTooltip'),
|
|
).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('button', { name: 'plugin install' })).not.toBeInTheDocument()
|
|
expect(screen.getByTestId('plugin-category-trigger')).toHaveAttribute(
|
|
'data-can-install',
|
|
'false',
|
|
)
|
|
})
|
|
|
|
it('hides the debug action when debug permission is unavailable', () => {
|
|
mockCanDebugger.mockReturnValue(false)
|
|
|
|
renderIntegrationsPage({ section: 'trigger' })
|
|
|
|
expect(
|
|
screen.queryByLabelText('plugin.privilege.noDebugPermissionTooltip'),
|
|
).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('button', { name: 'plugin debug' })).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('hides plugin update settings action when permission management is unavailable', () => {
|
|
mockCanSetPermissions.mockReturnValue(false)
|
|
|
|
renderIntegrationsPage({ section: 'trigger' })
|
|
|
|
expect(
|
|
screen.queryByRole('button', { name: 'plugin.autoUpdate.autoUpdate' }),
|
|
).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('opens the sidebar plugin permissions quick settings and updates permissions', async () => {
|
|
const user = userEvent.setup()
|
|
renderIntegrationsPage({ section: 'provider' })
|
|
|
|
await user.click(screen.getByRole('button', { name: 'plugin.privilege.permissions' }))
|
|
|
|
const dialog = screen.getByRole('dialog', { name: 'plugin.privilege.permissions' })
|
|
expect(within(dialog).getByText('plugin.privilege.quickWhoCanInstall')).toBeInTheDocument()
|
|
expect(within(dialog).getByText('plugin.privilege.quickWhoCanDebug')).toBeInTheDocument()
|
|
|
|
await user.click(
|
|
within(dialog).getByRole('radio', {
|
|
name: 'plugin.privilege.quickWhoCanInstall: plugin.privilege.noone',
|
|
}),
|
|
)
|
|
|
|
expect(mockSetReferenceSettings).toHaveBeenCalledWith({
|
|
install_permission: 'noone',
|
|
debug_permission: 'admins',
|
|
})
|
|
|
|
await user.click(within(dialog).getByRole('button', { name: 'common.operation.close' }))
|
|
|
|
expect(
|
|
screen.queryByRole('dialog', { name: 'plugin.privilege.permissions' }),
|
|
).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('hides the sidebar plugin permissions quick settings when permission management is unavailable', () => {
|
|
mockCanSetPermissions.mockReturnValue(false)
|
|
renderIntegrationsPage({ section: 'provider' })
|
|
|
|
expect(
|
|
screen.queryByRole('button', { name: 'plugin.privilege.permissions' }),
|
|
).not.toBeInTheDocument()
|
|
expect(screen.queryByText('plugin.privilege.quickWhoCanInstall')).not.toBeInTheDocument()
|
|
expect(screen.queryByText('plugin.privilege.quickWhoCanDebug')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('uses the no-action sidebar spacing when install permission is unavailable', () => {
|
|
mockCanManagement.mockReturnValue(false)
|
|
|
|
renderIntegrationsPage({ section: 'provider' })
|
|
|
|
expect(
|
|
screen.getByText('common.settings.integrations').parentElement?.parentElement,
|
|
).toHaveClass('mb-3', 'pt-1', 'pb-0.5')
|
|
expect(
|
|
screen.getByRole('link', { name: 'common.settings.provider' }).parentElement,
|
|
).toHaveClass('py-4')
|
|
})
|
|
|
|
it('keeps the integrations sidebar expanded without a collapse control', () => {
|
|
const { container } = renderIntegrationsPage({ section: 'provider' })
|
|
|
|
expect(container.firstElementChild).toHaveStyle({
|
|
'--model-provider-warning-left': 'calc(240px + 200px)',
|
|
})
|
|
|
|
expect(screen.getByText('common.settings.integrations')).toBeInTheDocument()
|
|
expect(screen.getByText('common.settings.integrations')).toHaveClass(
|
|
'title-2xl-semi-bold',
|
|
'text-text-primary',
|
|
)
|
|
expect(screen.getByText('common.settings.integrations').parentElement).toHaveClass(
|
|
'h-6',
|
|
'items-center',
|
|
)
|
|
expect(screen.getByRole('button', { name: 'plugin install' })).toHaveAttribute(
|
|
'data-show-trigger-arrow',
|
|
'false',
|
|
)
|
|
expect(screen.getByRole('button', { name: 'plugin install' })).toHaveClass('justify-start')
|
|
expect(screen.getByRole('button', { name: 'plugin tasks' })).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: 'plugin debug' })).toHaveTextContent(
|
|
'plugin.debugInfo.title',
|
|
)
|
|
expect(screen.getByRole('button', { name: 'plugin debug' })).toHaveClass(
|
|
'h-8',
|
|
'w-full',
|
|
'gap-2',
|
|
'rounded-lg',
|
|
'py-1',
|
|
'pr-1',
|
|
'pl-2',
|
|
'system-sm-medium',
|
|
)
|
|
expect(screen.getByRole('button', { name: 'plugin debug' }).parentElement).toHaveClass('w-46')
|
|
expect(screen.getByRole('button', { name: 'plugin.privilege.permissions' })).toHaveTextContent(
|
|
'plugin.privilege.permissions',
|
|
)
|
|
expect(screen.getByRole('button', { name: 'plugin.privilege.permissions' })).toHaveClass(
|
|
'h-8',
|
|
'w-full',
|
|
'gap-2',
|
|
'rounded-lg',
|
|
'py-1',
|
|
'pr-1',
|
|
'pl-2',
|
|
'system-sm-medium',
|
|
)
|
|
expect(screen.queryByText('common.settings.swaggerAPIAsTool')).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('link', { name: 'MCP' })).not.toBeInTheDocument()
|
|
expect(screen.getByRole('link', { name: 'common.settings.customEndpoint' })).toHaveAttribute(
|
|
'href',
|
|
'/integrations/custom-endpoint',
|
|
)
|
|
expect(screen.getByRole('link', { name: 'plugin.categorySingle.trigger' })).toHaveAttribute(
|
|
'href',
|
|
'/integrations/trigger',
|
|
)
|
|
expect(
|
|
screen.queryByRole('button', { name: 'common.settings.collapse' }),
|
|
).not.toBeInTheDocument()
|
|
expect(screen.queryByRole('button', { name: 'common.settings.expand' })).not.toBeInTheDocument()
|
|
})
|
|
})
|