vue-jest
vue-jest copied to clipboard
`compilerOptions.isCustomElement` config has no effect when running multiple test files at once
Hi all,
I'm currently in the process to migrate an enterprisey Vue2 application to Vue3. The app uses custom element components built with lit-html all over the place to be in line with corporate design guidelines. For component testing we rely heavily on Vue Testing Library.
Unfortunately I ran into the same (or a similar) issue with configuring compilerOptions.isCustomElement
for component tests that was reported by @antoniogiroz in the vue-test-utils project: https://github.com/vuejs/vue-test-utils/issues/1865
The isCustomElement
compiler option is required to tell Vue's template compiler how to detect whether an element is a vue component or a native custom element. If Vue fails to resolve a component it will print a warning like this:
[Vue warn]: Failed to resolve component: vaadin-button If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement. at <Anonymous ref="VTU_COMPONENT" > at <VTUROOT>
Although it is possible to add compilerOptions
to the vue-jest
object in the globals
section of Jest's config which gets passed to the template compiler by the vue3-jest library, this does not work properly for the isCustomElement
function when running all test spec files at once. Even though the isCustomElement
function was defined in Jest's config file (see extract below), Vue prints warnings to the console output that it failed to resolve a custom element component.
globals
object from jest.config.js
:
globals: {
'vue-jest': {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('vaadin-'),
},
},
},
The cause for this behavior seems to be that the globals
object in Jest's config "must be json-serializable" (see also globals
[object] in Jest's documentation) and Jest will omit everything that can not be serialized such as functions.
Interestingly enough, Jest does not seem to omit the isCustomElement
function from the config when running a single test file with jest $PATH_TO_FILE/$FILE_NAME.spec.js
. It appears that serializing of the globals
config does only happen when running multiple test spec files at once.
You can find a project that demonstrates this behavior utilizing a vaadin-button web component here: https://github.com/beneee/vue3-jest-custom-elements-demo
Is there any other possibility to configure compilerOptions.isCustomElement
when running tests with vue3-jest? Or is there any other way planned as configuring it in Jest configuration's globals
object will not work properly for functions?
I wonder why it's not having the same problem for just one spec? Very weird.
Is there any other possibility to configure compilerOptions.isCustomElement when running tests with vue3-jest? Or is there any other way planned as configuring it in Jest configuration's globals object will not work properly for functions?
My first idea was configure the compiler options using the mounting options:
describe('Counter.vue', () => {
it('renders button', () => {
const { getByRole } = render(Counter, {
global: {
config: {
compilerOptions: {
isCustomElement: (tag: string) => {
return tag.startsWith('vaadin-')
},
},
},
},
})
expect(getByRole('button')).toBeInTheDocument()
})
})
But this didn't seem to have any effect.
I also tried putting a console.log
in the Jest config:
// ...
'vue-jest': {
compilerOptions: {
isCustomElement: (tag) => {
console.log('OK!')
return tag.startsWith('vaadin-')
}
},
},
},
The log is never printed when running yarn jest
. For some reason, like you said, the global config is only applied on a per spec basis.
The cause for this behavior seems to be that the globals object in Jest's config "must be json-serializable" (see also globals [object] in Jest's documentation) and Jest will omit everything that can not be serialized such as functions.
If this is truly the problem, but is it working when running just one spec? Does it only serialize the config when running all specs? This seems really odd.
What a strange bug. I think we need to investigate why is doesn't occur when running just one spec at at time.
Could be related ... I dug into node_modules a bit and notice this is returning false, so the code that relates to isCustomElement may not actually be executed:
Edit: tried hardcoding both true
and false
, makes no difference. Definitely need to dig into this more deeply. First thing I would do is follow the trail and make sure the custom compilerOptions
you are specifying are actually passed. Are they passed but ignored? Is it definitely a Jest serialization error?

