Published on

Testing and Implementing Our useToggleEvents Composable

Authors

Overview

In a previous post, we built a useToggleEvents composable using Vue.js and TypeScript. Let's put it to good use and build something useful! Before that however, I'll make sure this composable is fully tested by creating a few unit tests with vitest and vue-test-utils.

If you're not familiar with Vitest, it's a 'Blazing Fast Unit Test Framework' powered by Vite. I'll also use the vue-test-utils testing suite utilities to handle the testing functions.

Table of Contents

What Are We Testing?

So what exactly are we testing here? If you read the previous post you'll know that our useToggleEvents composable has the following functionality:

  • toggles a boolean value between true and false
  • allow subscriptions to boolean value changes.
  • executes a specific set of callbacks determined by the current boolean state
  • specify whether a callback should be called once, or in perpetuity
  • unsubscribe a callback at any point in time.

Writing Unit Tests

I'll start by creating a component that implements our useToggleEvents composable, making sure we reset the components state before each test is run, by wrapping it inside a beforeEach method. I'll set up some state variables and methods to help track what events have been triggered. Then we'll test each of the previously listed features.

useToggleEvents.spec.js
import { ref } from 'vue'
import { test, expect, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import { useToggleEvents } from '../../composables/useToggleEvents'

beforeEach(async (context) => {
  const Component = {
    template: `
      <div>
        <button @click="toggle">Toggle</button>
      </div>
    `,
    setup() {
      const [onTrue, onFalse, toggle, booleanValue] = useToggleEvents()
      const triggeredOnTrue = ref(false)
      const triggeredOnFalse = ref(false)
      const triggeredOnceCount = ref(0)
      const triggeredUnsubscribeCount = ref(0)

      onTrue(() => {
        triggeredOnTrue.value = true
      })

      onTrue(() => {
        triggeredUnsubscribeCount.value++
      })

      onTrue(
        () => {
          triggeredOnceCount.value++
        },
        { once: true }
      )

      onFalse(() => {
        triggeredOnFalse.value = true
      })

      return {
        onTrue,
        onFalse,
        toggle,
        booleanValue,
        triggeredOnTrue,
        triggeredOnFalse,
        triggeredOnceCount,
        triggeredUnsubscribeCount
      }
    }
  }

  context.wrapper = mount(Component)
  context.toggleButton = context.wrapper.find('button')
})

test('booleanValue is correctly toggled', async ({ wrapper, toggleButton }) => {
  toggleButton.trigger('click')
  await wrapper.vm.$nextTick()
  expect(wrapper.vm.booleanValue).toBeTruthy()

  toggleButton.trigger('click')
  await wrapper.vm.$nextTick()
  expect(wrapper.vm.booleanValue).toBeFalsy()
})

test('onTrue callbacks are called', async ({ wrapper, toggleButton }) => {
  toggleButton.trigger('click')
  await wrapper.vm.$nextTick()
  expect(wrapper.vm.triggeredOnTrue).toBe(true)
})

test('onFalse callbacks are called', async ({ wrapper, toggleButton }) => {
  toggleButton.trigger('click')
  toggleButton.trigger('click')
  await wrapper.vm.$nextTick()
  expect(wrapper.vm.triggeredOnFalse).toBe(true)
})

test('once callbacks are only called once', async ({ wrapper, toggleButton }) => {
  toggleButton.trigger('click')
  toggleButton.trigger('click')
  toggleButton.trigger('click')
  await wrapper.vm.$nextTick()
  expect(wrapper.vm.triggeredOnceCount).toEqual(1)
})

test('subscriptions can unsubscribe from events.', async ({ wrapper, toggleButton }) => {
  toggleButton.trigger('click')
  await wrapper.vm.$nextTick()
  expect(wrapper.vm.triggeredUnsubscribeCount).toEqual(1)
})

The tests all pass and I can sleep soundly knowing this composable will perform as expected.

Implementing the useToggleEvents Composable

There are tons of use-cases for this composable, but I'll pick a simple one for demonstration purposes. Let's implement a dark mode toggle for a website, using Tailwind CSS and our useToggleEvents composable.

Let's create a component that will implement our useToggleEvents function to toggle between light and dark modes. Tailwind CSS simplifies the process by including a dark variant that can be used to style our app differently when we enable dark mode. For example, setting the classNames dark:text-white text-black will only display white text if dark mode is enabled. We can toggle dark mode by adding and removing the dark class on the HTML element.

We'll call our useToggleEvents component like this const [onDarkMode, onLightMode, toggleDarkMode, isDark] = useToggleEvents(). The main advantage of returning an Array here is that I can easily rename the functions and variables in a concise manner. There's no question what these functions and variables are, and quickly and accurately communicate their use to other developers.

We have all the tools we need. Let's write a dark mode toggle in under 10 lines of code!

<script setup lang="ts">
import { useToggleEvents } from '@/composables/useToggleEvents'

const [onDarkMode, onLightMode, toggleDarkMode, isDark] = useToggleEvents()

onDarkMode(() => {
  document.documentElement.classList.add('dark')
})

onLightMode(() => {
  document.documentElement.classList.remove('dark')
})
</script>

<template>
  <main class="flex items-center justify-center">
    <button
      @click="toggleDarkMode"
      type="button"
      class="text-white bg-gradient-to-br from-purple-600 to-blue-500 hover:bg-gradient-to-bl focus:ring-4 focus:outline-none focus:ring-blue-300 dark:focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-2 mb-2"
    >
      Toggle {{ isDark ? 'Light' : 'Dark' }} Mode
    </button>
  </main>
</template>

I hope this illustrates how powerful a design pattern like this can be. By abstracting away the complexity of state management and event handling, the useToggleEvents composable makes it easier than ever to create dynamic, interactive web applications that respond to user input in a seamless and intuitive way.

More Uses For Our Composable

Additionally, here a few more ideas on where this useToggleEvents composable is the perfect choice.

  • Toggling the display of a sidebar or modal
  • Implementing a "show more" or "show less" feature for a list or text
  • Toggling between different view modes (e.g. grid vs. list)
  • Implementing a "read more" or "read less" feature for a long article or blog post
  • Toggling the visibility of a tooltip or popover
  • Implementing a "toggle switch" or "checkbox" component
  • Implementing a "like" or "favorite" feature for a piece of content

View the demo and documentation for this composable here.

Enjoyed this Article? Sign up for my newsletter already!