[Vue warn]: Unknown custom element: <nuxt-link> - When running jest unit tests - vue.js

Problem
I'm using nuxt 1.4 with routing using Jest to do unit testing. My application doesn't throw errors and seems to work perfectly. However when running my unit test npm run unit (which runs jest) it throws an error in the terminal: [Vue warn]: Unknown custom element: <nuxt-link> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
Expected
I would expect it to not throw this error since my application is working.
Files
package.json:
{
"name": "vue-starter",
"version": "1.0.0",
"description": "Nuxt.js project",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
"precommit": "npm run lint",
"test": "npm run lint && npm run unit",
"unit": "jest",
"unit:report": "jest --coverage"
},
"dependencies": {
"babel-jest": "^22.4.1",
"jest-serializer-vue": "^1.0.0",
"node-sass": "^4.7.2",
"npm": "^5.7.1",
"nuxt": "^1.0.0",
"sass-loader": "^6.0.7",
"vue-jest": "^2.1.1"
},
"devDependencies": {
"#vue/test-utils": "^1.0.0-beta.12",
"babel-eslint": "^8.2.1",
"eslint": "^4.15.0",
"eslint-friendly-formatter": "^3.0.0",
"eslint-loader": "^1.7.1",
"eslint-plugin-vue": "^4.0.0",
"jest": "^22.4.2"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"jest": {
"moduleFileExtensions": [
"js",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
},
"snapshotSerializers": [
"<rootDir>/node_modules/jest-serializer-vue"
]
}
}
The component that I test:
<template>
<div>
<nuxt-link class="name" :to="{ path: `entity/${item.id}`, params: { id: item.id }}">{{item.name}}</nuxt-link>
<button class="connect" #click="connect">{{ btnText }}</button>
</div>
</template>
<script>
// import nuxtLink from '../.nuxt/components/nuxt-link';
const connectionStatusMap = [
'Connect',
'Connected',
'Pending',
'Cancel',
];
export default {
/*components: {
'nuxt-link': nuxtLink,
},*/
props: {
item: {
type: Object
}
},
...
}
</script>
My test script:
import TestItem from '../components/TestItem';
import { shallow, mount, createLocalVue } from '#vue/test-utils';
import Vuex from 'vuex';
import VueRouter from 'vue-router';
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueRouter)
...
it(`should show the entity`, () => {
const wrapper = mount(TestItem, {
propsData: { item },
localVue,
store,
// stubs: ['nuxt-link'],
})
expect(wrapper.find('.name').text()).toBe(item.name);
});
it(`should show allow me to connect if I'm not yet connected`, () => {
const wrapper = shallow(TestItem, {
propsData: { item },
localVue,
store,
stubs: ['nuxt-link'],
})
expect(wrapper.find('.connect').text()).toBe('Connect');
});
...
I tried
I tried creating a localVue and also stubbing the component as suggested in this github comment
I also tried shallow/mount but that did not seem to work either.

This is how I was able to get rid of the annoying warning:
Include RouterLinkStub, eg.:
import { shallowMount, createLocalVue, RouterLinkStub } from '#vue/test-utils';
Map NuxtLink stub to RouterLinkStub
const wrapper = shallowMount(TestItem, {
...
stubs: {
NuxtLink: RouterLinkStub
}
})
And in case you were checking nuxt-link text or something, change:
const link = wrapper.find('nuxt-link');
to
const link = wrapper.find(RouterLinkStub);
Found this gold on https://onigra.github.io/blog/2018/03/19/vue-test-utils-router-link-stub/
Good thing you don't need to know japanese to read code...

Although the answers provided could work. What I ended up using was based on this guide
const wrapper = mount(TestItem, {
propsData: { item },
localVue,
store,
stubs: {
NuxtLink: true,
// Any other component that you want stubbed
},
});

I managed to get it working using this workaround for Storybook:
import { mount, createLocalVue } from '#vue/test-utils'
import Component from '#/components/Component.vue'
const localVue = createLocalVue()
localVue.component('nuxt-link', {
props: ['to'],
template: '<slot>NuxtLink</slot>',
})
describe('Test Component', () => {
const wrapper = mount(Component, {
stubs: ['nuxt-link'],
localVue
})
})

