How to Test Visibility in Vue with Vitest

In this tutorial, we will learn how to unit-test conditional rendering and visibility for Components and HTML elements in Vue. We will be using Vitest as the testing framework along with Vue Test Utils.

As a prerequisite, you should have set the environment in Vite config to jsdom or happy-dom:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'jsdom',
  },
})

You can find the code for this tutorial on GitHub.

Example Component

As an example let’s imagine we have this component in our app:

<script setup>
  import { ref } from 'vue'
  import Example from './Example.vue'

  const isHeadlineVisible = ref(false)
  const canRenderComponent = ref(true)
</script>

<template>
  <div>
    <h3
      v-show="isHeadlineVisible"
      data-test="headline"
    >
      I am now visible, yay!
    </h3>

    <button
      data-test="toggle-headline"
      @click="isHeadlineVisible = !isHeadlineVisible"
    >
      Toggle Headline
    </button>

    <button
      data-test="toggle-component"
      @click="canRenderComponent = !canRenderComponent"
    >
      Toggle Component
    </button>

    <Example v-if="canRenderComponent" />
  </div>
</template>

The component contains a button that toggles the visibility of an H3 tag using v-show. Furthermore, we have another button that toggles rendering a Vue component, Example, using v-if.

Each HTML element has a data-test attribute so that we can target the element specifically in our test and not rely on the HTML tag name as a selector.

vitest vue v-show

Testing Visibility

To get started, we will test visibility. We will implement the test for the H3 element that asserts the outcome of using v-show when provided a truthy or a falsy value.

First, outline the expected behavior as test cases:

import { describe } from 'vitest'

describe('App', () => {
  it.todo('should hide the headline by default')
  it.todo('should toggle the headline visibility when clicking on the "toggle" button')
})

Using .todo makes Vitest skip the test, however, it will show up in the logs as a todo, so you don’t forget about it!

Next, implement the test for the default case. By default, our boolean ref is set to false, hence, the headline should not be visible:

import { shallowMount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import App from './App.vue'

describe('App', () => {
  it('should hide the headline by default', () => {
    const wrapper = shallowMount(App, {
      attachTo: document.body,
    })

    expect(wrapper.find('[data-test="headline"]').isVisible()).toBe(false)
  })

  it.todo('should toggle the headline visibility when clicking on the "toggle" button')
})

You might have gone for checking the visibility rule in the component styles to assert visibility, but there is a method in each DOMWrapper called isVisible that checks that for us.

Moreover, it will be truthy when the element/component opacity is 0, or when the visibility CSS rule is set to hidden.

We have also used attachTo, which will make sure that isVisible checks the rules applied by CSS classes too. Since Jsdom doesn’t really render anything.

The default case is handled, now, write the test for the case for when the headline should be visible.

Simulate the button click by calling .trigger('click') on the button wrapper to toggle the boolean value:

import { shallowMount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import App from './App.vue'

describe('App', () => {
  it('should hide the headline by default', () => {
    const wrapper = shallowMount(App)

    expect(wrapper.find('[data-test-id="headline"]').isVisible()).toBe(false)
  })

  it('should toggle the headline visibility when clicking on the "toggle" button', async () => {
    const wrapper = shallowMount(App, {
      attachTo: document.body,
    })

    await wrapper.find('[data-test-id="toggle-headline"]').trigger('click')

    expect(wrapper.find('[data-test="headline"]').isVisible()).toBe(true)
  })
})

As you can notice, we are also marking this test case as async, so that we can use await inside. We need to await for the trigger method to resolve, and the DOM to update.

Finally, since we are testing toggling, we can add the code that will hide the headline to the same test case:

import { shallowMount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import App from './App.vue'

describe('App', () => {
  it('should hide the headline by default', () => {
    const wrapper = shallowMount(App)

    expect(wrapper.find('[data-test-id="headline"]').isVisible()).toBe(false)
  })

  it('should toggle the headline visibility when clicking on the "toggle" button', async () => {
    const wrapper = shallowMount(App, {
      attachTo: document.body,
    })

    await wrapper.get('[data-test="toggle-headline"]').trigger('click')

    expect(wrapper.find('[data-test="headline"]').isVisible()).toBe(true)

    await wrapper.get('[data-test="toggle-headline"]').trigger('click')

    expect(wrapper.find('[data-test="headline"]').isVisible()).toBe(false)
  })
})

When running vitest we can see that our tests are passing, yay!

vitest vue v-show isVisible

Testing Conditional Rendering

Now that we have covered testing visibility, let’s test rendering! To get started, add the test cases with descriptions. They should be similar to the ones we had earlier:

it.todo('should render the Example component by default')
it.todo('should toggle rendering the Example component when clicking on the "Toggle Component" button')

First, let’s implement the test for the default case, since we are testing rendering, we have to use exists instead of isVisible:

it('should render the Example component by default', () => {
  const wrapper = shallowMount(App)

  expect(wrapper.findComponent({ name: 'Example' }).exists()).toBe(true)
})

We are also using findComponent instead of find. With components we use findComponent, with HTML elements we use find.

vue test utils find component
vue test utils find element

Next, implement the toggle case, similar to what we have done before, we can just test both cases of toggling:

it('should toggle rendering the Example component when clicking on the "Toggle Component" button', async () => {
  const wrapper = shallowMount(App)

  await wrapper.get('[data-test="toggle-component"]').trigger('click')

  expect(wrapper.findComponent({ name: 'Example' }).exists()).toBe(false)

  await wrapper.get('[data-test="toggle-component"]').trigger('click')

  expect(wrapper.findComponent({ name: 'Example' }).exists()).toBe(true)
})

If you have Vitest still running then you should see the tests passing:

vitest passing tests

Conclusion

In conclusion:

  • if you are testing rendering, use exists.
  • If we are testing visibility, use isVisible.
  • If you are selecting an HTML element use .find,
  • if you are selecting a Vue component use findComponent.
  • use attachTo when asserting with isVisible.

You can find more cool and helpful unit testing tutorials here. c ya!

Amenallah Hsoumi
Amenallah Hsoumi

Senior Software Engineer, Indie Hacker, building cool stuff on the web!

Articles: 19