
@djirdehh
A Full Day of Vue.js

06. Mixins, Filters, & Testing
Mixins allow us to neatly reuse shared functionality between components.
Mixins
Mixins
<template>
<h1>This is Component #1</h1>
</template>
<script>
export default {
name: "Component1",
created() {
console.log(`${this.options.name} has been created!`);
},
...
};
</script>
<style lang="css">
<!-- Styles-->
</style>
<template>
<h1>This is Component #2</h1>
</template>
<script>
export default {
name: "Component2",
created() {
console.log(`${this.options.name} has been created!`);
},
...
};
</script>
<style lang="css">
<!-- Styles-->
</style>
Mixins
const createdMixin = {
created() {
console.log(`${this.options.name} has been created!`);
}
};
Mixins
<template>
...
</template>
<script>
export default {
name: "ComponentName",
mixins: [nameOfMixin]
};
</script>
Mixins
<template>
<h1>This is Component #1</h1>
</template>
<script>
import {createdMixin} from './mixins';
export default {
name: "Component1",
mixins: [createdMixin]
...
};
</script>
<style lang="css">
<!-- Styles-->
</style>
<template>
<h1>This is Component #2</h1>
</template>
<script>
import {createdMixin} from './mixins';
export default {
name: "Component2",
mixins: [createdMixin]
...
};
</script>
<style lang="css">
<!-- Styles-->
</style>
Mixins
export const helloWorldMixin = {
data() {
return {
reversedGreeting: '!dlrow olleH',
}
},
computed: {
getGreeting() {
return this.reversedGreeting.split("").reverse().join("");
}
}
}
export const createdMixin = {
created() {
console.log(`${this.options.name} has been created!`);
}
}
mixins.js
Mixins
<template>
<h1>This is Component #1</h1>
<p>{{getGreeting}}</p>
</template>
<script>
import {helloWorldMixin, createdMixin} from './mixins';
export default {
name: "Component1",
mixins: [helloWorldMixin, createdMixin]
...
};
</script>
<style lang="css">
<!-- Styles-->
</style>
<template>
<h1>This is Component #2</h1>
<p>{{getGreeting}}</p>
</template>
<script>
import {helloWorldMixin, createdMixin} from './mixins';
export default {
name: "Component2",
mixins: [helloWorldMixin, createdMixin]
...
};
</script>
<style lang="css">
<!-- Styles-->
</style>
Component1.vue
Component2.vue
Mixins
What if the mixin and component have overlapping information?
Mixins
const greetingMixin = {
data() {
return {
greeting: 'Hello World!',
}
}
}
Vue.component('greeting-component', {
mixins: [greetingMixin],
data() {
return {
greeting: 'Good Morning!',
}
},
created() {
console.log(this.greeting); // 'Good Morning!'
}
});
Component data takes priority
Mixins
const greetingMixin = {
methods: {
greetingMethod() {
console.log('Hello World!');
}
},
components: {
'greeting-component': ComponentA
}
}
Vue.component('greeting-component', {
mixins: [greetingMixin],
methods: {
greetingMethod() {
console.log('Good Morning!');
}
},
components: {
'greeting-component': ComponentB
}
});
Component options takes priority as well
Mixins
const greetingMixin = {
created() {
console.log('Hello World!');
}
}
Vue.component('greeting-component', {
mixins: [greetingMixin],
created() {
console.log('Good Morning!');
}
});
// 'Hello World!'
// 'Good Morning!'
Lifecycle hooks run in mixins first, then in components
Mixins
Apart from lifecycle hooks; I would strongly try to avoid replicating mixin data/properties with that of components.
Mixins
We can also define mixins globally!
Vue.mixin({ });
Mixins
"Use global mixins sparsely and carefully, because it affects every single Vue instance created, including third party components."
* Vue Docs - https://vuejs.org/v2/guide/mixins.html
Filters allow us to return formatted versions of data without making changes to the data source.
Filters
Filters are used in mustache interpolations and v-bind expressions.
Filters
{{ property | nameOfFilter }}
v-bind:src="property | nameOfFilter"
Filters
<template>
...
</template>
<script>
export default {
name: "ComponentName",
filters: {
nameOfFilter(value) {
return ...
}
}
};
</script>
Filters
filters: {
nameOfFilter(value) {
return ...
}
}
Always the data value that is being filtered as the first argument.
Filters
<template>
<div>
{{ greeting }}
{{ greeting | reverseString }}
</div>
</template>
<script>
export default {
name: "GreetingComponent",
data() {
return {
greeting: 'Hello World!';
}
},
filters: {
reverseString(originalValue) {
return originalValue.split("").reverse().join("");
},
}
};
</script>
<style lang="css">
<!-- Styles-->
</style>
Filters
{{ property | nameOfFilter(arg1, arg2) }}
filters: {
nameOfFilter(value, arg1, arg2) {
return ...
}
}
Filters
{{ property | filter1 | filter 2 | filter 3 }}
property value is first arg
returned value of prev filter is first arg
returned value of prev filter is first arg
Filters
We can also define filters globally!
Vue.filter('name-of-filter', function() {});
Testing
Testing can help reveal bugs before they appear, instill confidence in your web application, and make it easy to onboard new developers on an existing codebase.
Testing - Basics
Unit Testing
Tests are made to parts of an application in isolation.
End to End Testing
Tests focus on whether an application has been built appropriately from start to finish.
Two main ways to test web applications
2
1
Many unit test environments/suites exist
-
Mocha: A JavaScript testing framework. Allows us to specify our test suites with describe and it blocks.
-
Chai: A testing assertion library. Provides interfaces for us to create assertions for our tests (should..., expect..., assert...).
-
Jest: A JavaScript testing framework that comes with an assertion library, mocking support, snapshot testing, etc. with minimal to no configuration/set-up.
and many more....
Testing - Basics

