使用 Vue Test Utils 时无法读取未定义的 属性 '$i18n'
Cannot read property '$i18n' of undefined when using Vue Test Utils
我正在尝试使用 vue-test-utils 测试使用 VueI18n 进行翻译的 BaseDialog 组件。我无法对 运行 进行测试,出现以下错误:
TypeError: Cannot read property '$i18n' of undefined
at VueComponent.default (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/src/components/BaseDialog/BaseDialog.vue:2671:220)
at getPropDefaultValue (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:1662:11)
at validateProp (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:1619:13)
at loop (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4612:17)
at initProps (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4643:33)
at initState (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4586:21)
at VueComponent.Vue._init (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4948:5)
at new VueComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:5095:12)
at createComponentInstanceForVnode (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:3270:10)
at init (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:3101:45)
at createComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:5919:9)
at createElm (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:5866:9)
at VueComponent.patch [as __patch__] (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:6416:7)
at VueComponent.Vue._update (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:3904:19)
at VueComponent.updateComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4025:10)
at Watcher.get (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4426:25)
at new Watcher (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4415:12)
at mountComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4032:3)
at VueComponent.Object.<anonymous>.Vue.$mount (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:8350:10)
at mount (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/@vue/test-utils/dist/vue-test-utils.js:8649:21)
at shallowMount (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/@vue/test-utils/dist/vue-test-utils.js:8677:10)
at Object.it (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/src/components/BaseDialog/__tests__/BaseDialog.spec.js:22:21)
at Object.asyncJestTest (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/jasmine_async.js:108:37)
at resolve (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/queue_runner.js:56:12)
at new Promise (<anonymous>)
at mapper (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/queue_runner.js:43:19)
at promise.then (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/queue_runner.js:87:41)
at process.internalTickCallback (internal/process/next_tick.js:77:7)
我已经尝试了列出的所有解决方案 here 但没有结果。
相关代码如下:
// BaseDialog.spec.js
import { shallowMount, createLocalVue } from '@vue/test-utils'
import BaseDialog from '@/components/BaseDialog/BaseDialog'
import VueI18n from 'vue-i18n'
describe('BaseDialog', () => {
it('is called', () => {
let localVue = createLocalVue()
localVue.use(VueI18n)
const messages = {
gb: {
'ui.universal.label.ok': 'OK',
'ui.universal.label.cancel': 'Cancel'
}
}
const i18n = new VueI18n({
locale: 'gb',
fallbackLocale: 'gb',
messages
})
const wrapper = shallowMount(BaseDialog, {
i18n,
localVue
})
expect(wrapper.name()).toBe('BaseDialog')
expect(wrapper.isVueInstance()).toBeTruthy()
})
})
// BaseDialog.vue
<template>
<transition :name="animation">
<div v-if="isActive" class="dialog modal is-active" :class="size">
<div class="modal-background" @click="cancel('outside')" />
<div class="modal-card animation-content">
<header v-if="title" class="modal-card-head">
<p class="modal-card-title">{{ title }}</p>
</header>
<section
class="modal-card-body"
:class="{ 'is-titleless': !title, 'is-flex': hasIcon }"
>
<div class="media">
<div v-if="hasIcon" class="media-left">
<b-icon
:icon="icon ? icon : iconByType"
:pack="iconPack"
:type="type"
:both="!icon"
size="is-large"
/>
</div>
<div class="media-content">
<p v-html="message" />
<div v-if="hasInput" class="field">
<div class="control">
<input
ref="input"
v-model="prompt"
class="input"
:class="{ 'is-danger': validationMessage }"
v-bind="inputAttrs"
@keyup.enter="confirm"
/>
</div>
<p class="help is-danger">{{ validationMessage }}</p>
</div>
</div>
</div>
</section>
<footer class="modal-card-foot">
<button
v-if="showCancel"
ref="cancelButton"
class="button"
@click="cancel('button')"
>
{{ cancelText }}
</button>
<button
ref="confirmButton"
class="button"
:class="type"
@click="confirm"
>
{{ confirmText }}
</button>
</footer>
</div>
</div>
</transition>
</template>
<script>
import Modal from '../BaseModal/BaseModal'
import config from '../../utils/config'
import { removeElement } from '../../utils/helpers'
export default {
name: 'BaseDialog',
extends: Modal,
props: {
title: {
type: String,
default: null
},
message: {
type: String,
default: null
},
icon: {
type: String,
default: null
},
iconPack: {
type: String,
default: null
},
hasIcon: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'is-primary'
},
size: {
type: String,
default: null
},
confirmText: {
type: String,
default: () => {
return config.defaultDialogConfirmText
? config.defaultDialogConfirmText
: this.$i18n('ui.universal.label.ok')
}
},
cancelText: {
type: String,
default: () => {
return config.defaultDialogCancelText
? config.defaultDialogCancelText
: this.$i18n('ui.universal.label.cancel')
}
},
hasInput: Boolean, // Used internally to know if it's prompt
inputAttrs: {
type: Object,
default: () => ({})
},
onConfirm: {
type: Function,
default: () => {}
},
focusOn: {
type: String,
default: 'confirm'
}
},
data() {
const prompt = this.hasInput ? this.inputAttrs.value || '' : ''
return {
prompt,
isActive: false,
validationMessage: ''
}
},
computed: {
/**
* Icon name (MDI) based on the type.
*/
iconByType() {
switch (this.type) {
case 'is-info':
return 'information'
case 'is-success':
return 'check-circle'
case 'is-warning':
return 'alert'
case 'is-danger':
return 'alert-circle'
default:
return null
}
},
showCancel() {
return this.cancelOptions.indexOf('button') >= 0
}
},
beforeMount() {
// Insert the Dialog component in body tag
this.$nextTick(() => {
document.body.appendChild(this.$el)
})
},
mounted() {
this.isActive = true
if (typeof this.inputAttrs.required === 'undefined') {
this.$set(this.inputAttrs, 'required', true)
}
this.$nextTick(() => {
// Handle which element receives focus
if (this.hasInput) {
this.$refs.input.focus()
} else if (this.focusOn === 'cancel' && this.showCancel) {
this.$refs.cancelButton.focus()
} else {
this.$refs.confirmButton.focus()
}
})
},
methods: {
/**
* If it's a prompt Dialog, validate the input.
* Call the onConfirm prop (function) and close the Dialog.
*/
confirm() {
if (this.$refs.input !== undefined) {
if (!this.$refs.input.checkValidity()) {
this.validationMessage = this.$refs.input.validationMessage
this.$nextTick(() => this.$refs.input.select())
return
}
}
this.onConfirm(this.prompt)
this.close()
},
/**
* Close the Dialog.
*/
close() {
this.isActive = false
// Timeout for the animation complete before destroying
setTimeout(() => {
this.$destroy()
removeElement(this.$el)
}, 150)
}
}
}
</script>
<style lang="scss">
.dialog {
.modal-card {
max-width: 460px;
width: auto;
.modal-card-head {
font-size: $size-5;
font-weight: $weight-semibold;
}
.modal-card-body {
.field {
margin-top: 16px;
}
}
.modal-card-foot {
justify-content: flex-end;
.button {
display: inline; // Fix Safari centering
min-width: 5em;
font-weight: $weight-semibold;
}
}
@include tablet {
min-width: 320px;
}
}
&.is-small {
.modal-card,
.input,
.button {
@include control-small;
}
}
&.is-medium {
.modal-card,
.input,
.button {
@include control-medium;
}
}
&.is-large {
.modal-card,
.input,
.button {
@include control-large;
}
}
}
</style>
我真的不知道还能在这里尝试什么。这是一个项目的开始,在这个项目中,我必须支持 9 种语言,每个语言有 500 多个键,所以我必须让它工作。非常感谢任何帮助。
问题是我在道具中引用了 this
。在实例化组件之前处理道具,因此我无法访问 this
。总是一些小事让你bash头撞墙哈哈哈。
我正在尝试使用 vue-test-utils 测试使用 VueI18n 进行翻译的 BaseDialog 组件。我无法对 运行 进行测试,出现以下错误:
TypeError: Cannot read property '$i18n' of undefined
at VueComponent.default (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/src/components/BaseDialog/BaseDialog.vue:2671:220)
at getPropDefaultValue (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:1662:11)
at validateProp (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:1619:13)
at loop (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4612:17)
at initProps (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4643:33)
at initState (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4586:21)
at VueComponent.Vue._init (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4948:5)
at new VueComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:5095:12)
at createComponentInstanceForVnode (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:3270:10)
at init (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:3101:45)
at createComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:5919:9)
at createElm (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:5866:9)
at VueComponent.patch [as __patch__] (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:6416:7)
at VueComponent.Vue._update (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:3904:19)
at VueComponent.updateComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4025:10)
at Watcher.get (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4426:25)
at new Watcher (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4415:12)
at mountComponent (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:4032:3)
at VueComponent.Object.<anonymous>.Vue.$mount (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/vue/dist/vue.runtime.common.dev.js:8350:10)
at mount (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/@vue/test-utils/dist/vue-test-utils.js:8649:21)
at shallowMount (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/@vue/test-utils/dist/vue-test-utils.js:8677:10)
at Object.it (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/src/components/BaseDialog/__tests__/BaseDialog.spec.js:22:21)
at Object.asyncJestTest (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/jasmine_async.js:108:37)
at resolve (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/queue_runner.js:56:12)
at new Promise (<anonymous>)
at mapper (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/queue_runner.js:43:19)
at promise.then (/Users/mmelv/Workspace/Projects/Vue/vue-pux-port/node_modules/jest-jasmine2/build/queue_runner.js:87:41)
at process.internalTickCallback (internal/process/next_tick.js:77:7)
我已经尝试了列出的所有解决方案 here 但没有结果。
相关代码如下:
// BaseDialog.spec.js
import { shallowMount, createLocalVue } from '@vue/test-utils'
import BaseDialog from '@/components/BaseDialog/BaseDialog'
import VueI18n from 'vue-i18n'
describe('BaseDialog', () => {
it('is called', () => {
let localVue = createLocalVue()
localVue.use(VueI18n)
const messages = {
gb: {
'ui.universal.label.ok': 'OK',
'ui.universal.label.cancel': 'Cancel'
}
}
const i18n = new VueI18n({
locale: 'gb',
fallbackLocale: 'gb',
messages
})
const wrapper = shallowMount(BaseDialog, {
i18n,
localVue
})
expect(wrapper.name()).toBe('BaseDialog')
expect(wrapper.isVueInstance()).toBeTruthy()
})
})
// BaseDialog.vue
<template>
<transition :name="animation">
<div v-if="isActive" class="dialog modal is-active" :class="size">
<div class="modal-background" @click="cancel('outside')" />
<div class="modal-card animation-content">
<header v-if="title" class="modal-card-head">
<p class="modal-card-title">{{ title }}</p>
</header>
<section
class="modal-card-body"
:class="{ 'is-titleless': !title, 'is-flex': hasIcon }"
>
<div class="media">
<div v-if="hasIcon" class="media-left">
<b-icon
:icon="icon ? icon : iconByType"
:pack="iconPack"
:type="type"
:both="!icon"
size="is-large"
/>
</div>
<div class="media-content">
<p v-html="message" />
<div v-if="hasInput" class="field">
<div class="control">
<input
ref="input"
v-model="prompt"
class="input"
:class="{ 'is-danger': validationMessage }"
v-bind="inputAttrs"
@keyup.enter="confirm"
/>
</div>
<p class="help is-danger">{{ validationMessage }}</p>
</div>
</div>
</div>
</section>
<footer class="modal-card-foot">
<button
v-if="showCancel"
ref="cancelButton"
class="button"
@click="cancel('button')"
>
{{ cancelText }}
</button>
<button
ref="confirmButton"
class="button"
:class="type"
@click="confirm"
>
{{ confirmText }}
</button>
</footer>
</div>
</div>
</transition>
</template>
<script>
import Modal from '../BaseModal/BaseModal'
import config from '../../utils/config'
import { removeElement } from '../../utils/helpers'
export default {
name: 'BaseDialog',
extends: Modal,
props: {
title: {
type: String,
default: null
},
message: {
type: String,
default: null
},
icon: {
type: String,
default: null
},
iconPack: {
type: String,
default: null
},
hasIcon: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'is-primary'
},
size: {
type: String,
default: null
},
confirmText: {
type: String,
default: () => {
return config.defaultDialogConfirmText
? config.defaultDialogConfirmText
: this.$i18n('ui.universal.label.ok')
}
},
cancelText: {
type: String,
default: () => {
return config.defaultDialogCancelText
? config.defaultDialogCancelText
: this.$i18n('ui.universal.label.cancel')
}
},
hasInput: Boolean, // Used internally to know if it's prompt
inputAttrs: {
type: Object,
default: () => ({})
},
onConfirm: {
type: Function,
default: () => {}
},
focusOn: {
type: String,
default: 'confirm'
}
},
data() {
const prompt = this.hasInput ? this.inputAttrs.value || '' : ''
return {
prompt,
isActive: false,
validationMessage: ''
}
},
computed: {
/**
* Icon name (MDI) based on the type.
*/
iconByType() {
switch (this.type) {
case 'is-info':
return 'information'
case 'is-success':
return 'check-circle'
case 'is-warning':
return 'alert'
case 'is-danger':
return 'alert-circle'
default:
return null
}
},
showCancel() {
return this.cancelOptions.indexOf('button') >= 0
}
},
beforeMount() {
// Insert the Dialog component in body tag
this.$nextTick(() => {
document.body.appendChild(this.$el)
})
},
mounted() {
this.isActive = true
if (typeof this.inputAttrs.required === 'undefined') {
this.$set(this.inputAttrs, 'required', true)
}
this.$nextTick(() => {
// Handle which element receives focus
if (this.hasInput) {
this.$refs.input.focus()
} else if (this.focusOn === 'cancel' && this.showCancel) {
this.$refs.cancelButton.focus()
} else {
this.$refs.confirmButton.focus()
}
})
},
methods: {
/**
* If it's a prompt Dialog, validate the input.
* Call the onConfirm prop (function) and close the Dialog.
*/
confirm() {
if (this.$refs.input !== undefined) {
if (!this.$refs.input.checkValidity()) {
this.validationMessage = this.$refs.input.validationMessage
this.$nextTick(() => this.$refs.input.select())
return
}
}
this.onConfirm(this.prompt)
this.close()
},
/**
* Close the Dialog.
*/
close() {
this.isActive = false
// Timeout for the animation complete before destroying
setTimeout(() => {
this.$destroy()
removeElement(this.$el)
}, 150)
}
}
}
</script>
<style lang="scss">
.dialog {
.modal-card {
max-width: 460px;
width: auto;
.modal-card-head {
font-size: $size-5;
font-weight: $weight-semibold;
}
.modal-card-body {
.field {
margin-top: 16px;
}
}
.modal-card-foot {
justify-content: flex-end;
.button {
display: inline; // Fix Safari centering
min-width: 5em;
font-weight: $weight-semibold;
}
}
@include tablet {
min-width: 320px;
}
}
&.is-small {
.modal-card,
.input,
.button {
@include control-small;
}
}
&.is-medium {
.modal-card,
.input,
.button {
@include control-medium;
}
}
&.is-large {
.modal-card,
.input,
.button {
@include control-large;
}
}
}
</style>
我真的不知道还能在这里尝试什么。这是一个项目的开始,在这个项目中,我必须支持 9 种语言,每个语言有 500 多个键,所以我必须让它工作。非常感谢任何帮助。
问题是我在道具中引用了 this
。在实例化组件之前处理道具,因此我无法访问 this
。总是一些小事让你bash头撞墙哈哈哈。