Categories
Vue 3 Testing

Testing Vue 3 Apps — Form Triggered Events and Set Values of Form Elements

With apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue 3 apps by writing a simple app and testing it.

Testing Form Triggered Events

We can test the events that are triggered when we submit the form.

For example, we can write:

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

const EmailInput = {
  template: `
    <div>
      <input type="email" v-model="email">
      <button @click="submit">Submit</button>
    </div>
  `,
  data() {
    return {
      email: ''
    }
  },
  methods: {
    submit() {
      this.$emit('submit', this.email)
    }
  }
}

test('sets the value', async () => {
  const wrapper = mount(EmailInput)
  const input = wrapper.find('input')
  await input.setValue('abc@mail.com')
  expect(input.element.value).toBe('abc@mail.com')
})

test('submit event is triggered', async () => {
  const wrapper = mount(EmailInput)
  await wrapper.find('button').trigger('click')
  expect(wrapper.emitted()).toHaveProperty('submit')
})

In the 2nd test, we get the button and trigger the click event.

Then we check that the submit event is emitted with the wrapper.emitted method.

We can check what the event triggered with the argument that it’s emitted with.

For example, we can write:

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

const EmailInput = {
  template: `
    <div>
      <input type="email" v-model="email">
      <button @click="submit">Submit</button>
    </div>
  `,
  data() {
    return {
      email: ''
    }
  },
  methods: {
    submit() {
      this.$emit('submit', this.email)
    }
  }
}

test('emits the input to its parent', async () => {
  const wrapper = mount(EmailInput)
  await wrapper.find('input').setValue('abc@mail.com')
  await wrapper.find('button').trigger('click')
  expect(wrapper.emitted('submit')[0][0]).toBe('abc@mail.com')
})

We mount the EmailInput .

Then we call setValue on the input to set its value.

Then we trigger the click event on the button.

And we check that the submit event is emitted with the input value.

Working with Various Form Elements

We can manipulate other form elements in our tests.

For instance, we can write:

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

const FormComponent = {
  template: `
    <div>
      <form @submit.prevent="submit">
        <input type="email" v-model="form.email">
        <textarea v-model="form.description"/>

        <select v-model="form.city">
          <option value="new-york">New York</option>
          <option value="toronto">Toronto</option>
        </select>

        <input type="checkbox" v-model="form.subscribe"/>
        <input type="radio" value="weekly" v-model="form.interval"/>
        <input type="radio" value="monthly" v-model="form.interval"/>

         <button type="submit">Submit</button>
      </form>
    </div>
  `,
  data() {
    return {
      form: {
        email: '',
        description: '',
        city: '',
        subscribe: false,
        interval: ''
      }
    }
  },
  methods: {
    async submit() {
      this.$emit('submit', this.form)
    }
  }
}

test('submits a form', async () => {
  const wrapper = mount(FormComponent)
  await wrapper.find('input[type=email]').setValue('name@mail.com')
  await wrapper.find('textarea').setValue('Lorem ipsum')
  await wrapper.find('select').setValue('toronto')
  await wrapper.find('input[type=checkbox]').setValue()
  await wrapper.find('input[type=radio][value=monthly]').setValue()
})

We can use the setValue method with textarea , selecrt , checkboxes and radio buttons.

Also, we can trigger complex events.

For instance, we can write:

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

const FormComponent = {
  template: `
    <div>
      <form @submit.prevent="submit">
        <input type="email" v-model="form.email">
        <textarea v-model="form.description"/>

        <select v-model="form.city">
          <option value="new-york">New York</option>
          <option value="toronto">Toronto</option>
        </select>

        <input type="checkbox" v-model="form.subscribe"/>
        <input type="radio" value="weekly" v-model="form.interval"/>
        <input type="radio" value="monthly" v-model="form.interval"/>

        <button type="submit">Submit</button>
      </form>
    </div>
  `,
  data() {
    return {
      form: {
        email: '',
        description: '',
        city: '',
        subscribe: false,
        interval: ''
      }
    }
  },
  methods: {
    async submit() {
      this.$emit('submit', this.form)
    }
  }
}