new Calculator()
2 + 2 | we expect 4
5 - 3 | we expect 2
4 x 4 | we expect 16
Testing - Basics
describe('Calculator', () => { it('sums 2 and 2 to 4', () => {
...
});
it('subtracts 5 and 3 to 2', () => {
... }); });
describe functions segment each logical unit of tests.
it functions test each expectation we'd want to assert.
Testing - Basics
describe('Calculator', () => { describe('sum()', () => { it('sums 2 and 2 to 4', () => {
... }); });
describe('subtract()', () => {
it('subtracts 5 and 3 to 2', () => {
... }); }); });
Testing - Basics
describe('Calculator', () => { it('sums 2 and 2 to 4', () => {
const calc = new Calculator();
expect(calc.sum(2,2)).toEqual(4);
});
it('subtracts 5 and 3 to 2', () => {
const calc = new Calculator(); expect(calc.subtract(5,3)).toEqual(2); }); });
Testing - Basics
expect(...).toEqual(...);
Unique functions from the testing framework/assertion library (e.g. Jest) being used.
Testing - Basics
Testing - Basics
The Arrange, Act, Assert (AAA) pattern
describe('Calculator', () => { it('sums 2 and 2 to 4', () => {
const calc = new Calculator(); const sumResult = calc.sum(2,2); expect(sumResult).toEqual(4); });
});
Arrange
Act
Assert
Testing - Basics
We'll now look to test a basic Vue component.
list of todos
input to create new todo
element to remove certain todo
1
2
3
Simple version of TodoMVC
Testing - Vue Component

Testing - Vue Component
<template>
<div id="app">
<section class="todoapp">
<header class="header">
<h1 class="title">todos</h1>
<input class="new-todo"
placeholder="What needs to be done?"
v-model="newTodo"
@keyup.enter="addTodo">
</header>
<section class="main">
<ul class="todo-list">
<li class="todo"
v-for="(todo, index) in todos"
:key="index">
<div class="view">
<label>{{ todo }}</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
</li>
</ul>
</section>
</section>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
todos: [],
newTodo: ''
}
},
methods: {
addTodo() {
if (this.newTodo !== '') {
this.todos.push(this.newTodo);
this.newTodo = '';
}
},
removeTodo(todo) {
this.todos.splice(this.todos.indexOf(todo), 1);
}
}
};
</script>
App.vue
describe('App.vue', () => {
it('should render correct initial content', () => {
...
});
});
Testing - Vue Component

