vue-search-input
Version:
A Vue.js 3 search input component, inspired by the global search input of Storybook and GitHub.
262 lines (192 loc) • 6.68 kB
text/typescript
import { mount } from '@vue/test-utils'
import { fieldType } from '@/SearchInput.types'
import SearchInput from '@/SearchInput.vue'
const INPUT_SELECTOR = 'input[data-search-input="true"]'
const createWrapper = (opts?: Record<string, unknown>) => {
return mount(SearchInput, opts)
}
const createWrapperContainer = () => {
const wrapperContainer = {
components: {
SearchInput,
},
data() {
return {
searchText1: '',
searchText2: '',
}
},
template: `
<div>
<SearchInput v-model="searchText1" />
<SearchInput v-model="searchText2" />
</div>
`,
}
return mount(wrapperContainer, {
attachTo: document.body,
})
}
describe('SearchInput.vue', () => {
beforeEach(() => {
document.body.innerHTML = ''
})
it.each(fieldType)('should render an input with type %s', (typeProp) => {
const wrapper = createWrapper({
props: {
type: typeProp,
},
})
const input = wrapper.find(`input[type="${typeProp}"]`)
expect(input).toBeTruthy()
})
it('should render a search icon', async () => {
const wrapper = createWrapper()
const i = await wrapper.find('i.search-icon.search')
expect(i).toBeTruthy()
})
it('should pass class to the input wrapper', async () => {
const wrapper = createWrapper({
attrs: {
class: 'test-class',
},
})
const div = await wrapper.find('div')
expect(div.classes()).toContain('test-class')
})
it('should pass event listeners to the input', async () => {
const onClick = vi.fn()
const wrapper = createWrapper({ attrs: { onClick } })
wrapper.find('input').trigger('click')
expect(onClick).toHaveBeenCalled()
})
it('sets the value', async () => {
const wrapper = createWrapper()
const input = wrapper.find('input')
await input.setValue('test_value')
expect(input.element.value).toBe('test_value')
})
it('emits the updated value', async () => {
const wrapper = createWrapper()
const input = wrapper.find('input')
await input.setValue('test_value')
expect(wrapper.emitted()['update:modelValue'][0]).toEqual(['test_value'])
})
it('clears the value when the clear icon is clicked', async () => {
const wrapper = createWrapper({
props: {
modelValue: 'test',
},
})
const button = await wrapper.find('button.search-icon.clear')
await button.trigger('mousedown')
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([''])
})
it('clears the value with the "esc" key', async () => {
const wrapper = createWrapper({
props: {
modelValue: 'test',
},
})
const input = wrapper.find('input')
await input.trigger('keydown', {
key: 'Escape',
})
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([''])
})
it('focuses the input when the "/" key is pressed', async () => {
const wrapper = createWrapper({
attachTo: document.body,
})
const event = new KeyboardEvent('keydown', { key: '/' })
document.dispatchEvent(event)
const input = wrapper.find(INPUT_SELECTOR)
expect(input.element).toBe(document.activeElement)
})
it('removes the keydown event listener when unmounted', async () => {
const removeSpy = vi.spyOn(document, 'removeEventListener').mockImplementation(() => {})
const wrapper = createWrapper()
wrapper.unmount()
expect(removeSpy).toHaveBeenCalled()
})
it('removes the keydown event listener when shortcutListenerEnabled prop turns false', async () => {
const removeSpy = vi.spyOn(document, 'removeEventListener').mockImplementation(() => {})
const wrapper = createWrapper()
wrapper.setProps({
shortcutListenerEnabled: false,
})
await wrapper.vm.$nextTick()
expect(removeSpy).toHaveBeenCalled()
})
it('selects the input text when focused with the "/" key', async () => {
const wrapper = createWrapper({
props: {
modelValue: 'test',
},
})
const event = new KeyboardEvent('keydown', { key: '/' })
document.dispatchEvent(event)
const input = await wrapper.find(INPUT_SELECTOR)
const inputEl = input.element as HTMLInputElement
expect(inputEl.selectionStart).toBe(0)
expect(inputEl.selectionEnd).toBe(4)
})
it('focuses the input text when the "/" key is pressed', async () => {
const wrapper = createWrapperContainer()
const inputs = await wrapper.findAll(INPUT_SELECTOR)
Object.defineProperty(inputs[0].element as HTMLInputElement, 'offsetWidth', { value: 10, writable: true })
Object.defineProperty(inputs[1].element as HTMLInputElement, 'offsetWidth', { value: 10, writable: true })
const event = new KeyboardEvent('keydown', { key: '/' })
document.dispatchEvent(event)
expect(inputs[0].element).toBe(document.activeElement)
})
it('should render a shortcut icon when the hideShortcutIconOnBlur prop is false', async () => {
const wrapper = createWrapper({
props: {
hideShortcutIconOnBlur: false,
},
})
const i = await wrapper.find('i.search-icon.shortcut')
expect(i).toBeTruthy()
})
it('renders the prepend slot', async () => {
const wrapper = createWrapper({
slots: {
prepend: '<div class="prepend">prepend content</div>',
},
})
const prepend = wrapper.find('.prepend')
const i = wrapper.find('i.search-icon.search')
expect(prepend.element.nextElementSibling).toEqual(i.element)
})
it('renders the prepend-inner slot', async () => {
const wrapper = createWrapper({
slots: {
'prepend-inner': '<div class="prepend-inner">prepend-inner content</div>',
},
})
const prependInner = wrapper.find('.prepend-inner')
const i = wrapper.find('i.search-icon.search')
expect(i.element.nextElementSibling).toEqual(prependInner.element)
})
it('renders the append slot', async () => {
const wrapper = createWrapper({
slots: {
append: '<div class="append">append content</div>',
},
})
const append = wrapper.find('.append')
const i = wrapper.find('i.search-icon.shortcut')
expect(append.element.nextElementSibling).toEqual(i.element)
})
it('renders the append-outer slot', async () => {
const wrapper = createWrapper({
slots: {
'append-outer': '<div class="append-outer">append-outer content</div>',
},
})
const appendOuter = wrapper.find('.append-outer')
const i = wrapper.find('i.search-icon.shortcut')
expect(i.element.nextElementSibling).toEqual(appendOuter.element)
})
})