diff --git a/package.json b/package.json
index f68f340cc0..3a927097b0 100644
--- a/package.json
+++ b/package.json
@@ -171,6 +171,7 @@
"nuxt": "^4.1.2",
"release-it": "^19.0.5",
"vitest": "^3.2.4",
+ "vitest-axe": "^0.1.0",
"vitest-environment-nuxt": "^1.0.1",
"vue-tsc": "^3.1.0"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 145e36070d..06b97aed97 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -216,6 +216,9 @@ importers:
vitest:
specifier: ^3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.7)(happy-dom@19.0.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0)
+ vitest-axe:
+ specifier: ^0.1.0
+ version: 0.1.0(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.7)(happy-dom@19.0.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0))
vitest-environment-nuxt:
specifier: ^1.0.1
version: 1.0.1(@vue/test-utils@2.4.6)(happy-dom@19.0.2)(magicast@0.3.5)(playwright-core@1.55.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.7)(happy-dom@19.0.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0))
@@ -2884,6 +2887,10 @@ packages:
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
engines: {node: '>=10'}
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
array-ify@1.0.0:
resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==}
@@ -2922,6 +2929,10 @@ packages:
peerDependencies:
postcss: ^8.1.0
+ axe-core@4.10.3:
+ resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==}
+ engines: {node: '>=4'}
+
axios@1.10.0:
resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==}
@@ -3629,6 +3640,9 @@ packages:
resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==}
engines: {node: '>=0.3.1'}
+ dom-accessibility-api@0.5.16:
+ resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+
dom-serializer@2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
@@ -4477,6 +4491,10 @@ packages:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
+ indent-string@4.0.0:
+ resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+ engines: {node: '>=8'}
+
indent-string@5.0.0:
resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
engines: {node: '>=12'}
@@ -5999,6 +6017,10 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
+ redent@3.0.0:
+ resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
+ engines: {node: '>=8'}
+
redis-errors@1.2.0:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'}
@@ -6441,6 +6463,10 @@ packages:
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
engines: {node: '>=18'}
+ strip-indent@3.0.0:
+ resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+ engines: {node: '>=8'}
+
strip-indent@4.0.0:
resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==}
engines: {node: '>=12'}
@@ -7057,6 +7083,11 @@ packages:
yaml:
optional: true
+ vitest-axe@0.1.0:
+ resolution: {integrity: sha512-jvtXxeQPg8R/2ANTY8QicA5pvvdRP4F0FsVUAHANJ46YCDASie/cuhlSzu0DGcLmZvGBSBNsNuK3HqfaeknyvA==}
+ peerDependencies:
+ vitest: '>=0.16.0'
+
vitest-environment-nuxt@1.0.1:
resolution: {integrity: sha512-eBCwtIQriXW5/M49FjqNKfnlJYlG2LWMSNFsRVKomc8CaMqmhQPBS5LZ9DlgYL9T8xIVsiA6RZn2lk7vxov3Ow==}
@@ -10100,6 +10131,8 @@ snapshots:
dependencies:
tslib: 2.8.1
+ aria-query@5.3.2: {}
+
array-ify@1.0.0: {}
assertion-error@2.0.1: {}
@@ -10138,6 +10171,8 @@ snapshots:
postcss: 8.5.6
postcss-value-parser: 4.2.0
+ axe-core@4.10.3: {}
+
axios@1.10.0:
dependencies:
follow-redirects: 1.15.9
@@ -10831,6 +10866,8 @@ snapshots:
diff@8.0.2: {}
+ dom-accessibility-api@0.5.16: {}
+
dom-serializer@2.0.0:
dependencies:
domelementtype: 2.3.0
@@ -11880,6 +11917,8 @@ snapshots:
imurmurhash@0.1.4: {}
+ indent-string@4.0.0: {}
+
indent-string@5.0.0: {}
index-to-position@1.1.0: {}
@@ -12275,8 +12314,7 @@ snapshots:
p-locate: 6.0.0
optional: true
- lodash-es@4.17.21:
- optional: true
+ lodash-es@4.17.21: {}
lodash.capitalize@4.2.1: {}
@@ -13885,6 +13923,11 @@ snapshots:
dependencies:
picomatch: 2.3.1
+ redent@3.0.0:
+ dependencies:
+ indent-string: 4.0.0
+ strip-indent: 3.0.0
+
redis-errors@1.2.0: {}
redis-parser@3.0.0:
@@ -14500,6 +14543,10 @@ snapshots:
strip-final-newline@4.0.0: {}
+ strip-indent@3.0.0:
+ dependencies:
+ min-indent: 1.0.1
+
strip-indent@4.0.0:
dependencies:
min-indent: 1.0.1
@@ -15148,6 +15195,16 @@ snapshots:
terser: 5.43.1
yaml: 2.8.0
+ vitest-axe@0.1.0(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.7)(happy-dom@19.0.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0)):
+ dependencies:
+ aria-query: 5.3.2
+ axe-core: 4.10.3
+ chalk: 5.6.2
+ dom-accessibility-api: 0.5.16
+ lodash-es: 4.17.21
+ redent: 3.0.0
+ vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.7)(happy-dom@19.0.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0)
+
vitest-environment-nuxt@1.0.1(@vue/test-utils@2.4.6)(happy-dom@19.0.2)(magicast@0.3.5)(playwright-core@1.55.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.7)(happy-dom@19.0.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0)):
dependencies:
'@nuxt/test-utils': 3.19.2(@vue/test-utils@2.4.6)(happy-dom@19.0.2)(magicast@0.3.5)(playwright-core@1.55.0)(typescript@5.8.3)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.7)(happy-dom@19.0.2)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0))
diff --git a/test/components/Accordion.spec.ts b/test/components/Accordion.spec.ts
index d760308477..47565f74a2 100644
--- a/test/components/Accordion.spec.ts
+++ b/test/components/Accordion.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import Accordion from '../../src/runtime/components/Accordion.vue'
import type { AccordionProps, AccordionSlots } from '../../src/runtime/components/Accordion.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('Accordion', () => {
const items = [{
@@ -62,4 +64,12 @@ describe('Accordion', () => {
const html = await ComponentRender(nameOrHtml, options, Accordion)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Accordion, {
+ props: { items, modelValue: '1' }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/Alert.spec.ts b/test/components/Alert.spec.ts
index 9f851f8e1b..4574c0a946 100644
--- a/test/components/Alert.spec.ts
+++ b/test/components/Alert.spec.ts
@@ -3,6 +3,8 @@ import Alert from '../../src/runtime/components/Alert.vue'
import type { AlertProps, AlertSlots } from '../../src/runtime/components/Alert.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/alert'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
describe('Alert', () => {
const variants = Object.keys(theme.variants.variant) as any
@@ -34,4 +36,22 @@ describe('Alert', () => {
const html = await ComponentRender(nameOrHtml, options, Alert)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Alert, {
+ props: {
+ title: 'Alert',
+ icon: 'i-lucide-lightbulb',
+ description: 'This is a description',
+ actions: [{ label: 'Action' }],
+ close: true,
+ avatar: {
+ src: 'https://github.com/benjamincanac.png',
+ alt: 'Benjamin Canac'
+ }
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/AuthForm.spec.ts b/test/components/AuthForm.spec.ts
index b77a7697ff..7a2b39f97e 100644
--- a/test/components/AuthForm.spec.ts
+++ b/test/components/AuthForm.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import AuthForm from '../../src/runtime/components/AuthForm.vue'
import type { AuthFormProps, AuthFormSlots } from '../../src/runtime/components/AuthForm.vue'
import ComponentRender from '../component-render'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
describe('AuthForm', () => {
const fields = [{
@@ -44,4 +46,26 @@ describe('AuthForm', () => {
const html = await ComponentRender(nameOrHtml, options, AuthForm)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(AuthForm, {
+ props: {
+ fields,
+ title: 'Title',
+ description: 'Description',
+ icon: 'i-lucide-user',
+ providers: [{ label: 'Google', icon: 'i-simple-icons-google' }],
+ separator: 'or',
+ submit: { label: 'Submit' }
+
+ }
+ })
+
+ expect(await axe(wrapper.element, {
+ rules: {
+ // The passwordVisibility button has an invalid aria-controls value - it need to point to the id of the password input not its name.
+ 'aria-valid-attr-value': { enabled: false }
+ }
+ })).toHaveNoViolations()
+ })
})
diff --git a/test/components/Avatar.spec.ts b/test/components/Avatar.spec.ts
index 26dd9c8cb1..ab3a369f6c 100644
--- a/test/components/Avatar.spec.ts
+++ b/test/components/Avatar.spec.ts
@@ -3,6 +3,8 @@ import Avatar from '../../src/runtime/components/Avatar.vue'
import type { AvatarProps, AvatarSlots } from '../../src/runtime/components/Avatar.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/avatar'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
describe('Avatar', () => {
const sizes = Object.keys(theme.variants.size) as any
@@ -26,4 +28,15 @@ describe('Avatar', () => {
const html = await ComponentRender(nameOrHtml, options, Avatar)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Avatar, {
+ props: {
+ alt: 'Benjamin Canac',
+ src: 'https://github.com/benjamincanac.png'
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/AvatarGroup.spec.ts b/test/components/AvatarGroup.spec.ts
index b849346a51..3336169b6f 100644
--- a/test/components/AvatarGroup.spec.ts
+++ b/test/components/AvatarGroup.spec.ts
@@ -5,6 +5,8 @@ import AvatarGroup from '../../src/runtime/components/AvatarGroup.vue'
import type { AvatarGroupProps, AvatarGroupSlots } from '../../src/runtime/components/AvatarGroup.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/avatar-group'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
const AvatarGroupWrapper = defineComponent({
components: {
@@ -34,4 +36,14 @@ describe('AvatarGroup', () => {
const html = await ComponentRender(nameOrHtml, options, AvatarGroupWrapper)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(AvatarGroupWrapper, {
+ props: {
+ max: 2
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/Badge.spec.ts b/test/components/Badge.spec.ts
index 2e6e36e627..b4f8941696 100644
--- a/test/components/Badge.spec.ts
+++ b/test/components/Badge.spec.ts
@@ -3,6 +3,8 @@ import Badge from '../../src/runtime/components/Badge.vue'
import type { BadgeProps, BadgeSlots } from '../../src/runtime/components/Badge.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/badge'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
describe('Badge', () => {
const sizes = Object.keys(theme.variants.size) as any
@@ -34,4 +36,18 @@ describe('Badge', () => {
const html = await ComponentRender(nameOrHtml, options, Badge)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Badge, {
+ props: {
+ label: 'Badge',
+ icon: 'i-lucide-rocket',
+ leadingIcon: 'i-lucide-arrow-left',
+ trailingIcon: 'i-lucide-arrow-right',
+ avatar: { src: 'https://github.com/benjamincanac.png' }
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/Banner.spec.ts b/test/components/Banner.spec.ts
index e36a283f99..1224e52dbe 100644
--- a/test/components/Banner.spec.ts
+++ b/test/components/Banner.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import Banner from '../../src/runtime/components/Banner.vue'
import type { BannerProps, BannerSlots } from '../../src/runtime/components/Banner.vue'
import ComponentRender from '../component-render'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
describe('Banner', () => {
const props = { id: 'banner' }
@@ -29,4 +31,18 @@ describe('Banner', () => {
const html = await ComponentRender(nameOrHtml, options, Banner)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Banner, {
+ props: {
+ id: 'banner',
+ title: 'Title',
+ icon: 'i-lucide-rocket',
+ actions: [{ label: 'Learn more', trailingIcon: 'i-lucide-arrow-right' }],
+ close: true
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/BlogPost.spec.ts b/test/components/BlogPost.spec.ts
index 7c3646f505..bcaaacb397 100644
--- a/test/components/BlogPost.spec.ts
+++ b/test/components/BlogPost.spec.ts
@@ -3,6 +3,8 @@ import BlogPost from '../../src/runtime/components/BlogPost.vue'
import type { BlogPostProps, BlogPostSlots } from '../../src/runtime/components/BlogPost.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/blog-post'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('BlogPost', () => {
const variants = Object.keys(theme.variants.variant) as any
@@ -60,4 +62,15 @@ describe('BlogPost', () => {
const html = await ComponentRender(nameOrHtml, options, BlogPost)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(BlogPost, {
+ props: {
+ ...props,
+ authors: [author1, author2]
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/BlogPosts.spec.ts b/test/components/BlogPosts.spec.ts
index 2fea60c365..59255dc7e2 100644
--- a/test/components/BlogPosts.spec.ts
+++ b/test/components/BlogPosts.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import BlogPosts from '../../src/runtime/components/BlogPosts.vue'
import type { BlogPostsProps, BlogPostsSlots } from '../../src/runtime/components/BlogPosts.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('BlogPosts', () => {
const posts = [{
@@ -35,4 +37,12 @@ describe('BlogPosts', () => {
const html = await ComponentRender(nameOrHtml, options, BlogPosts)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(BlogPosts, {
+ props
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/Breadcrumb.spec.ts b/test/components/Breadcrumb.spec.ts
index bc2f8522a8..0d4d625ff8 100644
--- a/test/components/Breadcrumb.spec.ts
+++ b/test/components/Breadcrumb.spec.ts
@@ -2,12 +2,15 @@ import { describe, it, expect } from 'vitest'
import Breadcrumb from '../../src/runtime/components/Breadcrumb.vue'
import type { BreadcrumbProps, BreadcrumbSlots } from '../../src/runtime/components/Breadcrumb.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('Breadcrumb', () => {
const items = [{
label: 'Home',
avatar: {
- src: 'https://github.com/benjamincanac.png'
+ src: 'https://github.com/benjamincanac.png',
+ alt: 'Benjamin Canac'
},
to: '/'
}, {
@@ -42,4 +45,12 @@ describe('Breadcrumb', () => {
const html = await ComponentRender(nameOrHtml, options, Breadcrumb)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Breadcrumb, {
+ props
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/Button.spec.ts b/test/components/Button.spec.ts
index 9501754380..f912992c8d 100644
--- a/test/components/Button.spec.ts
+++ b/test/components/Button.spec.ts
@@ -6,6 +6,7 @@ import ComponentRender from '../component-render'
import theme from '#build/ui/button'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { flushPromises } from '@vue/test-utils'
+import { axe } from 'vitest-axe'
import {
UForm
@@ -111,4 +112,20 @@ describe('Button', () => {
resolve?.(null)
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Button, {
+ props: {
+ label: 'Button',
+ avatar: {
+ src: 'https://github.com/benjamincanac.png',
+ alt: 'Benjamin Canac'
+ },
+ trailingIcon: 'i-lucide-arrow-right'
+
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/Calendar.spec.ts b/test/components/Calendar.spec.ts
index 699336871c..9f928758b9 100644
--- a/test/components/Calendar.spec.ts
+++ b/test/components/Calendar.spec.ts
@@ -5,6 +5,7 @@ import type { CalendarProps, CalendarSlots } from '../../src/runtime/components/
import ComponentRender from '../component-render'
import theme from '#build/ui/calendar'
import { CalendarDate } from '@internationalized/date'
+import { axe } from 'vitest-axe'
describe('Calendar', () => {
const sizes = Object.keys(theme.variants.size) as any
@@ -67,4 +68,17 @@ describe('Calendar', () => {
expect(wrapper.emitted()).toMatchObject({ 'update:modelValue': [[date]] })
})
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Calendar, {
+ props: {
+ modelValue: new CalendarDate(2025, 1, 1),
+ range: true,
+ multiple: true,
+ numberOfMonths: 2
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/Card.spec.ts b/test/components/Card.spec.ts
index dfafab3d1e..7418e23938 100644
--- a/test/components/Card.spec.ts
+++ b/test/components/Card.spec.ts
@@ -3,6 +3,8 @@ import Card from '../../src/runtime/components/Card.vue'
import type { CardProps, CardSlots } from '../../src/runtime/components/Card.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/card'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('Card', () => {
const variants = Object.keys(theme.variants.variant) as any
@@ -21,4 +23,10 @@ describe('Card', () => {
const html = await ComponentRender(nameOrHtml, options, Card)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Card)
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/Carousel.spec.ts b/test/components/Carousel.spec.ts
index 659cef6152..7cc7b3977e 100644
--- a/test/components/Carousel.spec.ts
+++ b/test/components/Carousel.spec.ts
@@ -3,24 +3,26 @@ import { describe, it, expect } from 'vitest'
import Carousel from '../../src/runtime/components/Carousel.vue'
import type { CarouselProps, CarouselSlots } from '../../src/runtime/components/Carousel.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
const CarouselWrapper = defineComponent({
components: {
UCarousel: Carousel as any
},
template: `
-
+
`
})
describe('Carousel', () => {
const items = [
- { src: 'https://picsum.photos/600/600?random=1' },
- { src: 'https://picsum.photos/600/600?random=2' },
- { src: 'https://picsum.photos/600/600?random=3' },
- { src: 'https://picsum.photos/600/600?random=4' },
- { src: 'https://picsum.photos/600/600?random=5' },
- { src: 'https://picsum.photos/600/600?random=6' }
+ { src: 'https://picsum.photos/600/600?random=1', alt: 'Image 1' },
+ { src: 'https://picsum.photos/600/600?random=2', alt: 'Image 2' },
+ { src: 'https://picsum.photos/600/600?random=3', alt: 'Image 3' },
+ { src: 'https://picsum.photos/600/600?random=4', alt: 'Image 4' },
+ { src: 'https://picsum.photos/600/600?random=5', alt: 'Image 5' },
+ { src: 'https://picsum.photos/600/600?random=6', alt: 'Image 6' }
]
const props = { items }
@@ -42,4 +44,16 @@ describe('Carousel', () => {
const html = await ComponentRender(nameOrHtml, options, CarouselWrapper)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(CarouselWrapper, {
+ props: {
+ items,
+ arrows: true,
+ dots: true
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/ChangelogVersion.spec.ts b/test/components/ChangelogVersion.spec.ts
index bd2abd9d1b..22880b87bc 100644
--- a/test/components/ChangelogVersion.spec.ts
+++ b/test/components/ChangelogVersion.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import ChangelogVersion from '../../src/runtime/components/ChangelogVersion.vue'
import type { ChangelogVersionProps, ChangelogVersionSlots } from '../../src/runtime/components/ChangelogVersion.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('ChangelogVersion', () => {
it.each([
@@ -37,4 +39,21 @@ describe('ChangelogVersion', () => {
const html = await ComponentRender(nameOrHtml, options, ChangelogVersion)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(ChangelogVersion, {
+ props: {
+ title: 'Version 1.0.0',
+ description: 'Initial release',
+ date: '2025-01-01',
+ badge: 'Badge',
+ authors: [{ name: 'Benjamin Canac', description: 'benjamincanac', avatar: { src: 'https://github.com/benjamincanac.png', alt: 'Benjamin Canac' } }],
+ indicator: true,
+ to: '/changelog',
+ image: 'https://picsum.photos/640/360'
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/ChangelogVersions.spec.ts b/test/components/ChangelogVersions.spec.ts
index 7e00198192..f9907eb6d4 100644
--- a/test/components/ChangelogVersions.spec.ts
+++ b/test/components/ChangelogVersions.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import ChangelogVersions from '../../src/runtime/components/ChangelogVersions.vue'
import type { ChangelogVersionsProps, ChangelogVersionsSlots } from '../../src/runtime/components/ChangelogVersions.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('ChangelogVersions', () => {
const versions = [{
@@ -38,4 +40,12 @@ describe('ChangelogVersions', () => {
const html = await ComponentRender(nameOrHtml, options, ChangelogVersions)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(ChangelogVersions, {
+ props
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/ChatMessage.spec.ts b/test/components/ChatMessage.spec.ts
index eda09e22bd..b9dadeb12f 100644
--- a/test/components/ChatMessage.spec.ts
+++ b/test/components/ChatMessage.spec.ts
@@ -3,13 +3,15 @@ import ChatMessage from '../../src/runtime/components/ChatMessage.vue'
import type { ChatMessageProps, ChatMessageSlots } from '../../src/runtime/components/ChatMessage.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/chat-message'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('ChatMessage', () => {
const variants = Object.keys(theme.variants.variant) as any
const props = {
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user' as const,
- parts: [{ type: 'text', text: 'Hello, how are you?' }]
+ parts: [{ type: 'text' as const, text: 'Hello, how are you?' }]
}
it.each([
@@ -31,4 +33,12 @@ describe('ChatMessage', () => {
const html = await ComponentRender(nameOrHtml, options, ChatMessage)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(ChatMessage, {
+ props
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/ChatMessages.spec.ts b/test/components/ChatMessages.spec.ts
index 4243ca11f6..f7d6f693e2 100644
--- a/test/components/ChatMessages.spec.ts
+++ b/test/components/ChatMessages.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import ChatMessages from '../../src/runtime/components/ChatMessages.vue'
import type { ChatMessagesProps, ChatMessagesSlots } from '../../src/runtime/components/ChatMessages.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('ChatMessages', () => {
const statuses = ['ready', 'submitted', 'streaming', 'error'] as any
@@ -10,11 +12,11 @@ describe('ChatMessages', () => {
messages: [{
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'user' as const,
- parts: [{ type: 'text', text: 'Hello, how are you?' }]
+ parts: [{ type: 'text' as const, text: 'Hello, how are you?' }]
}, {
id: '6045235a-a435-46b8-989d-2df38ca2eb47',
role: 'assistant' as const,
- parts: [{ type: 'text', text: 'I am fine, thank you!' }]
+ parts: [{ type: 'text' as const, text: 'I am fine, thank you!' }]
}]
}
@@ -36,4 +38,12 @@ describe('ChatMessages', () => {
const html = await ComponentRender(nameOrHtml, options, ChatMessages)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(ChatMessages, {
+ props
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/ChatPalette.spec.ts b/test/components/ChatPalette.spec.ts
index 3119b636b0..4f448ed27c 100644
--- a/test/components/ChatPalette.spec.ts
+++ b/test/components/ChatPalette.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import ChatPalette from '../../src/runtime/components/ChatPalette.vue'
import type { ChatPaletteProps, ChatPaletteSlots } from '../../src/runtime/components/ChatPalette.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('ChatPalette', () => {
it.each([
@@ -16,4 +18,10 @@ describe('ChatPalette', () => {
const html = await ComponentRender(nameOrHtml, options, ChatPalette)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(ChatPalette)
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/ChatPrompt.spec.ts b/test/components/ChatPrompt.spec.ts
index 3e97009948..cd7fa08efc 100644
--- a/test/components/ChatPrompt.spec.ts
+++ b/test/components/ChatPrompt.spec.ts
@@ -3,6 +3,8 @@ import ChatPrompt from '../../src/runtime/components/ChatPrompt.vue'
import type { ChatPromptProps, ChatPromptSlots } from '../../src/runtime/components/ChatPrompt.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/chat-prompt'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('ChatPrompt', () => {
const variants = Object.keys(theme.variants.variant) as any
@@ -22,4 +24,14 @@ describe('ChatPrompt', () => {
const html = await ComponentRender(nameOrHtml, options, ChatPrompt)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(ChatPrompt, {
+ props: {
+ placeholder: 'Placeholder'
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/ChatPromptSubmit.spec.ts b/test/components/ChatPromptSubmit.spec.ts
index c8085a6c1f..a17afdef66 100644
--- a/test/components/ChatPromptSubmit.spec.ts
+++ b/test/components/ChatPromptSubmit.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import ChatPromptSubmit from '../../src/runtime/components/ChatPromptSubmit.vue'
import type { ChatPromptSubmitProps } from '../../src/runtime/components/ChatPromptSubmit.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('ChatPromptSubmit', () => {
const statuses = ['ready', 'submitted', 'streaming', 'error'] as any
@@ -15,4 +17,14 @@ describe('ChatPromptSubmit', () => {
const html = await ComponentRender(nameOrHtml, options, ChatPromptSubmit)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(ChatPromptSubmit, {
+ props: {
+ status: 'ready'
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/Checkbox.spec.ts b/test/components/Checkbox.spec.ts
index 1ca750448f..988e80c605 100644
--- a/test/components/Checkbox.spec.ts
+++ b/test/components/Checkbox.spec.ts
@@ -6,6 +6,8 @@ import theme from '#build/ui/checkbox'
import { renderForm } from '../utils/form'
import { mount, flushPromises } from '@vue/test-utils'
import type { FormInputEvents } from '../../src/module'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('Checkbox', () => {
const sizes = Object.keys(theme.variants.size) as any
@@ -42,6 +44,16 @@ describe('Checkbox', () => {
expect(html).toMatchSnapshot()
})
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Checkbox, {
+ props: {
+ label: 'Test checkbox'
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
describe('emits', () => {
test('update:modelValue event', async () => {
const wrapper = mount(Checkbox)
diff --git a/test/components/CheckboxGroup.spec.ts b/test/components/CheckboxGroup.spec.ts
index 61987e7dfe..f47b15c50b 100644
--- a/test/components/CheckboxGroup.spec.ts
+++ b/test/components/CheckboxGroup.spec.ts
@@ -7,6 +7,8 @@ import themeCheckbox from '#build/ui/checkbox'
import { flushPromises, mount } from '@vue/test-utils'
import { renderForm } from '../utils/form'
import type { FormInputEvents } from '../../src/module'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('CheckboxGroup', () => {
const sizes = Object.keys(theme.variants.size) as any
@@ -49,6 +51,21 @@ describe('CheckboxGroup', () => {
expect(html).toMatchSnapshot()
})
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(CheckboxGroup, {
+ props: {
+ items: [
+ { value: '1', label: 'Option 1' },
+ { value: '2', label: 'Option 2' }
+ ],
+ legend: 'Legend'
+
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
describe('emits', () => {
test('update:modelValue event', async () => {
const wrapper = mount(CheckboxGroup, { props: { items: ['Option 1', 'Option 2'] } })
diff --git a/test/components/Chip.spec.ts b/test/components/Chip.spec.ts
index 60bbf0fe0d..c81972384d 100644
--- a/test/components/Chip.spec.ts
+++ b/test/components/Chip.spec.ts
@@ -3,6 +3,8 @@ import Chip from '../../src/runtime/components/Chip.vue'
import type { ChipProps, ChipSlots } from '../../src/runtime/components/Chip.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/chip'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('Chip', () => {
const sizes = Object.keys(theme.variants.size) as any
@@ -26,4 +28,14 @@ describe('Chip', () => {
const html = await ComponentRender(nameOrHtml, options, Chip)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Chip, {
+ props: {
+ text: 'Text'
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/Collapsible.spec.ts b/test/components/Collapsible.spec.ts
index 0083699ad1..6a617f770d 100644
--- a/test/components/Collapsible.spec.ts
+++ b/test/components/Collapsible.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import Collapsible from '../../src/runtime/components/Collapsible.vue'
import type { CollapsibleProps, CollapsibleSlots } from '../../src/runtime/components/Collapsible.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('Collapsible', () => {
const props = { open: true }
@@ -21,4 +23,14 @@ describe('Collapsible', () => {
const html = await ComponentRender(nameOrHtml, options, Collapsible)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Collapsible, {
+ props: {
+ open: true
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/ColorPicker.spec.ts b/test/components/ColorPicker.spec.ts
index eddf7276b5..97e083eb4a 100644
--- a/test/components/ColorPicker.spec.ts
+++ b/test/components/ColorPicker.spec.ts
@@ -4,6 +4,7 @@ import ColorPicker from '../../src/runtime/components/ColorPicker.vue'
import type { ColorPickerProps } from '../../src/runtime/components/ColorPicker.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/color-picker'
+import { axe } from 'vitest-axe'
describe('ColorPicker', () => {
const sizes = Object.keys(theme.variants.size) as any
@@ -28,6 +29,12 @@ describe('ColorPicker', () => {
expect(html).toMatchSnapshot()
})
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(ColorPicker)
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
describe('emits', () => {
test('update:modelValue event', async () => {
const wrapper = await mountSuspended(ColorPicker)
diff --git a/test/components/CommandPalette.spec.ts b/test/components/CommandPalette.spec.ts
index 97d41123af..a0dfc305c0 100644
--- a/test/components/CommandPalette.spec.ts
+++ b/test/components/CommandPalette.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import CommandPalette from '../../src/runtime/components/CommandPalette.vue'
import type { CommandPaletteProps, CommandPaletteSlots } from '../../src/runtime/components/CommandPalette.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('CommandPalette', () => {
const groups = [{
@@ -36,17 +38,17 @@ describe('CommandPalette', () => {
items: [{
label: 'bug',
chip: {
- color: 'error'
+ color: 'error' as const
}
}, {
label: 'feature',
chip: {
- color: 'success'
+ color: 'success' as const
}
}, {
label: 'enhancement',
chip: {
- color: 'info'
+ color: 'info' as const
}
}]
}, {
@@ -55,7 +57,8 @@ describe('CommandPalette', () => {
items: [{
label: 'benjamincanac',
avatar: {
- src: 'https://github.com/benjamincanac.png'
+ src: 'https://github.com/benjamincanac.png',
+ alt: 'Benjamin Canac'
},
to: 'https://github.com/benjamincanac',
target: '_blank'
@@ -95,4 +98,26 @@ describe('CommandPalette', () => {
const html = await ComponentRender(nameOrHtml, options, CommandPalette)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(CommandPalette, {
+ props: {
+ groups,
+ close: true,
+ modelValue: [groups[2]?.items[0], groups[1]?.items[0]],
+ multiple: true
+ }
+ })
+
+ expect(await axe(wrapper.element, {
+ rules: {
+ // ARIA input fields must have an accessible name (aria-input-field-name)"
+ // Fix any of the following:
+ // aria-label attribute does not exist or is empty
+ // aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty
+ // Element has no title attribute
+ 'aria-input-field-name': { enabled: false }
+ }
+ })).toHaveNoViolations()
+ })
})
diff --git a/test/components/Container.spec.ts b/test/components/Container.spec.ts
index 7eb0bfb36c..cf047e73e1 100644
--- a/test/components/Container.spec.ts
+++ b/test/components/Container.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import Container from '../../src/runtime/components/Container.vue'
import type { ContainerProps, ContainerSlots } from '../../src/runtime/components/Container.vue'
import ComponentRender from '../component-render'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
describe('Container', () => {
it.each([
@@ -14,4 +16,10 @@ describe('Container', () => {
const html = await ComponentRender(nameOrHtml, options, Container)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Container)
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/ContextMenu.spec.ts b/test/components/ContextMenu.spec.ts
index b19aa6f2ba..e5a3a8dd36 100644
--- a/test/components/ContextMenu.spec.ts
+++ b/test/components/ContextMenu.spec.ts
@@ -5,6 +5,7 @@ import type { ContextMenuProps, ContextMenuSlots } from '../../src/runtime/compo
import theme from '#build/ui/context-menu'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { expectSlotProps } from '../utils/types'
+import { axe } from 'vitest-axe'
const ContextMenuWrapper = defineComponent({
components: {
@@ -105,6 +106,16 @@ describe('ContextMenu', () => {
expect(wrapper.html()).toMatchSnapshot()
})
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(ContextMenuWrapper, {
+ props
+ })
+
+ await wrapper.find('span').trigger('click.right')
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
+
test('should have the correct types', () => {
// normal
expectSlotProps('item', () => ContextMenu({
diff --git a/test/components/DashboardGroup.spec.ts b/test/components/DashboardGroup.spec.ts
index a3ad02c3d2..fe20a55916 100644
--- a/test/components/DashboardGroup.spec.ts
+++ b/test/components/DashboardGroup.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import DashboardGroup from '../../src/runtime/components/DashboardGroup.vue'
import type { DashboardGroupProps, DashboardGroupSlots } from '../../src/runtime/components/DashboardGroup.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('DashboardGroup', () => {
it.each([
@@ -14,4 +16,10 @@ describe('DashboardGroup', () => {
const html = await ComponentRender(nameOrHtml, options, DashboardGroup)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(DashboardGroup)
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/DashboardNavbar.spec.ts b/test/components/DashboardNavbar.spec.ts
index 10fe34f6c3..79a0c44ce6 100644
--- a/test/components/DashboardNavbar.spec.ts
+++ b/test/components/DashboardNavbar.spec.ts
@@ -4,6 +4,7 @@ import DashboardGroup from '../../src/runtime/components/DashboardGroup.vue'
import DashboardNavbar from '../../src/runtime/components/DashboardNavbar.vue'
import type { DashboardNavbarProps, DashboardNavbarSlots } from '../../src/runtime/components/DashboardNavbar.vue'
import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
const DashboardWrapper = defineComponent({
components: {
@@ -43,4 +44,14 @@ describe('DashboardNavbar', () => {
const wrapper = await mountSuspended(DashboardWrapper, options)
expect(wrapper.html()).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(DashboardWrapper, {
+ props: {
+ title: 'Dashboard'
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/DashboardPanel.spec.ts b/test/components/DashboardPanel.spec.ts
index dbadc3837d..df4e5c4746 100644
--- a/test/components/DashboardPanel.spec.ts
+++ b/test/components/DashboardPanel.spec.ts
@@ -4,6 +4,7 @@ import DashboardGroup from '../../src/runtime/components/DashboardGroup.vue'
import DashboardPanel from '../../src/runtime/components/DashboardPanel.vue'
import type { DashboardPanelProps, DashboardPanelSlots } from '../../src/runtime/components/DashboardPanel.vue'
import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
const DashboardWrapper = defineComponent({
components: {
@@ -40,4 +41,15 @@ describe('DashboardPanel', () => {
const wrapper = await mountSuspended(DashboardWrapper, options)
expect(wrapper.html()).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(DashboardWrapper, {
+ props: {
+ id: 'test',
+ resizable: true
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/DashboardResizeHandle.spec.ts b/test/components/DashboardResizeHandle.spec.ts
index 1afec32366..5cf13e9e80 100644
--- a/test/components/DashboardResizeHandle.spec.ts
+++ b/test/components/DashboardResizeHandle.spec.ts
@@ -4,6 +4,7 @@ import DashboardGroup from '../../src/runtime/components/DashboardGroup.vue'
import DashboardResizeHandle from '../../src/runtime/components/DashboardResizeHandle.vue'
import type { DashboardResizeHandleProps, DashboardResizeHandleSlots } from '../../src/runtime/components/DashboardResizeHandle.vue'
import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
const DashboardWrapper = defineComponent({
components: {
@@ -31,4 +32,10 @@ describe('DashboardResizeHandle', () => {
const wrapper = await mountSuspended(DashboardWrapper, options)
expect(wrapper.html()).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(DashboardWrapper)
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/DashboardSearch.spec.ts b/test/components/DashboardSearch.spec.ts
index 9e8e595193..3a390a549a 100644
--- a/test/components/DashboardSearch.spec.ts
+++ b/test/components/DashboardSearch.spec.ts
@@ -4,6 +4,7 @@ import DashboardGroup from '../../src/runtime/components/DashboardGroup.vue'
import DashboardSearch from '../../src/runtime/components/DashboardSearch.vue'
import type { DashboardSearchProps } from '../../src/runtime/components/DashboardSearch.vue'
import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
const DashboardWrapper = defineComponent({
components: {
@@ -47,4 +48,22 @@ describe('DashboardSearch', () => {
const wrapper = await mountSuspended(DashboardWrapper, options)
expect(wrapper.html()).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(DashboardWrapper, {
+ props
+ })
+
+ expect(await axe(wrapper.element, {
+ // "ARIA input fields must have an accessible name (aria-input-field-name)"
+ //
+ // Fix any of the following:
+ // aria-label attribute does not exist or is empty
+ // aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty
+ // Element has no title attribute
+ rules: {
+ 'aria-input-field-name': { enabled: false }
+ }
+ })).toHaveNoViolations()
+ })
})
diff --git a/test/components/DashboardSearchButton.spec.ts b/test/components/DashboardSearchButton.spec.ts
index b3e7dc93e6..ba4fb5dae0 100644
--- a/test/components/DashboardSearchButton.spec.ts
+++ b/test/components/DashboardSearchButton.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import DashboardSearchButton from '../../src/runtime/components/DashboardSearchButton.vue'
import type { DashboardSearchButtonProps } from '../../src/runtime/components/DashboardSearchButton.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('DashboardSearchButton', () => {
it.each([
@@ -15,4 +17,16 @@ describe('DashboardSearchButton', () => {
const html = await ComponentRender(nameOrHtml, options, DashboardSearchButton)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(DashboardSearchButton, {
+ props: {
+ label: 'Open',
+ icon: 'i-lucide-house',
+ kbds: ['alt', 'o']
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/DashboardSidebar.spec.ts b/test/components/DashboardSidebar.spec.ts
index 31a4450ef4..0d46f87218 100644
--- a/test/components/DashboardSidebar.spec.ts
+++ b/test/components/DashboardSidebar.spec.ts
@@ -4,6 +4,7 @@ import DashboardGroup from '../../src/runtime/components/DashboardGroup.vue'
import DashboardSidebar from '../../src/runtime/components/DashboardSidebar.vue'
import type { DashboardSidebarProps, DashboardSidebarSlots } from '../../src/runtime/components/DashboardSidebar.vue'
import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
const DashboardWrapper = defineComponent({
components: {
@@ -50,4 +51,16 @@ describe('DashboardSidebar', () => {
const wrapper = await mountSuspended(DashboardWrapper, options)
expect(wrapper.html()).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(DashboardWrapper, {
+ props: {
+ id: 'test',
+ resizable: true,
+ collapsible: true
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/DashboardSidebarCollapse.spec.ts b/test/components/DashboardSidebarCollapse.spec.ts
index ea9169af5d..5c0ae3dc23 100644
--- a/test/components/DashboardSidebarCollapse.spec.ts
+++ b/test/components/DashboardSidebarCollapse.spec.ts
@@ -4,6 +4,7 @@ import DashboardGroup from '../../src/runtime/components/DashboardGroup.vue'
import DashboardSidebarCollapse from '../../src/runtime/components/DashboardSidebarCollapse.vue'
import type { DashboardSidebarCollapseProps } from '../../src/runtime/components/DashboardSidebarCollapse.vue'
import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
const DashboardWrapper = defineComponent({
components: {
@@ -27,4 +28,10 @@ describe('DashboardSidebarCollapse', () => {
const wrapper = await mountSuspended(DashboardWrapper, options)
expect(wrapper.html()).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(DashboardWrapper)
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/DashboardSidebarToggle.spec.ts b/test/components/DashboardSidebarToggle.spec.ts
index 8b37c657bb..a1d9814f3a 100644
--- a/test/components/DashboardSidebarToggle.spec.ts
+++ b/test/components/DashboardSidebarToggle.spec.ts
@@ -4,6 +4,7 @@ import DashboardGroup from '../../src/runtime/components/DashboardGroup.vue'
import DashboardSidebarToggle from '../../src/runtime/components/DashboardSidebarToggle.vue'
import type { DashboardSidebarToggleProps } from '../../src/runtime/components/DashboardSidebarToggle.vue'
import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { axe } from 'vitest-axe'
const DashboardWrapper = defineComponent({
components: {
@@ -27,4 +28,10 @@ describe('DashboardSidebarToggle', () => {
const wrapper = await mountSuspended(DashboardWrapper, options)
expect(wrapper.html()).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(DashboardWrapper)
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/DashboardToolbar.spec.ts b/test/components/DashboardToolbar.spec.ts
index 7b9c7da716..d6587cdeb0 100644
--- a/test/components/DashboardToolbar.spec.ts
+++ b/test/components/DashboardToolbar.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import DashboardToolbar from '../../src/runtime/components/DashboardToolbar.vue'
import type { DashboardToolbarProps, DashboardToolbarSlots } from '../../src/runtime/components/DashboardToolbar.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('DashboardToolbar', () => {
it.each([
@@ -17,4 +19,10 @@ describe('DashboardToolbar', () => {
const html = await ComponentRender(nameOrHtml, options, DashboardToolbar)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(DashboardToolbar)
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/Drawer.spec.ts b/test/components/Drawer.spec.ts
index 970fb5a997..003a2cf3d0 100644
--- a/test/components/Drawer.spec.ts
+++ b/test/components/Drawer.spec.ts
@@ -3,6 +3,8 @@ import Drawer from '../../src/runtime/components/Drawer.vue'
import type { DrawerProps, DrawerSlots } from '../../src/runtime/components/Drawer.vue'
import ComponentRender from '../component-render'
import theme from '#build/ui/drawer'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('Drawer', () => {
const directions = Object.keys(theme.variants.direction) as any
@@ -31,4 +33,17 @@ describe('Drawer', () => {
const html = await ComponentRender(nameOrHtml, options, Drawer)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Drawer, {
+ props: {
+ open: true,
+ portal: false,
+ title: 'Title',
+ description: 'Description'
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/DropdownMenu.spec.ts b/test/components/DropdownMenu.spec.ts
index 59b5807ccc..9eb4f3b16a 100644
--- a/test/components/DropdownMenu.spec.ts
+++ b/test/components/DropdownMenu.spec.ts
@@ -4,6 +4,8 @@ import type { DropdownMenuProps, DropdownMenuSlots } from '../../src/runtime/com
import ComponentRender from '../component-render'
import theme from '#build/ui/dropdown-menu'
import { expectSlotProps } from '../utils/types'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('DropdownMenu', () => {
const sizes = Object.keys(theme.variants.size) as any
@@ -12,7 +14,8 @@ describe('DropdownMenu', () => {
[{
label: 'My account',
avatar: {
- src: 'https://github.com/benjamincanac.png'
+ src: 'https://github.com/benjamincanac.png',
+ alt: 'Benjamín Canac'
},
type: 'label'
}],
@@ -112,6 +115,22 @@ describe('DropdownMenu', () => {
expect(html).toMatchSnapshot()
})
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(DropdownMenu, {
+ props
+ })
+
+ expect(await axe(wrapper.element, {
+ rules: {
+ // "Certain ARIA roles must contain particular children (aria-required-children)"
+ //
+ // Fix any of the following:
+ // Element has children which are not allowed: img[tabindex]
+ 'aria-required-children': { enabled: false }
+ }
+ })).toHaveNoViolations()
+ })
+
test('should have the correct types', () => {
// normal
expectSlotProps('item', () => DropdownMenu({
diff --git a/test/components/Error.spec.ts b/test/components/Error.spec.ts
index 7d232e5ec7..b239ca5ad3 100644
--- a/test/components/Error.spec.ts
+++ b/test/components/Error.spec.ts
@@ -2,6 +2,8 @@ import { describe, it, expect } from 'vitest'
import Error from '../../src/runtime/components/Error.vue'
import type { ErrorProps, ErrorSlots } from '../../src/runtime/components/Error.vue'
import ComponentRender from '../component-render'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('Error', () => {
const error = {
@@ -30,4 +32,16 @@ describe('Error', () => {
const html = await ComponentRender(nameOrHtml, options, Error)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(Error, {
+ props: {
+ error,
+ redirect: '/blog',
+ clear: { label: 'Home' }
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/FieldGroup.spec.ts b/test/components/FieldGroup.spec.ts
index 303eff3252..feff946563 100644
--- a/test/components/FieldGroup.spec.ts
+++ b/test/components/FieldGroup.spec.ts
@@ -4,6 +4,8 @@ import type { FieldGroupProps, FieldGroupSlots } from '../../src/runtime/compone
import ComponentRender from '../component-render'
import { UInput, UButton } from '#components'
import buttonTheme from '#build/ui/button'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
describe('FieldGroup', () => {
const sizes = Object.keys(buttonTheme.variants.size) as any
@@ -45,4 +47,16 @@ describe('FieldGroup', () => {
const html = await ComponentRender(nameOrHtml, options, FieldGroup)
expect(html).toMatchSnapshot()
})
+
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(FieldGroup, {
+ slots: {
+ default: {
+ template: ` Click me! `
+ }
+ }
+ })
+
+ expect(await axe(wrapper.element)).toHaveNoViolations()
+ })
})
diff --git a/test/components/FileUpload.spec.ts b/test/components/FileUpload.spec.ts
index e5c7cca86a..e86e2c9f2b 100644
--- a/test/components/FileUpload.spec.ts
+++ b/test/components/FileUpload.spec.ts
@@ -6,6 +6,8 @@ import ComponentRender from '../component-render'
import { renderForm } from '../utils/form'
import type { FormInputEvents } from '../../src/module'
import theme from '#build/ui/file-upload'
+import { axe } from 'vitest-axe'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
// Mock URL.createObjectURL to return deterministic blob URLs
URL.createObjectURL = vi.fn((file: File | Blob) => {
@@ -89,6 +91,31 @@ describe('FileUpload', () => {
expect(html).toMatchSnapshot()
})
+ it('passes accessibility tests', async () => {
+ const wrapper = await mountSuspended(FileUpload, {
+ props: {
+ label: 'Upload files',
+ description: 'Select files to upload',
+ required: true
+ }
+ })
+
+ expect(await axe(wrapper.element, {
+ rules: {
+ // "Form elements must have labels (label)"
+ // Fix any of the following:
+ // Element does not have an implicit (wrapped)