@@ -83,16 +82,18 @@ defineOgImageComponent('Default', {
/
-
diff --git a/test/nuxt/a11y.spec.ts b/test/nuxt/a11y.spec.ts
index 7491247cf..104c008e6 100644
--- a/test/nuxt/a11y.spec.ts
+++ b/test/nuxt/a11y.spec.ts
@@ -139,6 +139,7 @@ import {
HeaderAccountMenu,
HeaderConnectorModal,
HeaderSearchBox,
+ InputBase,
LicenseDisplay,
LoadingSpinner,
PackageProvenanceSection,
@@ -2176,6 +2177,59 @@ describe('component accessibility audits', () => {
})
})
+ describe('InputBase', () => {
+ it('should have no accessibility violations (with aria-label)', async () => {
+ const component = await mountSuspended(InputBase, {
+ attrs: { 'aria-label': 'Search input' },
+ })
+ const results = await runAxe(component)
+ expect(results.violations).toEqual([])
+ })
+
+ it('should have no accessibility violations with placeholder', async () => {
+ const component = await mountSuspended(InputBase, {
+ attrs: { 'placeholder': 'Search...', 'aria-label': 'Search' },
+ })
+ const results = await runAxe(component)
+ expect(results.violations).toEqual([])
+ })
+
+ it('should have no accessibility violations when disabled', async () => {
+ const component = await mountSuspended(InputBase, {
+ attrs: { 'disabled': '', 'aria-label': 'Disabled input' },
+ })
+ const results = await runAxe(component)
+ expect(results.violations).toEqual([])
+ })
+
+ it('should have no accessibility violations with size small', async () => {
+ const component = await mountSuspended(InputBase, {
+ props: { size: 'small' },
+ attrs: { 'aria-label': 'Small input' },
+ })
+ const results = await runAxe(component)
+ expect(results.violations).toEqual([])
+ })
+
+ it('should have no accessibility violations with size large', async () => {
+ const component = await mountSuspended(InputBase, {
+ props: { size: 'large' },
+ attrs: { 'aria-label': 'Large input' },
+ })
+ const results = await runAxe(component)
+ expect(results.violations).toEqual([])
+ })
+
+ it('should have no accessibility violations with noCorrect false', async () => {
+ const component = await mountSuspended(InputBase, {
+ props: { noCorrect: false },
+ attrs: { 'aria-label': 'Input with corrections' },
+ })
+ const results = await runAxe(component)
+ expect(results.violations).toEqual([])
+ })
+ })
+
describe('SearchSuggestionCard', () => {
it('should have no accessibility violations for user suggestion', async () => {
const component = await mountSuspended(SearchSuggestionCard, {
diff --git a/test/nuxt/components/Input/Base.spec.ts b/test/nuxt/components/Input/Base.spec.ts
new file mode 100644
index 000000000..f2c3b02fe
--- /dev/null
+++ b/test/nuxt/components/Input/Base.spec.ts
@@ -0,0 +1,143 @@
+import { describe, expect, it } from 'vitest'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
+import InputBase from '~/components/Input/Base.vue'
+
+describe('InputBase', () => {
+ describe('rendering', () => {
+ it('renders with default empty value', async () => {
+ const component = await mountSuspended(InputBase)
+ const input = component.find('input')
+ expect((input.element as HTMLInputElement).value).toBe('')
+ })
+
+ it('renders with initial modelValue', async () => {
+ const component = await mountSuspended(InputBase, {
+ props: { modelValue: 'hello' },
+ })
+ const input = component.find('input')
+ expect((input.element as HTMLInputElement).value).toBe('hello')
+ })
+
+ it('renders empty string when modelValue is undefined or null', async () => {
+ const withUndefined = await mountSuspended(InputBase, {
+ props: { modelValue: undefined },
+ })
+ expect((withUndefined.find('input').element as HTMLInputElement).value).toBe('')
+
+ const withNull = await mountSuspended(InputBase, {
+ props: { modelValue: null as unknown as string },
+ })
+ expect((withNull.find('input').element as HTMLInputElement).value).toBe('')
+ })
+ })
+
+ describe('v-model', () => {
+ it('updates modelValue when user types', async () => {
+ const component = await mountSuspended(InputBase, {
+ props: { modelValue: '' },
+ })
+ const input = component.find('input')
+ await input.setValue('test')
+ expect(component.emitted('update:modelValue')).toBeTruthy()
+ expect(component.emitted('update:modelValue')?.at(-1)).toEqual(['test'])
+ })
+
+ it('reflects modelValue prop changes', async () => {
+ const component = await mountSuspended(InputBase, {
+ props: { modelValue: 'initial' },
+ })
+ await component.setProps({ modelValue: 'updated' })
+ const input = component.find('input')
+ expect((input.element as HTMLInputElement).value).toBe('updated')
+ })
+ })
+
+ describe('noCorrect prop', () => {
+ it('applies noCorrect attributes when noCorrect is true (default)', async () => {
+ const component = await mountSuspended(InputBase)
+ const input = component.find('input')
+ expect(input.attributes('autocapitalize')).toBe('off')
+ expect(input.attributes('autocomplete')).toBe('off')
+ expect(input.attributes('autocorrect')).toBe('off')
+ expect(input.attributes('spellcheck')).toBe('false')
+ })
+
+ it('does not apply noCorrect attributes when noCorrect is false', async () => {
+ const component = await mountSuspended(InputBase, {
+ props: { noCorrect: false },
+ })
+ const input = component.find('input')
+ expect(input.attributes('autocapitalize')).toBeUndefined()
+ expect(input.attributes('autocomplete')).toBeUndefined()
+ expect(input.attributes('autocorrect')).toBeUndefined()
+ expect(input.attributes('spellcheck')).toBeUndefined()
+ })
+ })
+
+ describe('focus and blur', () => {
+ it('emits focus when input is focused', async () => {
+ const component = await mountSuspended(InputBase)
+ const input = component.find('input')
+ await input.trigger('focus')
+ expect(component.emitted('focus')).toHaveLength(1)
+ })
+
+ it('emits blur when input loses focus', async () => {
+ const component = await mountSuspended(InputBase)
+ const input = component.find('input')
+ await input.trigger('focus')
+ await input.trigger('blur')
+ expect(component.emitted('blur')).toHaveLength(1)
+ })
+ })
+
+ describe('exposed API', () => {
+ it('exposes focus() that focuses the input', async () => {
+ const container = document.createElement('div')
+ document.body.appendChild(container)
+ const component = await mountSuspended(InputBase, { attachTo: container })
+ const input = component.find('input')
+ expect(container.contains(document.activeElement)).toBe(false)
+ component.vm.focus()
+ await component.vm.$nextTick()
+ expect(container.contains(document.activeElement)).toBe(true)
+ expect(document.activeElement).toBe(input.element)
+ container.remove()
+ })
+
+ it('exposes blur() that blurs the input', async () => {
+ const container = document.createElement('div')
+ document.body.appendChild(container)
+ const component = await mountSuspended(InputBase, { attachTo: container })
+ const input = component.find('input')
+ input.element.focus()
+ expect(container.contains(document.activeElement)).toBe(true)
+ expect(document.activeElement).toBe(input.element)
+ component.vm.blur()
+ await component.vm.$nextTick()
+ expect(container.contains(document.activeElement)).toBe(false)
+ expect(document.activeElement).not.toBe(input.element)
+ container.remove()
+ })
+ })
+
+ describe('accessibility (attrs fallthrough)', () => {
+ it('accepts placeholder via attrs', async () => {
+ const component = await mountSuspended(InputBase, {
+ attrs: { 'placeholder': 'Search packages...', 'aria-label': 'Search input' },
+ })
+ const input = component.find('input')
+ expect(input.attributes('placeholder')).toBe('Search packages...')
+ expect(input.attributes('aria-label')).toBe('Search input')
+ })
+
+ it('accepts disabled via attrs', async () => {
+ const component = await mountSuspended(InputBase, {
+ attrs: { disabled: '' },
+ })
+ const input = component.find('input')
+ expect(input.attributes('disabled')).toBeDefined()
+ expect((input.element as HTMLInputElement).disabled).toBe(true)
+ })
+ })
+})
diff --git a/test/nuxt/components/compare/PackageSelector.spec.ts b/test/nuxt/components/compare/PackageSelector.spec.ts
index b334eab03..5b004f420 100644
--- a/test/nuxt/components/compare/PackageSelector.spec.ts
+++ b/test/nuxt/components/compare/PackageSelector.spec.ts
@@ -119,16 +119,6 @@ describe('PackageSelector', () => {
input = component.find('input')
expect(input.attributes('placeholder')).toBeTruthy()
})
-
- it('has search icon', async () => {
- const component = await mountSuspended(PackageSelector, {
- props: {
- modelValue: [],
- },
- })
-
- expect(component.find('.i-carbon\\:search').exists()).toBe(true)
- })
})
describe('adding packages', () => {