I added below lines of code to get this working.
In your test file
import NuxtLink from "path to nuxt-link.js"
Mycomponent.components.NuxtLink = NuxtLink
In your jest conf file
transformIgnorePatterns: [
"path to nuxt-link.js"
],
Or you could add below line in mount options
mount(Mycomponent, {stubs: ["nuxt-link"]})

I have:
// path: ./test/jest.setup.js
import Vue from 'vue'
import VueTestUtils from '#vue/test-utils'
// Mock Nuxt components
VueTestUtils.config.stubs['nuxt-link'] = '<a><slot /></a>'
VueTestUtils.config.stubs['no-ssr'] = '<span><slot /></span>'
and
// path: ./jest.config.js
module.exports = {
// ... other stuff
setupFilesAfterEnv: ['./test/jest.setup.js']
}
... and this solves all my jest test in the nuxt app

To anyone getting the Unknow custom element: <router-link>
My issue was, I used mount instead of shallow when creating the component.
shallow usage:
Like mount, it creates a Wrapper that contains the mounted and
rendered Vue component, but with stubbed child components.
Source: https://vue-test-utils.vuejs.org/en/api/shallow.html
Here is a working example
import { shallow } from '#vue/test-utils';
import ContentCard from '../../components/ContentCard.vue';
import NuxtLink from '../../.nuxt/components/nuxt-link';
const createComponent = propsData => shallow(ContentCard, { propsData });
describe('ContentCard', () => {
let component;
beforeEach(() => {
ContentCard.components = ContentCard.components || {};
ContentCard.components.NuxtLink = NuxtLink;
});
describe('Properties', () => {
it('has an imgSrc property', () => {
component = createComponent({ imgSrc: 'X' });
expect(component.props().imgSrc).toBe('X');
});
});
});

...
import NuxtLink from '../.nuxt/components/nuxt-link.js'
...
TestItem.components = TestItem.components || {};
TestItem.components.NuxtLink = NuxtLink;
const wrapper = shallow(TestItem, {
...
});
...

// test/jestSetup.js
import Vue from 'vue'
import Vuetify from 'vuetify'
import { config } from '#vue/test-utils'
Vue.use(Vuetify)
config.stubs.NuxtLink = { template: '<a><slot /></a>' }

Related

Vuetify icons gives an error with storybook