Sorry for not getting back to this topic earlier.
I also dug into node_modules but decided to look into how jest executes the tests and passes the config to whatever is running the tests.
The first thing I discovered was that everything works just fine without any "Failed to resolve component" warnings printed to the console when running multiple test spec files at once but in sequence with Jest's --runInBand
option (npx jest --runInBand
). The globals
config object will not be serialized by Jest when running all test files in band. (Also added this case to my demo project, if you want to replicate this behavior.)
Running a single test file (e.g. jest --no-cache src/components/Counter.spec.ts
) will also trigger jest to run an in band test run which seems to be the reason why running a single spec file worked fine without any Vue compiler warnings when I set up my demo project.
Things get a bit more interesting when running a parallel test run. In this case Jest will create workers to run the individual spec files that execute the tests in child processes. The globals
config object (and other config) is sent to the child process with the subprocess.send
function from Node.js' child_process
. I'm quite sure that this is the spot where the isCustomElement
config gets lost as the message that is sent to the child process goes through serialization:
"The message goes through serialization and parsing. The resulting message might not be the same as what is originally sent." (Source)
What the heck, how do we fix this 🤔 nice job isolating this to only happening in parallel runs.
A kind of FYI: I had in my jest.config.js this transform:
'.*\\.(vue)$': '@vue/vue3-jest'
but then the compilerOptions were NOT taken into account.
Changing this to '^.+\\.vue$': '@vue/vue3-jest'
then it does !
But now I have failing unit-tests due to stubbed web-components.
(eg. adding bob-header to the stubs will make this fail: expect(wrapper.find('bob-header-stub').exists()).toBeTruthy();
)
Hey guys, any updates on that? Those warnings are quite annoying. Here is a workaround:
it ('...' => {
const wrapper = shallowMount(Component, {
global: {
stubs: {
'custom-element': {
template: '<i />',
},
},
}
})
})
Appreciate the workaround @Oleksii14 but with stubbing, its not possible to test out the inner html data or values :( Still looking for a better way to conserve the component without compiler issues
I think I found the two root causes of this bug.
The first one is already mentioned in the posts above. If you define a configuration for the vue compiler in your jest.config.js like this:
globals: {
"vue-jest": {
compilerOptions: {
isCustomElement: tag => tag.startsWith("vaadin-")
}
}
}
The function isCustomElement
is not ending up in the vue-jest config. Probably due to a parsing issue. You can verify this inside vue3-jest/lib/utils.js by logging the return value of the getVueJestConfig
function.
However, there is a second issue after this first issue. Looking at vue3-jest/lib/process.js lines 69ff, you will see the function processTemplate
. This function gets the configuration that was defined inside jest.config.js via getVueJestConfig
in line 76. But the configuration is never actually given to the compiler call in lines 88ff .
The following code snippet makes the warnings disappear.
function processTemplate(descriptor, filename, config) {
...
// replace const vueJestConfig = getVueJestConfig(config) with
const vueJestConfig = { compilerOptions: { isCustomElement: tag => tag.startsWith("vaadin-") } }
...
const result = compileTemplate({
id: filename,
source: template.content,
filename,
preprocessLang: template.lang,
preprocessOptions: vueJestConfig[template.lang],
compilerOptions: {
bindingMetadata: bindings,
mode: 'module',
...vueJestConfig.compilerOptions // add this line
}
})
One additional hint: The processTemplate
function is only called when something changes. There is some caching going on. When I start the tests without changing anything, this code is not executed. However, if I, for example, change the vue-jest config in a random way:
globals: {
random: "change",
"vue-jest": {
compilerOptions: {
isCustomElement: tag => tag.startsWith("vaadin-")
}
}
}
It will be execute once. If you try that with the changes above the warnings will go away.
PS: I am working with version 26.0.1. I have now realized that at least the second issue is fixed in the 27 alpha version. However, I cannot retest the whole problem because none of my tests run with this version.
What's not working with Jest 27 for you?
I'm also running into this problem, it there any indication on who and when this problem will be addressed?
It looks like this is fixed in alpha 27 @stephan-roolvink, see the comment two above yours: https://github.com/vuejs/vue-jest/issues/389#issuecomment-1027177566
Unless someone who really needs this to be fixed is going to submit a PR, it probably won't be fixed - at least, I'm not working on this issue right now, since it's apparently fixed in a newer version of Vue Jest. What version are you on?
I use alpha 27 and the issue still exists. I tried the suggestions from this link https://vuejs.org/guide/extras/web-components.html#using-custom-elements-in-vue, but without any success.
Hello, anyone solved this issue with Vue 3, vue jest ? I wanna test my components using Vue testing library, but I use Element UI and can find way to fix errors: console.warn node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:40 [Vue warn]: Failed to resolve component: el-card If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement. at <CoursesCards ref="VTU_COMPONENT" > at <VTUROOT>
@Edgaras318 I think the issue you are having is different, it looks like you did not install the Element UI library in your tests.
You probably need something like:
import ElementPlus from 'element-plus'
import { render } from '@testing-library/vue'
render(Foo, {
globals: {
plugins: [ElementPlus]
}
})
Not tested, but it's a plugin - if you install it, those elements should work just fine. This issue is about custom elements such as Web Components.
Thanks man ! I solved it!
In case this will help someone to get rid of the vuejs warnings:
- In jest.config.js I added the following line:
module.exports = {
setupFiles: ['<rootDir>/config/test-setup.js'],
...
- In test-setup.js I added this code
window.console.warn = () => { return '';}
This code will get rid of all of the warnings, but you can modify the test-setup.js code to get rid just of a particular type of warning. I hope this will help someone.
The following worked in my case:
- Create helper for wrapper with out
font-awesome-icon
componnet
import { shallowMount, mount } from '@vue/test-utils';
export const useWrapperWithoutIcon = (component, setup, shallow = false) => {
return (shallow ? shallowMount : mount)(component, {
global: {
stubs: {
'font-awesome-icon': {
template: '<i />',
},
},
},
...setup,
});
};
- And using as follows:
import BaseButton from './index.vue';
import { useWrapperWithoutIcon } from '../../../__test__/wrapperFactory';
describe('base-button', () => {
it('should have custom btn name from default slot', async () => {
const wrapper = useWrapperWithoutIcon(BaseButton, {
slots: {
default: 'Submit',
},
});
expect(wrapper.text()).toContain('Submit');
});
});
- In the parameters of this, it is possible to use (mount , shallowMount ) -> depending on the passed parameter
In case this will help someone to get rid of the vuejs warnings:
- In jest.config.js I added the following line:
module.exports = { setupFiles: ['<rootDir>/config/test-setup.js'], ...
- In test-setup.js I added this code
window.console.warn = () => { return '';}
This code will get rid of all of the warnings, but you can modify the test-setup.js code to get rid just of a particular type of warning. I hope this will help someone.
Yes I guess instead of banging ur head and trying to fix it while it will be fixed in vue-jest
or @vue3/jest
in future this is the simplest solution
const originalWarn = window.console.warn;
window.console.warn = e => {
return e.includes("If this is a native custom element")
? ""
: originalWarn(e);
};
Any progress on this matter ?
In my case I'm using nuxt
and I have some nuxt elements like ClientOnly
and NuxtLayout
being used on my components. This warning was being thrown for me but I solved with:
Example.vue
<template>
<NuxtLayout>
....
</NuxtLayout>
</template>
jest.config.js
module.exports = {
setupFiles: ['<rootDir>/tests/setup'],
...
tests/setup.js
import { config } from '@vue/test-utils'
import { defineComponent } from 'vue'
const ClientOnly = defineComponent({
name: 'ClientOnly',
template: '<template><slot /></template>'
})
const NuxtLayout = defineComponent({
name: 'NuxtLayout',
template: '<template><slot /></template>'
})
config.global.stubs = { ClientOnly, NuxtLayout }
using @vue/[email protected]
Is it possible to solve this without a hack?
What is the implications of adding something to isCustomElement
? It is just warnings or is something broken in the test suite?
Has anyone got a reproduction that actually shows a test running incorrectly due to this bug? That would surely help someone debug it.
There are no errors, but the problem is the web component does not seem to render correctly, and I can't access its content.
For instance, I have a component that wraps a web component and looks as follows:
// BaseHeader.vue
<x-header :title="props.title" :subtitle="props.subtitle"></x-header>
But I get exactly that result when I print the HTML with vue-test-utils or testing-library:
<x-header title="The title" subtitle="the subtitle"></x-header>
Instead of something like:
<x-header>
<h1>The title</h1>
<p>The subtitle</p>
</x-header>
So, I can't access this content in the test using getByRole
or whatever.
I had to use e2e tests with Playwright to run the tests.
page.getByRole('heading', { name: 'The title' });
The above code works perfectly with Playwright.
I'm sorry I can't share the code because the web components I'm using are private. But it could happen with other web components.
If you need more info, I could prepare something more sophisticated.
I'm sorry I can't share the code because the web components I'm using are private. But it could happen with other web components. If you need more info, I could prepare something more sophisticated.
This would be good. It's a bit of work on your end, but having a minimal reproduction someone can run makes it much more likely someone will try to fix the bug.
Hi!
I've created this repo with two examples. A unit test that fails and a similar e2e test with Playwright works.
https://github.com/antoniogiroz/testing-web-components
I hope this small example is enough to show the problem.
@antoniogiroz I could be losing my mind, but your reproduction uses Vitest - this is the repo for vue-jest, which makes Vue work with the Jest test runner.
Have you reported this to the wrong repo? Should this be in https://github.com/vitest-dev/vitest?