test('submits a form', async () => {
  const wrapper = mount(FormComponent)
  const email = 'name@mail.com'
  const description = 'Lorem ipsum'
  const city = 'toronto'
  await wrapper.find('input[type=email]').setValue(email)
  await wrapper.find('textarea').setValue(description)
  await wrapper.find('select').setValue(city)
  await wrapper.find('input[type=checkbox]').setValue()
  await wrapper.find('input[type=radio][value=monthly]').setValue()
  await wrapper.find('form').trigger('submit.prevent')
  expect(wrapper.emitted('submit')[0][0]).toMatchObject({
    email,
    description,
    city,
    subscribe: true,
    interval: 'monthly',
  })
})

We have the same component.

In the test, we mount the component and call setValue to set the value of each field.

Then we check if the object we emitted with the submit event matches the expected object with the toMatchObject method.

We test the this.form object in the submit method matches what we have in the last line.

Conclusion

We can test form triggered events and set the values of various form elements in our Vue 3 component tests with Vue Test Utils.

Categories
Vue 3 Testing

Testing Vue 3 Apps — Reactive Properties and Form Handling

With apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue 3 apps by writing a simple app and testing it.

Passing Data to Components

We can pass data to components.

For example, we can write:

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

const Name = {
  template: `
    <div>
      <input v-model="name">
      <div v-if="error">{{ error }}</div>
    </div>
  `,
  props: {
    minLength: {
      type: Number
    }
  },
  computed: {
    error() {
      if (this.name.length < this.minLength) {
        return `Name must be at least ${this.minLength} characters.`
      }
      return
    }
  }
}

test('renders an error if length is too short', () => {
  const wrapper = mount(Name, {
    props: {
      minLength: 10
    },
    data() {
      return {
        name: 'short'
      }
    }
  })
  expect(wrapper.html()).toContain('Name must be at least 10 characters')
})

We have the Name component with an input field and an error display.

The error computed property checks if the name is too short and show an error message if it is.

In the test, we pass in the minLength prop to the component.

And the data method has the name reactive property set.

Then we show the error message since the name value’s length is shorter than 10.

Using setProps

We can also use the setProps method to set the props.

For example, we can write:

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

const Show = {
  template: `
    <div>
      <div v-if="show">{{ greeting }}</div>
    </div>
  `,
  props: {
    show: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      greeting: 'Hello'
    }
  }
}

test('renders a greeting when show is true', async () => {
  const wrapper = mount(Show)
  expect(wrapper.html()).toContain('Hello')
  await wrapper.setProps({ show: false })
  expect(wrapper.html()).not.toContain('Hello')
})

We test the Show component and check if the 'Hello' is rendered in the component.

Then we call setProps to set the show prop to false .

Then we check that 'Hello' isn’t rendered.

Test Form Handling

We can test form handling by interacting with form elements.

For example, we can write:

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

const EmailInput = {
  template: `
    <div>
      <input type="email" v-model="email">
      <button @click="submit">Submit</button>
    </div>
  `,
  data() {
    return {
      email: ''
    }
  },
  methods: {
    submit() {
      this.$emit('submit', this.email)
    }
  }
}

test('sets the value', async () => {
  const wrapper = mount(EmailInput)
  const input = wrapper.find('input')
  await input.setValue('abc@mail.com')
  expect(input.element.value).toBe('abc@mail.com')
})

We have an EmailInput component with an input component.

Then we add a test to mount the EmailInput component.

Then we get the input with find .

And then we call input.setValue to set its value.

Then we get the value from the input with input.element.value .

Conclusion

We can pass in the data for the props and test the rendered Vue 3 components with Vue Test Utils.

Also, we can test form inputs with this.

Categories
Vue 3 Testing

Testing Vue 3 Apps — Testing Event Handling

With apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue 3 apps by writing a simple app and testing it.

Event Handling

We can test event handling in our Vue 3 components easily with Vue Test Utils.

For example, we can write:

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

const Counter = {
  template: '<button @click="handleClick">Increment</button>',
  data() {
    return {
      count: 0
    }
  },
  methods: {
    handleClick() {
      this.count += 1
      this.$emit('increment', this.count)
    }
  }
}

test('emits increment event when button is clicked', () => {
  const wrapper = mount(Counter)
  wrapper.find('button').trigger('click')
  expect(wrapper.emitted()).toHaveProperty('increment')
})

We have the Counter component that has a button that runs the handleclick method when we click it.

The handleClick method emits the increment event.

In the test, we mount the Counter component.

