#javascript #unit-testing #vue.js #jestjs #vue-component
#javascript #модульное тестирование #vue.js #jestjs #vue-компонент
Вопрос:
Я изо всех сил пытаюсь выполнить модульное тестирование некоторых компонентов в приложении, и я был бы очень признателен, если бы кто-нибудь мог мне помочь. Я уже настроил среду Jest для тестирования в Vue, и все работает нормально, пока я не попытаюсь протестировать компонент, который полагается на другие модули с функциями в нем. Эти функции являются помощниками, полезными для более быстрого импорта действий vuex и геттеров в компоненты. Здесь есть несколько строк кода, которые, я надеюсь, помогут решить проблему:
- это ошибка, которую Jest выдает в консоли:
/app # yarn test:unit /common / page - header / ProjectWizard.spec.js
yarn run v1 .22 .5
$ jest / common / page - header / ProjectWizard.spec.js
FAIL src / specs / common / page - header / projectWizard.spec.js
● Test suite failed to run
Configuration error:
Could not locate module @ / state / helpers mapped as:
/app/$1.
Please check your configuration
for these entries: {
"moduleNameMapper": {
"/^@/(.*)$/": "/app/$1"
},
"resolver": undefined
}
57 |
import VueGoogleAutocomplete from '../../../node_modules/vue-google-autocomplete'
58 |
> 59 |
import {
|
^
60 | contractTypesMethods,
61 | contractTypesGetters,
62 | installationTypesMethods,
at createNoMappedModuleFoundError(node_modules / jest - resolve / build / index.js: 551: 17)
at src / components / builder / ProjectWizard.vue: 59: 1
at Object. < anonymous > (src / components / builder / ProjectWizard.vue: 491: 3)
- это файл ProjectWizard:
<template>
<form-wizard
@on-complete="onComplete(model)"
error-color="#f1b44c"
color="#EC641C"
nextButtonText="Avanti"
backButtonText="Indietro"
:finishButtonText="(isModifying) ? 'Conferma modifiche' : 'Crea Progetto'"
id="project-form-wizard"
style="overflow: hidden;"
>
<tab-content title="Informazioni preliminari" icon="mdi mdi-folder-information" :before-change="validateFirstTab">
<vue-form-generator-component :model="model" :schema="zeroTabSchema" :options="formOptions" ref="zeroTabSchema">
</vue-form-generator-component>
<b-form-group featured class="col-12" label="Indirizzo (Via etc.)" label-for="indirizzo-progetto">
<vue-google-autocomplete required keypress types="" ref="address" id="indirizzo-progetto"
classname="form-control" placeholder="Inserisci qui l'indirizzo del progetto"
v-on:placechanged="getAddressData" country="it">
</vue-google-autocomplete>
</b-form-group>
<vue-form-generator-component :model="model" :schema="firstTabSchema" :options="formOptions" ref="firstTabForm">
</vue-form-generator-component>
</tab-content>
<tab-content
title="Dettagli progetto"
icon="mdi mdi-home-search-outline"
:before-change="validateSecondTab"
>
<vue-form-generator-component
:model="model"
:schema="secondTabSchema"
:options="formOptions"
ref="secondTabForm"
></vue-form-generator-component>
</tab-content>
</form-wizard>
</template>
<script>
import {
FormWizard,
TabContent
} from "vue-form-wizard";
import VueFormGenerator from 'vue-form-generator'
import {
resources
} from "../../../src/utils/validation"
Object.assign(VueFormGenerator.validators.resources, resources)
import VueGoogleAutocomplete from '../../../node_modules/vue-google-autocomplete'
import {
contractTypesMethods,
contractTypesGetters,
installationTypesMethods,
installationTypesGetters,
interventionTypesMethods,
interventionTypesGetters,
contactsMethods,
projectManagementMethods,
projectManagementGetters,
projectsMethods
} from "@/state/helpers";
// STATELESS
import {
fetchContacts
} from '@/stateless/modules/common'
export default {
data() {
var vm = this;
return {
...contractTypesGetters,
...installationTypesGetters,
...interventionTypesGetters,
...projectManagementGetters,
contractTypes: [],
selectedContract: "",
installationTypesFiltered: [],
interventionTypesFiltered: [],
installationTypes: [],
interventionTypes: [],
contactPersons: [],
address: '',
model: {
projectName: '',
address: {
partialStreet: '',
streetNumber: '',
city: '',
province: '',
country: '',
CAP: '',
state: ''
},
contractType: '',
installationType: '',
interventionType: '',
contactPerson: '',
description: ''
},
formOptions: {
validationErrorClass: "has-error",
validationSuccessClass: "has-success",
validateAfterChanged: true,
},
zeroTabSchema: {
fields: [{
type: "input",
inputType: "text",
label: "Nome del progetto",
model: "projectName",
required: true,
featured: true,
maxlength: 64,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-12'
},]
},
firstTabSchema: {
fields: [
{
type: "input",
inputType: "text",
label: "Via",
model: "address.partialStreet",
required: true,
featured: true,
maxlength: 128,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-6'
},
{
type: "input",
inputType: "text",
label: "Numero civico",
model: "address.streetNumber",
required: true,
featured: true,
maxlength: 16,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-6'
},
{
type: "input",
inputType: "text",
label: "Città",
model: "address.city",
required: true,
featured: true,
maxlength: 64,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-6'
},
{
type: "input",
inputType: "text",
label: "Provincia",
model: "address.province",
required: true,
featured: true,
maxlength: 2,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-6'
},
{
type: "input",
inputType: "text",
label: "Paese",
model: "address.country",
required: true,
featured: true,
maxlength: 32,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-6'
},
{
type: "input",
inputType: "text",
label: "CAP",
model: "address.CAP",
required: true,
featured: true,
min: 5,
maxlength: 8,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-6'
},
{
type: "input",
inputType: "text",
visible: false,
label: "Regione",
model: "address.state",
required: true,
featured: true,
maxlength: 32,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-6'
},
]
},
secondTabSchema: {
fields: [{
type: "select",
label: "Tipo di appalto",
model: "contractType",
required: true,
featured: true,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-12',
selectOptions: {
hideNoneSelectedText: true
},
onChanged(event) {
const filterdTypes = vm.installationTypes.filter((element) => {
return element.contractType === event.contractType
})
let installation = document.getElementById('insediamento')
installation.innerHTML = ''
installation.disabled = false
for (let i = 0; i < filterdTypes.length; i ) {
installation.options[i] = new Option(
filterdTypes[i].name,
filterdTypes[i].id
)
}
installation.value = ""
let intervention = document.getElementById('tipologia-di-intervento')
intervention.value = ""
},
values: () => {
return this.contractTypes
},
},
{
type: "select",
disabled: "true",
label: "Insediamento",
model: "installationType",
required: true,
featured: true,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-12',
selectOptions: {
hideNoneSelectedText: true
},
onChanged(event) {
const filterdTypes = vm.interventionTypes.filter((element) => {
return element.installationType === event.installationType
})
let intervention = document.getElementById('tipologia-di-intervento')
intervention.innerHTML = ''
intervention.disabled = false
for (let i = 0; i < filterdTypes.length; i ) {
intervention.options[i] = new Option(
filterdTypes[i].name,
filterdTypes[i].id
)
}
intervention.value = ""
},
values: () => {
return this.isModifying ? this.installationTypes : []
},
},
{
type: "select",
disabled: "true",
noneSelectedText: "",
label: "Tipologia di intervento",
model: "interventionType",
required: true,
featured: true,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-12',
selectOptions: {
hideNoneSelectedText: true
},
values: () => {
return this.isModifying ? this.interventionTypes : []
},
},
{
type: "select",
label: "Referente di progetto",
model: "contactPerson",
required: true,
featured: true,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-12',
selectOptions: {
hideNoneSelectedText: true
},
hint: '<div id="new-contact-creation-project"><i class="mdi mdi-account-multiple-plus"></i> <span>Crea nuovo referente</span></div>',
values: () => {
return this.contactPersons
},
},
{
type: "textArea",
label: "Descrizione",
model: "description",
featured: true,
maxlength: 512,
validator: VueFormGenerator.validators.string,
styleClasses: 'col-12'
}
]
}
}
},
components: {
FormWizard,
TabContent,
'vue-form-generator-component': VueFormGenerator.component,
VueGoogleAutocomplete
},
computed: {
isModifying() {
return (this.$router.currentRoute.name == 'builderHome') ? false : true
},
},
methods: {
...contractTypesMethods,
...installationTypesMethods,
...interventionTypesMethods,
...contactsMethods,
...projectsMethods,
...projectManagementMethods,
onComplete: function (json) {
if (this.isModifying) {
this.updateProject({
projectId: this.currentProject().id,
name: this.model.projectName,
description: this.model.description,
address: this.model.address.partialStreet,
streetNumber: this.model.address.streetNumber,
city: this.model.address.city,
province: this.model.address.province,
country: this.model.address.country,
zipCode: this.model.address.CAP,
contactPerson: this.model.contactPerson,
contractType: this.model.contractType,
installationType: this.model.installationType,
interventionType: this.model.interventionType
}).then(() => location.reload())
} else {
this.addProject(json)
.then(response => {
if (response) {
this.$router.push({
name: 'BOMUpload',
params: { projectId: response.data.data.createProject.project.id }
})
}
})
}
},
validateFirstTab: function () {
if (!this.model.projectName) {
return alert('Il nome del progetto è obbligatorio')
}
return this.$refs.firstTabForm.validate();
},
validateSecondTab: function () {
return this.$refs.secondTabForm.validate();
},
newContactPersonCreation() {
this.$emit('new-contact-person-creation')
},
getAddressData: function (addressData, placeResultData) {
this.address = addressData;
this.model.address.country = addressData.country;
this.model.address.city = addressData.locality;
if (placeResultData.address_components[4].short_name == 'IT') {
this.model.address.province = placeResultData.address_components[2].short_name
} else if (placeResultData.address_components[4].short_name.length > 2) {
this.model.address.province = placeResultData.address_components[3].short_name
} else {
this.model.address.province = placeResultData.address_components[4].short_name
}
this.model.address.partialStreet = addressData.route;
this.model.address.streetNumber = addressData.street_number;
this.model.address.CAP = addressData.postal_code;
this.model.address.state = addressData.administrative_area_level_1;
},
getContacts() {
fetchContacts()
.then(response => {
this.contactPersons = response.map(contact => {
return { id: contact.node.id, name: contact.node.name }
})
this.model.contactPerson = this.contactPersons.slice(-2).pop().id
if (this.isModifying) {
this.model.contactPerson = this.currentProject().contactPerson.id
}
})
.catch(err => console.error('Riga 417 ProjectWizard.vue', err))
}
},
mounted() {
if (this.isModifying) {
this.model.projectName = this.currentProject().name
this.model.address.partialStreet = this.currentProject().address.address
this.model.address.streetNumber = this.currentProject().address.streetNumber
this.model.address.city = this.currentProject().address.city
this.model.address.province = this.currentProject().address.province
this.model.address.country = this.currentProject().address.country
this.model.address.CAP = this.currentProject().address.zipCode
this.model.description = this.currentProject().description
}
var newContact = document.querySelector("#new-contact-creation-project")
newContact.onclick = () => {
this.newContactPersonCreation()
};
// CONTRACT TYPES
this.fetchContractTypes()
.then(response => {
if (response) {
let processedContractTypes = []
this.contractTypes = this.allContractTypes()
this.contractTypes.forEach(contractType => {
let contractTypeObj = { id: contractType.node.id, name: contractType.node.name }
processedContractTypes.push(contractTypeObj)
})
this.contractTypes = processedContractTypes
if (this.isModifying){
this.model.contractType = this.currentProject().contractType.id
}
} else {
alert('Not working fetchContractTypes')
}
})
// INSTALLATION TYPES
this.fetchInstallationTypes()
.then(response => {
if (response) {
let processedInstallationTypes = []
this.installationTypes = this.allInstallationTypes()
this.installationTypes.forEach(installationType => {
let installationTypeObj = {
id: installationType.node.id,
name: installationType.node.name,
contractType: installationType.node.contractType.id
}
processedInstallationTypes.push(installationTypeObj)
})
this.installationTypes = processedInstallationTypes
if (this.isModifying){
this.model.installationType = this.currentProject().installationType.id
}
} else {
alert('Not working fetchInstallationTypes')
}
})
// INTERVENTION TYPES
this.fetchInterventionTypes()
.then(response => {
if (response) {
let processedInterventionTypes = []
this.interventionTypes = this.allInterventionTypes()
this.interventionTypes.forEach(interventionType => {
let interventionTypeObj = {
id: interventionType.node.id,
name: interventionType.node.name,
installationType: interventionType.node.installationType.id,
}
processedInterventionTypes.push(interventionTypeObj)
})
this.interventionTypes = processedInterventionTypes
if (this.isModifying){
this.model.interventionType = this.currentProject().interventionType.id
}
} else {
alert('Not working fetchInterventionTypes')
}
})
// CONTACT PERSONS
this.getContacts()
},
}
</script>
- это мой файл конфигурации jest:
module.exports = {
verbose: true,
roots: ["<rootDir>/src/", "<rootDir>/src/specs/"],
moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
moduleNameMapper: {
'^@/(.*)
- и, наконец, это код в файле спецификации:
import { mount } from '@vue/test-utils'
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'
import Component from '@/src/components/builder/ProjectWizard'
Vue.use(BootstrapVue)
describe('ProjectWizard', () => {
it('should render a card for each project received from its parent', () => {
const wrapper = mount(Component)
console.log(wrapper)
})
})
Я не понимаю, почему Jest не может найти эти функции, поскольку местоположение модуля правильное.
Комментарии:
1. Может быть, предоставить код
ProjectWizard.vue
?2. импорт или запрос from
node_modules
с относительным путем является нестандартным и избыточным и может создавать проблемы с webpack и вызывать проблемы. Я бы попробовалimport VueGoogleAutocomplete from 'vue-google-autocomplete'
вместо этого.
: '<rootDir>/$1',
"~(.*)$": "<rootDir>/$1"
},
transform: {
"^. \.js$": "babel-jest",
'. \.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2|svg)
- и, наконец, это код в файле спецификации:
Я не понимаю, почему Jest не может найти эти функции, поскольку местоположение модуля правильное.
Комментарии:
1. Может быть, предоставить код
ProjectWizard.vue
?2. импорт или запрос from
node_modules
с относительным путем является нестандартным и избыточным и может создавать проблемы с webpack и вызывать проблемы. Я бы попробовалimport VueGoogleAutocomplete from 'vue-google-autocomplete'
вместо этого.
: 'jest-transform-stub',
"^. \.vue$": "vue-jest"
},
snapshotSerializers: [
"<rootDir>/node_modules/jest-serializer-vue"
],
transformIgnorePatterns: [
'<rootDir>/node_modules/(?!vue-google-autocomplete)',
'<rootDir>/(?!)'
]
}
- и, наконец, это код в файле спецификации:
Я не понимаю, почему Jest не может найти эти функции, поскольку местоположение модуля правильное.
Комментарии:
1. Может быть, предоставить код
ProjectWizard.vue
?2. импорт или запрос from
node_modules
с относительным путем является нестандартным и избыточным и может создавать проблемы с webpack и вызывать проблемы. Я бы попробовалimport VueGoogleAutocomplete from 'vue-google-autocomplete'
вместо этого.