I'm getting warnings during tests running on my new Vue.js project. Wherever a component uses the router either in the template as a <router-link> or programatically as this.$router.push('/');
The tests are passing but logging these warnings:
ERROR LOG: '[Vue warn]: Error in render: "TypeError: Cannot read property 'resolve' of undefined"
found in
---> <RouterLink>
<Root>'
I'm using Vue2 and the project is based on the webpack project generated by the cli tool.
My unit test index.js:
import Vue from 'vue';
import VueNativeSock from 'vue-native-websocket';
import Router from 'vue-router';
Vue.use(Router);
Vue.use(VueNativeSock, process.env.WEBSOCKET_ADDR, { format: 'json', reconnection: true });
Vue.config.productionTip = false;
const testsContext = require.context('./specs', true, /\.spec$/);
testsContext.keys().forEach(testsContext);
const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/);
srcContext.keys().forEach(srcContext);
My main.js:
import Vue from 'vue';
import VueNativeSock from 'vue-native-websocket';
import VueHead from 'vue-head';
import App from './App';
import router from './router';
Vue.config.productionTip = process.env.NODE_ENV === 'production';
Vue.use(VueNativeSock, process.env.WEBSOCKET_ADDR, { format: 'json', reconnection: true });
Vue.use(VueHead);
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App },
head: {
link: [
{ rel: 'icon', href: '/static/favicon-32x32.png', sizes: '32x32', type: 'image/png' },
],
},
});
Any idea what I'm missing to get these warnings to go away?
A basic test might look like this:
import Vue from 'vue';
import ViewDTCs from '#/components/ViewDTCs';
describe('ViewDTCs.vue', () => {
const Constructor = Vue.extend(ViewDTCs);
const vm = new Constructor().$mount();
ViewDTCs.$socket = new WebSocket(process.env.WEBSOCKET_ADDR);
it('has a created hook', () => {
expect(typeof ViewDTCs.created).to.equal('function');
});
it('should render page', () => {
expect(vm.$el.textContent).to.contain('Retrieving DTCs');
});
});
It looks like your routes might not be set up anywhere in your test environment. If you're using Karma and Avoriaz or Vue Test Utils, I've been able to test components that contain routes like this:
import { mount } from 'vue-test-utils'
import router from 'src/router' // path to your router
import MyComponent from 'src/components/MyComponent'
describe('MyComponent.vue', () => {
let wrapper
beforeEach(() => {
wrapper = mount(MyComponent, {
router: router
})
})
it('has a created hook', () => {
expect(typeof wrapper.created).to.equal('function')
})
...
})
Related
I'm having an issue with a linting error in a vue.js project. The error that I get looks like this:
/Users/mikecuddy/Desktop/coding/data_science_projects/statues/client/src/store/modules/common.js
4:1 error Dependency cycle via #/store/index:4 import/no-cycle
I have no idea how to get rid of this error. I tried renaming files, using this.$router and this.$store with no luck. Here is some of my code:
router -> index.js:
The data path is the main one I want to get to. Notice that I have the store import files commented out - that does get rid of the dependency error but then I have issues with doing something like:
this.$store.state.common.loginFlag
as opposed as importing the store and doing this:
store.state.common.loginFlag
import Vue from 'vue';
import VueRouter from 'vue-router';
// import store from '../store/index.js';
// import store from '#/store/index';
import Home from '../views/Home.vue';
Vue.use(VueRouter);
const routes = [
{
path: '/data',
name: 'Data',
component: () => import('../views/Data.vue'),
beforeEnter: (to, from, next) => {
if (this.$store.state.common.loginFlag === false) {
next('/login');
} else {
next();
}
},
beforeRouteLeave: (to, from, next) => {
if (this.$store.state.common.loginFlag === false) {
next('/login');
} else {
next();
}
},
},
];
const router = new VueRouter({
routes,
});
export default router;
store/modules/common.js:
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
import router from '../../router';
Vue.use(Vuex);
const data = {
userNotFound: false,
passwordNoMatch: false,
loginFlag: false,
};
const getters = {
userNotFound: (state) => state.userNotFound,
passwordNoMatch: (state) => state.passwordNoMatch,
loginFlag: (state) => state.loginFlag,
};
const actions = {
login: ({ commit }, { payload }) => {
const path = 'http://localhost:5000/login';
axios.post(path, payload)
.then((res) => {
if (res.data.login_flag) {
commit('session/setUserObject', res.data.user, { root: true });
commit('setLoginFlag', res.data.login_flag);
// Tried this:
router.push{ name: 'Data' }
// As well as trying this:
this.$router.push({ name: 'Data' });
}
commit('setNoPasswordMatch', res.data.Password_no_match);
commit('setUserNotFound', res.data.Not_found);
})
.catch((error) => {
console.log(error);
});
},
};
// I have mutations but did not think they'd be needed
const mutations = {};
export default {
namespaced: true,
state: data,
getters,
actions,
mutations,
};
In the common.js file I've tried commenting out:
import router from '../../router';
and that seemed to work - got the Dependency cycle error to go away and in the router/index.js file I was able to get to the route but had an issue with this.$store.state.common.loginFlag when I commented out import store from '#/store/index'; If I leave in the import of: import store from '#/store/index';
then I get the dependency cycle error.
I've also found some help at these other stack pages:
TypeError: Cannot read properties of undefined (reading '$router') vuejs
dependency cycle detected import/no-cycle
I will say that I hate using linters and that's what's giving me the problem here.
Here is the code for store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
import common from './modules/common';
import session from './modules/session';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
common,
session,
},
});
Looks like the reason for the dependency cycle here is when you are importing router setup in the store module, and the router in turn imports the whole store. It's okay to use store in router, but try to move routing/redirect logic (these lines):
// Tried this:
router.push{ name: 'Data' }
// As well as trying this:
this.$router.push({ name: 'Data' });
from /modules/common.js to the component or global router hook level, so you avoid router import in the store module.
I am getting a '[vuex] unknown action type: RawHTML' when trying to dispatch an action on a component.
Those errors are usually caused by not correctly namespaced modules, but I am not using modules here.
store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const SETRAWHTML = ''
const store = new Vuex.Store({
state: {
raw:''
},
mutations: {
[SETRAWHTML](state,str){
state.raw=str
},
},
actions: {
RawHtml({commit}, str) {
commit(SETRAWHTML, str)
},
},
getters: {
getRawHTML (state) {
return state.raw
}
},
})
export default store;
main.ts
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
my component
click(){
store.dispatch('RawHTML', this.rawHTML)
}
Thanks in advance
The name of actions is case-sensitive and you should dispatch the action with the same name as it's defined in the store, RawHTML and RawHtml don't refer to the same action, so you should respect the case and write the action dispatch as follows :
click(){
store.dispatch('RawHtml', this.rawHTML)
}
I'm using Vue CLI which uses main.js to mount the app versus Server-Side Rendering (according to the tutorial I follow here) which uses app.js, entry-client.js and entry-server.js.
I tried to bypass main.js, but I'm having an error since its seems its required some how.
How can I work to keep main.js with app.js, entry-client.js and entry-server.js.
It might be a very basic question and I could maybe use the content of app.js in main.js, be I want to do things right.
main.js :
import Vue from 'vue'
import bootstrap from 'bootstrap'
import App from './App.vue'
import router from './router'
import store from './store'
import VueResource from 'vue-resource'
import BootstrapVue from 'bootstrap-vue'
import './registerServiceWorker'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.config.productionTip = false
Vue.use(VueResource)
Vue.use(BootstrapVue)
new Vue({
bootstrap,
router,
store,
render: h => h(App)
}).$mount('#app')
app.js :
import Vue from 'vue'
import App from './App.vue'
import bootstrap from 'bootstrap'
import { createRouter } from './router'
import store from './store'
import VueResource from 'vue-resource'
import BootstrapVue from 'bootstrap-vue'
import './registerServiceWorker'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.config.productionTip = false
Vue.use(VueResource)
Vue.use(BootstrapVue)
export function createApp () {
// create router instance
const router = createRouter()
const app = new Vue({
bootstrap,
router,
store,
render: h => h(App)
})
return { app, bootstrap, router, store }
}
server-client.js
import { createApp } from './app'
const { app, router } = createApp()
router.onReady(() => {
app.$mount('#app')
})
entry-server.js
import { createApp } from './app'
export default context => {
// since there could potentially be asynchronous route hooks or components,
// we will be returning a Promise so that the server can wait until
// everything is ready before rendering.
return new Promise((resolve, reject) => {
const { app, router } = createApp()
// set server-side router's location
router.push(context.url)
// wait until router has resolved possible async components and hooks
router.onReady(() => {
const matchedComponents = router.getMatchedComponents()
// no matched routes, reject with 404
if (!matchedComponents.length) {
return reject({ code: 404 })
}
// the Promise should resolve to the app instance so it can be rendered
resolve(app)
}, reject)
})
}
Any help is greatly appreciated.
I just found how. By default, Vue CLI 3 comes with no vue.config.js. I had to create one at the root of the folder to override the entry which was set to /src/main.js.
I used the vue.config.js found on this github and onverwrittes the entry to ./src/entry-${target}
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')
const nodeExternals = require('webpack-node-externals')
const merge = require('lodash.merge')
const TARGET_NODE = process.env.WEBPACK_TARGET === 'node'
const createApiFile = TARGET_NODE
? './create-api-server.js'
: './create-api-client.js'
const target = TARGET_NODE
? 'server'
: 'client'
module.exports = {
configureWebpack: () => ({
entry: `./src/entry-${target}`,
target: TARGET_NODE ? 'node' : 'web',
node: TARGET_NODE ? undefined : false,
plugins: [
TARGET_NODE
? new VueSSRServerPlugin()
: new VueSSRClientPlugin()
],
externals: TARGET_NODE ? nodeExternals({
whitelist: /\.css$/
}) : undefined,
output: {
libraryTarget: TARGET_NODE
? 'commonjs2'
: undefined
},
optimization: {
splitChunks: undefined
},
resolve:{
alias: {
'create-api': createApiFile
}
}
}),
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options =>
merge(options, {
optimizeSSR: false
})
)
}
}
This is my component:
const Vue = require("vue/dist/vue.js");
const qs = require("querystring");
module.exports = Vue.component("Page",function(resolve){
console.log($route);
let id = this.$route.params.id;
fetch("/getPage.json",{
method:"POST",
body:qs.stringify({
id
})
}).then(r=>r.json())
.then(j=>{
console.log(j);
resolve({
template:`<h1>`+JSON.stringify(j)+"</h1>"
});
})
});
And this is my router:
const router = new VueRouter({
mode:"history",
routes:[
{
path:"/",
component:Home
},
{
path:"/me",
component:Me
},
{
path:"/create-page",
component:createPage
},
{
path:"/p/:id",
component:Page
}
]
});
Vue.use(VueRouter);
When i run this app i get:
ReferenceError: $route is not defined
I tried using this.$route but it's not working. I'm using an arrow function. When i do this it works:
const Vue = require("vue/dist/vue.js");
module.exports = Vue.component("Page",function(resolve){
resolve({
template:`<h1>{{$route.params.id}}`
});
});
Please help me figure out how to fix this.
in your main vue app
new Vue(...)
you have to use vue-router first
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
...
new Vue(...)
...
and the go ahead with route declaration
you forgot to add router in Vue instance
in you main.js or app.js or index.js (entry point)
const app = new Vue({
router
})
documentation
You should NOT use arrow functions
data: function() {
return {
usertype: this.$route.params.type
};
},
This worked for me.
Im creating a custom plugin that encapsulates a bunch of authentication functionality with vuex and vue-authenticate.
The problem im having is figuring out the correct way to load and install the module into VueJS, im not sure if its my webpack or vuejs knowledge that is lacking but so far I have the following
/node_modules/plugin/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import routes from './routes'
import store from './vuex/store'
import EventBus from './bus/eventBus'
import config from './config'
import ping from './vuex/modules/apiArchitect/ping/store'
import auth from './vuex/modules/apiArchitect/auth/store'
import user from './vuex/modules/apiArchitect/user/store'
Vue.use(Vuex)
Vue.use(EventBus)
const store = new Vuex.Store({
modules: {
ping,
user,
auth
},
strict: true
})
let apiArchitect = {}
apiArchitect.install = function (Vue, options) {
Vue.prototype.$apiArchitect = store,
Vue.prototype.$apiArchitect.$config = config
Vue.prototype.$apiArchitect.$routes = routes
if (typeof window !== 'undefined' && window.Vue) {
window.Vue.use(apiArchitect)
}
}
export default apiArchitect
/src/main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import apiArchitect from 'vue-apiarchitect'
import addRouteGuards from 'vue-apiarchitect/src/addRouteGuards'
Vue.config.productionTip = false
Vue.config.env = process.env.NODE_ENV
Vue.use(router)
Vue.use(apiArchitect)
console.log(apiArchitect)
addRouteGuards(router)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
})
So far I am able to import the plugin and run the install hook with Vue.use(apiArchitect) I can access this.$apiArchitect in my App.vue.
The problem I have is that the plugin provides some auth routes stored in $apiArchitect.routes these routes need to be merged with the routes provided by router. If I try access $apiArchitect.routes in main.js I get an 'undefined' error I can only access them after vue has been instantiated. If I actually try console.log apiArchitect in main.js all I see is an object containing an install function none of the plugin i've provided which makes me belive its not installing correctly.
Does anyone know how i can access the apiArchitect.$routes property in main.js or a better way of achieving this?
Thanks
You can add routes dynamically with router.addRoutes() since 2.2.x.
The argument must be an Array using the same route config format with
the routes constructor option.
For example, you can use addRoutes in created hook of the root component:
// your plugin
const myPlugin = {
install: function(Vue, options) {
Vue.prototype.$myPlugin = {
routes: [{
path: '/myplugin', component: options.myComponent
}]
}
}
}
// components
const testComponent = { template: '<p>Component called by plugin route</p>' }
const Home = { template: '<p>Default component</p>' }
// router
const router = new VueRouter({
routes: [{
path: '/', component: Home
}]
})
Vue.use(VueRouter)
Vue.use(myPlugin, { myComponent: testComponent })
new Vue({
el: '#app',
router,
created() {
this.$router.addRoutes(this.$myPlugin.routes); // dinamically add routes
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<button #click="$router.push('/')">Home</button>
<button #click="$router.push('/myplugin')">Custom</button>
<router-view></router-view>
</div>