Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions app/components/CollapsibleSection.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { shallowRef, computed } from 'vue'
import { LinkBase } from '#components'

interface Props {
title: string
Expand Down Expand Up @@ -104,14 +103,18 @@ useHead({
/>
</button>

<span>
<LinkBase :to="`#${id}`">
{{ title }}
</LinkBase>
<button
type="button"
class="text-start focus-visible:outline-accent/70 rounded"
:aria-expanded="isOpen"
:aria-controls="contentId"
@click="toggle"
>
{{ title }}
<span v-if="subtitle" class="block text-2xs normal-case tracking-normal">{{
subtitle
}}</span>
</span>
</button>
</component>

<!-- Actions slot for buttons or other elements -->
Expand Down
53 changes: 53 additions & 0 deletions test/nuxt/a11y.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2247,6 +2247,59 @@ describe('component accessibility audits', () => {
const results = await runAxe(component)
expect(results.violations).toEqual([])
})

it('title button has aria-expanded=true when open and false after click', async () => {
const component = await mountSuspended(CollapsibleSection, {
props: { title: 'Dependencies', id: 'deps-section' },
slots: { default: '<p>Some content</p>' },
})

// Find the title button (second button in the heading β€” the one with visible text)
const buttons = component.findAll('button')
const titleButton = buttons.find(b => b.text().includes('Dependencies'))
expect(titleButton).toBeDefined()

// Initially open
expect(titleButton!.attributes('aria-expanded')).toBe('true')

// After click it should be collapsed
await titleButton!.trigger('click')
expect(titleButton!.attributes('aria-expanded')).toBe('false')

// Click again to re-expand
await titleButton!.trigger('click')
expect(titleButton!.attributes('aria-expanded')).toBe('true')
})

it('title button has aria-controls pointing to the content element', async () => {
const component = await mountSuspended(CollapsibleSection, {
props: { title: 'Section Title', id: 'ctrl-test' },
slots: { default: '<p>Controlled content</p>' },
})

const buttons = component.findAll('button')
const titleButton = buttons.find(b => b.text().includes('Section Title'))
expect(titleButton).toBeDefined()

const controls = titleButton!.attributes('aria-controls')
expect(controls).toBe('ctrl-test-collapsible-content')

// The referenced content element should exist
expect(component.find(`#${controls}`).exists()).toBe(true)
})

it('should have no accessibility violations with a subtitle', async () => {
const component = await mountSuspended(CollapsibleSection, {
props: {
title: 'Section Title',
subtitle: 'A subtitle that describes the section',
id: 'subtitle-section',
},
slots: { default: '<p>Content</p>' },
})
const results = await runAxe(component)
expect(results.violations).toEqual([])
})
})

describe('TerminalExecute', () => {
Expand Down
Loading