I'm developing some tests for single file components in VueJs. These components use font-awesome.
This is my App, as you can see I have fontawesome available for all child components.
import { createApp } from 'vue';
import App from './App.vue';
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap";
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome';
import { fas } from "#fortawesome/free-solid-svg-icons";
import { library } from '#fortawesome/fontawesome-svg-core';
library.add(fas);
createApp(App)
.component("font-awesome-icon", FontAwesomeIcon)
.mount('#app');
Here's a test
import { mount } from '#vue/test-utils'
import ListComponent from '#/components/ListComponent.vue'
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome';
let someData = [{
name: 'Some person name',
key: '2222222',
moreInfo: [
{title: 'aa'},
{title: 'bb'},
]
},
{
name: 'Some other person name',
key: '12321123,
moreInfo: [
{title: 'cc'},
{title: 'x'},
]
},
}];
let wrapper = mount(ListComponent, {
propsData: {
someData
},
stubs: {
'font-awesome-icon': FontAwesomeIcon
}
});
describe('ListadoImputados.vue', () => {.... tests ....});
Test details are not important, I don't know how to add / include font-awesome-icon in the context so i can avoid getting the following warnings
console.warn node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:40
[Vue warn]: Failed to resolve component: font-awesome-icon
I tried adding this dependency as a mock and stub but no luck. Also importing Fontawesome at the top of the file does not work, the warning is still showing. I was thinking maybe in creating a vue app in the test file and inject the component like this
createApp(App)
.component("font-awesome-icon", FontAwesomeIcon)
.mount('#app');
but I'm copying and pasting code and I'm not sure this is the right way.
Is there a way to add this dependencies to my test context?
I'm using Vue 3, vue-test-utils + jest
In Vue Test Utils v2 (for Vue 3), the stubs mounting option is moved into global.stubs. Also note that a stub does nothing by definition, so stubbing the component only requires providing the component name.
Your mounting options should look like this:
const wrapper = mount(ListComponent, {
global: {
stubs: ['FontAwesomeIcon']
}
})
If for some reason you need the actual component, you could technically provide the component definition as a "stub", but you'd also need to initialize the icons for it as you would in the app's startup:
// assume only `faUserSecret` icon used in `ListComponent`
import { library } from '#fortawesome/fontawesome-svg-core'
import { faUserSecret } from '#fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome'
library.add(faUserSecret)
//...
const wrapper = mount(ListComponent, {
global: {
stubs: { FontAwesomeIcon }
}
})
Related
I have created a component as part of my component library that I am building with Vue3 and Vite. Everything works well, except when I try to use environment variables. I want the app that consumes this component library to be able to provide the component with environment specific data.
I have played around and found that if I have a .env file as part of the component library project, I am able to access those variables, but I want to be able to provide that during runtime and not during build time.
Here is my vite.config.ts
import { defineConfig } from "vite";
import { resolve } from "path";
import vue from "#vitejs/plugin-vue";
import dts from "vite-plugin-dts";
export default ({ mode }) => {
return defineConfig({
optimizeDeps: {
exclude: ["vue-demi"],
},
plugins: [
vue(),
dts({
insertTypesEntry: true,
}),
],
server: {
open: true,
},
build: {
lib: {
entry: resolve(__dirname, "src/lib.ts"),
name: "complib",
fileName: "complib",
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
exports: "named",
},
},
},
});
};
The entry looks like:
import { App, install } from "vue-demi";
import TestComp from "./components/TestComp.vue";
import "./tailwind.css";
install();
export default {
install: (app: App) => {
app.component("TestComp", TestComp);
},
};
export { Header };
And here is a minimal component TestComp.vue:
<script setup lang="ts">
import { onMounted } from "vue";
onMounted(() => {
console.log(import.meta.env.VITE_TEST_VAR);
});
</script>
<template>
<span>Test Comp</span>
</template>
I get this warning in my Chrome Console:
runtime-core.esm-bundler.js?d2dd:38 [Vue warn]: The `compilerOptions` config option is only respected when using a build of Vue.js that includes the runtime compiler (aka "full build"). Since you are using the runtime-only build, `compilerOptions` must be passed to `#vue/compiler-dom` in the build setup instead.
and then this warning:
[Vue warn]: Failed to resolve component: amplify-s3-image
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.
But the component actually works, since it shows the image from S3 how it's supposed to.
This is what my main.js looks like:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import Amplify from "aws-amplify";
import AmplifyVue from '#aws-amplify/ui-vue';
import store from './store'
import awsconfig from './aws-exports';
import aws_exports from './aws-exports';
import { applyPolyfills, defineCustomElements } from '#aws-amplify/ui-components/loader';
Amplify.configure(awsconfig);
//Amplify.configure(aws_exports);
applyPolyfills().then(() => {
defineCustomElements(window);
});
const app = createApp(App).use(router).use(store).use(AmplifyVue)
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('amplify-');
app.mount('#app');
and this is in my vue.config.js:
const { defineConfig } = require('#vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
},
)
module.exports = {
chainWebpack: config => {
config
.plugin('html')
.tap(args => {
args[0].title = "Opensquare";
return args;
},options => {
options['compilerOptions'] = {
...options.compilerOptions || {},
isCustomElement: tag => tag.startsWith('amplify-')
}
return options;
})
}
}
What am I doing wrong? It's not blocking, since the component actually works, but reading the Console with this WARN popping up is a nightmare.
I'm looking for way to make the same logic of require.context of webpack in vitejs, I've found this plugin named vite-plugin-import-context, I tried it out but there's something that I didn't understand which is import dynamicImport from '../src/index' in the basic usage :
import { UserConfigExport } from 'vite';
import vue from '#vitejs/plugin-vue';
import dynamicImport from '../src/index';// <-- this is not described
export default (): UserConfigExport => {
return {
plugins: [vue(), dynamicImport(/*options*/)],
};
};
require should never be used in source code when using Vite. It's ESM only.
For Vite v2, import.meta.globEager can be used.
For Vite >v2, import.meta.globEager is deprecated. Use import.meta.glob('*', { eager: true }) instead.
import.meta.glob() is webpack alternative of require.context() . It has been added in vite since v2.3.4 .
Here is a link to the doc https://vitejs.dev/guide/features.html#glob-import
Yep, that example is directly taken from examples folder in the repo so it works only in that repo.
If you install the plugin via npm or yarn, the line should look like import dynamicImport from 'vite-plugin-import-context'
Here is how I import all the plugins/modules:
main.ts
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
// styles here
import '#/assets/styles/tailwind.css';
import '#/assets/styles/main.scss';
// install all modules under `modules/`
Object.values(
import.meta.glob<{ install: (ctx: any) => void }>('/src/modules/*.ts', { eager: true })
).forEach((module) => module.install?.(app));
app.mount('#app');
and here is how I keep things ready in my modules folder to export:
modules/pinia.ts
import { createPinia } from 'pinia';
export const install = (app: any) => {
app.use(createPinia());
};
modules/router.ts
import type { RouteRecordRaw } from 'vue-router';
import { createRouter, createWebHashHistory } from 'vue-router';
import generatedRoutes from '~pages';
import { setupLayouts } from 'virtual:generated-layouts';
const routes: RouteRecordRaw[] = setupLayouts(generatedRoutes);
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export const install = (app: any) => {
app.use(router);
};
I have a Vue application that uses used Vue Analytics, which creates a this.$ga.page method to do page tracking. Now, I need to test my component, using Jest, but it says this.$ga.page is not defined. I'm initializing Vue Analytics in main.js.
How do I include main.js before the component in the test file? I know we need to add this in a beforeEach method, but I don't know exactly how to do that.
Vue Analytics init in main.js
import VueAnalytics from 'vue-analytics'
Vue.use(VueAnalytics, {
id: 'UA-150437966-1'
})
My Homepage test
import { mount } from '#vue/test-utils'
import Homepage from '../../src/Pages/Homepage'
describe('Homepage', () => {
const wrapper = mount(Homepage)
it('has a button', () => {
expect(wrapper.contains('button')).toBe(true)
})
})
Homepage.vue excerpt
created: function() {
//analytics
this.$ga.page({
page: "/",
title: "Home page",
location: window.location.href
});
}
};
Error I'm getting
Homepage › encountered a declaration exception
TypeError: Cannot read property 'page' of undefined
67 | //analytics
68 |
> 69 | this.$ga.page({
Vue Test Utils recommends setting up your plugins in a localVue to avoid polluting the global Vue. This is what it would look like:
import { localVue, mount } from '#vue/test-utils'
import Homepage from '#/components/Homepage'
localVue.use(VueAnalytics, { id: 'UA-150437966-1' })
describe('Homepage', () => {
let wrapper = null
beforeEach(() => {
wrapper = mount(Homepage, {
localVue,
})
})
// ...
})
On the other hand, if it doesn't matter in your tests that the global Vue is modified, you could setup your Jest 23.x environment with setupTestFrameworkScriptFile:
// jest.config.js
module.exports = {
setupTestFrameworkScriptFile: '<rootDir>/tests/jest-setup.js',
}
And in your setup file, you could initialize Vue Analytics:
// <rootDir>/tests/jest-setup.js
import Vue from 'vue'
import VueAnalytics from 'vue-analytics'
Vue.use(VueAnalytics, {
id: 'UA-150437966-1'
})
I'm trying to test a component that uses a child component WarnOnUnsavedModal. I'm not trying to test the child component, however.
The child component uses <b-modal>, and in node_modules, that imports a component called bBtn.
When I try to run my test file, it fails with the following message:
import bBtn from '../button/button';
^^^^
SyntaxError: Unexpected identifier
My test file:
import BootstrapVue, { bBtn } from 'bootstrap-vue';
import { mount, createLocalVue } from '#vue/test-utils';
import ComponentName from '../ComponentName.vue';
const localVue = createLocalVue();
localVue.use(BootstrapVue);
describe('ComponentName', () => {
it('Has props', () => {
const wrapper = mount(ComponentName, {
store,
createLocalVue,
stubs: {
ModalWarnOnSave,
'b-btn': bBtn,
},
propsData: {
resourceType: 'General',
},
});
expect(1 + 1).toBe(2);
});
});
I've tried adding a line like this to the stubs:
stubs: {
ModalWarnOnSave: true,
},
Why isn't that component being picked up here? I've tried swapping out localVue.use() with Vue.use(), to no avail.
What do I need to do to get this test to run? I'm happy to ignore the child file that's causing the problem.
Try this:
import BootstrapVue, { BButton } from 'bootstrap-vue'
bBtn is not the exported name of the b-button component. b-btn is an alias component name when Vue.use'ing BootstrapVue.
See https://bootstrap-vue.js.org/docs/components/button#importing-individual-components