Hello everyone I'm facing an issue When I use the storybook with vuetify framework and when I run the project it gives the following error multiple times.
Cannot read properties of undefined (reading 'component')
TypeError: Cannot read properties of undefined (reading 'component')
at remapInternalIcon (http://localhost:6006/vendors~main.f3adcb00877b75c26c76.bundle.js:267378:37)
at VueComponent.getIcon
and here is I'm using storybook v6.1.11 npm packages
"#storybook/addon-essentials": "^6.5.16",
"#storybook/addon-actions": "^6.1.11",
"#storybook/addon-controls": "^6.5.16",
"#storybook/addon-docs": "^6.1.11",
"#storybook/addon-links": "^6.1.11",
"#storybook/addons": "^6.1.11",
"#storybook/preset-scss": "^1.0.3",
"#storybook/vue": "^6.1.11",
"vuetify-loader": "^1.7.0",
"vuetify": "^2.6.0"
knowing that the my project is working fine and all of the components renders correctly even v-icon
main.js
const path = require('path');
module.exports = {
addons: [
'#storybook/addon-controls',
'#storybook/addon-docs',
'#storybook/addon-actions',
'#storybook/preset-scss',
],
webpackFinal: async (config, { configType }) => {
config.module.rules.push({
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
include: path.resolve(__dirname, '../'),
});
config.module.rules.push({
resolve: {
alias: {
'#': path.resolve(__dirname, '../src'),
vue: 'vue/dist/vue.js',
'vue$': 'vue/dist/vue.esm.js',
},
},
});
// Return the altered config
return config;
},
};
preview.js
import { configure, addDecorator } from "#storybook/vue";
import "!style-loader!css-loader!sass-loader!./scss-loader.scss";
import "vuetify/dist/vuetify.css";
import "#mdi/font/css/materialdesignicons.css";
import i18n from '../src/plugins/i18n';
import Vue from "vue";
import Vuetify from "vuetify";
Vue.use(Vuetify);
Vue.prototype.$t = function(...args){
return i18n.t(...args);
}
addDecorator(() => ({
i18n,
vuetify: new Vuetify({
rtl: true,
icons: {
iconfont: "mdi",
}
}),
template:
'<v-app style="background-color: white"><v-main><story/></v-main></v-app>',
}));
// automatically import all files ending in *.stories.js
configure(require.context("../stories", true, /\.stories\.js$/), module);
manager.js
import "#storybook/addon-actions/register";
import "#storybook/addon-links/register";
Here is an example of my stories
import Calendar from "../src/components/Calendar/calendar.vue";
export default {
title: "Components/Calendar",
component: Calendar,
};
const Template = (args, { argTypes }) => ({
components: { Calendar },
props: Object.keys(argTypes),
template: `<Calendar v-bind="$props" />`,
});
export const WithLabel = Template.bind({});
WithLabel.args = {
label: "Pick a date",
};
I don't know What is the issue.
Thanks in advance.

TypeError: Cannot read properties of undefined (reading 'html')

I am trying to introduce Jest to my current project.
However, during the initial setup, I encountered this error and it is not running properly.
How can I solve this?
I am currently using vue2 from vue-cli.
● Test suite failed to run
TypeError: Cannot read properties of undefined (reading 'html')
at new JSDOMEnvironment (node_modules/jest-environment-jsdom/build/index.js:72:44)
at async TestScheduler.scheduleTests (node_modules/#jest/core/build/TestScheduler.js:317:13)
at async runJest (node_modules/#jest/core/build/runJest.js:407:19)
at async _run10000 (node_modules/#jest/core/build/cli/index.js:338:7)
at async runCLI (node_modules/#jest/core/build/cli/index.js:190:3)
This is my test code.
import SettlementProcessing from "#/views/calculate/SettlementProcessing.vue";
import { shallowMount } from "#vue/test-utils";
import Vuetify from "vuetify";
describe("Settlement Component", () => {
let vuetify;
beforeEach(() => {
vuetify = new Vuetify();
});
it("정산 처리 타이틀이 나와야 한다.", () => {
const sc = shallowMount(SettlementProcessing, { vuetify });
expect(true).toBe(true);
});
});
Here is my package.json.
"devDependencies": {
"#vue/cli-plugin-babel": "~4.5.0",
"#vue/test-utils": "^2.0.0-rc.21",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^28.1.0",
"jest": "^28.1.0",
"vue-cli-plugin-vuetify": "~2.4.0",
"vue-jest": "^3.0.7",
}
Here is my jest.config.json.
// jest.config.js
module.exports = {
preset: "#vue/cli-plugin-unit-jest",
moduleFileExtensions: [
"js",
"json",
"vue",
],
transform: {
"^[^.]+.vue$": "vue-jest",
"^.+\\.js$": "babel-jest",
},
moduleNameMapper: {
"^#/(.*)$": "<rootDir>/src/$1",
},
testMatch: [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[jt]s?(x)",
],
testPathIgnorePatterns: ["/node_modules/", "/dist/"],
collectCoverage: false,
collectCoverageFrom: ["**/*.{js,vue}", "!**/node_modules/**"],
};
How can I solve this??
I had the same issue when updating my react app to jest 28. The issue was the missing jest-environment-jsdom package which was not yet necessary in jest 27.
See https://jestjs.io/docs/28.x/upgrading-to-jest28
You should create a localVue instance and use Vuetify on it. This can be achieved either in a tests/setup.js file (which runs for all jest tests) or separately in each unit test that uses Vuetify.
Sample code without setup.js (if you use setup.js, the code will be slightly different, you can check the documentation below)
import SettlementProcessing from "#/views/calculate/SettlementProcessing.vue";
import { createLocalVue, shallowMount } from "#vue/test-utils";
import Vuetify from "vuetify";
const localVue = createLocalVue()
localVue.use(Vuetify)
describe("Settlement Component", () => {
it("정산 처리 타이틀이 나와야 한다.", () => {
const sc = shallowMount(SettlementProcessing, { localVue } );
expect(true).toBe(true);
});
});
The documentation is here:
https://vuetifyjs.com/en/getting-started/unit-testing/#bootstrapping-vuetify
It was fixed for me by adding jest-environment-jsdom.

Vue test utils for Vue3 : document is not defined

I try to bootstrap a simple app based on the following Vue3, Vite, Vitest
I also installed the vue 3 compatible version of vue test utils to test vue components.
I have an error trying to replicate the basic example in the docs :
import { mount } from "#vue/test-utils";
import { expect, test } from 'vitest'
// The component to test
const MessageComponent = {
template: "<p>{{ msg }}</p>",
props: ["msg"],
};
test("displays message", () => {
const wrapper = mount(MessageComponent, {
props: {
msg: "Hello world",
},
});
// Assert the rendered text of the component
expect(wrapper.text()).toContain("Hello world");
});
FAIL src/tests/hello-world.test.ts > displays message
ReferenceError: document is not defined
❯ Proxy.mount node_modules/#vue/test-utils/dist/vue-test-utils.cjs.js:7840:14
7838| addToDoNotStubComponents(component);
7839| registerStub({ source: originalComponent, stub: component });
7840| var el = document.createElement('div');
| ^
7841| if (options === null || options === void 0 ? void 0 : options.attachTo) {
7842| var to = void 0;
Re-running tests... [ src/tests/hello-world.test.ts ]
My package.json
{
"name": "vite-vue3-poc",
"version": "0.0.0",
"scripts": {
"serve": "vite preview",
"build": "vite build",
"coverage": "vitest --coverage",
"dev": "vite",
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"#mdi/font": "5.9.55",
"prettier": "^2.5.1",
"roboto-fontface": "*",
"vue": "^3.2.25",
"vuetify": "^3.0.0-alpha.0",
"webfontloader": "^1.0.0"
},
"devDependencies": {
"#vitejs/plugin-vue": "^2.0.0",
"#vue/cli-plugin-babel": "5.0.0-beta.7",
"#vue/cli-service": "5.0.0-beta.7",
"#vue/test-utils": "^2.0.0-rc.18",
"#vuetify/vite-plugin": "^1.0.0-alpha.3",
"sass": "^1.38.0",
"sass-loader": "^10.0.0",
"vite": "^2.7.2",
"vitest": "^0.1.23",
"vue-cli-plugin-vuetify": "~2.4.5",
"vuetify-loader": "^2.0.0-alpha.0"
}
}
vite.config.js
import { defineConfig } from "vite";
import vue from "#vitejs/plugin-vue";
import vuetify from "#vuetify/vite-plugin";
import path from "path";
/// <reference types="vitest" />
// Configure Vitest (https://vitest.dev/config)
// https://vitejs.dev/config/
export default defineConfig({
test: {
/* for example, use global to avoid globals imports (describe, test, expect): */
// globals: true,
},
plugins: [
vue(),
// https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin
vuetify({
autoImport: true,
}),
],
define: { "process.env": {} },
resolve: {
alias: {
"#": path.resolve(__dirname, "src"),
},
},
});
Finally fixed it by manually installing jsdom and declaring it in vite.config.ts
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
},
...
}
Like others have pointed out, you need to set environment: 'jsdom' in vitest.config.ts. Alternatively, you could set environment: 'happy-dom'.
In the example provided by the Vitest documentation, they used to use happy-dom instead of jsdom. From what I gather, happy-dom is a faster alternative to jsdom. I'm using happy-dom in my project, and I'm happy with it! :)
EDIT: I changed my wording to reflect the fact that the Vitest example used to use happy-dom. As of this writing, it uses jsdom.
No need to install jsdom manually. By setting environment: "jsdom" in the test property, Vitest automatically asks you if you want to install it.
This config helped me
Your vite.config.ts
import { fileURLToPath, URL } from "node:url"
import { defineConfig } from "vite"
import type { UserConfig as VitestUserConfigInterface } from "vitest/config"
import vue from "#vitejs/plugin-vue"
const vitestConfig: VitestUserConfigInterface = {
test: {
globals: true,
environment: "jsdom",
},
}
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"#": fileURLToPath(new URL("./src", import.meta.url)),
},
},
test: vitestConfig.test,
})