Then we get the button with find and call trigger with the click method.

And then we check if the increment event is emitted with the wrapper.emitted method.

The returned object has the event names as the key.

We can do other checks in our tests with regard to events.

For instance, we can write:

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

const Counter = {
  template: '<button @click="handleClick">Increment</button>',
  data() {
    return {
      count: 0
    }
  },
  methods: {
    handleClick() {
      this.count += 1
      this.$emit('increment', this.count)
    }
  }
}

test('emits increment event when button is clicked', () => {
  const wrapper = mount(Counter)
  wrapper.find('button').trigger('click')
  expect(wrapper.emitted()).toHaveProperty('increment')
  const incrementEvent = wrapper.emitted('increment')
  expect(incrementEvent).toHaveLength(1)
  expect(incrementEvent[0]).toEqual([1])

})

We get the increment event object with:

const incrementEvent = wrapper.emitted('increment')

Then we check how many time it’s emitted with:

expect(incrementEvent).toHaveLength(1)

And we check the value that it’s emitted with, which the 2nd argument of this.$emit with:

expect(incrementEvent[0]).toEqual([1])

Asserting Complex Events

We can test more complex event objects.

For example, we can write:

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

const Counter = {
  template: '<button @click="handleClick">Increment</button>',
  data() {
    return {
      count: 0
    }
  },
  methods: {
    handleClick() {
      this.count += 1
      this.$emit('increment', {
        count: this.count,
        isEven: this.count % 2 === 0
      })
    }
  }
}

test('emits increment event when button is clicked', () => {
  const wrapper = mount(Counter)
  wrapper.find('button').trigger('click')
  wrapper.find('button').trigger('click')
  expect(wrapper.emitted('increment')).toHaveLength(2)
  const [event1, event2] = wrapper.emitted('increment')
  expect(event1).toEqual([
    {
      count: 1,
      isEven: false
    }
  ])
  expect(event2).toEqual([
    {
      count: 2,
      isEven: true
    }
  ])
})

We trigger the click event on the button twice.

Then we check the number of times the increment event is emitted with:

expect(wrapper.emitted('increment')).toHaveLength(2)

Then we get the event objects that are emitted with:

const [event1, event2] = wrapper.emitted('increment')

Then we check the event object that we emitted with the event with:

expect(event1).toEqual([{
  count: 1,
  isEven: false
}])
expect(event2).toEqual([{
  count: 2,
  isEven: true
}])

Conclusion

We can test event emissions with Vue 3 and Vue Test Utils.

Categories
Vue 3 Testing

Getting Started with Testing Vue 3 Apps

With apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue 3 apps by writing a simple app and testing it.

Getting Started with Testing

We can get started with testing by creating the project with Vue CLI.

We add the ‘unit testing’ option when we create then project.

Then the files and settings for creating and running unit tests will be added.

We should see the tests folder with the test files.

Now we can create our own test by writing:

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

const MessageComponent = {
  template: '<p>{{ msg }}</p>',
  props: ['msg'],
}

test('displays message', () => {
  const wrapper = mount(MessageComponent, {
    props: {
      msg: 'Hello world'
    }
  })
  expect(wrapper.text()).toContain('Hello world')
})

Then we can run npm run test:unit to run the test.

We should see a checkmark since ‘Hello World’ is rendered as the prop.

We call mount to mount the MessageComponent .

And the props has the props.

Then we call wrapper.text to get the text of the rendered component.

test takes a string that has the test description and the callback has the test code.

Conditional Rendering

We can test conditional rendering as we do in the first example.

For example, we can write:

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

const Nav = {
  template: `
    <nav>
      <a id="profile" href="/profile">Profile</a>
      <a v-if="admin" id="admin" href="/admin">Admin</a>
    </nav>
  `,
  data() {
    return {
      admin: false
    }
  }
}

test('displays profile', () => {
  const wrapper = mount(Nav)
  const profileLink = wrapper.get('#profile')
  expect(profileLink.text()).toEqual('Profile')
})

We have the Nav component and get the element with the ID profile with wrapper.get .

Then we get the text with the text method.

We can check whether an element with the given selector exists with:

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

const Nav = {
  template: `
    <nav>
      <a id="profile" href="/profile">Profile</a>
      <a v-if="admin" id="admin" href="/admin">Admin</a>
    </nav>
  `,
  data() {
    return {
      admin: false
    }
  }
}

