Vue: How to test dynamic components import? - javascript

I have this simple Vue SFC which renders the component given the prop value.
<template>
<component :is="component" v-bind="stepProps" />
</template>
<script>
import { _ } from 'core'
export default {
name: 'SetupFlow',
props: {
type: {
type: String,
required: true
},
step: {
type: String,
required: true
},
stepProps: Object
},
computed: {
component () {
const camelCaseName = _.camelCase(this.step)
const name = camelCaseName.charAt(0).toUpperCase() + camelCaseName.slice(1)
return () => import(`#/components/ProfileSetup/GMB/${name}`)
}
}
}
</script>
In my test, I just need to make sure that the component imported is being rendered. Here is my test file so far:
import { createLocalVue, shallowMount } from '#vue/test-utils'
import SetupFlow from '#/components/ProfileSetup/SetupFlow'
const localVue = createLocalVue()
describe('SetupFlow.vue', () => {
let propsData
let stubs
beforeEach(() => {
propsData = {
type: 'GMB',
step: 'step-example' // this file does not exist, so I need to mock `import`
}
})
it('renders the given step component', async () => {
const wrapper = shallowMount(SetupFlow, { localVue, propsData })
})
})
This is an error I get when running the test:
Any ideas how to mock import so that step-example returns a mock vue component?

Related

'Cannot read properties of undefined' with React Testing library for decoding JWT token

I'm fairly new to React testing library and am using a function within a useEffect to decode a user token from keycloak when they sign up, to determine what type of user they are and render different Menu's based on that. This new Jwt function I created though, is causing a lot of my previously working tests within other files to fail as it throws an error like below:
I'm sure how to deal with this error on the testing side, should I be mocking this decoder function within the test file?
This is my main file:
import React, {useEffect, useState, useMemo, useCallback} from "react";
import {withTranslation} from "react-i18next";
import "./index.scss";
import {historyObject} from "historyObject";
import {Heading} from "#xriba/ui";
import { keycloak } from "utils/keycloak";
export const Menu = (props) => {
const {t} = props;
const [userHasCommunities, setUserHasCommunities] = useState(false);
useEffect(() => {
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
if (JSON.parse(jsonPayload).communities?.length > 0){
setUserHasCommunities(true)
}
return JSON.parse(jsonPayload);
};
parseJwt(keycloak.token);
}, [userHasCommunities])
const buildItem = useCallback((item) => {
const isCurrent = historyObject.location.pathname.includes(item.path);
return <li key={item.path} className={isCurrent ? "selected" : ""} onClick={() => historyObject.push(item.path)}>
<Heading level={"4"}>{t(item.title)}</Heading>
</li>
}, [t])
const renderMenu = useMemo(() => {
let menuItems = []
if (userHasCommunities){
menuItems = [
{
path: "/portfolio",
title: "pages.portfolio.menu_entry"
},
{
path: "/transactions",
title: "pages.transactions.menu_entry"
},
{
path: "/community",
title: "pages.community.menu_entry"
},
{
path: "/reports",
title: "pages.reports.menu_entry"
}
]
} else {
menuItems = [
{
path: "/portfolio",
title: "pages.portfolio.menu_entry"
},
{
path: "/transactions",
title: "pages.transactions.menu_entry"
},
{
path: "/reports",
title: "pages.reports.menu_entry"
}
]
}
return menuItems.map(i => buildItem(i))
}, [userHasCommunities, buildItem])
return <div className={"menu"}>
<ul>
{renderMenu}
</ul>
</div>
}
export default withTranslation()(Menu)
And my current test file:
import React from "react";
import {shallow} from "enzyme";
import {Menu} from "features/layouts/logged-in/menu/index";
import {historyObject} from "historyObject";
import { keycloak } from "utils/keycloak";
jest.mock("utils/keycloak");
jest.mock("historyObject")
describe("Menu test", () => {
const props = {
t: jest.fn(),
};
beforeEach(() => {
jest.resetAllMocks();
jest.spyOn(React, "useEffect").mockImplementationOnce(x => x()).mockImplementationOnce(x => x()).mockImplementationOnce(x => x()).mockImplementationOnce(x => x());
});
it('should render Menu', () => {
const wrapper = shallow(<Menu {...props} />);
expect(wrapper).toBeDefined();
wrapper.find('li').at(0).prop('onClick')();
expect(historyObject.push).toHaveBeenCalled()
});
it('should render Menu with an object as current', () => {
historyObject.location.pathname = "/portfolio"
const wrapper = shallow(<Menu {...props} />);
expect(wrapper).toBeDefined();
expect(wrapper.find('.selected')).toHaveLength(1);
});
});
Thanks in advance for any advice!

Override a mocked consumer body for certain tests

