All skills
skilld-dev avatar

/vue-test-utils-skilld

@b9c39cb

ALWAYS use when writing, debugging, or reviewing code that imports "@vue/test-utils" or mentions vue test-utils, VTU, mount, shallowMount, findComponent, wrapper queries, or migrating from Vue Test Utils v1. Provides the current API surface, mounting options, wrapper methods, and testing recipes for @vue/test-utils 2.5.1 on Vue 3.

Use this Skill: https://skilld.dev/gh/skilld-dev/vue-ecosystem-skills/vue-test-utils-skilld

This session only. Nothing lands on disk.

referencesrecipes.md

≈1.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Testing recipes

Common tasks with the smallest working code. All APIs verified against @vue/test-utils 2.5.1.

Events and user interaction

const wrapper = mount(Component)

await wrapper.find('button').trigger('click')
await wrapper.find('input').trigger('keydown.enter', { key: 'Enter' })

expect(wrapper.emitted('submit')).toHaveLength(1)
expect(wrapper.emitted('submit')![0]).toEqual([{ name: 'foo' }])

Key and modifier events: keydown.ctrl.enter sets ctrlKey and code (dist/src/constants/dom-events.d.ts:46-47).

Forms and v-model

setValue covers text inputs, textareas, checkboxes, radios, selects, and multiselects.

await wrapper.find('input[type="text"]').setValue('hello')
await wrapper.find('input[type="checkbox"]').setValue(true)
await wrapper.find('select').setValue('option-value')
await wrapper.find('select[multiple]').setValue(['a', 'b'])

Component-level v-model (props in, update:modelValue out):

const wrapper = mount(CustomInput, {
  props: { modelValue: 'initial' }
})

await wrapper.find('input').setValue('new')
expect(wrapper.emitted('update:modelValue')![0]).toEqual(['new'])

await wrapper.setProps({ modelValue: 'other' })

Slots

mount(Card, {
  slots: {
    default: '<div>card body</div>',
    header: ({ title }) => h('h2', title),   // scoped slot as function
    footer: MyFooterComponent
  }
})

Scoped slot props are exposed as params inside string templates: '{{ params.item.name }}'.

Props, data, provide

mount(UserList, {
  props: { userIds: [1, 2] },
  data: () => ({ loading: false }),
  global: {
    provide: { [userKey as symbol]: fakeUser },
    mocks: { $t: (k: string) => k }
  }
})

Composables

Mount a minimal host and read results from wrapper.vm.

const useCounter = () => {
  const count = ref(0)
  const inc = () => count.value++
  return { count, inc }
}

const Host = defineComponent({
  setup() {
    return { ...useCounter() }
  },
  template: '<button @click="inc">{{ count }}</button>'
})

const wrapper = mount(Host)
await wrapper.find('button').trigger('click')
expect(wrapper.vm.count).toBe(1)

For composables used by multiple components, prefer integration tests through those components.

Async setup and Suspense

mount throws if the component's setup() is async. Wrap in Suspense.

const Host = defineComponent({
  components: { AsyncComp },
  template: '<Suspense><AsyncComp /></Suspense>'
})

const wrapper = mount(Host)
await flushPromises()

Read the async component's state through wrapper.findComponent(AsyncComp).vm; the host wrapper's own vm is empty. https://test-utils.vuejs.org/guide/advanced/async-suspense

Router

Two approaches; pick one per test.

Stub the link, mock the injected globals:

mount(NavBar, {
  global: {
    stubs: { 'router-link': RouterLinkStub },
    mocks: { $route: { path: '/' } }
  }
})

Or install a real router:

const router = createRouter({
  history: createMemoryHistory(),
  routes: [{ path: '/', component: Home }]
})
mount(App, { global: { plugins: [router] } })
await router.isReady()

Vuex / Pinia

Vuex is a plugin; state flows through global.plugins.

const store = createStore({ state: () => ({ count: 1 }), mutations, getters })
mount(App, { global: { plugins: [store] } })

For Pinia, global: { plugins: [createTestingPinia()] } from @pinia/testing.

Teleport

// Option 1: stub it
mount(NavBar, { global: { stubs: { teleport: true } } })

// Option 2: provide a target and query the document
beforeEach(() => {
  const el = document.createElement('div')
  el.id = 'modal'
  document.body.appendChild(el)
})

const wrapper = mount(NavBar, { attachTo: document.body })
expect(document.querySelector('#modal .signup')).toBeTruthy()
wrapper.unmount()

attachTo cases must call unmount() (or rely on enableAutoUnmount) to detach.

SSR

import { renderToString } from '@vue/test-utils'

const html = await renderToString(App, { props: { id: 1 } })
expect(html).toContain('<div>')

Requires the optional @vue/server-renderer peer dependency (package.json:73-81).

Element visibility and focus

isVisible() needs no attachment for v-show and CSS display: none, but layout-driven visibility and document.activeElement assertions need attachTo:

const div = document.createElement('div')
document.body.appendChild(div)

const wrapper = mount(Menu, { attachTo: div })
expect(wrapper.find('input').element).toBe(document.activeElement)

wrapper.unmount() // also removes the attached tree

Source: SKILL.md on GitHub

No alerts9d3 checks · Risk SAFE
  • Gen Agent Trust Hub9d

    This skill is a comprehensive documentation and best-practices resource for @vue/test-utils, the official testing library for Vue 3. It includes API references, migration guides from Vue 2, and troubleshooting information derived from real-world GitHub issues and discussions. No malicious code or security threats were detected.

  • Socket9d

    No alerts

  • Snyk9d

    Risk: LOW · No issues

Signed by skilld at b9c39cb. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Activeupdated last week

README badge

README badge for skilld-dev/vue-ecosystem-skills/vue-test-utils-skilld