Writing Unit test for project in Nuxt, Vue, jest & Vuetify - vue.js

TL;DR
Unit Test for component copied from Vuetify link are passing but my actual unit test for my component written in pug is not passing.
Details:
I am working on a project which is built using Vue, NUXT, Vuetify and pug as template language. I am configuring the project to write test cases with JEST test runner. The unit test is currently failing and I would need some steps to identify the problem.
I have read through following:
https://vue-test-utils.vuejs.org/guides/#getting-started
https://vuetifyjs.com/en/getting-started/unit-testing/
Following versions are in use:
nuxt: ^2.0.0
nuxtjs/vuetify: ^1.10.2
pug: ^2.0.4
pug-plain-loader: ^1.0.0
jest: ^24.1.0
vue-jest: ^4.0.0-0
Following is the relevant folder structure
<project root>/jest.config.js
<project root>/components/common/CustomCard.vue
<project root>/components/common/MessageDialog.vue
<project root>/tests/unit/components/common/TestMessageDialog.spec.js
<project root>/tests/unit/components/common/TestCustomCard.spec.js
Following is are seemingly relevant parts of the configurations:
jest.config.js
module.exports = {
verbose: true,
moduleNameMapper: {
'^#/(.*)$': '<rootDir>/$1',
'^~/(.*)$': '<rootDir>/$1',
'^vue$': 'vue/dist/vue.common.js'
},
moduleFileExtensions: ['js', 'vue', 'json'],
transform: {
'^.+\\.js$': 'babel-jest',
'.*\\.(vue)$': 'vue-jest'
},
'collectCoverage': false,
'collectCoverageFrom': [
'<rootDir>/components/**/*.vue',
'<rootDir>/pages/**/*.vue'
],
globals: {
'vue-jest': {
pug: {
doctype: 'html'
}
}
},
preset: '#vue/cli-plugin-unit-jest/presets/no-babel'
}
TestMessageDialog.spec.js
import {
mount,
createLocalVue
} from '#vue/test-utils'
import Vuetify from 'vuetify'
import MessageDialog from '#/components/common/MessageDialog.vue'
describe('MessageDialog.vue', () => {
const sampleData = {
titleText: 'title text with unique id : 2020.03.12.00'
}
let wrappedMessageDialog
beforeEach(() => {
const vuetify = new Vuetify()
const localVue = createLocalVue()
wrappedMessageDialog = mount(
MessageDialog,
{
localVue,
vuetify,
propsData: sampleData
}
)
})
it('renders correctly when passed props', () => {
expect(
wrappedMessageDialog
.find({ name: 'v-card-title' })
.text()
).toMatch(sampleData.titleText)
})
})
MessageDialog.vue
<template lang="pug">
v-dialog(v-model="isVisible" width="600")
v-card
v-card-title {{ titleText }}
</template>
<script>
export default {
props: {
titleText: {
default: '',
type: String
}
}
}
</script>
I am getting following error
FAIL tests/unit/components/common/TestMessageDialog.spec.js
[vue-test-utils]: find did not return Component, cannot call text() on empty Wrapper
31 | wrappedMessageDialog
32 | .find({ name: 'v-card-title' })
> 33 | .text()
| ^
34 | ).toMatch(sampleData.titleText)
35 | })
36 | })
at throwError (node_modules/#vue/test-utils/dist/vue-test-utils.js:1709:9)
at ErrorWrapper.text (node_modules/#vue/test-utils/dist/vue-test-utils.js:8767:3)
at Object.it (tests/unit/components/common/TestMessageDialog.spec.js:33:10)
console.error node_modules/vue/dist/vue.common.dev.js:630
[Vue warn]: Unknown custom element: <v-dialog> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
found in
---> <Anonymous>
<Root>
Similar warnings for <v-dialog>, <v-card> & <v-card-title>

Looks like you will want to register the components in the MessageDialog component. E.g.
<script>
import Foo from './Foo.vue;
...
export default {
components: { Foo }
...
}
...
</script>
More info: https://v2.vuejs.org/v2/guide/components-registration.html#Local-Registration

Related

Load SVGs components in storybook VUE not working

Hello everyone I'm using vue 3 with storybook 6.5.16 and when i import the SVGs as a component using svg-inline-loader i get the following error in storybook app:
enter image description here
(Failed to execute 'createElement' on 'Document' svg is not a valid name)
Storybook main.js
const path = require('path');
module.exports = {
stories: [
'../src/**/*.stories.mdx',
'../src/**/*.stories.#(js|jsx|ts|tsx)',
],
addons: [
'#storybook/addon-links',
'#storybook/addon-essentials',
'#storybook/addon-interactions',
],
framework: '#storybook/vue3',
core: {
builder: '#storybook/builder-webpack5',
},
webpackFinal: async (config, { configType }) => {
// `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
config.module.rules.push({
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
{
loader: "sass-loader",
options: {
additionalData: `
#import "#/assets/scss/main.scss";
`,
implementation: require('sass'),
},
},
],
});
(() => {
const ruleSVG = config.module.rules.find(rule => {
if (rule.test) {
const test = rule.test.toString();
const regular = /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/;
const regularString = regular.toString();
if (test === regularString) {
return rule;
}
}
});
ruleSVG.test = /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/;
})();
config.module.rules.push({
test: /\.svg$/,
use: ['svg-inline-loader'],
});
config.resolve.alias['#'] = path.resolve('src');
return config;
},
}
package.json file
enter image description here
SVG Vue components
<template>
<div
ref="icon"
class="v-icon"
#click="onClick"
>
<component
:is="iconName"
class="v-icon__svg"
/>
</div>
</template>
<script>
import Cards from '#/assets/icons/Cards.svg';
export default {
name: 'VIcon',
components: {
Cards,
},
props: {
iconName: {
type: String,
required: true,
},
},
};
</script>
.babelrc file
{
"presets": ["#babel/preset-env", "#babel/preset-react"]
}
i tried to use vue-svg-loader to replace svg-inline-loader but it didn't work and I got another error while building the app
ModuleBuildError: Module build failed: Error: Cannot find module './Block'
I also tried to use babel-loader in conjunction with vue-svg-loader but unfortunately I also got an error:
enter image description here
has anyone come across this or can you show your use cases of using SVGs components in Storybook and Vue 3?

How to use Environment Variables inside Vue3+Vite component library?

I have created a component as part of my component library that I am building with Vue3 and Vite. Everything works well, except when I try to use environment variables. I want the app that consumes this component library to be able to provide the component with environment specific data.
I have played around and found that if I have a .env file as part of the component library project, I am able to access those variables, but I want to be able to provide that during runtime and not during build time.
Here is my vite.config.ts
import { defineConfig } from "vite";
import { resolve } from "path";
import vue from "#vitejs/plugin-vue";
import dts from "vite-plugin-dts";
export default ({ mode }) => {
return defineConfig({
optimizeDeps: {
exclude: ["vue-demi"],
},
plugins: [
vue(),
dts({
insertTypesEntry: true,
}),
],
server: {
open: true,
},
build: {
lib: {
entry: resolve(__dirname, "src/lib.ts"),
name: "complib",
fileName: "complib",
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
exports: "named",
},
},
},
});
};
The entry looks like:
import { App, install } from "vue-demi";
import TestComp from "./components/TestComp.vue";
import "./tailwind.css";
install();
export default {
install: (app: App) => {
app.component("TestComp", TestComp);
},
};
export { Header };
And here is a minimal component TestComp.vue:
<script setup lang="ts">
import { onMounted } from "vue";
onMounted(() => {
console.log(import.meta.env.VITE_TEST_VAR);
});
</script>
<template>
<span>Test Comp</span>
</template>

