fix: Add view action condition in RowMenu component

This commit is contained in:
twwu
2026-05-25 18:19:26 +08:00
parent 8e2ac9d5e6
commit 80fa09a153
2 changed files with 138 additions and 3 deletions

View File

@ -0,0 +1,130 @@
import type { Role } from '@/models/access-control'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import RowMenu from '../row-menu'
const mockWorkspacePermissionKeys = vi.hoisted(() => ({
value: ['workspace.role.manage'] as string[],
}))
const mockCopyRole = vi.hoisted(() => vi.fn())
const mockDeleteRole = vi.hoisted(() => vi.fn())
vi.mock('@/context/app-context', () => ({
useSelector: <T,>(selector: (state: { workspacePermissionKeys: string[] }) => T): T => selector({
workspacePermissionKeys: mockWorkspacePermissionKeys.value,
}),
}))
vi.mock('@/service/access-control/use-workspace-roles', () => ({
useCopyWorkspaceRole: () => ({
mutateAsync: mockCopyRole,
}),
useDeleteWorkspaceRole: () => ({
mutateAsync: mockDeleteRole,
isPending: false,
}),
}))
vi.mock('@langgenius/dify-ui/toast', () => ({
toast: {
success: vi.fn(),
},
}))
const createRole = (overrides: Partial<Role> = {}): Role => ({
id: 'role-1',
tenant_id: 'tenant-1',
type: 'workspace',
category: 'global_system_default',
name: 'Owner',
description: 'Workspace owner',
is_builtin: true,
permission_keys: [],
role_tag: '',
...overrides,
})
const openMenu = async () => {
const user = userEvent.setup()
await user.click(screen.getByRole('button', { name: 'common.operation.moreActions' }))
return user
}
describe('RowMenu', () => {
beforeEach(() => {
vi.clearAllMocks()
mockWorkspacePermissionKeys.value = ['workspace.role.manage']
})
describe('Rendering', () => {
it('should render view action for the owner system role', async () => {
render(
<RowMenu
roleCategory="global_system_default"
role={createRole({ role_tag: 'owner' })}
/>,
)
await openMenu()
expect(screen.getByRole('menuitem', { name: 'common.operation.view' })).toBeInTheDocument()
expect(screen.queryByRole('menuitem', { name: 'common.operation.edit' })).not.toBeInTheDocument()
})
it('should hide view action for non-owner system roles', async () => {
render(
<RowMenu
roleCategory="global_system_default"
role={createRole({ id: 'role-editor', name: 'Editor' })}
/>,
)
await openMenu()
expect(screen.queryByRole('menuitem', { name: 'common.operation.view' })).not.toBeInTheDocument()
expect(screen.getByRole('menuitem', { name: 'common.operation.edit' })).toBeInTheDocument()
})
it('should hide view action for custom roles', async () => {
render(
<RowMenu
roleCategory="global_custom"
role={createRole({
id: 'role-custom',
category: 'global_custom',
name: 'Custom role',
is_builtin: false,
})}
/>,
)
await openMenu()
expect(screen.queryByRole('menuitem', { name: 'common.operation.view' })).not.toBeInTheDocument()
expect(screen.getByRole('menuitem', { name: 'common.operation.edit' })).toBeInTheDocument()
expect(screen.getByRole('menuitem', { name: 'common.operation.duplicate' })).toBeInTheDocument()
expect(screen.getByRole('menuitem', { name: 'common.operation.delete' })).toBeInTheDocument()
})
})
describe('User Interactions', () => {
it('should call onView when clicking the owner view action', async () => {
const onView = vi.fn()
const role = createRole({ role_tag: 'owner' })
render(
<RowMenu
roleCategory="global_system_default"
role={role}
onView={onView}
/>,
)
const user = await openMenu()
await user.click(screen.getByRole('menuitem', { name: 'common.operation.view' }))
expect(onView).toHaveBeenCalledTimes(1)
expect(onView).toHaveBeenCalledWith(role)
})
})
})

View File

@ -76,6 +76,7 @@ const RowMenu = ({
const canManageRoles = hasPermission(workspacePermissionKeys, 'workspace.role.manage')
const hasViewAction = roleCategory === 'global_system_default' && role.role_tag === 'owner'
const hasEditAction = (roleCategory === 'global_custom' || (roleCategory === 'global_system_default' && role.role_tag !== 'owner')) && canManageRoles
const hasDuplicateAction = roleCategory === 'global_custom' && canManageRoles
const hasDeleteAction = roleCategory === 'global_custom' && canManageRoles
@ -87,9 +88,13 @@ const RowMenu = ({
<span aria-hidden className="i-ri-more-fill h-4 w-4 text-text-tertiary" />
</DropdownMenuTrigger>
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="min-w-[160px]">
<DropdownMenuItem className="system-sm-semibold text-text-secondary" onClick={handleView}>
{t('operation.view', { ns: 'common' })}
</DropdownMenuItem>
{
hasViewAction && (
<DropdownMenuItem className="system-sm-semibold text-text-secondary" onClick={handleView}>
{t('operation.view', { ns: 'common' })}
</DropdownMenuItem>
)
}
{
hasEditAction && (
<DropdownMenuItem className="system-sm-semibold text-text-secondary" onClick={handleEdit}>