Testing Vuetify (Vue.js) - Second call on mount throws error - javascript

I am currently experiencing a behaviour when testing my Vue Application (specifically when vuetify is included). I am using Jest as Test Runner but experienced the same behaviour with mocha.
The first thing to notice is that the problem only occurs with mount from the #vue/test-utils and not shallowMount. Also it only occurs if you use mount twice (I guess the reason is the pollution of the Vue Object but more on that later).
Now my component is manly just a wrapper around a basic v-data-table with the property value bound to its items and some custom slots for checkboxes instead of text.
Now the problem. First this is what the first variant of my test looks like (it's basically how it's recommended by vuetify. take a look here. As the test itsself doesn't really matter I'll just expect true to be true here
import Vue from 'vue';
import Vuetify from 'vuetify';
import { mount, createLocalVue, shallowMount } from '#vue/test-utils';
import PermissionTable from '#/components/PermissionTable.vue';
import { expect } from 'chai';
const localVue = createLocalVue();
// Vue.use is not in the example but leaving it will cause the error that
// the data table is not registered
Vue.use(Vuetify);
describe('Permissiontable.vue', () => {
let vuetify;
let tableItems;
beforeEach(() => {
tableItems = [];
vuetify = new Vuetify();
});
it('will test the first thing',() => {
const wrapper = mount(PermissionTable, {
localVue,
vuetify,
propsData: {
value: tableItems
}
});
expect(true).to.be(true);
});
it('will test the second thing',() => {
const wrapper = mount(PermissionTable, {
localVue,
vuetify,
propsData: {
value: tableItems
}
});
expect(true).to.be(true);
});
});
Now as already commented without using Vue.use(Vuetify) I'll get the error that the component v-data-table is not registered. With it I'm left with the following behaviour
Test the first thing runs as expected and succeeds
The the second thing fails the following Error
Type Error: Cannot read property '$scopedSlots' of undefined
and fails at mount(....). To make the behaviour even weirder, if I debug and stop at this line,
run the mount manually in the debug console it fails as well on the first time with the same error.
If I run it again it works. If I for example execute this test 4 times this will happen. 1st will succeed -> 2nd fails -> 3rd and 4th will succeed again.
Now I am sure that functions behave the same way if they get the same input. So the Input to the mount must be altered by the first call. My guess is that the Vue class gets polluted somehow. So if I look at the documentation for localVue this utility is made to prevent pollution of the global Vue class. So I altered my code to
import Vue from 'vue';
import Vuetify from 'vuetify';
import { mount, createLocalVue, shallowMount } from '#vue/test-utils';
import PermissionTable from '#/components/PermissionTable.vue';
import { expect } from 'chai';
describe('Permissiontable.vue', () => {
let vuetify;
let tableItems;
let localVue;
beforeEach(() => {
tableItems = [];
localVue = createLocalVue();
vuetify = new Vuetify();
localVue.use(vuetify);
});
it('will test the first thing',() => {
const wrapper = mount(PermissionTable, {
localVue,
vuetify,
propsData: {
value: tableItems
}
});
expect(true).to.be(true);
});
it('will test the second thing',() => {
const wrapper = mount(PermissionTable, {
localVue,
vuetify,
propsData: {
value: tableItems
}
});
expect(true).to.be(true);
});
});
So I create a new Instance of localVue and vuetify for every test. and make localVue use vuetify. Now this brings me back to the error
[Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I also experimented with various alterations of injecting vuetify (instantiated) or Vuetify. using the global Vue.use etc. At the end I'm always left with one of those two behaviours.
Now the workouround seems to be to write each test in a single file which works but I think is really bad practice and I want to understand what exactly happens here.
I also created Issues for Vuetify and Vue-Test-Utils now as I can't imagine this is expected behaviour, as well as making a stripped down Version of my Component with a test as a Repo
rm-test-mount-fails-on-second-exec
To Reproduce:
Clone Repo
npm install
npm run test:unit
Versions:
Vue: 2.6.10
Vue-Test-Utils: 1.0.0-beta.29
Vuetify: 2.1.12

The bad news
Turns out this is indeed a bug in vue-test-utils at the moment. After I opened up issues I discovered another issue with a problem pretty
similar to mine and I'm pretty sure the root cause for this is the same as in my case.
Apperently this is due to a change that happend in the vue utils in v.beta.29
The issue can be found here #1130
The good News
There is a workaround to get your tests working again until this bug is resolved. You need to mount with the option sync: false so mounting in the top example would look like
const wrapper = mount(PermissionTable, {
localVue,
vuetify,
propsData: {
value: tableItems
},
sync: false
});
I still think this is a serious bug as identical tests should behave in the same way every time you run them no matter the settings. I will update this post as soon as there is news that this has been addressed.

The only thing that seems out of place in your first piece of code is that you are writing Vue.use(Vuetify) and also using an instance of Vuetify when doing mount.
I suggest that you keep the Vue.use(Vuetify) and mount your component like this:
const wrapper = mount(PermissionTable, {
localVue, // vuetify is removed from this object
propsData: {
value: tableItems
}
});
On a side note, unit tests generally should use shallowMount. I am not aware of your use case, but, if possible, please use it instead of mount

Related

Jest and React Testing Library returning undefined error when using React.Children in a component

I have a component that uses React.Children internally to do some changes to the children components when rendering it. When I try to test it using Jest and React Testing Library, I get the error TypeError: Cannot read properties of undefined (reading 'Children'), and it points to the line where I'm using React.Children.map.
I tried to write a simple component to see if it was a problem on the more complex component, but it seems to be happening as well. Here's the test component I created:
import React from 'react';
export default function Testing({ children }) {
return <div>{React.Children.map(children, (child) => child)}</div>;
}
And here's the test:
import { render } from '#testing-library/react';
import Testing from './Testing';
describe('Home', () => {
it('should render successfully', () => {
const { baseElement } = render(<Testing>Testing</Testing>);
expect(baseElement).toBeTruthy();
});
});
And here's the returned error:
detail: TypeError: Cannot read properties of undefined (reading 'Children')
at Testing (/Users/user/projects/my-project/src/features/Home/Testing.tsx:4:22)
I tried importing React into the test to see if it would make a difference, but I doesn't. I also tried to look for this on both Jest and React Testing Library docs, but I couldn't find anything. I also couldn't find references to this problem on the internet, which is a bit strange as I believe I'm not the first one who's testing components that uses React.Children internally.
Any help would be welcomed! Thanks!
Its because children is undefined. Try by using React?.Children?.map(children, (child) => child)in your code

Can't call custom hook from custom hook in react

I have a React app created with create-react-app.
I'm trying to make a custom hook using Microsoft Authentication Library (MSAL). MSAL has a custom React hook that I want to call from my own custom hook.
When I use a hook (any hook) inside my custom hook in a separate file I get this in the browser:
Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
// ourhook/index.ts
import { useEffect } from "react";
export const useMsal2 = () => {
useEffect(() => {
console.log("Hello from our hook!");
});
};
// app.tsx
import React from "react";
import { useMsal2 } from "./ourhook";
const App = () => {
useMsal2();
return <div>App</div>;
};
export default App;
If I call
const { instance } = useMsal();
directly from App.tsx everything works fine. It only appears to be a problem if my custom hook is in its own file.
From what I see I'm not violating any hook rules. I'm calling a hook that's calling a hook, and the first call is from a top level component.
I have read other threads here about hooks in hooks, but none of them has an answer that fits this problem.
Have I missed something about hook rules, or what might be causing this?
Okay, I forgot that we tried to have /ourhook as a freestanding project and then copy pasted it into a create react app app.
Some of you were right, it did have its own version of react.
I'm just going to hide under a rock for the rest of the week.
Thanks for all your help! <3
Try to add this comment just above:
import { useMsal } from "#azure/msal-react";
export const useMsal2 = () => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { instance } = useMsal();
const request = "";
return {
loginRedirect: () => console.log(""),
}
};
I don't know what useMsal looks like, but from what I see, you don't actually violate any hook rule.

ReferenceError: navigator is not defined (mxgraph with next.js) [duplicate]

Trying to create an xterm react component in Next.js I got stuck as I'm not able to get over an error message I've never got before.
I'm trying to import a npm client-side module called xterm, but if I add the import line the application crashes.
import { Terminal } from 'xterm'
The error reads Server Error... ReferenceError: self is not defined
and then shows this chunk of code as Source
module.exports = require("xterm");
According to some research I did, this has to do with Webpack and could be helped if something like this was done:
output: {
globalObject: 'this'
}
Would you know how to fix this?
The error occurs because the library requires Web APIs to work, which are not available when Next.js pre-renders the page on the server-side.
In your case, xterm tries to access the window object which is not present on the server. To fix it, you have to dynamically import xterm so it only gets loaded on the client-side.
There are a couple of ways to achieve this in Next.js.
#1 Using dynamic import()
Move the import to your component's useEffect, then dynamically import the library and add your logic there.
useEffect(() => {
const initTerminal = async () => {
const { Terminal } = await import('xterm')
const term = new Terminal()
// Add logic with `term`
}
initTerminal()
}, [])
#2 Using next/dynamic with ssr: false
Create a component where you add the xterm logic.
// components/terminal-component
import { Terminal } from 'xterm'
function TerminalComponent() {
const term = new Terminal()
// Add logic around `term`
return <></>
}
export default TerminalComponent
Then dynamically import that component when using it.
import dynamic from 'next/dynamic'
const TerminalComponent = dynamic(() => import('<path-to>/components/terminal-component'), {
ssr: false
})
As an alternative, you could add the logic directly when dynamically importing the library with next/dynamic to avoid having an extra file for it.
import dynamic from 'next/dynamic'
const Terminal = dynamic(
{
loader: () => import('xterm').then((mod) => mod.Terminal),
render: (props, Terminal) => {
const term = new Terminal()
// Add logic with `term`
return <></>
}
},
{
ssr: false
}
)

vue-test-utils: could not overwrite property $route, this is usually caused by a plugin that has added the property as a read-only value

I've looked at other answers with this problem, and it seems to be caused by trying to import vue-router into the test. This however, is not the case for my problem. Here is my test code:
import { mount, shallowMount, createLocalVue } from '#vue/test-utils'
import ListDetails from '../components/homepage/ListDetails'
import EntityList from '../components/homepage/EntityList'
import BootstrapVue from 'bootstrap-vue'
import Vuex from 'vuex'
import faker from 'faker'
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(BootstrapVue)
describe('ListDetails.vue', () => {
it('gets the correct page list when router list id param present', () => {
const selected_list = {
id: faker.random.number(),
name: faker.lorem.words(),
entries: []
}
testRouteListIdParam(selected_list)
})
})
Then in testRouteListIdParam, I have:
function testRouteListIdParam(selected_list) {
// just assume this sets up a mocked store properly.
const store = setUpStore(selected_list, true)
const $route = {
path: `/homepage/lists/${selected_list.id}`
}
const wrapper = mount(ListDetails, {
mocks: {
$route
},
store,
localVue
})
}
As soon as mount() happens, I get the error:
[vue-test-utils]: could not overwrite property $route, this is usually caused by a plugin that has added the property as a read-only value
Any ideas why this would be happening? Again, I'm not using VueRouter anywhere in the unit tests, so I'm not sure why I'm getting the error. Could it be BootstrapVue or Vuex that are messing things up?
So this is a bug with vue-test-utils. If you are using VueRouter anywhere (even if it's not used in any unit test), you will get the above error.
A work around is to use process.env.NODE_ENV in your unit tests and set it to 'test', and wherever you're using VueRouter, check process.env.NODE_ENV like so:
if (!process || process.env.NODE_ENV !== 'test') {
Vue.use(VueRouter)
}
at least until vue-test-utils bug is fixed, this should fix this problem
I think these docs are relevant to your situation:
Common gotchas
Installing Vue Router adds $route and $router as read-only properties on Vue prototype.
This means any future tests that try to mock $route or $router will fail.
To avoid this, never install Vue Router globally when you're running tests; use a localVue as detailed above.
The error you're seeing indicates that one of your components (and outside your test code) is installing VueRouter (e.g., Vue.use(VueRouter)).
To address the issue, search for the VueRouter installation in your component code path, including any of their imports, and refactor it so that the import is not required there. Typically, the installation of VueRouter exists only in main.js or its imports.
GitHub demo
I encountered this, and it was because I was importing vueRouter into a controller, outside of a vueComponent, where this.$router wasn't defined.
import router from '#/router';
router.push('/foo')
janedoe's answer can work but it's often risky to modify production code just to make some tests pass. I prefer to bypass the bug by doing this:
Run your test in watch mode
npx jest --watch src/components/Foo.test.ts
Locate Vue.use(VueRouter) in your code and diagnose what is the chain of components indirectly running the code by adding this just above
const error = new Error();
console.log(
error.stack
?.split('\n')
.filter((line) => line.includes('.vue'))
.join('\n'),
);
This logs a list of file path like
console.log
at Object.<anonymous> (/path/to/components/Bar.vue:1:1)
at Object.<anonymous> (/path/to/components/Foo.vue:1:1)
Chose a component in this list and, in your test file, mock it
jest.mock('/path/to/components/Bar.vue');

How to test plain Vue components (not single file components)

All of these tries are throwing an error:
var testUtils=require('#vue/test-utils'), Vue=require('vue');
require('jsdom-global')();
testUtils.mount(Vue.component('test', {
template:'<div>test</div>'
}));
testUtils.mount(Vue.component('test', {
render:function(el) { return el('div', 'test'); }
}));
testUtils.mount({
template:'<div>test</div>'
});
#vue/test-utils/dist/vue-test-utils.js:2471
var componentInstance = node.child;
TypeError: Cannot read property 'child' of undefined
I have also tried to use localVue, to use shallowMount instead of mount and tried to pass Vue.options.components.test after registrating it globally (and some other things that came to my mind) but nothing works.
Isn't there any way to test vue components without using single file components, webpack and/or other technologies that are making things unnecessary complicated and require a build process? Or is this just a lack of documentation?
You need to load the DOM before requiring #vue/test-utils. Change your code to this:
require('jsdom-global')();
var testUtils=require('#vue/test-utils'), Vue=require('vue');
// ...
Likely Jest loads JSDOM in some script before Vue is required, which is why it works there.
There's a simple guide for that in their GitHub repo.
https://github.com/vuejs/vue-test-utils/pull/1373/files#diff-b64ec6abf844db0ffa41aaf83deb3043f880b0beb988a14e0b2ace310848a335
require('jsdom-global')()
const assert = require('assert')
const Vue = require('vue')
const VueTestUtils = require('#vue/test-utils')
const App = Vue.component('app', {
data() {
return {
msg: 'Hello Vue Test Utils'
}
},
template: `
<div>{{ msg }}</div>
`
})
const wrapper = VueTestUtils.shallowMount(App)
assert.strictEqual('Hello Vue Test Utils', wrapper.text())

Categories

Resources