Skip to content

Commit 6e6bb7d

Browse files
committed
test: cover input-base
1 parent aad32c3 commit 6e6bb7d

File tree

3 files changed

+208
-1
lines changed

3 files changed

+208
-1
lines changed

app/components/Input/Base.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const emit = defineEmits<{
2323
const el = useTemplateRef('el')
2424
2525
const model = computed({
26-
get: () => props.modelValue ?? '',
26+
get: () => props.modelValue,
2727
set: (value: string) => emit('update:modelValue', value),
2828
})
2929

test/nuxt/a11y.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ import {
104104
HeaderAccountMenu,
105105
HeaderConnectorModal,
106106
HeaderSearchBox,
107+
InputBase,
107108
LicenseDisplay,
108109
LoadingSpinner,
109110
PackageProvenanceSection,
@@ -2126,6 +2127,59 @@ describe('component accessibility audits', () => {
21262127
})
21272128
})
21282129

2130+
describe('InputBase', () => {
2131+
it('should have no accessibility violations (with aria-label)', async () => {
2132+
const component = await mountSuspended(InputBase, {
2133+
attrs: { 'aria-label': 'Search input' },
2134+
})
2135+
const results = await runAxe(component)
2136+
expect(results.violations).toEqual([])
2137+
})
2138+
2139+
it('should have no accessibility violations with placeholder', async () => {
2140+
const component = await mountSuspended(InputBase, {
2141+
attrs: { 'placeholder': 'Search...', 'aria-label': 'Search' },
2142+
})
2143+
const results = await runAxe(component)
2144+
expect(results.violations).toEqual([])
2145+
})
2146+
2147+
it('should have no accessibility violations when disabled', async () => {
2148+
const component = await mountSuspended(InputBase, {
2149+
attrs: { 'disabled': '', 'aria-label': 'Disabled input' },
2150+
})
2151+
const results = await runAxe(component)
2152+
expect(results.violations).toEqual([])
2153+
})
2154+
2155+
it('should have no accessibility violations with size small', async () => {
2156+
const component = await mountSuspended(InputBase, {
2157+
props: { size: 'small' },
2158+
attrs: { 'aria-label': 'Small input' },
2159+
})
2160+
const results = await runAxe(component)
2161+
expect(results.violations).toEqual([])
2162+
})
2163+
2164+
it('should have no accessibility violations with size large', async () => {
2165+
const component = await mountSuspended(InputBase, {
2166+
props: { size: 'large' },
2167+
attrs: { 'aria-label': 'Large input' },
2168+
})
2169+
const results = await runAxe(component)
2170+
expect(results.violations).toEqual([])
2171+
})
2172+
2173+
it('should have no accessibility violations with noCorrect false', async () => {
2174+
const component = await mountSuspended(InputBase, {
2175+
props: { noCorrect: false },
2176+
attrs: { 'aria-label': 'Input with corrections' },
2177+
})
2178+
const results = await runAxe(component)
2179+
expect(results.violations).toEqual([])
2180+
})
2181+
})
2182+
21292183
describe('SearchSuggestionCard', () => {
21302184
it('should have no accessibility violations for user suggestion', async () => {
21312185
const component = await mountSuspended(SearchSuggestionCard, {
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { mountSuspended } from '@nuxt/test-utils/runtime'
3+
import InputBase from '~/components/Input/Base.vue'
4+
5+
describe('InputBase', () => {
6+
describe('rendering', () => {
7+
it('renders with default empty value', async () => {
8+
const component = await mountSuspended(InputBase)
9+
const input = component.find('input')
10+
expect((input.element as HTMLInputElement).value).toBe('')
11+
})
12+
13+
it('renders with initial modelValue', async () => {
14+
const component = await mountSuspended(InputBase, {
15+
props: { modelValue: 'hello' },
16+
})
17+
const input = component.find('input')
18+
expect((input.element as HTMLInputElement).value).toBe('hello')
19+
})
20+
21+
it('renders empty string when modelValue is undefined or null', async () => {
22+
const withUndefined = await mountSuspended(InputBase, {
23+
props: { modelValue: undefined },
24+
})
25+
expect((withUndefined.find('input').element as HTMLInputElement).value).toBe('')
26+
27+
const withNull = await mountSuspended(InputBase, {
28+
props: { modelValue: null as unknown as string },
29+
})
30+
expect((withNull.find('input').element as HTMLInputElement).value).toBe('')
31+
})
32+
})
33+
34+
describe('v-model', () => {
35+
it('updates modelValue when user types', async () => {
36+
const component = await mountSuspended(InputBase, {
37+
props: { modelValue: '' },
38+
})
39+
const input = component.find('input')
40+
await input.setValue('test')
41+
expect(component.emitted('update:modelValue')).toBeTruthy()
42+
expect(component.emitted('update:modelValue')?.at(-1)).toEqual(['test'])
43+
})
44+
45+
it('reflects modelValue prop changes', async () => {
46+
const component = await mountSuspended(InputBase, {
47+
props: { modelValue: 'initial' },
48+
})
49+
await component.setProps({ modelValue: 'updated' })
50+
const input = component.find('input')
51+
expect((input.element as HTMLInputElement).value).toBe('updated')
52+
})
53+
})
54+
55+
describe('noCorrect prop', () => {
56+
it('applies noCorrect attributes when noCorrect is true (default)', async () => {
57+
const component = await mountSuspended(InputBase)
58+
const input = component.find('input')
59+
expect(input.attributes('autocapitalize')).toBe('off')
60+
expect(input.attributes('autocomplete')).toBe('off')
61+
expect(input.attributes('autocorrect')).toBe('off')
62+
expect(input.attributes('spellcheck')).toBe('false')
63+
})
64+
65+
it('does not apply noCorrect attributes when noCorrect is false', async () => {
66+
const component = await mountSuspended(InputBase, {
67+
props: { noCorrect: false },
68+
})
69+
const input = component.find('input')
70+
expect(input.attributes('autocapitalize')).toBeUndefined()
71+
expect(input.attributes('autocomplete')).toBeUndefined()
72+
expect(input.attributes('autocorrect')).toBeUndefined()
73+
expect(input.attributes('spellcheck')).toBeUndefined()
74+
})
75+
})
76+
77+
describe('focus and blur', () => {
78+
it('emits focus when input is focused', async () => {
79+
const component = await mountSuspended(InputBase)
80+
const input = component.find('input')
81+
await input.trigger('focus')
82+
expect(component.emitted('focus')).toHaveLength(1)
83+
})
84+
85+
it('emits blur when input loses focus', async () => {
86+
const component = await mountSuspended(InputBase)
87+
const input = component.find('input')
88+
await input.trigger('focus')
89+
await input.trigger('blur')
90+
expect(component.emitted('blur')).toHaveLength(1)
91+
})
92+
})
93+
94+
describe('exposed API', () => {
95+
it('exposes focus() that focuses the input', async () => {
96+
const container = document.createElement('div')
97+
document.body.appendChild(container)
98+
const component = await mountSuspended(InputBase, { attachTo: container })
99+
const input = component.find('input')
100+
expect(container.contains(document.activeElement)).toBe(false)
101+
component.vm.focus()
102+
await component.vm.$nextTick()
103+
expect(container.contains(document.activeElement)).toBe(true)
104+
expect(document.activeElement).toBe(input.element)
105+
container.remove()
106+
})
107+
108+
it('exposes blur() that blurs the input', async () => {
109+
const container = document.createElement('div')
110+
document.body.appendChild(container)
111+
const component = await mountSuspended(InputBase, { attachTo: container })
112+
const input = component.find('input')
113+
input.element.focus()
114+
expect(container.contains(document.activeElement)).toBe(true)
115+
expect(document.activeElement).toBe(input.element)
116+
component.vm.blur()
117+
await component.vm.$nextTick()
118+
expect(container.contains(document.activeElement)).toBe(false)
119+
expect(document.activeElement).not.toBe(input.element)
120+
container.remove()
121+
})
122+
123+
it('exposes getBoundingClientRect()', async () => {
124+
const component = await mountSuspended(InputBase)
125+
const rect = component.vm.getBoundingClientRect()
126+
expect(rect).toBeDefined()
127+
expect(typeof rect?.width).toBe('number')
128+
expect(typeof rect?.height).toBe('number')
129+
})
130+
})
131+
132+
describe('accessibility (attrs fallthrough)', () => {
133+
it('accepts placeholder via attrs', async () => {
134+
const component = await mountSuspended(InputBase, {
135+
attrs: { 'placeholder': 'Search packages...', 'aria-label': 'Search input' },
136+
})
137+
const input = component.find('input')
138+
expect(input.attributes('placeholder')).toBe('Search packages...')
139+
expect(input.attributes('aria-label')).toBe('Search input')
140+
})
141+
142+
it('accepts disabled via attrs', async () => {
143+
const component = await mountSuspended(InputBase, {
144+
attrs: { disabled: '' },
145+
})
146+
const input = component.find('input')
147+
expect(input.attributes('disabled')).toBeDefined()
148+
expect((input.element as HTMLInputElement).disabled).toBe(true)
149+
// should add just `disabled`, not `disabled="true"`
150+
expect((input.element as HTMLInputElement).getHTML()).not.toContain('disabled=')
151+
})
152+
})
153+
})

0 commit comments

Comments
 (0)