I have the following test which works.
import React from 'react';
import { mount } from 'enzyme';
import MyLogin from '../../src/components/MyLogin';
const mockContent = {
data: {
config: {
myImage: 'mock_image',
},
},
};
jest.mock('../../src/utils/config', () => ({
Consumer: ({ children }) => children(mockContent),
}));
describe('Component Test', () => {
beforeEach(() => {
jest.resetModules();
});
const render = () => mount(
<MyLogin />
);
it('should render the component', () => {
const renderedModule = render();
expect(renderedModule).toMatchSnapshot();
});
});
But I wish to add another test where the values inside the mockContent changes.
I have added a isValid key.
Thus tried the following where I am trying to over ride mock only for this test.
But this doesn't work. No errors. No effect from this mock.
If I had added this new key isValid at the above mock outside the test, it does have the desired effect. (Can't do that of course cos that would affect my other tests)
Could I get some help with this pls.
describe('Component Test', () => {
// other tests
it('my new test', () => {
// attempting to override the above mock.
const myNewMockContent = {
data: {
config: {
myImage: 'mock_image_2',
isValid: true,
},
},
};
jest.mock('../../src/utils/config', () => ({
Consumer: ({ children }) => children(myNewMockContent),
}));
const renderedModule = render();
expect(renderedModule).toMatchSnapshot();
});
});

How to render Vue Component dynamically with render()

In Vuejs 3 I want to use the render() function to create a VNode, passing it a Vue Component. This component varies depending on the current route.
In vite.js I haven't found a way to import a component dynamically inside my ViewComponent computed function.
With webpack I could normally use return require(`./pages/${matchingPage}.vue`).default, but this is not possible with vitejs as I will get a Require is not a function error.
So I tried return import(`./pages/${matchingPage}.vue`) but it returns a Promise, not a Component
//main.js
import {createApp, h} from 'vue'
import routes from './routes'
const SimpleRouterApp = {
data: () => ({
currentRoute: window.location.pathname
}),
computed: {
ViewComponent () {
const matchingPage = routes[this.currentRoute] || '404'
return import(`./pages/${matchingPage}.vue`)
}
},
render () {
return h(this.ViewComponent)
},
created () {
window.addEventListener('popstate', () => {
this.currentRoute = window.location.pathname
})
}
}
createApp(SimpleRouterApp).mount('#app')
What other ways can I try so render() can return a Component dynamically?
You could use async-components :
import {createApp, h,defineAsyncComponent} from 'vue'
....
render () {
const matchingPage = routes[this.currentRoute] || '404'
const ViewComponent= defineAsyncComponent(
() =>import(`./pages/${matchingPage}.vue`)
)
return h(ViewComponent)
},

How to organize Vue unit test with factory functions?

I'm trying to write unit tests for my Dashboard.vue component using factory functions so that I could overwrite the store and wrapper as per needed.
Here is the code
import { mount, createLocalVue } from '#vue/test-utils'
import mergeWith from 'lodash.mergewith'
import mutationobserver from 'mutationobserver-shim'
import Vuex from 'vuex'
import BootstrapVue from 'bootstrap-vue'
import Dashboard from '#/views/dashboard/Dashboard'
import { FontAwesomeIcon } from '#fortawesome/vue-fontawesome'
import { library as faLibrary } from '#fortawesome/fontawesome-svg-core'
import { faUser, faThumbsUp, faSignOutAlt, faBorderAll, faAlignJustify, faTrashAlt, faRandom } from '#fortawesome/free-solid-svg-icons'
import flushPromises from 'flush-promises'
jest.mock('#/services/app.service.js')
faLibrary.add(faUser, faThumbsUp, faSignOutAlt, faBorderAll, faAlignJustify, faTrashAlt, faRandom)
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(BootstrapVue)
localVue.use(mutationobserver) // This is a necessary polyfill for Bootstrap-Vue
localVue.component('font-awesome-icon', FontAwesomeIcon)
function customizer (ovjValue, srcValue) {
/*
If the property that takes precedence is an array,
overwrite the value rather than merging the arrays
*/
if (Array.isArray(srcValue)) {
return srcValue
}
/*
If the property that takes precedence is an empty object,
overwrite the property with an empty object
*/
if (srcValue instanceof Object && Object.keys(srcValue).length === 0) {
return srcValue
}
}
describe('DashBoard component tests', () => {
let state
// let actions
// let getters
let store
let wrapper
let dashBoardData = [
{ db_name: 'Jobs', dxp_dashboardref: 1, dxp_hidden: 0, dxp_position: 1, dxp_ref: 926 },
{ db_name: 'Firms', dxp_dashboardref: 2, dxp_hidden: 0, dxp_position: 2, dxp_ref: 927 },
{ db_name: 'CRM', dxp_dashboardref: 5, dxp_hidden: 0, dxp_position: 3, dxp_ref: 987 }
]
// beforeEach(() => {
state = {
auth: {
user: {
auids: '',
md_clock: 0,
md_picture: '',
ps_fname1: '',
ps_surname: '',
psname: 'Test Test',
psref: 0
}
},
app: {
dashBoardData: []
}
}
function createStore (overrides) {
const defaultStoreConfig = {
// state: {
// state
// },
getters: {
getDashBoardData: () => dashBoardData
},
actions: {
loadDashboard: jest.fn(),
updateDashBoardData: jest.fn()
}
}
return new Vuex.Store(
state,
mergeWith(defaultStoreConfig, overrides, customizer)
)
}
function createWrapper (overrrides) {
const defaultMountingOptions = {
localVue,
store: createStore()
}
return mount(
Dashboard,
mergeWith(
defaultMountingOptions,
overrrides,
customizer)
)
}
// START: Testing existence of DOM Elements tests
it('is a Vue instance', () => {
const wrapper = createWrapper({})
expect(wrapper.isVueInstance).toBeTruthy()
})
})
Essentially, I'm trying to use a createWrapper method which has a default store unless overrides or customizer are passed. When I run the test I get the following errors
console.error node_modules/vuex/dist/vuex.common.js:899
[vuex] unknown getter: getDashBoardData
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: Error in render: "TypeError: Cannot read property 'length' of undefined"
Now, I have two questions:
Why am I being thrown unknown getter when I have declared it in the defaultStoreConfig ?
The second error comes from the state. For some reason it doesn't recognize the state variable I'm passing. Any ideas why ?
If I simply declare the wrapper inside a beforeEach like so I can pass some of my test but for others which I need to overwrite either getters or actions, I'm not able to do that unless I have the factory functions
getters = {
getDashBoardData: () => dashBoardData
},
actions = {
loadDashboard: jest.fn(),
updateDashBoardData: jest.fn()
}
store = new Vuex.Store({
state,
actions,
getters
})
})
Any help will be highly appreciated!
Solved this by passing the state inside defaultStoreConfig rather than separately when creating the store
Code:
const defaultStoreConfig = {
state: {
auth: {
user: {
auids: '',
md_clock: 0,
md_picture: '',
ps_fname1: '',
ps_surname: '',
psname: 'Test Test',
psref: 0
}
},
app: {
dashBoardData: []
}
},
getters: {
getDashBoardData: () => dashBoardData
},
actions: {
loadDashboard: jest.fn(),
updateDashBoardData: jest.fn()
}
}
Test:
it('is a Vue instance', () => {
const wrapper = createWrapper()
expect(wrapper.isVueInstance).toBeTruthy()
})

