CrnaStena
CrnaStena

Reputation: 3167

Vue.js filters and testing

I used vue-cli to create sample project vue init webpack my-test3, and opted for including both e2e and unit tests.

Question 1: Based on documentation for template filters I tried to add new filter as such in main.js:

import Vue from 'vue'
import App from './App'

/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App/>',
  components: { App },
  filters: {
    capitalize: function (value) {
      if (!value) return ''
      value = value.toString()
      return value.charAt(0).toUpperCase() + value.slice(1)
    }
  }
})

And my App.vue:

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <hello></hello>
    <world></world>

    <div class="test-test">{{ 'tesT' | capitalize }}</div>
  </div>
</template>

<script>
import Hello from './components/Hello'
import World from './components/World'

export default {
  name: 'app',
  components: {
    Hello,
    World
  }
}
</script>

I get warning(error):

[Vue warn]: Failed to resolve filter: capitalize (found in component <app>)

If I modify main.js to register filter before initializing new Vue app, then it works without problem.

import Vue from 'vue'
import App from './App'

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  template: '<App/>',
  components: { App }
})

Why one works and other not?

Question 2: With example above that works Vue.filter(..., I added a test for World.vue component:

<template>
  <div class="world">
    <h1>{{ msg | capitalize }}</h1>
    <h2>{{ desc }}</h2>

    Items (<span>{{ items.length }}</span>)
    <ul v-for="item in items">
        <li>{{ item }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'world',
  data () {
    return {
      msg: 'worldly App',
      desc: 'This is world description.',
      items: [ 'Mon', 'Wed', 'Fri', 'Sun' ]
    }
  }
}
</script>

And World.spec.js:

import Vue from 'vue'
import World from 'src/components/World'

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

describe('World.vue', () => {
  it('should render correct title', () => {
    const vm = new Vue({
      el: document.createElement('div'),
      render: (h) => h(World)
    })
    expect(vm.$el.querySelector('.world h1').textContent)
      .to.equal('Worldly App')
  })
  it('should render correct description', () => {
    const vm = new Vue({
      el: document.createElement('div'),
      render: (w) => w(World)
    })
    expect(vm.$el.querySelector('.world h2').textContent)
      .to.equal('This is world description.')
  })
})

For the above test to pass, I need to include Vue.filter(... definition for filter capitalize, otherwise tests would fail. So my question is, how to structure filters/components and initialize them, so testing is easier?

I feel like I should not have to register filters in unit tests, that should be part of the component initialization. But if component is inheriting/using filter defined from main app, testing component will not work.

Any suggestions, comments, reading materials?

Upvotes: 3

Views: 5197

Answers (2)

A good practice is create a filters folder and define your filters inside this folder in individual files and define all your filters globally if you want to have access to them in all of your components, eg:


    // capitalize.js

    export default function capitalize(value) {
      if (!value) return '';
      value = value.toString().toLowerCase();
      value = /\.\s/.test(value) ? value.split('. ') : [value];

      value.forEach((part, index) => {
        let firstLetter = 0;
        part = part.trim();

        firstLetter = part.split('').findIndex(letter => /[a-zA-Z]/.test(letter));
        value[index] = part.split('');
        value[index][firstLetter] = value[index][firstLetter].toUpperCase();
        value[index] = value[index].join('');
      });

      return value.join('. ').trim();
    }

To test it successfully and if you use @vue/test-utils, to test your single file components, you can do that with createLocalVue, like this:

// your-component.spec.js

import { createLocalVue, shallowMount } from '@vue/test-utils';
import capitalize from '../path/to/your/filter/capitalize.js';
import YOUR_COMPONENT from '../path/to/your/component.vue';

describe('Describe YOUR_COMPONENT component', () => {
  const localVue = createLocalVue();

  // this is the key line
  localVue.filter('capitalize', capitalize);

  const wrapper = shallowMount(YOUR_COMPONENT, {
    // here you can define the required data and mocks
    localVue,
    propsData: {
      yourProp: 'value of your prop',
    },
    mocks: {
      // here you can define all your mocks
      // like translate function and others.
    },
  });

  it('passes the sanity check and creates a wrapper', () => {
    expect(wrapper.isVueInstance()).toBe(true);
  });
});

I hope to help you.

Regards.

Upvotes: 2

user900362
user900362

Reputation:

My best guess is that if you define filters inside a component, they will be only available for use inside this component. In your case, you can only use capitalize in the main Vue instance, not its child components. Moving this to a global level solves the issue.

For the second question, you are doing the right thing by adding the filter definition in the testing file.

Upvotes: 0

Related Questions