Skip to content
Closed
9 changes: 7 additions & 2 deletions app/components/CopyToClipboardButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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: []
Expand Down Expand Up @@ -85,6 +89,7 @@ function handleClick() {
}

@media (hover: none) {
/* On touch devices, hide the button since hover is not available */
.copyButton {
display: none;
}
Expand Down
4 changes: 2 additions & 2 deletions app/components/Package/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ const fundingUrl = computed(() => {
class="py-1.5 px-2.5 sm:me-2"
:tabindex="showScrollToTop ? 0 : -1"
/>
<div class="flex-inline items-center flex-nowrap gap-1 font-mono text-fg-muted">
<div class="flex-inline items-center flex-nowrap gap-3 font-mono text-fg-muted">
<template v-if="displayVersion && hasProvenance(displayVersion)">
<TooltipApp
:text="
Expand All @@ -271,7 +271,7 @@ const fundingUrl = computed(() => {
:to="packageRoute(packageName, resolvedVersion, '#provenance')"
:aria-label="$t('package.provenance_section.view_more_details')"
classicon="i-lucide:shield-check"
class="py-1.25 px-2 me-2"
class="py-1.5 px-2 me-2"
/>
</TooltipApp>
</template>
Expand Down
60 changes: 60 additions & 0 deletions test/nuxt/components/CopyToClipboardButton.spec.ts
Original file line number Diff line number Diff line change
@@ -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)
})
Comment on lines +55 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Final mismatch test uses a weak/reversed assertion for its stated intent.

This can pass when aria-label is only a substring of visible text, so it does not reliably guard against label/content-name mismatch.

Proposed test tightening
-    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)
+    const ariaLabel = (button.attributes('aria-label') ?? '').trim()
+    const visibleText = button.text().trim()
+    // The aria-label should equal the visible text (not some other string)
+    expect(ariaLabel).toBe(visibleText)

})
Loading