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)
  })
})