Vue.js 3: Vue is not defined with mount when running unit tests with external library

I am currently experienced the following error when running my unit tests:
ReferenceError: Vue is not defined
> 10 | import hljsVuePlugin from '#highlightjs/vue-plugin';
| ^
Here is my component code:
<template>
<div>
<highlightjs autodetect :code="'hello world'" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import hljsVuePlugin from '#highlightjs/vue-plugin';
export default defineComponent({
components: {
highlightjs: hljsVuePlugin.component,
},
});
</script>
in my test file, I'm simply trying to mount this component:
const wrapper = mount(FooBlah, {
global: {
stubs: {
highlightjs: {
template: '<div />',
},
},
},
});
Here are my libraries versions:
"vue": "^3.2.33",
"#highlightjs/vue-plugin": "^2.1.2",
"highlight.js": "^11.6.0",
"#vue/cli-plugin-unit-jest": "~5.0.4",
"#vue/test-utils": "^2.0.2",
"#vue/vue3-jest": "^27.0.0",
Is there any way I could tell him to simply ignore the highlightjs the component in the jest.config ?
As mentionned in the comments, this fixed my issue:
jest.mock('#highlightjs/vue-plugin', () => ({
hljsVuePlugin: {
component: jest.fn(),
},
}));
const wrapper = mount(FooBlah, {
global: {
stubs: {
highlightjs: {
template: '<div />',
},
},
},
})
You can find more information here: How can I mock an ES6 module import using Jest?

Vue 3 Components Not Loading

I've spent the last 5 hours trying to figure out what's not working with my setup.
What I basically want to do is have a "wrapper" app that loads my common codebase (which is a Vue component).
App.js
import { createApp } from "vue";
import AppWrapper from "#src/js/AppWrapper.vue";
import Router from "#common/libraries/Router.js";
const vm = createApp(AppWrapper);
vm.use(Router);
vm.mount("#app");
AppWrapper.vue
<template>
<div>
<app></app>
</div>
</template>
<script>
import App from "#common/App.vue";
export default {
created() {},
components: {
App,
},
};
</script>
<style lang="scss" scoped></style>
App.vue
<template>
<div>
<h1>Ok</h1>
</div>
</template>
<script>
export default {
created() {},
};
</script>
Somehow, the app tag isn't replaced and my component is not loading in the page.
Here's the webpack file:
const Webpack = require("webpack");
const path = require("path");
const { VueLoaderPlugin } = require("vue-loader");
const LiveReloadPlugin = require("webpack-livereload-plugin");
module.exports = (env) => {
return {
entry: {
app: ["./app/src/common/App.js"],
},
output: {
path: path.resolve(__dirname, "public/"),
filename: "js/[name].js",
sourceMapFilename: "js/[name].js.map",
},
resolve: {
alias: {
"#src": path.resolve(__dirname, "app/src/"),
"#common": path.resolve(__dirname, "app/src/common/"),
},
},
module: {
rules: [
{
test: /\.vue$/,
use: ["vue-loader"],
},
],
},
watchOptions: {
aggregateTimeout: 300,
poll: 500,
ignored: /node_modules/,
},
plugins: [new VueLoaderPlugin(), new LiveReloadPlugin()],
};
};
Am I missing something?
Thanks!
Alright, I figured it out. Vue was loaded twice, once in my app package.json and once in the common sources. Removing it from one location worked!