Testing - Vue Component
import App from "./App";
describe("App", () => {
it("should render correct initial content", () => {
// ...
});
});
App.spec.js
Testing - Vue Component
To assert the HTML content of our component - we need to have our component mounted().
Before we have our component mounted() , we can create the component constructor.
Testing - Vue Component
import Vue from "vue";
import App from "./App";
describe("App", () => {
it("should render correct initial content", () => {
const Constructor = Vue.extend(App);
});
});
App.spec.js
Testing - Vue Component
import Vue from "vue";
import App from "./App";
describe("App", () => {
it("should render correct initial content", () => {
const Constructor = Vue.extend(App);
const vm = new Constructor().$mount();
});
});
App.spec.js
Testing - Vue Component
import Vue from "vue";
import App from "./App";
describe("App", () => {
it("should render correct initial content", () => {
const Constructor = Vue.extend(App);
const vm = new Constructor().$mount();
expect(vm.$el.querySelector(".title").textContent).toBe("todos");
expect(vm.$el.querySelector(".new-todo").placeholder).toBe(
"What needs to be done?"
);
});
});
App.spec.js
Testing - Vue Component
describe('App.vue', () => { it('should render correct initial content', () => {
...
});
it('should set correct default data', () => {
...
});
});
Testing - Vue Component
todos: [ ] ,
newTodo: ' '
import Vue from "vue";
import App from "./App";
describe("App", () => {
it("should render correct initial content", () => {
// ...
});
it("should set correct default data", () => {
const initialData = App.data();
});
});
App.spec.js
Testing - Vue Component
import Vue from "vue";
import App from "./App";
describe("App", () => {
it("should render correct initial content", () => {
// ...
});
it("should set correct default data", () => {
const initialData = App.data();
expect(initialData.todos).toEqual([]);
expect(initialData.newTodo).toEqual("");
});
});
App.spec.js
Testing - Vue Component
vue-test-utils?
Testing - Vue Component
Testing - vue-test-utils
vue-test-utils is a utility library that provides useful methods that help make testing easier.
Testing - vue-test-utils
We're able to mount a component by simply doing:
const wrapper = mount(Component);
import { mount } from "@vue/test-utils";
Testing - vue-test-utils
const vm = wrapper.vm;
const html = wrapper.html();
const button = wrapper.find('button');
-
Access the component instance:
-
Retrieve component's html:
-
Find element (wrapper):
-
Get Props:
const propValue = wrapper.props('propKey');
-
Trigger Event:
wrapper.trigger('click');
-
Set Methods:
wrapper.setMethods({});
and a lot more...
Testing - vue-test-utils

Testing - vue-test-utils
.html()
.setProps()
.setData()
.setMethods()
.vm
component
wrapper
Testing - vue-test-utils
mount()
Creates a Wrapper that contains the mounted Vue component.
shallowMount()
Creates a Wrapper that contains the mounted Vue component, but with stubbed child components.
import { mount, shallowMount } from "@vue/test-utils";
import { mount } from "@vue/test-utils";
Testing - vue-test-utils
Tests the component in isolation.
shallowMount()
It's faster.
great to use when we want to focus on a single component...
Testing - vue-test-utils
import Vue from "vue";
import App from "./App";
describe("App.vue", () => {
it("should render correct initial content", () => {
const Constructor = Vue.extend(App);
const vm = new Constructor().$mount();
expect(vm.$el.querySelector(".title").textContent).toBe("todos");
expect(vm.$el.querySelector(".new-todo").placeholder).toBe(
"What needs to be done?"
);
});
it("should set correct default data", () => {
const initialData = App.data();
expect(initialData.todos).toEqual([]);
expect(initialData.newTodo).toEqual("");
});
});
App.spec.js
Testing - vue-test-utils
npm install @vue/test-utils --save-dev
Testing - vue-test-utils
import App from "./App";
import { shallowMount } from "@vue/test-utils";
describe("App.vue", () => {
it("should render correct initial content", () => {
...
});
it("should set correct default data", () => {
...
});
});
App.spec.js
Testing - vue-test-utils
import App from "./App";
import { shallowMount } from "@vue/test-utils";
describe("App.vue", () => {
let wrapper = shallowMount(App);
it("should render correct initial content", () => {
...
});
it("should set correct default data", () => {
...
});
});
App.spec.js
Testing - vue-test-utils
import App from "./App";
import { shallowMount } from "@vue/test-utils";
describe("App.vue", () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(App);
});
it("should render correct initial content", () => {
...
});
it("should set correct default data", () => {
...
});
});
Testing - vue-test-utils
import App from "./App";
import { shallowMount } from "@vue/test-utils";
describe("App.vue", () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(App);
});
it("should render correct initial content", () => {
expect(wrapper.find(".title").text()).toBe("todos");
expect(wrapper.find(".new-todo").element.placeholder).toBe(
"What needs to be done?"
);
});
it("should set correct default data", () => {
...
});
});
App.spec.js
Testing - vue-test-utils
import App from "./App";
import { shallowMount } from "@vue/test-utils";
describe("App.vue", () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(App);
});
it("should render correct initial content", () => {
expect(wrapper.find(".title").text()).toBe("todos");
expect(wrapper.find(".new-todo").element.placeholder).toBe(
"What needs to be done?"
);
});
it("should set correct default data", () => {
expect(wrapper.vm.todos).toEqual([]);
expect(wrapper.vm.newTodo).toEqual("");
});
});
App.spec.js
Testing - vue-test-utils

