diff --git a/client/src/components/SidePanel/Agents/DuplicateAgent.tsx b/client/src/components/SidePanel/Agents/DuplicateAgent.tsx
index 089dea0732..1e598d528d 100644
--- a/client/src/components/SidePanel/Agents/DuplicateAgent.tsx
+++ b/client/src/components/SidePanel/Agents/DuplicateAgent.tsx
@@ -37,6 +37,7 @@ export default function DuplicateAgent({ agent_id }: { agent_id: string }) {
size="sm"
variant="outline"
aria-label={localize('com_ui_duplicate_agent')}
+ title={localize('com_ui_duplicate_agent')}
type="button"
onClick={handleDuplicate}
>
diff --git a/client/src/components/SidePanel/Agents/__tests__/AgentFooter.spec.tsx b/client/src/components/SidePanel/Agents/__tests__/AgentFooter.spec.tsx
index 8d882bffe8..cfceeacb33 100644
--- a/client/src/components/SidePanel/Agents/__tests__/AgentFooter.spec.tsx
+++ b/client/src/components/SidePanel/Agents/__tests__/AgentFooter.spec.tsx
@@ -362,9 +362,15 @@ describe('AgentFooter', () => {
}
return undefined;
});
+ mockUseHasAccess.mockReturnValue(true);
+ mockUseResourcePermissions.mockReturnValue({
+ hasPermission: () => false,
+ isLoading: false,
+ permissionBits: 0,
+ });
render(
);
- expect(screen.queryByTestId('grant-access-dialog-agent')).toBeInTheDocument(); // Still shows because hasAccess is true
- expect(screen.queryByTestId('duplicate-agent')).not.toBeInTheDocument(); // Should not show for different author
+ expect(screen.queryByTestId('grant-access-dialog-agent')).not.toBeInTheDocument(); // No share permission
+ expect(screen.queryByTestId('duplicate-button')).not.toBeInTheDocument(); // No edit permission
});
test('adjusts UI based on permissions', () => {
@@ -420,7 +426,84 @@ describe('AgentFooter', () => {
render(
);
expect(screen.queryByTestId('delete-button')).not.toBeInTheDocument();
expect(screen.queryByTestId('grant-access-dialog-agent')).not.toBeInTheDocument();
- // Duplicate button should still show as it doesn't depend on permissions loading
+ expect(screen.queryByTestId('duplicate-button')).not.toBeInTheDocument();
+ });
+
+ test('shows duplicate button for non-owner with EDIT permission', () => {
+ mockUseAuthContext.mockReturnValue(createAuthContext(mockUsers.different));
+ mockUseWatch.mockImplementation(({ name }) => {
+ if (name === 'agent') {
+ return {
+ _id: 'agent-db-123',
+ name: 'Test Agent',
+ author: 'user-123',
+ projectIds: ['project-1'],
+ isCollaborative: false,
+ };
+ }
+ if (name === 'id') {
+ return 'agent-123';
+ }
+ return undefined;
+ });
+ mockUseResourcePermissions.mockReturnValue({
+ hasPermission: (bit: number) => bit === 2,
+ isLoading: false,
+ permissionBits: 2,
+ });
+ render(
);
+ expect(screen.getByTestId('duplicate-button')).toBeInTheDocument();
+ });
+
+ test('hides duplicate button for non-owner with only VIEW permission', () => {
+ mockUseAuthContext.mockReturnValue(createAuthContext(mockUsers.different));
+ mockUseWatch.mockImplementation(({ name }) => {
+ if (name === 'agent') {
+ return {
+ _id: 'agent-db-123',
+ name: 'Test Agent',
+ author: 'user-123',
+ projectIds: ['project-1'],
+ isCollaborative: false,
+ };
+ }
+ if (name === 'id') {
+ return 'agent-123';
+ }
+ return undefined;
+ });
+ mockUseResourcePermissions.mockReturnValue({
+ hasPermission: () => false,
+ isLoading: false,
+ permissionBits: 1,
+ });
+ render(
);
+ expect(screen.queryByTestId('duplicate-button')).not.toBeInTheDocument();
+ });
+
+ test('shows duplicate button for admin who is not the author', () => {
+ mockUseAuthContext.mockReturnValue(createAuthContext(mockUsers.admin));
+ mockUseWatch.mockImplementation(({ name }) => {
+ if (name === 'agent') {
+ return {
+ _id: 'agent-db-123',
+ name: 'Test Agent',
+ author: 'user-123',
+ projectIds: ['project-1'],
+ isCollaborative: false,
+ };
+ }
+ if (name === 'id') {
+ return 'agent-123';
+ }
+ return undefined;
+ });
+ mockUseResourcePermissions.mockReturnValue({
+ hasPermission: () => false,
+ isLoading: false,
+ permissionBits: 0,
+ });
+ render(
);
expect(screen.getByTestId('duplicate-button')).toBeInTheDocument();
});
});