Open Giergiel96 opened 2 years ago
After some more digging i found out, that the issue is that the "_vue" variable holding the scope with the imports get overwritten by the template script. This is the code generated by the vue-jest after adding scriptResult, scriptSetupResult and templateResult (generate-code.js):
Wait - so the bug is only if your package is named vue
?
Not sure how best to fix this one. I guess our code gen isn't ideal.
This one's weird. It looks like babel creates a namespace out of the last part of the import path (i.e. @random-package/vue
or @package/components/vue
). When you import anything other from anything that will result in creation of the _vue
namespace, then the problem disappears since it will autoincrement and create _vue1
, _vue2
starting from the number 1 🤷 What we did for now was to write a babel plugin that runs only during tests, that looks for any _vue
variable declaration and does not import from vue
directly and "increments" the name to _vue1
so it does not get overwritten by the impots from template code 😂
Example of that is in the reproduction repo.
Weird one... I am not sure I have time to fix this, if you want to try, that'd be great. Alternatively - just rename your package to not be named vue. Not ideal, but an easy work around in the mean time.
@Giergiel96 do you have an example of the babel plugin and how you solved it?
I just stumbled onto this issue after debugging a similar issue for a while.
I have an inertiajs vue project I'm upgrading to vue 3.
Inertiajs imports look like this:
import { useForm } from '@inertiajs/vue3';
with @inertiajs/vue3
being resolved to _vue
via generateUidIdentifier
Here's the an example of transpired code:
(redacted to show relevant bits)
>>scriptResult START
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _vue = require("@inertiajs/vue3");
var _vue2 = require("vue");
var _default = exports.default = {
layout: _LayoutGrey,
props: ['targetedmessages', 'sort', 'dir', 'statuses', 'audiences', 'advancedFiltersCount'],
components: {
...
},
data() {
return {
filters: (0, _vue.useForm)({
basic: {
...
},
advanced: {
...
}
})
};
}
};
>> scriptResult END
>> tempalteResult START
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.render = render;
var _vue = require("vue");
var _hoisted_1 = {
id: "mainContent",
tabindex: "-1",
"class": "text-xl"
};
...
generateUidIdentifier
works well to increment _vue in the scriptResult part, however when generating tempalteResult I guess we lose the context that the _vue identifier is already in use.
I'm going to look into @Giergiel96's work around.
But anyone using jest, vue and inertiajs is likely going to run into this problem.
I found my own workaround for this issue.
babel-plugin-inject-vue.js
/**
* This Babel plugin fixes an issue in vue3-jest related to how imports are handled
* in Vue files. The problem arises from a combination of factors:
*
* In JavaScript, imports like `import { useForm } from '@inertiajs/vue3';` are transformed
* into lines like `_vue = require("@inertiajs/vue3");`. The `_vue` name is generated by
* Babel's `generateUidIdentifier` method, which converts "@inertiajs/vue3" to "vue3", removes
* numbers, and adds an underscore, resulting in `_vue`.
*
* Normally, Babel increments `_vue` to `_vue2` if there are duplicate identifiers, preventing
* conflicts. However, in vue3-jest, the template and script parts of Vue files are processed
* separately, so the increment is lost. This can lead to both `_vue = require("@inertiajs/vue3");`
* and `_vue = require("vue");` in the same file, with one potentially overwriting the other.
*
* This plugin works around the issue by injecting `import { render } from 'vue';` ("render" is arbitrary) at the top
* of any Vue file before any transpiling occurs. This ensures that `_vue = require("vue");`
* is always declared first, so any other imports resolving to `_vue` will be incremented
* first to `_vue2`, preventing overwrites.
*/
module.exports = function ({ types: t }) {
return {
visitor: {
Program(path, state) {
// Only process .vue files
const filename = state.filename || '';
if (!filename.endsWith('.vue')) return;
// Check if the file has a <script> block
if (path.node.body.length > 0) {
// Add an import statment for vue (equivalent to `import { render } from 'vue'`)
const vueImport = t.importDeclaration(
[t.importSpecifier(t.identifier('render'), t.identifier('render'))],
t.stringLiteral('vue')
);
path.unshiftContainer('body', vueImport);
}
},
},
};
};
babel.config.js
module.exports = {
env: {
test: {
...
plugins: [
...
'./babel-plugin-inject-vue'
],
},
},
};
I'd love to contribute some fix here but I'm not competent in babel to understand what the correct fix would be.
After migrating to vue 3, and vue-jest@29.2.0 some tests stopped working for us. After some digging we found out that if you have a package named like "@some-ui-kit/vue", named imports from it will get cut from the component's scope durin test runtime. Changing the import path to anything different changes the issue, but it used to work perfectly fine with Vue 2 compatible versions.
As you can see, the example "TEST" const disappears from the scope:
But if you rename the "package", then it works:
Reproduction repo: https://github.com/Giergiel96/repro-vue-jest.git