input to create new todo
1
Testing - vue-test-utils
describe("App.vue", () => {
it("should render correct initial content", () => {
...
});
it("should set correct default data", () => {
...
});
describe("the user populates the text input field", () => {
...
});
});
Testing - vue-test-utils
describe("App.vue", () => {
...
describe("the user populates the text input field", () => {
it("should update the 'newTodo' data property", () => {
...
});
});
});
Testing - vue-test-utils
import App from "./App";
describe("App", () => {
// ...
describe("the user populates the text input field", () => {
it("should update the 'newTodo' data property", () => {
const inputField = wrapper.find(".new-todo");
});
});
});
App.spec.js
Testing - vue-test-utils
import App from "./App";
describe("App", () => {
// ...
describe("the user populates the text input field", () => {
it("should update the 'newTodo' data property", () => {
const inputField = wrapper.find(".new-todo");
inputField.element.value = "New Todo";
inputField.trigger("input");
});
});
});
App.spec.js
Testing - vue-test-utils
import App from "./App";
describe("App", () => {
// ...
describe("the user populates the text input field", () => {
it("should update the 'newTodo' data property", () => {
const inputField = wrapper.find(".new-todo");
inputField.element.value = "New Todo";
inputField.trigger("input");
expect(wrapper.vm.newTodo).toEqual("New Todo");
});
});
});
App.spec.js
Testing - vue-test-utils