vue3, vite and vue-router#next, cannot resolve component router-view

I created a project using Vite and added vue-router#next. I am using the router inside of the main.js as I've browsed around and seemed like this was the problem, however it does not fix the issue I am having.
// package.json
{
"name": "rng-alpha",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"vue": "^3.0.5",
"vue-router": "^4.0.5"
},
"devDependencies": {
"#vitejs/plugin-vue": "^1.1.5",
"#vue/compiler-sfc": "^3.0.7",
"vite": "^2.0.5"
}
}
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router/router';
const app = createApp(App);
app.use(router);
app.mount('#app');
// router.js
import {
createWebHistory,
createRouter
} from 'vue-router'
import Home from '../components/Home.vue'
const routes = [{
path: "/",
name: "Home",
component: Home
}]
const router = createRouter[{
history: createWebHistory,
routes,
}]
export default router
// App.vue
<template>
<div>
<div>hi</div>
<router-view />
</div>
</template>
The warnings I am getting are the following:
How can I make the router-view work? Since I am not able to utilize it right now.
createRouter is a method that should be called using () not [] :
const router = createRouter({
history: createWebHistory,
routes,
})

Import vue package in laravel

What is the corect way to import vue packages in laravel 5.6? It comes with vue and bootstrap preinstall. I see they are all compile in app.js from public directory but I can figure out how to import https://github.com/moreta/vue-search-select and use it. After I tried to import it on my own:
Error:
ncaught TypeError: Vue.component is not a function
At line:
Vue.component('search-user', __webpack_require__(42));
Until now I tried this:
assets/js/bootstrap.js:
import { BasicSelect } from 'vue-search-select';
window.BasicSelect = BasicSelect;
assets/js/app.js:
require('./bootstrap');
window.Vue = require('vue');
window.Vue = require('vue-search-select');
Vue.component('search-user', require('./components/SearchUser.vue'));
var app = new Vue({
el: '#app'
})
components
<template>
<basic-select :options="options"
:selected-option="item"
placeholder="select item"
#select="onSelect">
</basic-select>
</template>
<script>
export default {
data() {
return {
keywords: null,
options: []
};
},
watch: {
keywords(after, before) {
if (this.keywords.length > 0)
this.fetch();
}
},
methods: {
fetch() {
axios.get('/api/search', {params: {keywords: this.keywords}})
.then(response => this.options = response.data)
.catch(error => {
});
},
onSelect (item) {
this.item = item
},
reset () {
this.item = {}
},
selectOption () {
// select option from parent component
this.item = this.options[0]
},
components: {
BasicSelect
}
}
}
</script>
I ran: npm install and npm run watch:
"devDependencies": {
"ajv": "^6.0.0",
"bootstrap": "^4.0.0",
"cross-env": "^5.1",
"laravel-mix": "^2.0",
"lodash": "^4.17.4",
"popper.js": "^1.12",
"uikit": "^3.0.0-beta.35",
"vue": "^2.5.7",
"vue-search-select": "^2.5.0"
},
"dependencies": {
"axios": "^0.17.1",
"jquery": "^3.3.1"
}
I think that the simple will do
window.Vue = require('vue');
require('vue-search-select');
Then in your components you can import what you need on top:
import { BasicSelect } from 'vue-search-select';
export default {
data() {
return {
keywords: null,
options: [],
item: null
};
},
...
One missing detail that tricked me with this one, you need to register the components like this, otherwise it won't be found:
components: {
ModelSelect,
BasicSelect
},