I want to manage cookies in the browser and use this external plugin vue-cookies. But if I call its window.$cookies.isKey(Cookies), I get an error: console.log(currentUser) = false.
Cookie exists on browser
Main.js:
import Vue from "vue"
import VueCookies from 'vue-cookies'
import App from "./App.vue"
Vue.use(VueCookies)
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
routes.js:
router.beforeEach((to, from, next) => {
const currentUser = window.$cookies.isKey(Cookies);
console.log(currentUser);
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!currentUser) {
window.location.href = "https://localhost";
} else {
next();
} {
next();
}
}
});
Any idea?
let fetchData = async function() {
return $.get("https://jsonplaceholder.typicode.com/todos/1");
}
Vue.use(VueRouter);
const cComponent = {
data() {
return {
fetchedData: null
}
},
template: `<div>{{$route.params.id+': '}}{{fetchedData}}</div>`,
async beforeRouteEnter(to, from, next) {
let data = await fetchData();
next(vm => vm.fetchedData = data);
},
async beforeRouteUpdate(to, from, next) {
console.log("beforeRouteUpdate");
let data = await fetchData();
this.fetchedData = data;
next();
}
}
const routes = [{
path: "/path/:id",
component: cComponent,
}]
const router = new VueRouter({
mode: "history",
routes,
})
const app = new Vue({
el: "#app",
router,
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<script src=" https://unpkg.com/vue-router#3.5.1/dist/vue-router.js"></script>
<div id="app">
<router-link to="/path/1">An URL</router-link>
<router-link to="/path/2">Another one</router-link>
<router-link to="/path/3">And another one</router-link>
<router-view :key="$route.path"></router-view>
</div>
This is the fetch function that retrieves data from a server:
let fetchPage = async (route) => {
return $.get('route');
}
This is my beforeRouteUpdate navigation guard, which fetchedData property of the Vue instance (declared in data option beforehand) does not changes.
async beforeRouteUpdate(to, from, next) {
this.fetchedData = await fetchPage(to.path);
//fetchData is not changed while changing route param
next();
}
The next() callback is activated at the end (URL does change), but this.fetchedData shows null in Vue DevTools (initializing value).
On the other hand, if I do the same thing with async/await in beforeRouteEnter hook, everything works perfectly.
async beforeRouteEnter(to, from, next) {
let data = await fetchPage(to.path);
next(app => app.fetchedData = data); //fetchedData has fetched data as supposed.
},
I tried replacing this with app just like in beforeRouteEnter hook. But there's still no result.
What am I missing? Is this the best practice with guard hooks and how should I use them better? (ignore Exception handling part)
Updated: SPA experience for code snippet on StackOverflow is kinda not perfect so you better try using my provided JS (here). It has the same problem but I simplified it.
Versions
Vue 2.6
VueRouter 3.5
I'm trying to use vue-apexcharts in my Vue project and I'm trying to import the library via script tags to keep the bundle size down when im building my app.
My code looks something like this:
import Vue from "vue";
export default new Vue({
data: {},
components: {},
created() {
const apexcharts = document.createElement("script");
apexcharts.setAttribute("src", "https://cdn.jsdelivr.net/npm/apexcharts");
document.head.appendChild(apexcharts);
const vueApexcharts = document.createElement("script");
vueApexcharts.setAttribute("src", "https://cdn.jsdelivr.net/npm/vue-apexcharts");
document.head.appendChild(vueApexcharts);
},
});
I'm not sure how to register apexcharts and use it within components once I've added the script tags. Normally I'd find the library reference in the global window but can't find anything there.
Thanks in advance!
Edit
I'm trying to achieve something like this:
import Vue from "vue";
const loadScript = (src) =>
new Promise((resolve, reject) => {
const script = document.createElement("script");
script.setAttribute("src", src);
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
const loadApexCharts = () =>
loadScript("https://cdn.jsdelivr.net/npm/apexcharts");
const loadVueApexCharts = () =>
loadScript("https://cdn.jsdelivr.net/npm/vue-apexcharts");
const initVue = () => {
Vue.component("apexcharts", window.VueApexCharts);
new Vue({
data: {},
components: {},
created() {
console.log(window.VueApexCharts, 'log')
},
});
};
loadApexCharts()
.then(loadVueApexCharts)
.then(initVue)
.catch((err) => console.warn(err));
But my log returns undefined in this case
ApexCharts needs to be loaded before VueApexCharts, so you have to ensure the script-loading order with Promises. The CDN scripts define window.ApexCharts and window.VueApexCharts, respectively, so once the scripts are loaded, you can register the apexcharts component for use in the app:
// main.js
const loadScript = src => new Promise((resolve, reject) => {
const script = document.createElement('script')
script.setAttribute('src', src)
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
const loadApexCharts = () => loadScript('https://cdn.jsdelivr.net/npm/apexcharts')
const loadVueApexCharts = () => loadScript('https://cdn.jsdelivr.net/npm/vue-apexcharts')
const initVue = () => {
Vue.component('apexcharts', window.VueApexCharts)
new Vue({
render: (h) => h(App)
}).$mount('#app')
}
loadApexCharts()
.then(loadVueApexCharts)
.then(initVue)
.catch((err) => console.warn(err))
demo
app.js
const app = new Vue({
el: '#wrapper',
router,
store
});
router.js
const router = new VueRouter({
routes,
hashbang: false,
mode: 'history',
})
router.beforeEach((to, from, next) => {
NProgress.start()
NProgress.set(0.1)
next()
})
router.afterEach(() => {
setTimeout(() => NProgress.done(), 500)
})
Error
NProgress is not defined
I have already installed vue-nprogress using npm still it's showing undefined. How to solve this issue? Thanks in advance!
Import and use the plugin in the main.js file as follows:
...
import NProgress from 'vue-nprogress'
Vue.use(NProgress)
const nprogress = new NProgress()
const app = new Vue({
el: '#wrapper',
router,
store,
nprogress
});
then in router.js access to that instance using router.app.$nprogress :
const router = new VueRouter({
routes,
hashbang: false,
mode: 'history',
})
router.beforeEach((to, from, next) => {
router.app.$nprogress.start()
router.app.$nprogress.set(0.1)
next()
})
router.afterEach(() => {
setTimeout(() => router.app.$nprogress.done(), 500)
})
I have a component that contains statement like this.$route.fullPath, how should I mock value of fullPathof $route object if I want to test that component?
I disagree with the top answer - you can mock $route without any issue.
On the other hand, installing vue-router multiple times on the base constructor will cause you problems. It adds $route and $router as read only properties. Which makes it impossible to overwrite them in future tests.
There are two ways to achieve this with vue-test-utils.
Mocking vue-router with the mocks option
const $route = {
fullPath: 'full/path'
}
const wrapper = mount(ComponentWithRouter, {
mocks: {
$route
}
})
wrapper.vm.$route.fullPath // 'full/path'
You can also install Vue Router safely by using createLocalVue:
Installing vue-router safely in tests with createLocalVue
const localVue = createLocalVue()
localVue.use(VueRouter)
const routes = [
{
path: '/',
component: Component
}
]
const router = new VueRouter({
routes
})
const wrapper = mount(ComponentWithRouter, { localVue, router })
expect(wrapper.vm.$route).to.be.an('object')
Best not mock vue-router but rather use it to render the component, that way you get a proper working router. Example:
import Vue from 'vue'
import VueRouter from 'vue-router'
import totest from 'src/components/totest'
describe('totest.vue', () => {
it('should totest renders stuff', done => {
Vue.use(VueRouter)
const router = new VueRouter({routes: [
{path: '/totest/:id', name: 'totest', component: totest},
{path: '/wherever', name: 'another_component', component: {render: h => '-'}},
]})
const vm = new Vue({
el: document.createElement('div'),
router: router,
render: h => h('router-view')
})
router.push({name: 'totest', params: {id: 123}})
Vue.nextTick(() => {
console.log('html:', vm.$el)
expect(vm.$el.querySelector('h2').textContent).to.equal('Fred Bloggs')
done()
})
})
})
Things to note:
I'm using the runtime-only version of vue, hence render: h => h('router-view').
I'm only testing the totest component, but others might be required if they're referenced by totest eg. another_component in this example.
You need nextTick for the HTML to have rendered before you can look at it/test it.
One of the problems is that most of the examples I found referred to the old version of vue-router, see the migrations docs, eg. some examples use router.go() which now doesn't work.
No answer was helping me out, So I dig into vue-test-utils documentation and found myself a working answer, so you need to import.
import { shallowMount,createLocalVue } from '#vue/test-utils';
import router from '#/router.ts';
const localVue = createLocalVue();
We created a sample vue instance. While testing you need to use shallowMount so you can provide vue app instance and router.
describe('Components', () => {
it('renders a comment form', () => {
const COMMENTFORM = shallowMount(CommentForm,{
localVue,
router
});
})
})
You can easily pass router and to shallow mount and it does not gives you the error. If you want to pass store you use:
import { shallowMount,createLocalVue } from '#vue/test-utils';
import router from '#/router.ts';
import store from '#/store.ts';
const localVue = createLocalVue();
And then pass store:
describe('Components', () => {
it('renders a comment form', () => {
const COMMENTFORM = shallowMount(CommentForm,{
localVue,
router,
store
});
})
})
This solution solved the following errors:
Cannot read property 'params' of undefined when using this.$route.params.id
Unknown custom element router-link
✔
Easiest method i found is to use localVue
import { createLocalVue, mount } from '#vue/test-utils';
import VueRouter from 'vue-router';
import Vuex from 'vuex';
import ComponentName from '#/components/ComponentName.vue';
// Add store file if any getters is accessed
import store from '#/store/store';
describe('File name', () => {
const localVue = createLocalVue();
localVue.use(VueRouter);
// Can also be replaced with route(router.js) file
const routes = [
{
path: '/path',
component: ComponentName,
name: 'Route name'
}
];
const router = new VueRouter({ routes });
// if needed
router.push({
name: 'Route name',
params: {}
});
const wrapper = mount(ComponentName, {
localVue,
router,
store
});
test('Method()', () => {
wrapper.vm.methodName();
expect(wrapper.vm.$route.path)
.toEqual(routes[0].path);
});
});
Hope it helps!!!
Why are all answers so complicated? You can just do:
...
wrapper = mount(HappyComponent, {
mocks: {
$route: { fullPath: '' }
},
})
...
You dont have to specifically "mock" a router. Your application can set VueRouter in the global vue scope and you can still make it do what you want in your tests without issue.
Read the localVue usage with VueRouter: https://vue-test-utils.vuejs.org/guides/#using-with-vue-router.
I am currently pulling in a complex router from our main app and am able to jest.spyOn() calls to router.push() as well as setting the path before the component is created running shallowMount() for some route handling in a created() hook.
The Workaround
// someVueComponent.vue
<template>
... something
</template>
<script>
...
data () {
return {
authenticated: false
}
},
...
created () {
if(!this.authenticated && this.$route.path !== '/'){
this.$router.push('/')
}
}
</script>
// someVueComponent.spec.js
import Vuex from 'vuex'
import VueRouter from 'vue-router'
import { shallowMount, createLocalVue } from '#vue/test-utils'
import SomeVueComponent from 'MyApp/components/someVueComponent'
import MyAppRouter from 'MyApp/router'
import MyAppCreateStore from 'MyApp/createStore'
import merge from 'lodash.merge'
function setVueUseValues (localVue) {
localVue.use(Vuex)
localVue.use(VueRouter)
// other things here like custom directives, etc
}
beforeEach(() => {
// reset your localVue reference before each test if you need something reset like a custom directive, etc
localVue = createLocalVue()
setVueUseValues(localVue)
})
let localVue = createLocalVue()
setVueUseValues(localVue)
test('my app does not react to path because its default is "/"', () => {
const options = {
localVue,
router: MyAppRouter,
store: MyAppCreateStore()
}
const routerPushSpy = jest.spyOn(options.router, 'push')
const wrapper = shallowMount(SomeVueComponent, options)
expect(routerPushSpy).toHaveBeenCalledTimes(0)
})
test('my app reacts to path because its not "/" and were not authenticated', () => {
const options = {
localVue,
router: MyAppRouter,
store: MyAppCreateStore()
}
const routerPushSpy = jest.spyOn(options.router, 'push')
options.router.push('/nothomepath')
expect(routerPushSpy).toHaveBeenCalledWith('/nothomepath') // <- SomeVueComponent created hook will have $route === '/nothomepath' as well as fullPath
const wrapper = shallowMount(SomeVueComponent, options)
expect(routerPushSpy).toHaveBeenCalledWith('/') // <- works
})
The above is done with the idea that I need the $route state changed before SomeVueComponent.vue is created/mounted. Assuming you can create the wrapper and want to test that the component this.$router.push('/something') based on some other state or action you can always spy on the wrapper.vm instance
let routerPushSpy = jest.spyOn(wrapper.vm.$router, 'push') // or before hooks, etc
As of this writing there seems to be an open defect which keeps the following from working because vm.$route will always be undefined, making the above the only option (that I know of) as there is no other way to "mock" the $route because installing VueRouter writes read only properties to $route.
From the vue-test-utils docs https://vue-test-utils.vuejs.org/guides/#mocking-route-and-router:
import { shallowMount } from '#vue/test-utils'
const $route = {
path: '/some/path'
}
const wrapper = shallowMount(Component, {
mocks: {
$route
}
})
wrapper.vm.$route.path // /some/path
If your interested here is the github link to a reproduction of the issue: https://github.com/vuejs/vue-test-utils/issues/1136
All kudos to #SColvin for his answer; helped find an answer in my scenario wherein I had a component with a router-link that was throwing a
ERROR: '[Vue warn]: Error in render function: (found in <RouterLink>)'
during unit test because Vue hadn't been supplied with a router. Using #SColvin answer to rewrite the test originally supplied by vue-cli from
describe('Hello.vue', () =>
{
it('should render correct contents', () =>
{
const Constructor = Vue.extend(Hello);
const vm = new Constructor().$mount();
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App');
});
to
describe('Hello.vue', () =>
{
it('should render correct contents', () =>
{
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{ path: '/', name: 'Hello', component: Hello },
],
});
const vm = new Vue({
el: document.createElement('div'),
/* eslint-disable object-shorthand */
router: router,
render: h => h('router-view'),
});
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App');
});
});
Not needing to pass parameters in to the view I could simplify the component as the default render, no need to push and no need to wait nextTick. HTH someone else!
Adding to the great answer from #SColvin, here's an example of this working using Avoriaz:
import { mount } from 'avoriaz'
import Vue from 'vue'
import VueRouter from 'vue-router'
import router from '#/router'
import HappyComponent from '#/components/HappyComponent'
Vue.use(VueRouter)
describe('HappyComponent.vue', () => {
it('renders router links', () => {
wrapper = mount(HappyComponent, {router})
// Write your test
})
})
I believe this should work with vue-test-utils, too.
Take a look at this example using vue-test-utils, where I'm mocking both router and store.
import ArticleDetails from '#/components/ArticleDetails'
import { mount } from 'vue-test-utils'
import router from '#/router'
describe('ArticleDetails.vue', () => {
it('should display post details', () => {
const POST_MESSAGE = 'Header of our content!'
const EXAMPLE_POST = {
title: 'Title',
date: '6 May 2016',
content: `# ${POST_MESSAGE}`
}
const wrapper = mount(ArticleDetails, {
router,
mocks: {
$store: {
getters: {
getPostById () {
return EXAMPLE_POST
}
}
}
}
})
expect(wrapper.vm.$el.querySelector('h1.post-title').textContent.trim()).to.equal(EXAMPLE_POST.title)
expect(wrapper.vm.$el.querySelector('time').textContent.trim()).to.equal(EXAMPLE_POST.date)
expect(wrapper.vm.$el.querySelector('.post-content').innerHTML.trim()).to.equal(
`<h1>${POST_MESSAGE}</h1>`
)
})
})
This is what I've been doing as per this article:
it('renders $router.name', () => {
const scopedVue = Vue.extend();
const mockRoute = {
name: 'abc'
};
scopedVue.prototype.$route = mockRoute;
const Constructor = scopedVue.extend(Component);
const vm = new Constructor().$mount();
expect(vm.$el.textContent).to.equal('abc');
});
You can mock to vm.$router by setting vm._routerRoot._router
For example
var Constructor = Vue.extend(Your_Component)
var vm = new Constructor().$mount()
var your_mock_router = {hello:'there'}
vm.$router = your_mock_router //An error 'setting a property that has only a getter'
vm._routerRoot._router = your_mock_router //Wow, it works!
You can double check their source code here: https://github.com/vuejs/vue-router/blob/dev/dist/vue-router.js#L558
Easiest way i've found is to mock the $route.
it('renders $router.name', () => {
const $route = {
name: 'test name - avoriaz'
}
const wrapper = shallow(Component, {
mocks: {
$route
}
})
expect(wrapper.text()).to.equal($route.name)
})