test('displays profile', () => {
  const wrapper = mount(Nav)
  const profileLink = wrapper.get('#profile')
  expect(profileLink.text()).toEqual('Profile')
})

test('does not render an admin link', () => {
  const wrapper = mount(Nav)
  expect(wrapper.find('#admin').exists()).toBe(false)
})

wrapper.find(‘#admin’).exists() tries to find the element with the given selector.

Also, we can pass in our own data to change the value of the admin reactive property and test it:

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

const Nav = {
  template: `
    <nav>
      <a id="profile" href="/profile">Profile</a>
      <a v-if="admin" id="admin" href="/admin">Admin</a>
    </nav>
  `,
  data() {
    return {
      admin: true
    }
  }
}

test('renders an admin link', () => {
  const wrapper = mount(Nav, {
    data() {
      return {
        admin: true
      }
    }
  })
  expect(wrapper.get('#admin').text()).toEqual('Admin')
})

We pass in our own data function in the test so we set admin to true .

And now we can check that the Admin text is rendered along with the link.

Conclusion

We can test rendered Vue 3 components with Vue Test Utils.

Categories
Vue and D3

Loading Data into a Vue App with D3

D3 lets us add graphics to a front-end web app easily.

Vue is a popular front end web framework.

They work great together. In this article, we’ll look at how to load data into a Vue app with D3.

Loading CSV

We can load CSV data into our Vue app with the d3.csv method.

For example, we can write:

public/data.csv

name,age
John,30
Jane,32

App.vue

<template>
  <div id="app"></div>
</template>

<script>
import * as d3 from "d3";
export default {
  name: "App",
  async mounted() {
    const data = await d3.csv("/data.csv");
    for (const { name, age } of data) {
      console.log(name, age);
    }
  },
};
</script>

to load data from public/data.csv into our Vue app.

The public folder is served statically, so we can read data from there.

We just call d3.csv with the path to the CSV file.

Then it’ll parsed automatically into an array of objects.

Therefore, we get:

John 30
Jane 32

logged when we loop through the data array.

Loading JSON

We can load JSON into our app with the d3.json method.

For example, we can write:

public/data.json

[
  {
    "name": "John",
    "age": 30,
    "city": "New York"
  },
  {
    "name": "Jane",
    "age": 20,
    "city": "San Francisco"
  }
]

App.vue

<template>
  <div id="app"></div>
</template>

<script>
import * as d3 from "d3";
export default {
  name: "App",
  async mounted() {
    const data = await d3.json("/data.json");
    for (const { name, age } of data) {
      console.log(name, age);
    }
  },
};
</script>

We call d3.json to get the data.

Then we loop through the data array which has the parsed JSON array.

So we get the same result as we did in the previous example.

Loading XML File

We can load XML files with the d3.xml method.

To use it, we can write:

public/data.xml

<?xml version="1.0" encoding="UTF-8"?>
<root>
<row>
    <name>John</name>
    <age>30</age>
</row>
<row>
    <name>Jane</name>
    <age>32</age>
</row>
</root>

App.vue

<template>
  <div id="app"></div>
</template>

<script>
import * as d3 from "d3";
export default {
  name: "App",
  async mounted() {
    const data = await d3.xml("/data.xml");
    for (const n of data.documentElement.getElementsByTagName("name")) {
      console.log(n.textContent);
    }
  },
};
</script>

We call d3.xml to read the data.xml file.

The method returns the parsed XML DOM object.

Therefore, we can use the documentElement.getElementByTagName method to get the name elements by their tag name.

And then we can log the textContent to log the content.

Loading Tab Separated Files

We can read tab-separated files with the d3.tsv method.

For example, we can write:

public/data.tsv

name age city
John 30 New York
Jane 20 San Francisco

App.vue

<template>
  <div id="app"></div>
</template>

<script>
import * as d3 from "d3";
export default {
  name: "App",
  async mounted() {
    const data = await d3.tsv("/data.tsv");
    for (const { name, age, city } of data) {
      console.log(name, age, city);
    }
  },
};
</script>

to call the d3.tsv method to load data.tsv .

It returns a promise with an array of data.

Then we can loop through the data entry and log them.

Conclusion

We can load CSV, TSV, XML, and JSON files with D3 in our Vue app.