ReferenceError: computed is not defined on Vitest test suite - vue.js

Description
I'm migrating test suites from Jest to Vitest.
But i've a problem when i run test suites, an error occurs when a component has a computed property.
The common error is :
ReferenceError: computed is not defined
- /components/Ui/Avatar.vue:13:30
- /node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:157:22
- /node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:7084:29
- /node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:7039:11
- /node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:5401:13
- /node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:5376:17
- /node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:4978:21
- /node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:5515:21
- /node_modules/#vue/reactivity/dist/reactivity.cjs.js:189:25
- /node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:5622:56
Versions
"vitest": "^0.18.1"
"jsdom": "^20.0.0"
"#vue/test-utils": "^2.0.2"
Exemple
Here is my component code :
<template>
<image
:src="src"
:onerror="onErrorLoadImage"
:class="['avatar', { big }]"
/>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
src?: string
big?: boolean
errorImage?: string
}>(), {
src: '',
big: false,
errorImage: '/no-avatar.png',
})
const onErrorLoadImage = computed(() => `this.src='${props.errorImage}';this.onerror='';`)
</script>
And my test
import { describe, it, expect } from 'vitest'
import { mount } from '#vue/test-utils'
import UiAvatar from './Avatar.vue'
const componentName = 'img'
const src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='
const big = true
const errorImage = '/no-avatar.png'
describe('UiAvatar', () => {
it('should be render the component', () => {
const wrapper = mount(UiAvatar, {
propsData: {
src,
big,
errorImage
}
})
expect(wrapper.element.tagName).toBe(componentName)
})
})
Thanks :)

This can be solved by using the following npm packages:
unplugin-vue-components/vite
unplugin-auto-import/vite
They only need to specify a path where the import files will be generated, in my case I use a storybook folder since I also use the plugin there, but it can be any other path
vitest.config.ts

Related

dynamically highlight block with highlight.js in vue app

I have a VueJS where I have created a component for rendering the contents from a WYSIWYG component (tiptap).
I have the following content being returned from the backend
let x = 0;
enum A {}
function Baa() {}
I'm using highlight.js to highlight this code snippet in the following manner:
import { defineComponent, h, nextTick, onMounted, onUpdated, ref, watch } from 'vue';
// No need to use a third-party component to highlight code
// since the `#tiptap/extension-code-block-lowlight` library has highlight as a dependency
import highlight from 'highlight.js'
export const WYSIWYG = defineComponent({
name: 'WYSIWYG',
props: {
content: { type: String, required: true },
},
setup(props) {
const root = ref<HTMLElement>(null);
const highlightClass = 'hljs';
const hightlightCodes = async () => {
console.log(root.value?.querySelectorAll('pre code')[0]);
setTimeout(() => {
root.value?.querySelectorAll('pre code').forEach((el: HTMLElement) => {
highlight.highlightElement(el as HTMLElement);
});
}, 2000);
}
onMounted(hightlightCodes);
watch(() => props.content, hightlightCodes);
return function render() {
return h('div', {
class: 'WYSIWYG',
ref: root,
innerHTML: props.content
});
};
},
});
Now, when I visit the page by typing the URL in the browser, it highlights the typescript code
Whenever I visit a different page and click on my browser's "Go back" button, it makes the code completely vanishes
What I have tried
I can see that the line root.value?.querySelectorAll('pre code') is returning the correct items and the correct code is present but the code vanishes after the 2 seconds passes - due to setTimeout.
How can I make highlight.js highlight the code parts whenever props.content changes?
Option 1
Use Highlight.js Vue integration (you need to setup the plugin first, check the link):
<script setup>
const props = defineProps({
content: { type: String, required: true },
})
</script>
<template>
<highlightjs :code="content" language="ts" />
</template>
Option 2
Use computed to reactively compute highlighted HTML of props.content
Use sync highlight(code, options) function to get the highlighted HTML
Use HTML as-is via innerHTML prop or v-html directive
<script setup>
import { computed } from 'vue'
import highlight from 'highlight.js'
const props = defineProps({
content: { type: String, required: true },
})
const html = computed(() => {
const { value } = highlight.highlight(props.content, { lang: 'ts' })
return value
})
</script>
<template>
<div v-html="html" />
</template>

How to test methods imported from other components or JS file

