fix(web): improve debug plugin copy accessibility (#40035)

This commit is contained in:
林玮 (Jade Lin)
2026-08-05 10:00:37 +00:00
committed by GitHub
parent fe7e2dc62f
commit 945503523b
2 changed files with 29 additions and 8 deletions
@@ -1,4 +1,5 @@
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { cleanup, render, screen, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import KeyValueItem from '../key-value-item'
@@ -34,9 +35,20 @@ describe('KeyValueItem', () => {
expect(screen.queryByText('sk-secret')).not.toBeInTheDocument()
})
it('copies actual value (not masked) when copy button is clicked', () => {
it('associates the label with the copy action and announces the result', async () => {
vi.useRealTimers()
const user = userEvent.setup()
render(<KeyValueItem label="Key" value="sk-secret" maskedValue="sk-***" />)
fireEvent.click(screen.getByRole('button', { name: 'common.operation.copy' }))
const keyGroup = screen.getByRole('group', { name: 'Key' })
const copyButton = within(keyGroup).getByRole('button', {
name: 'common.operation.copy: Key',
})
await user.click(copyButton)
expect(mockCopy).toHaveBeenCalledWith('sk-secret')
expect(copyButton).toHaveAccessibleName('common.operation.copy: Key')
expect(within(keyGroup).getByRole('status')).toHaveTextContent('common.operation.copied: Key')
})
})
@@ -3,7 +3,7 @@ import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import copy from 'copy-to-clipboard'
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useId, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { CopyCheck } from '../../base/icons/src/vender/line/files'
@@ -24,6 +24,7 @@ function KeyValueItem({
}: Props) {
const { t } = useTranslation()
const [isCopied, setIsCopied] = useState(false)
const labelId = useId()
const handleCopy = useCallback(() => {
copy(value)
setIsCopied(true)
@@ -40,11 +41,16 @@ function KeyValueItem({
}
}, [isCopied])
const copyLabel = t(($) => $[`operation.${isCopied ? 'copied' : 'copy'}`], { ns: 'common' })
const copiedLabel = t(($) => $['operation.copied'], { ns: 'common' })
const copyLabel = t(($) => $['operation.copy'], { ns: 'common' })
const copyButtonLabel = `${copyLabel}: ${label}`
const copyStatus = `${copiedLabel}: ${label}`
const tooltipLabel = isCopied ? copiedLabel : copyLabel
return (
<div className="flex items-center gap-1">
<div role="group" aria-labelledby={labelId} className="flex items-center gap-1">
<span
id={labelId}
className={cn(
'flex flex-col items-start justify-center system-xs-medium text-text-tertiary',
labelWidthClassName,
@@ -63,7 +69,7 @@ function KeyValueItem({
render={
<Button
variant="ghost"
aria-label={copyLabel}
aria-label={copyButtonLabel}
className="size-6 p-0"
onClick={handleCopy}
>
@@ -78,9 +84,12 @@ function KeyValueItem({
</Button>
}
/>
<TooltipContent placement="top">{copyLabel}</TooltipContent>
<TooltipContent placement="top">{tooltipLabel}</TooltipContent>
</Tooltip>
</div>
<span role="status" aria-atomic="true" className="sr-only">
{isCopied ? copyStatus : ''}
</span>
</div>
)
}