diff --git a/.agents/skills/frontend-testing/SKILL.md b/.agents/skills/frontend-testing/SKILL.md index 86675dfeba..21c46d75bc 100644 --- a/.agents/skills/frontend-testing/SKILL.md +++ b/.agents/skills/frontend-testing/SKILL.md @@ -38,13 +38,13 @@ Run these commands from `web/`. From the repository root, prefix them with `pnpm pnpm test # Watch mode -pnpm test:watch +pnpm test --watch # Run specific file pnpm test path/to/file.spec.tsx # Generate coverage report -pnpm test:coverage +pnpm test --coverage # Analyze component complexity pnpm analyze-component @@ -220,7 +220,10 @@ Every test should clearly separate: ### 2. Black-Box Testing - Test observable behavior, not implementation details -- Use semantic queries (getByRole, getByLabelText) +- Use semantic queries (`getByRole` with accessible `name`, `getByLabelText`, `getByPlaceholderText`, `getByText`, and scoped `within(...)`) +- Treat `getByTestId` as a last resort. If a control cannot be found by role/name, label, landmark, or dialog scope, fix the component accessibility first instead of adding or relying on `data-testid`. +- Remove production `data-testid` attributes when semantic selectors can cover the behavior. Keep them only for non-visual mocked boundaries, editor/browser shims such as Monaco, canvas/chart output, or third-party widgets with no accessible DOM in the test environment. +- Do not assert decorative icons by test id. Assert the named control that contains them, or mark decorative icons `aria-hidden`. - Avoid testing internal state directly - **Prefer pattern matching over hardcoded strings** in assertions: diff --git a/e2e/features/step-definitions/apps/share-app.steps.ts b/e2e/features/step-definitions/apps/share-app.steps.ts index d5742bdaa8..3ec038b065 100644 --- a/e2e/features/step-definitions/apps/share-app.steps.ts +++ b/e2e/features/step-definitions/apps/share-app.steps.ts @@ -40,7 +40,7 @@ Then('the shared app page should be accessible', async function (this: DifyWorld When('I run the shared workflow app', async function (this: DifyWorld) { const page = this.getPage() - const runButton = page.getByTestId('run-button') + const runButton = page.getByRole('button', { name: 'Execute' }) await expect(runButton).toBeEnabled({ timeout: 15_000 }) await runButton.click() diff --git a/web/__tests__/base/notion-page-selector-flow.test.tsx b/web/__tests__/base/notion-page-selector-flow.test.tsx index 6295d2dc00..ef813ee4bc 100644 --- a/web/__tests__/base/notion-page-selector-flow.test.tsx +++ b/web/__tests__/base/notion-page-selector-flow.test.tsx @@ -111,7 +111,7 @@ describe('Base Notion Page Selector Flow', () => { await user.type(screen.getByTestId('notion-search-input'), 'missing-page') expect(screen.getByText('common.dataSource.notion.selector.noSearchResult')).toBeInTheDocument() - await user.click(screen.getByTestId('notion-search-input-clear')) + await user.click(screen.getByRole('button', { name: 'common.operation.clear' })) expect(screen.getByTestId('notion-page-name-root-1')).toBeInTheDocument() await user.click(screen.getByTestId('notion-page-preview-root-1')) @@ -134,7 +134,7 @@ describe('Base Notion Page Selector Flow', () => { expect(onSelectCredential).toHaveBeenCalledWith('c1') - await user.click(screen.getByTestId('notion-credential-selector-btn')) + await user.click(screen.getByRole('combobox', { name: /Workspace 1/ })) await user.click(screen.getByTestId('notion-credential-item-c2')) expect(mockInvalidPreImportNotionPages).toHaveBeenCalledWith({ datasetId: 'dataset-1', credentialId: 'c2' }) diff --git a/web/__tests__/share/text-generation-run-once-flow.test.tsx b/web/__tests__/share/text-generation-run-once-flow.test.tsx index 2a5d1b882c..1471effa2d 100644 --- a/web/__tests__/share/text-generation-run-once-flow.test.tsx +++ b/web/__tests__/share/text-generation-run-once-flow.test.tsx @@ -119,7 +119,7 @@ describe('RunOnce – integration flow', () => { fireEvent.change(screen.getByPlaceholderText('Bio'), { target: { value: 'Hello' } }) // Phase 3 – submit - fireEvent.click(screen.getByTestId('run-button')) + fireEvent.click(screen.getByRole('button', { name: 'share.generation.run' })) expect(onSend).toHaveBeenCalledTimes(1) // Phase 4 – simulate "running" state @@ -132,7 +132,7 @@ describe('RunOnce – integration flow', () => { />, ) - const stopBtn = screen.getByTestId('stop-button') + const stopBtn = screen.getByRole('button', { name: 'share.generation.stopRun:{"defaultValue":"Stop Run"}' }) expect(stopBtn).toBeInTheDocument() fireEvent.click(stopBtn) expect(onStop).toHaveBeenCalledTimes(1) @@ -145,7 +145,7 @@ describe('RunOnce – integration flow', () => { runControl={{ onStop, isStopping: true }} />, ) - expect(screen.getByTestId('stop-button')).toBeDisabled() + expect(screen.getByRole('button', { name: 'share.generation.stopRun:{"defaultValue":"Stop Run"}' })).toBeDisabled() }) it('clear resets all field types and allows re-submit', async () => { @@ -174,7 +174,7 @@ describe('RunOnce – integration flow', () => { // Re-fill and submit fireEvent.change(screen.getByPlaceholderText('Question'), { target: { value: 'New' } }) - fireEvent.click(screen.getByTestId('run-button')) + fireEvent.click(screen.getByRole('button', { name: 'share.generation.run' })) expect(onSend).toHaveBeenCalledTimes(1) }) @@ -212,7 +212,7 @@ describe('RunOnce – integration flow', () => { fireEvent.change(screen.getByPlaceholderText('Text'), { target: { value: 'hello' } }) fireEvent.change(screen.getByTestId('code-editor'), { target: { value: '{"a":1}' } }) - fireEvent.click(screen.getByTestId('run-button')) + fireEvent.click(screen.getByRole('button', { name: 'share.generation.run' })) expect(onSend).toHaveBeenCalledTimes(1) }) }) diff --git a/web/app/(commonLayout)/datasets/layout.spec.tsx b/web/app/(commonLayout)/datasets/layout.spec.tsx index 9c01cffba8..7abc2253ce 100644 --- a/web/app/(commonLayout)/datasets/layout.spec.tsx +++ b/web/app/(commonLayout)/datasets/layout.spec.tsx @@ -63,12 +63,12 @@ describe('DatasetsLayout', () => { render(( -
datasets
+
datasets
)) expect(screen.getByRole('status')).toBeInTheDocument() - expect(screen.queryByTestId('datasets-content')).not.toBeInTheDocument() + expect(screen.queryByText('datasets')).not.toBeInTheDocument() expect(mockReplace).not.toHaveBeenCalled() }) @@ -80,11 +80,11 @@ describe('DatasetsLayout', () => { render(( -
datasets
+
datasets
)) - expect(screen.queryByTestId('datasets-content')).not.toBeInTheDocument() + expect(screen.queryByText('datasets')).not.toBeInTheDocument() await waitFor(() => { expect(mockReplace).toHaveBeenCalledWith('/apps') }) @@ -98,11 +98,11 @@ describe('DatasetsLayout', () => { render(( -
datasets
+
datasets
)) - expect(screen.getByTestId('datasets-content')).toBeInTheDocument() + expect(screen.getByText('datasets')).toBeInTheDocument() expect(mockReplace).not.toHaveBeenCalled() }) }) diff --git a/web/app/(commonLayout)/role-route-guard.spec.tsx b/web/app/(commonLayout)/role-route-guard.spec.tsx index ca1550f0b8..ef409393b0 100644 --- a/web/app/(commonLayout)/role-route-guard.spec.tsx +++ b/web/app/(commonLayout)/role-route-guard.spec.tsx @@ -48,12 +48,12 @@ describe('RoleRouteGuard', () => { render(( -
content
+
content
)) expect(screen.getByRole('status')).toBeInTheDocument() - expect(screen.queryByTestId('guarded-content')).not.toBeInTheDocument() + expect(screen.queryByText('content')).not.toBeInTheDocument() expect(mockReplace).not.toHaveBeenCalled() }) @@ -64,11 +64,11 @@ describe('RoleRouteGuard', () => { render(( -
content
+
content
)) - expect(screen.queryByTestId('guarded-content')).not.toBeInTheDocument() + expect(screen.queryByText('content')).not.toBeInTheDocument() await waitFor(() => { expect(mockReplace).toHaveBeenCalledWith('/datasets') }) @@ -82,11 +82,11 @@ describe('RoleRouteGuard', () => { render(( -
content
+
content
)) - expect(screen.getByTestId('guarded-content')).toBeInTheDocument() + expect(screen.getByText('content')).toBeInTheDocument() expect(mockReplace).not.toHaveBeenCalled() }) @@ -98,11 +98,11 @@ describe('RoleRouteGuard', () => { render(( -
content
+
content
)) - expect(screen.getByTestId('guarded-content')).toBeInTheDocument() + expect(screen.getByText('content')).toBeInTheDocument() expect(screen.queryByRole('status')).not.toBeInTheDocument() expect(mockReplace).not.toHaveBeenCalled() }) diff --git a/web/app/components/app/annotation/__tests__/filter.spec.tsx b/web/app/components/app/annotation/__tests__/filter.spec.tsx index 8b69494e3f..5353a32c4b 100644 --- a/web/app/components/app/annotation/__tests__/filter.spec.tsx +++ b/web/app/components/app/annotation/__tests__/filter.spec.tsx @@ -243,10 +243,7 @@ describe('Filter', () => { ) // Act - const input = screen.getByPlaceholderText('common.operation.search') - const clearButton = input.parentElement?.querySelector('div.cursor-pointer') - if (clearButton) - fireEvent.click(clearButton) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.clear' })) // Assert expect(setQueryParams).toHaveBeenCalledWith({ ...queryParams, keyword: '' }) diff --git a/web/app/components/app/annotation/batch-action.tsx b/web/app/components/app/annotation/batch-action.tsx index 938dcb03bd..961f313746 100644 --- a/web/app/components/app/annotation/batch-action.tsx +++ b/web/app/components/app/annotation/batch-action.tsx @@ -55,15 +55,23 @@ const BatchAction: FC = ({ {t(`${i18nPrefix}.selected`, { ns: 'appAnnotation' })} -
- - -
+ + - diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-uploader.spec.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-uploader.spec.tsx index 5fc1cd25e1..8e6dd6cc28 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-uploader.spec.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-uploader.spec.tsx @@ -54,7 +54,7 @@ describe('CSVUploader', () => { const clickSpy = vi.spyOn(HTMLInputElement.prototype, 'click') renderComponent() - fireEvent.click(screen.getByText('appAnnotation.batchModal.browse')) + fireEvent.click(screen.getByRole('button', { name: 'appAnnotation.batchModal.browse' })) expect(clickSpy).toHaveBeenCalledTimes(1) clickSpy.mockRestore() @@ -137,7 +137,7 @@ describe('CSVUploader', () => { clickSpy.mockRestore() const valueSetter = vi.spyOn(fileInput, 'value', 'set') - const removeTrigger = screen.getByTestId('remove-file-button') + const removeTrigger = screen.getByRole('button', { name: /operation\.delete$/ }) fireEvent.click(removeTrigger) expect(updateFile).toHaveBeenCalledWith() diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/index.spec.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/index.spec.tsx index c5d7232e12..74b59ff79f 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/index.spec.tsx @@ -115,6 +115,14 @@ describe('BatchModal', () => { expect(props.onCancel).toHaveBeenCalledTimes(1) }) + it('should call onCancel when close button is clicked', () => { + const { props } = renderComponent() + + fireEvent.click(screen.getByRole('button', { name: /operation\.close$/ })) + + expect(props.onCancel).toHaveBeenCalledTimes(1) + }) + it('should submit the csv file, poll status, and notify when import completes', async () => { vi.useFakeTimers({ shouldAdvanceTime: true }) const { props } = renderComponent() diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx index dc63b5c9be..75c3e8a66c 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx @@ -97,7 +97,13 @@ const CSVUploader: FC = ({
{t('batchModal.csvUploadTitle', { ns: 'appAnnotation' })} - {t('batchModal.browse', { ns: 'appAnnotation' })} +
{dragging &&
} @@ -113,9 +119,14 @@ const CSVUploader: FC = ({
-
- -
+
)} diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx index 0f6c27fd5a..7f1905c025 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx @@ -91,9 +91,14 @@ const BatchModal: FC = ({
{t('batchModal.title', { ns: 'appAnnotation' })}
-
- -
+ { expect(screen.getByText(baseMember.name)).toBeInTheDocument() }) - const groupItem = screen.getByText(baseGroup.name).closest('div') - const groupRemove = groupItem?.querySelector('.h-4.w-4.cursor-pointer') as HTMLElement + const groupRemove = screen.getAllByRole('button', { name: /operation\.remove$/ })[0]! + fireEvent.click(groupRemove) await waitFor(() => { expect(screen.queryByText(baseGroup.name)).not.toBeInTheDocument() }) - const memberItem = screen.getByText(baseMember.name).closest('div') - const memberRemove = memberItem?.querySelector('.h-4.w-4.cursor-pointer') as HTMLElement + const memberRemove = screen.getAllByRole('button', { name: /operation\.remove$/ })[0]! + fireEvent.click(memberRemove) await waitFor(() => { diff --git a/web/app/components/app/app-access-control/__tests__/specific-groups-or-members.spec.tsx b/web/app/components/app/app-access-control/__tests__/specific-groups-or-members.spec.tsx index 7b198c4e66..e763521940 100644 --- a/web/app/components/app/app-access-control/__tests__/specific-groups-or-members.spec.tsx +++ b/web/app/components/app/app-access-control/__tests__/specific-groups-or-members.spec.tsx @@ -86,11 +86,13 @@ describe('SpecificGroupsOrMembers', () => { expect(screen.getByText(baseMember.name)).toBeInTheDocument() }) - const groupRemove = screen.getByText(baseGroup.name).closest('div')?.querySelector('.h-4.w-4.cursor-pointer') as HTMLElement + const removeButtons = screen.getAllByRole('button', { name: /operation\.remove$/ }) + const groupRemove = removeButtons[0]! + const memberRemove = removeButtons[1]! + fireEvent.click(groupRemove) expect(useAccessControlStore.getState().specificGroups).toEqual([]) - const memberRemove = screen.getByText(baseMember.name).closest('div')?.querySelector('.h-4.w-4.cursor-pointer') as HTMLElement fireEvent.click(memberRemove) expect(useAccessControlStore.getState().specificMembers).toEqual([]) }) diff --git a/web/app/components/app/app-access-control/add-member-or-group-pop.tsx b/web/app/components/app/app-access-control/add-member-or-group-pop.tsx index 38f9c2ab50..8d9bf19ea3 100644 --- a/web/app/components/app/app-access-control/add-member-or-group-pop.tsx +++ b/web/app/components/app/app-access-control/add-member-or-group-pop.tsx @@ -119,14 +119,40 @@ function SelectedGroupsBreadCrumb() { const handleReset = useCallback(() => { setSelectedGroupsForBreadcrumb([]) }, [setSelectedGroupsForBreadcrumb]) + const hasBreadcrumb = selectedGroupsForBreadcrumb.length > 0 + return (
- 0 && 'cursor-pointer text-text-accent')} onClick={handleReset}>{t('accessControlDialog.operateGroupAndMember.allMembers', { ns: 'app' })} + {hasBreadcrumb + ? ( + + ) + : ( + {t('accessControlDialog.operateGroupAndMember.allMembers', { ns: 'app' })} + )} {selectedGroupsForBreadcrumb.map((group, index) => { + const isLastGroup = index === selectedGroupsForBreadcrumb.length - 1 + return (
/ - handleBreadCrumbClick(index)}>{group.name} + {isLastGroup + ? {group.name} + : ( + + )}
) })} diff --git a/web/app/components/app/app-access-control/specific-groups-or-members.tsx b/web/app/components/app/app-access-control/specific-groups-or-members.tsx index 2cacd2cf03..ce6619ec80 100644 --- a/web/app/components/app/app-access-control/specific-groups-or-members.tsx +++ b/web/app/components/app/app-access-control/specific-groups-or-members.tsx @@ -120,6 +120,8 @@ type BaseItemProps = { onRemove?: () => void } function BaseItem({ icon, onRemove, children }: BaseItemProps) { + const { t } = useTranslation() + return (
@@ -128,9 +130,14 @@ function BaseItem({ icon, onRemove, children }: BaseItemProps) {
{children} -
- -
+
) } diff --git a/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx b/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx index 942a199a87..252eddb47e 100644 --- a/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx +++ b/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx @@ -103,6 +103,22 @@ describe('VersionInfoModal', () => { expect(handleClose).toHaveBeenCalledTimes(1) }) + it('should close when the close button is clicked', () => { + const handleClose = vi.fn() + + render( + , + ) + + fireEvent.click(screen.getByRole('button', { name: 'operation.close' })) + + expect(handleClose).toHaveBeenCalledTimes(1) + }) + it('should validate release note length and clear previous errors before publishing', () => { const handlePublish = vi.fn() const handleClose = vi.fn() diff --git a/web/app/components/app/app-publisher/version-info-modal.tsx b/web/app/components/app/app-publisher/version-info-modal.tsx index 264975a08b..4e5493d1b2 100644 --- a/web/app/components/app/app-publisher/version-info-modal.tsx +++ b/web/app/components/app/app-publisher/version-info-modal.tsx @@ -79,9 +79,14 @@ const VersionInfoModal: FC = ({
{versionInfo?.marked_name ? t('versionHistory.editVersionInfo', { ns: 'workflow' }) : t('versionHistory.nameThisVersion', { ns: 'workflow' })}
-
- -
+
diff --git a/web/app/components/app/configuration/config-var/__tests__/index.spec.tsx b/web/app/components/app/configuration/config-var/__tests__/index.spec.tsx index 51683ad948..604db8288a 100644 --- a/web/app/components/app/configuration/config-var/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/config-var/__tests__/index.spec.tsx @@ -233,9 +233,7 @@ describe('ConfigVar', () => { const item = screen.getByTitle('name · Name') const itemContainer = item.closest('div.group') expect(itemContainer).not.toBeNull() - const actionButtons = itemContainer!.querySelectorAll('div.h-6.w-6') - expect(actionButtons).toHaveLength(2) - fireEvent.click(actionButtons[0]!) + fireEvent.click(within(itemContainer as HTMLElement).getByRole('button', { name: 'common.operation.edit' })) const editDialog = await screen.findByRole('dialog') const saveButton = within(editDialog).getByRole('button', { name: 'common.operation.save' }) @@ -259,9 +257,7 @@ describe('ConfigVar', () => { const item = screen.getByTitle('first · First') const itemContainer = item.closest('div.group') expect(itemContainer).not.toBeNull() - const actionButtons = itemContainer!.querySelectorAll('div.h-6.w-6') - expect(actionButtons).toHaveLength(2) - fireEvent.click(actionButtons[0]!) + fireEvent.click(within(itemContainer as HTMLElement).getByRole('button', { name: 'common.operation.edit' })) const inputs = await screen.findAllByPlaceholderText('appDebug.variableConfig.inputPlaceholder') fireEvent.change(inputs[0]!, { target: { value: 'second' } }) @@ -285,9 +281,7 @@ describe('ConfigVar', () => { const item = screen.getByTitle('first · First') const itemContainer = item.closest('div.group') expect(itemContainer).not.toBeNull() - const actionButtons = itemContainer!.querySelectorAll('div.h-6.w-6') - expect(actionButtons).toHaveLength(2) - fireEvent.click(actionButtons[0]!) + fireEvent.click(within(itemContainer as HTMLElement).getByRole('button', { name: 'common.operation.edit' })) const inputs = await screen.findAllByPlaceholderText('appDebug.variableConfig.inputPlaceholder') fireEvent.change(inputs[1]!, { target: { value: 'Second' } }) @@ -318,7 +312,7 @@ describe('ConfigVar', () => { onPromptVariablesChange, }) - const removeBtn = screen.getByTestId('var-item-delete-btn') + const removeBtn = screen.getByRole('button', { name: 'common.operation.delete' }) fireEvent.click(removeBtn) expect(onPromptVariablesChange).toHaveBeenCalledWith([]) @@ -343,7 +337,7 @@ describe('ConfigVar', () => { }, ) - const deleteBtn = screen.getByTestId('var-item-delete-btn') + const deleteBtn = screen.getByRole('button', { name: 'common.operation.delete' }) fireEvent.click(deleteBtn) // confirmation modal should show up fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' })) @@ -411,8 +405,7 @@ describe('ConfigVar', () => { const itemContainer = item.closest('div.group') expect(itemContainer).not.toBeNull() - const actionButtons = itemContainer!.querySelectorAll('div.h-6.w-6') - fireEvent.click(actionButtons[0]!) + fireEvent.click(within(itemContainer as HTMLElement).getByRole('button', { name: 'common.operation.edit' })) const modalState = setShowExternalDataToolModal.mock.calls.at(-1)?.[0] @@ -460,8 +453,7 @@ describe('ConfigVar', () => { const itemContainer = item.closest('div.group') expect(itemContainer).not.toBeNull() - const actionButtons = itemContainer!.querySelectorAll('div.h-6.w-6') - fireEvent.click(actionButtons[0]!) + fireEvent.click(within(itemContainer as HTMLElement).getByRole('button', { name: 'common.operation.edit' })) const modalState = setShowExternalDataToolModal.mock.calls.at(-1)?.[0] diff --git a/web/app/components/app/configuration/config-var/__tests__/var-item.spec.tsx b/web/app/components/app/configuration/config-var/__tests__/var-item.spec.tsx index aae00bb2b7..6f4fe5f11a 100644 --- a/web/app/components/app/configuration/config-var/__tests__/var-item.spec.tsx +++ b/web/app/components/app/configuration/config-var/__tests__/var-item.spec.tsx @@ -39,7 +39,7 @@ describe('VarItem', () => { />, ) - fireEvent.click(screen.getByTestId('var-item-delete-btn')) + fireEvent.click(screen.getByRole('button', { name: 'common.operation.delete' })) expect(onRemove).toHaveBeenCalledTimes(1) }) diff --git a/web/app/components/app/configuration/config-var/config-select/__tests__/index.spec.tsx b/web/app/components/app/configuration/config-var/config-select/__tests__/index.spec.tsx index 337b3bfe1c..24517eb341 100644 --- a/web/app/components/app/configuration/config-var/config-select/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/config-var/config-select/__tests__/index.spec.tsx @@ -44,12 +44,7 @@ describe('ConfigSelect Component', () => { it('handles option deletion', () => { render() - const optionContainer = screen.getByDisplayValue('Option 1').closest('div') - const deleteButton = optionContainer?.querySelector('div[role="button"]') - - if (!deleteButton) - return - fireEvent.click(deleteButton) + fireEvent.click(screen.getAllByRole('button', { name: 'common.operation.delete' })[0]!) expect(defaultProps.onChange).toHaveBeenCalledWith(['Option 2']) }) @@ -86,7 +81,7 @@ describe('ConfigSelect Component', () => { it('applies delete hover styles', () => { render() const optionContainer = screen.getByDisplayValue('Option 1').closest('div') - const deleteButton = optionContainer?.querySelector('div[role="button"]') + const deleteButton = screen.getAllByRole('button', { name: 'common.operation.delete' })[0] if (!deleteButton) return diff --git a/web/app/components/app/configuration/config-var/config-select/index.tsx b/web/app/components/app/configuration/config-var/config-select/index.tsx index 24bc3b4a06..42878852d9 100644 --- a/web/app/components/app/configuration/config-var/config-select/index.tsx +++ b/web/app/components/app/configuration/config-var/config-select/index.tsx @@ -67,9 +67,10 @@ const ConfigSelect: FC = ({ onFocus={() => setFocusID(index)} onBlur={() => setFocusID(null)} /> -
{ onChange(options.filter((_, i) => index !== i)) setDeletingID(null) @@ -77,8 +78,8 @@ const ConfigSelect: FC = ({ onMouseEnter={() => setDeletingID(index)} onMouseLeave={() => setDeletingID(null)} > - -
+
))} diff --git a/web/app/components/app/configuration/config-var/var-item.tsx b/web/app/components/app/configuration/config-var/var-item.tsx index 80c0bf6ac6..17568683d2 100644 --- a/web/app/components/app/configuration/config-var/var-item.tsx +++ b/web/app/components/app/configuration/config-var/var-item.tsx @@ -9,6 +9,7 @@ import { } from '@remixicon/react' import * as React from 'react' import { useState } from 'react' +import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' import { BracketsX as VarIcon } from '@/app/components/base/icons/src/vender/line/development' import IconTypeIcon from './input-type-icon' @@ -36,6 +37,7 @@ const VarItem: FC = ({ onRemove, canDrag, }) => { + const { t } = useTranslation() const [isDeleting, setIsDeleting] = useState(false) return ( @@ -58,21 +60,24 @@ const VarItem: FC = ({
-
- -
- +
diff --git a/web/app/components/app/configuration/config/agent/agent-tools/__tests__/index.spec.tsx b/web/app/components/app/configuration/config/agent/agent-tools/__tests__/index.spec.tsx index d668737a0a..628e34bbe0 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/__tests__/index.spec.tsx @@ -368,7 +368,7 @@ describe('AgentTools', () => { it('should remove tool when delete action is clicked', async () => { const { getModelConfig } = renderAgentTools() - const deleteButton = screen.getByTestId('delete-removed-tool') + const deleteButton = screen.getByRole('button', { name: /operation\.delete/i }) if (!deleteButton) throw new Error('Delete button not found') await userEvent.click(deleteButton) diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx index 3242bcdcf8..8ad9ad1f8f 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx @@ -96,6 +96,7 @@ const AgentTools: FC = () => { } const [isDeleting, setIsDeleting] = useState(-1) + const getDeleteToolLabel = (tool: AgentTool) => `${t('operation.delete', { ns: 'common' })} ${tool.tool_label || tool.tool_name}` const getToolValue = (tool: ToolDefaultValue) => { const currToolInCollections = collectionList.find(c => c.id === tool.provider_id) const currToolWithConfigs = currToolInCollections?.tools.find(t => t.name === tool.tool_name) @@ -249,7 +250,7 @@ const AgentTools: FC = () => {
{t('toolNameUsageTip', { ns: 'tools' })}
)} {!item.isDeleted && !readonly && ( @@ -320,8 +323,10 @@ const AgentTools: FC = () => { )} -
{ const newModelConfig = produce(modelConfig, (draft) => { draft.agentConfig.tools.splice(index, 1) @@ -331,10 +336,9 @@ const AgentTools: FC = () => { }} onMouseOver={() => setIsDeleting(index)} onMouseLeave={() => setIsDeleting(-1)} - data-testid="delete-removed-tool" > - -
+