Mocking Vuex module action in component unit test

I'm currently trying to mock an action from a store's module. I can't seem to properly stub it, as I continue to get a message in my unit tests that says:
[vuex] unknown action type: moduleA/filterData
Here is a simplified version of the component under test:
Item.vue
<template>
<li class="list-item"
#click="toggleActive()">
{{ itemName }}
</li>
</template>
<script>
import store from '../store'
export default {
name: 'item',
props: {
itemName: {
type: String
}
},
data () {
return {
store,
isActive: false
}
},
methods: {
toggleActive () {
this.isActive = !this.isActive;
this.$store.dispatch('moduleA/filterData', { name: itemName } );
}
}
}
</script>
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import moduleA from './modules/moduleA'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
moduleA
}
});
export default store;
moduleA.js
/* imports */
const state = {
/* state */
}
const mutations = {
/* ... */
}
const actions = {
filterData({ state, commit }, payload) {
/* filter data and commit mutations */
}
}
const getters = {
/* getters */
}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
Item.spec.js
import Vue from 'vue'
import { mount } from '#vue/test-utils'
import { expect } from 'chai'
import sinon from 'sinon'
import Item from '../src/components/Item.vue'
import Vuex from 'vuex'
Vue.use(Vuex);
describe('Item.vue', () => {
let componentProps = {};
let wrapper;
let actions;
let store;
beforeEach(() => {
let name = 'Item Name';
// mock vuex action on click
actions = {
filterData: sinon.stub()
}
let moduleA = {
state: {},
actions
}
store = new Vuex.Store({
modules: {
moduleA
}
});
componentProps.itemName = name;
wrapper = mount(Item, {
store: store,
propsData: componentProps
});
})
it('Has a root element of list-item', () => {
expect(wrapper.is('.list-item')).to.equal(true);
})
it('Item getting prop name', () => {
expect(wrapper.text()).to.equal('Item Name');
})
it('Item is not active on load', () => {
expect(wrapper.vm.$data.isActive).to.equal(false);
})
it('Item is active after click', () => {
wrapper.trigger('click');
expect(wrapper.vm.$data.isActive).to.equal(true);
})
it('Item is not active after two clicks', () => {
wrapper.trigger('click');
wrapper.trigger('click');
expect(wrapper.vm.$data.isActive).to.equal(false);
})
})
This isn't causing my tests to fail, but I've been unable to find out how to properly mock/stub module actions from Vuex. Any help is appreciated.
So I've looked into this, and it turns out that I wasn't defining that my store within my test was namespaced, hence it wasn't recognizing my action:
beforeEach(() => {
let name = 'Item Name';
// mock vuex action on click
actions = {
filterData: sinon.stub()
}
let moduleA = {
namespaced: true,
state: {},
actions
}
store = new Vuex.Store({
modules: {
moduleA
}
});
componentProps.itemName = name;
wrapper = mount(Item, {
store: store,
propsData: componentProps
});
})
After including this my errors went away.

Categories