diff --git a/app/components/CopyToClipboardButton.vue b/app/components/CopyToClipboardButton.vue index f709aad730..9d342c01c0 100644 --- a/app/components/CopyToClipboardButton.vue +++ b/app/components/CopyToClipboardButton.vue @@ -16,8 +16,12 @@ const props = defineProps<{ const buttonCopyText = computed(() => props.copyText || $t('common.copy')) const buttonCopiedText = computed(() => props.copiedText || $t('common.copied')) -const buttonAriaLabelCopy = computed(() => props.ariaLabelCopy || $t('common.copy')) -const buttonAriaLabelCopied = computed(() => props.ariaLabelCopied || $t('common.copied')) +const buttonAriaLabelCopy = computed( + () => props.ariaLabelCopy || props.copyText || $t('common.copy'), +) +const buttonAriaLabelCopied = computed( + () => props.ariaLabelCopied || props.copiedText || $t('common.copied'), +) const emit = defineEmits<{ click: [] @@ -85,6 +89,7 @@ function handleClick() { } @media (hover: none) { + /* On touch devices, hide the button since hover is not available */ .copyButton { display: none; } diff --git a/app/components/Package/Header.vue b/app/components/Package/Header.vue index 7cf6b4c8bc..b04ad50205 100644 --- a/app/components/Package/Header.vue +++ b/app/components/Package/Header.vue @@ -253,7 +253,7 @@ const fundingUrl = computed(() => { class="py-1.5 px-2.5 sm:me-2" :tabindex="showScrollToTop ? 0 : -1" /> -
+
diff --git a/test/nuxt/components/CopyToClipboardButton.spec.ts b/test/nuxt/components/CopyToClipboardButton.spec.ts new file mode 100644 index 0000000000..138ee12343 --- /dev/null +++ b/test/nuxt/components/CopyToClipboardButton.spec.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from 'vitest' +import { mountSuspended } from '@nuxt/test-utils/runtime' +import CopyToClipboardButton from '~/components/CopyToClipboardButton.vue' + +describe('CopyToClipboardButton', () => { + it('aria-label matches visible copy text when copyText is provided', async () => { + const wrapper = await mountSuspended(CopyToClipboardButton, { + props: { + copied: false, + copyText: 'Copy package name', + }, + }) + + const button = wrapper.find('button') + expect(button.attributes('aria-label')).toBe('Copy package name') + expect(button.text()).toContain('Copy package name') + }) + + it('aria-label uses ariaLabelCopy when explicitly provided', async () => { + const wrapper = await mountSuspended(CopyToClipboardButton, { + props: { + copied: false, + copyText: 'Copy package name', + ariaLabelCopy: 'Copy the package name to clipboard', + }, + }) + + const button = wrapper.find('button') + expect(button.attributes('aria-label')).toBe('Copy the package name to clipboard') + }) + + it('aria-label reflects copiedText when copied is true', async () => { + const wrapper = await mountSuspended(CopyToClipboardButton, { + props: { + copied: true, + copyText: 'Copy package name', + copiedText: 'Copied!', + }, + }) + + const button = wrapper.find('button') + expect(button.attributes('aria-label')).toBe('Copied!') + expect(button.text()).toContain('Copied!') + }) + + it('aria-label matches visible text - no label/content mismatch', async () => { + const wrapper = await mountSuspended(CopyToClipboardButton, { + props: { + copied: false, + copyText: 'Copy install command', + }, + }) + + const button = wrapper.find('button') + const ariaLabel = button.attributes('aria-label') ?? '' + const visibleText = button.text() + // The aria-label should equal the visible text (not some other string) + expect(visibleText).toContain(ariaLabel) + }) +})