list of todos
2
Testing - vue-test-utils
describe("App.vue", () => {
...
describe("the user populates the text input field and presses Enter", () => {
it("should add a new todo to the 'todos' array", () => {
...
});
});
});
Testing - vue-test-utils
import App from "./App";
describe("App", () => {
// ...
describe("the user populates the text input field and presses Enter", () => {
it("should add a new todo to the 'todos' array", () => {
const inputField = wrapper.find(".new-todo");
});
});
});
App.spec.js
Testing - vue-test-utils
import App from "./App";
describe("App", () => {
// ...
describe("the user populates the text input field and presses Enter", () => {
it("should add a new todo to the 'todos' array", () => {
const inputField = wrapper.find(".new-todo");
inputField.element.value = "New Todo";
inputField.trigger("input");
inputField.trigger("keyup.enter");
});
});
});
App.spec.js
Testing - vue-test-utils
import App from "./App";
describe("App", () => {
// ...
describe("the user populates the text input field and presses Enter", () => {
it("should add a new todo to the 'todos' array", () => {
const inputField = wrapper.find(".new-todo");
inputField.element.value = "New Todo";
inputField.trigger("input");
inputField.trigger("keyup.enter");
expect(wrapper.vm.todos).toEqual(["New Todo"]);
});
});
});
App.spec.js
Testing - vue-test-utils
import App from "./App";
describe("App", () => {
// ...
describe("the user populates the text input field", () => {
it("should update the 'newTodo' data property", () => {
const inputField = wrapper.find(".new-todo");
inputField.element.value = "New Todo";
inputField.trigger("input");
expect(wrapper.vm.newTodo).toEqual("New Todo");
});
});
describe("the user populates the text input field and presses Enter", () => {
it("should add a new todo to the 'todos' array", () => {
const inputField = wrapper.find(".new-todo");
inputField.element.value = "New Todo";
inputField.trigger("input");
inputField.trigger("keyup.enter");
expect(wrapper.vm.todos).toEqual(["New Todo"]);
});
});
});
App.spec.js
Testing - vue-test-utils
import App from "./App";
describe("App", () => {
// ...
describe("the user populates the text input field", () => {
let inputField;
beforeEach(() => {
inputField = wrapper.find(".new-todo");
inputField.element.value = "New Todo";
inputField.trigger("input");
});
it("should update the 'newTodo' data property", () => {
expect(wrapper.vm.newTodo).toEqual("New Todo");
});
describe("and presses Enter", () => {
it("should add a new todo to the 'todos' array", () => {
inputField.trigger("keyup.enter");
expect(wrapper.vm.todos).toEqual(["New Todo"]);
});
});
});
});
App.spec.js
Testing - vue-test-utils
Mixins, Filters, & Testing - Chapter Summary!
-
Mixins allow us to neatly reuse functionality between components.
-
const nameofMixin = {} : mixins: [nameOfMixin]
-
Component data/properties take priority of identical Mixin information. Mixin lifecycle hooks run first.
-
Can define Mixins globally with Vue.mixin({}) - should be used sparingly!
-
Filters helps return formatted versions of data without making changes to the data source.
-
{{ property | nameOfFilter }} ------- filters: {nameOfFilter() {return ...}}
-
Used in mustache interpolations and v-bind expressions - {{ property | nameOfFilter }}
-
Data value that is filtered is always the first argument of the filter function.
-
vue-test-utils is Vue's official testing utility library.
-
Mount and shallow mount components with mount() and shallowMount() functions.
-
Mounted wrapper contains useful methods to help make testing easier:
wrapper.html();
wrapper.find();
wrapper.trigger();
wrapper.setData();
wrapper.setProps();
wrapper.setMethods();
Mixins, Filters, & Testing - Exercise 💪!
Mixins 💪
Mixins, Filters, & Testing - Exercise 💪!


Mixins 💪
Mixins, Filters, & Testing - Exercise 💪!
Filters 💪


Mixins, Filters, & Testing - Exercise 💪!
Filters 💪
To modify the price
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0
}).format(number);
To modify the circulating supply
new Intl.NumberFormat().format(number);
Mixins, Filters, & Testing - Exercise 💪!
Testing 💪

element to remove certain todo
3
button to remove all todos
4
Mixins, Filters, & Testing - Exercise 💪!
<template>
<div id="app">
<section class="todoapp">
...
<li class="todo"
v-for="(todo, key) in todos"
:key="key">
<div class="view">
<label>{{ todo }}</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
</li>
...
</section>
<footer class="footer">
<button @click="removeAllTodos" class="clear-completed">
Remove all
</button>
</footer>
</div>
</template>
<script>
export default {
...
methods: {
...,
removeTodo(todo) {
this.todos.splice(this.todos.indexOf(todo), 1);
},
removeAllTodos() {
this.todos = [];
}
}
};
</script>
App.vue
Mixins, Filters, & Testing - Exercise 💪!
import App from "./App";
describe("App", () => {
// ...
describe("the user populates the text input field", () => {
// ...
it("should update the 'newTodo' data property", () => {
// ...
});
describe("and presses Enter", () => {
it("should add a new todo to the 'todos' array", () => {
// ...
});
});
describe("and presses Enter and then removes the todo item", () => {
it("should have the new todo removed from the 'todos' array", () => {
// Trigger keyup.enter on inputField to submit todo
// Then find the Remove icon and trigger a click
});
});
});
describe("the user clicks the 'Remove all' label", () => {
it("should remove all todos from the 'todos' data property", () => {
// Quickly update the todos array with wrapper.setData({})
// Find the Remove all button and trigger a click
});
});
});
App.spec.js
Mixins, Filters, & Testing - Exercise 💪!
Testing
Mixins
Filters
Solution 👀
A Full Day of Vue.js Workshop - 06 Mixins, Filters, and Testing
By djirdehh
A Full Day of Vue.js Workshop - 06 Mixins, Filters, and Testing
- 475