I don´t have much experience with unit testing.
What is the proper way to test a method imported from a JS file or from another component?
This is a sample component I created a localSum just to use on the test.
<template>
<div class="fixed-center text-center text-h2">
<p>{{ getSum }}</p>
</div>
</template>
<script>
import { sum } from './testComponent.js';
export default {
name: 'TestComponent',
data() {
return {
a: 10,
b: 20
}
},
computed: {
getSum() {
return sum(this.a, this.b);
}
},
methods: {
localSum(a, b) {
return a + b;
}
}
};
</script>
The JS file:
export function sum(a, b) {
return a + b;
}
This is the test, maybe I should not be using wrapper.vm to access the method?
One note: On the real component I don't want to test the method directly, which is why I did not import sum from the JS file.
import { mount } from '#vue/test-utils';
import TestComponent from '#components/TestComponent.vue';
describe('Testing component', () => {
test('Testing local method', () => {
const wrapper = mount(TestComponent);
expect(wrapper.vm.localSum(10, 20)).toBe(30);
});
test('Testing method from js file', () => {
const wrapper = mount(TestComponent);
expect(wrapper.vm.getSum(10, 20)).toBe(30);
});
});
Test result:
Testing component
✓ Testing local method (6 ms)
✕ Testing method from js file (2 ms)
● Testing component › Testing method from js file
TypeError: wrapper.vm.getSum is not a function
Thanks!
In your example getSum() is a computed property that doesn't take any argument.
Therefore to test it you probably just need to do:
expect(wrapper.vm.getSum).toBe(30);
instead of:
expect(wrapper.vm.getSum(10, 20)).toBe(30);

vite 2 production env ref element is undefined with compostion api

I use vue3 with composition api, but when I build my project, the ref element always undefined.
I reproduced it, maybe I used it incorrectly, but I don't know why.
I defined a ref in hooks function.
const isShow = ref(false)
const rootRef = ref<HTMLDivElement>();
export default function () {
function changeShow() {
isShow.value = !isShow.value;
console.log(isShow.value, rootRef.value);
}
return { isShow, rootRef, changeShow };
}
Use rootRef in the HelloWorld.vue and linked element.
<script setup lang="ts">
import useShow from "../composables/useShow";
const { rootRef, isShow } = useShow();
</script>
<template>
<div ref="rootRef" v-show="isShow" class="test"></div>
</template>
Create a button in App.vue and bind click function.
<script setup lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
import useShow from "./composables/useShow";
const { changeShow } = useShow();
</script>
<template>
<button #click="changeShow">切换</button>
<HelloWorld />
</template>
When I click button, it works.
But when I build it and import from lib, it doesn't work.
My vite.config.ts is as follows:
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"#": path.resolve(__dirname, "src")
}
},
build: {
cssCodeSplit: true,
sourcemap: true,
lib: {
entry: path.resolve(__dirname, "src/index.ts"),
name: "my-project",
fileName: format => `my-project.${format}.js`
},
rollupOptions: {
external: ["vue"],
preserveEntrySignatures: "strict",
output: {
globals: {
vue: "Vue"
}
}
}
}
});
I think the problem is the definition of rootRef. It seems that only binding location can use it. This is no different from defining it in a component. I need to use it in multiple places.
Oddly, in this way, the Dev environment works fine, but Pro env is not available. Do I need to modify the build configuration of vite.
How do I do that?
The problem is your App.vue uses its own copy of composables/useShow instead of the one from the lib.
The solution is to export the composable from the lib so that your app can use the same one:
// src/index.ts
import { default as useShow } from './composables/useShow';
//...
export default {
//...
useShow
};
In App.vue, use the lib's composable:
import MyProject from "../dist/my-project.es";
const { changeShow } = MyProject.useShow();
GitHub PR

How to test for the existance of a bootstrap vue component in unit tests with jest?

So I have some code that has a b-form-input component and I am testing whether that component renders. I am using wrapper.find({name: "b-form-input"}).exists() to determine whether that bootstrap vue component exists. However this function continually returns false when I know that the component is rendering. Could I have some help on how to do this correctly?
Looking at the bootstrap-vue source code, it looks like the name of the element is BFormInput and not b-form-input (it was registered using kebab-case):
https://github.com/bootstrap-vue/bootstrap-vue/blob/2fb5ce823a577fcc2414d78bd43ed9e5351cb1c0/src/components/form-input/form-input.js#L33
...
export const BFormInput = /*#__PURE__*/ Vue.extend({
name: 'BFormInput',
...
You have two options to locate the component; using the name, or the component constructor. For example:
import BootstrapVue, { BFormInput } from 'bootstrap-vue';
import { shallowMount, createLocalVue } from '#vue/test-utils';
import HelloWorld from '#/components/HelloWorld.vue';
const localVue = createLocalVue();
localVue.use(BootstrapVue);
describe('HelloWorld.vue', () => {
it('BFormInput exists', () => {
const wrapper = shallowMount(HelloWorld, { localVue })
expect(wrapper.find({ name: 'BFormInput' }).exists()).toBe(true);
expect(wrapper.find(BFormInput).exists()).toBe(true);
});
});

vuejs how to compile single file component to js object

I have a simple Vue js single file component:
<template>
<div>
Hello!
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
And I want to compile it to
{
template: '<div>Hello!</div>'
}
of course, it should work with more complex examples, with included components and such.
How do I do that?
You can use vue-template-compiler package:
Install it:
npm i vue-template-compiler
Use it (test.vue is a component from your example):
const fs = require('fs');
const compiler = require('vue-template-compiler');
const content = fs.readFileSync('./test.vue', 'utf-8');
const res = compiler.parseComponent(content);
const template = res.template.content.trim();
const result = { template };
console.log(result);
Result:
{ template: '<div>\n Hello!\n</div>' }
Don't forget to add error checks to your code.