Vue-Test-Utils Unknown custom element: <router-link>

I'm using Jest to run my tests utilizing the vue-test-utils library.
Even though I've added the VueRouter to the localVue instance, it says it can't actually find the router-link component. If the code looks a little funky, it's because I'm using TypeScript, but it should read pretty close to ES6... Main thing is that the #Prop() is the same as passing in props: {..}
Vue component:
<template>
<div>
<div class="temp">
<div>
<router-link :to="temp.url">{{temp.name}}</router-link>
</div>
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
import { Prop } from 'vue-property-decorator'
import { Temp } from './Temp'
#Component({
name: 'temp'
})
export default class TempComponent extends Vue {
#Prop() private temp: Temp
}
</script>
<style lang="scss" scoped>
.temp {
padding-top: 10px;
}
</style>
Temp model:
export class Temp {
public static Default: Temp = new Temp(-1, '')
public url: string
constructor(public id: number, public name: string) {
this.id = id
this.name = name
this.url = '/temp/' + id
}
}
Jest test
import { createLocalVue, shallow } from '#vue/test-utils'
import TempComponent from '#/components/Temp.vue'
import { Temp } from '#/components/Temp'
import VueRouter from 'vue-router'
const localVue = createLocalVue()
localVue.use(VueRouter)
describe('Temp.vue Component', () => {
test('renders a router-link tag with to temp.url', () => {
const temp = Temp.Default
temp.url = 'http://some-url.com'
const wrapper = shallow(TempComponent, {
propsData: { temp }
})
const aWrapper = wrapper.find('router-link')
expect((aWrapper.attributes() as any).to).toBe(temp.url)
})
})
What am I missing? The test actually passes, it just throws the warning. In fact, here is the output:
Test Output:
$ jest --config test/unit/jest.conf.js
PASS ClientApp\components\__tests__\temp.spec.ts
Temp.vue Component
√ renders a router-link tag with to temp.url (30ms)
console.error node_modules\vue\dist\vue.runtime.common.js:589
[Vue warn]: Unknown custom element: <router-link> - did you register the
component correctly? For recursive components, make sure to provide the
"name" option.
(found in <Root>)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.677s
Ran all test suites.
Done in 6.94s.
Appreciate any help you can give!
Add the router-link stub to the shallow (or shallowMount) method options like this:
const wrapper = shallow(TempComponent, {
propsData: { temp },
stubs: ['router-link']
})
or this way:
import { RouterLinkStub } from '#vue/test-utils';
const wrapper = shallow(TempComponent, {
propsData: { temp },
stubs: {
RouterLink: RouterLinkStub
}
})
The error should go away after you do this.
With Vue 3 and Vue Test Utils Next (v4), it seems you just have to add your router (the return object from createRouter) as a plugin to your mountOptions:
import router from "#/router";
const mountOptions = {
global: {
plugins: [router],
},
};
https://next.vue-test-utils.vuejs.org/api/#global
Or a more full example:
import router from "#/router";
import Button from "#/components/Button.vue";
const mountOptions = {
global: {
mocks: {
$route: "home",
$router: {
push: jest.fn(),
},
},
plugins: [router],
},
};
it("Renders", () => {
const wrapper = shallowMount(Button, mountOptions);
expect(wrapper.get("nav").getComponent({ name: "router-link" })).toExist();
});
Note, in the example above I'm using a project setup with Vue CLI.
Worked for me:
[ Package.json ] file
...
"vue-jest": "^3.0.5",
"vue-router": "~3.1.5",
"vue": "~2.6.11",
"#vue/test-utils": "1.0.0-beta.29",
...
[ Test ] file
import App from '../../src/App';
import { mount, createLocalVue } from '#vue/test-utils';
import VueRouter from 'vue-router';
const localVue = createLocalVue();
localVue.use(VueRouter);
const router = new VueRouter({
routes: [
{
name: 'dashboard',
path: '/dashboard'
}
]
});
describe('Successful test', ()=>{
it('works', ()=>{
let wrapper = mount(App, {
localVue,
router
});
// Here is your assertion
});
});
Or you can try this:
const wrapper = shallow(TempComponent, {
propsData: { temp },
localVue
})