New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and
privacy statement. We’ll occasionally send you account related emails.
Already on GitHub?
Sign in
to your account
Labels
has PR
A pull request has already been submitted to solve the issue
🐞 bug
Something isn’t working
Comments
Does this only happen in the test, but works fine when using the code in an app? Then that mit be something related to the test-utils. I can transfer the issue if so.
Copy link
Contributor
Author
When using the component that the aforementioned test file is testing in a Vue app, it doesn’t render without issues. The most glaring issue is that in the mounted
hook, the $refs
object is still empty, even though I have two refs defined in the template section (both not being conditionally rendered). I’m getting two Missing ref owner context. ref cannot be used on hoisted vnodes. A vnode with ref must be created inside the render function.
warnings (likely one for each ref instance in the template. Furthermore, five Invalid VNode type: Symbol(Comment) (symbol)
warnings are printed after the created
hook is executed, but before the mounted
hook is executed. Finally, a Unhandled error during execution of mounted hook
warning is shown because in the mounted
hook, one of the refs is being accessed, but since it’s undefined, a TypeError
is thrown.
Now I’m not certain that either of those issues is necessarily related to what I reported initially. It might be that I’ve done something wrong when migrating the consuming app to Vue 3 or something of my tooling dependencies isn’t quite ready to be used with Vue 3, yet. But also, both the component that is the subject of the report above and the consuming app are relatively small and basic. I’ll try to reduce the test setup for this to figure out more.
I can confirm a similar issue while trying to migrate a v2 codebase to v3. Inside of a component watcher I expect this.$el to be set, but it is null.
I’m having the same problem also when migrating from v2 to v3 this.$el
ends up being null while in v2 it works properly
Copy link
Contributor
Author
In my naïve hopes to possibly fix this myself, I wrote a two test cases which — as far as I understand — should both pass:
- setting an instance’s watched data property
- rendering an instance’s watched prop
Both should have access to the instance’s $el
property. I wrote them in packages/runtime-core/__tests__/rendererComponent.spec.ts
.
should have access to instance’s “$el” property in watcher when setting instance data
// #2170 test('should have access to instance’s “$el” property in watcher when setting instance data', async () => { function returnThis(this: any) { return this } const dataWatchSpy = jest.fn(returnThis) let instance: any const Comp = { data() { return { testData: undefined } }, watch: { testData() { // @ts-ignore dataWatchSpy(this.$el) } }, created() { instance = this }, render() { return h('div') }, } const root = nodeOps.createElement('div') render(h(Comp), root) expect(dataWatchSpy).not.toHaveBeenCalled() instance.testData = 'data' await nextTick() expect(dataWatchSpy).toHaveBeenCalledWith(instance.$el) })
This test passes.
should have access to instance’s “$el” property in watcher when rendereing with watched prop’
// #2170 test('should have access to instance’s “$el” property in watcher when rendereing with watched prop', async () => { function returnThis(this: any) { return this } const propWatchSpy = jest.fn(returnThis) let instance: any const Comp = { props: { testProp: String }, watch: { testProp() { // @ts-ignore propWatchSpy(this.$el) } }, created() { instance = this }, render() { return h('div') }, } const root = nodeOps.createElement('div') render(h(Comp), root) await nextTick() expect(propWatchSpy).not.toHaveBeenCalled() render(h(Comp, { testProp: 'prop ' }), root) await nextTick() expect(propWatchSpy).toHaveBeenCalledWith(instance.$el) })
This test fails because propWatchSpy
is called with null
instead of what should be the instance’s $el
.
Copy link
Contributor
Author
I now understand @LinusBorg’s investigation and think I can provide a fix for this. If that’s alright, I’d like to submit a pull request for this.
LinusBorg
added
the
has PR
A pull request has already been submitted to solve the issue
label
Oct 4, 2020
yyx990803
pushed a commit
that referenced
this issue
Oct 5, 2020
Labels
has PR
A pull request has already been submitted to solve the issue
🐞 bug
Something isn’t working
Swiper Vue: Uncaught TypeError when using SwiperSlide as a wrapper
Check that this is really a bug
- I confirm
Reproduction link
Bug description
Check the browser console. You get the warning and error:
Unhandled error during execution of mounted hook
Uncaught` TypeError: Cannot read properties of undefined (reading ‘value’)
at VM54 swiper_vue.js:1229:22
This only happens, if I use my own Slide as a wrapper to add some extra content.
Expected Behavior
No error and warning. You should use the <swiper-slide>
in a wrapper as well.
Actual Behavior
Swiper is not initialized.
Swiper version
^8.0.0
Platform/Target and Browser Versions
mac
Validations
- Follow our Code of Conduct
- Read the docs.
- Check that there isn’t already an issue that request the same feature to avoid creating a duplicate.
- Make sure this is a Swiper issue and not a framework-specific issue
Would you like to open a PR for this bug?
- I’m willing to open a PR
Same issue using Vue 2.7.x
Same issue using Vue 2.7.x
I gave up with this plugin……
Same issue here with 2.7.x vue version
Same issue using Vue 2.6.x
Also having issues Vue 3 (Nuxt 3 rc.9)
Error comes from swiperRef
onMounted(() => { if (!swiperRef.value) return; swiperRef.value.on('_slideClass', updateClasses); eventAttached = true; });
Possible workaround would be passing ref directly. However, I’m having issues with sliders rendering outside .swiper-wrapper
<template> <swiper-slide :swiperRef="swiper"> <slot /> </swiper-slide> </template> <script> import { SwiperSlide, useSwiper } from 'swiper/vue' export default { components: { SwiperSlide, }, setup() { const swiper = useSwiper() return { swiper, } }, } </script>
Hi,
I’m working on a component that includes Password component. I would like to write unit test for it, but I get an error that I don’t understand when I try to unit test the component.
We’re using Primevue 3.3.5 with TS and Jest.
Here is the code without Password component that works great:
Code: Select all
<template>
<InputText v-model="letter" class="p-mr-2 p-ml-2 p-d-inline letter-input" :class="{'p-invalid' : error }" size="1" />
</template>
<script lang="ts">
import { defineComponent, watch } from "vue";
import * as yup from 'yup';
import { useField, useForm, useValidateForm } from 'vee-validate';
export default defineComponent({
name: "Letters",
props: {
modelValue: String,
submitTrigger: Boolean,
},
emits: ["update:modelValue", "error"],
setup(props, { emit }) {
const schema = yup.object({
letter: yup.string().min(1).max(1).matches(/^[a-z]*$/, 'letter must be a lowercase letter'),
});
const { meta } = useForm({
validationSchema: schema,
initialValues: {letter: ''}
});
const { errorMessage: error, value: letter } = useField('letter', schema);
const validate = useValidateForm();
watch(() => meta.value, () => {
console.log(error.value)
emit("update:modelValue", letter.value);
emit("error", error.value ? error.value : "");
});
watch(() => props.submitTrigger, () => {
validate().then(() => {
emit("error", error.value ? error.value : "");
});
});
return {
letter,
error,
};
},
})
</script>
<style scoped>
.letter-input {
text-align: center;
}
</style>
This component just has an input that should contain a lowercase letter. It emits the status of error to the parent component.
Here’s the unit test associated (very basic):
Code: Select all
import { mount, shallowMount } from "@vue/test-utils";
import InputText from 'primevue/inputtext';
import OneLetter from "@/components/OneLetter.vue";
describe("Letters.vue", () => {
let one_letter_component = shallowMount(OneLetter, {
global: {
components: {
'InputText': InputText,
}
}
});
beforeEach(() => {
one_letter_component = mount(OneLetter, {
global: {
components: {
'InputText': InputText,
}
}
});
});
it("initialize with empty letter", () => {
expect(one_letter_component.vm.letter).toEqual("");
});
it("set value letter on a one-letter input", async () => {
await one_letter_component.find(".letter-input").setValue("a");
expect(one_letter_component.vm.letter).toEqual("a");
});
});
Now the tricky part: I add the Password component and do nothing with it. I added the import in main.ts so it’s globally imported. Here’s the code of the component:
Code: Select all
<template>
<InputText v-model="letter" class="p-mr-2 p-ml-2 p-d-inline letter-input" :class="{'p-invalid' : error }" size="1" />
<Password />
</template>
<script lang="ts">
import { defineComponent, watch } from "vue";
import * as yup from 'yup';
import { useField, useForm, useValidateForm } from 'vee-validate';
export default defineComponent({
name: "Letters",
props: {
modelValue: String,
submitTrigger: Boolean,
},
emits: ["update:modelValue", "error"],
setup(props, { emit }) {
const schema = yup.object({
letter: yup.string().min(1).max(1).matches(/^[a-z]*$/, 'letter must be a lowercase letter'),
});
const { meta } = useForm({
validationSchema: schema,
initialValues: {letter: ''}
});
const { errorMessage: error, value: letter } = useField('letter', schema);
const validate = useValidateForm();
watch(() => meta.value, () => {
console.log(error.value)
emit("update:modelValue", letter.value);
emit("error", error.value ? error.value : "");
});
watch(() => props.submitTrigger, () => {
validate().then(() => {
emit("error", error.value ? error.value : "");
});
});
return {
letter,
error,
};
},
})
</script>
<style scoped>
.letter-input {
text-align: center;
}
</style>
Everything is unchanged except in the template, there is a <Password /> tag.
To use this component in test, I need to add the Password component to the global component list when mounting the component, so here’s the code of the test with just the addition of Password:
Code: Select all
import { mount, shallowMount } from "@vue/test-utils";
import InputText from 'primevue/inputtext';
import Password from "primevue/password";
import OneLetter from "@/components/OneLetter.vue";
describe("Letters.vue", () => {
let one_letter_component = shallowMount(OneLetter, {
global: {
components: {
'InputText': InputText,
'Password': Password,
}
}
});
beforeEach(() => {
one_letter_component = mount(OneLetter, {
global: {
components: {
'InputText': InputText,
'Password': Password,
}
}
});
});
it("initialize with empty letter", () => {
expect(one_letter_component.vm.letter).toEqual("");
});
it("set value letter on a one-letter input", async () => {
await one_letter_component.find(".letter-input").setValue("a");
expect(one_letter_component.vm.letter).toEqual("a");
});
});
And now there’s an error:
Code: Select all
FAIL tests/unit/one_letter.spec.ts
- Console
console.warn node_modules/.pnpm/registry.npmmirror.com+@vue+runtime-core@3.2.20/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:6482
[Vue warn]: Unhandled error during execution of mounted hook
at <Password>
at <Letters ref="VTU_COMPONENT" >
at <VTUROOT>
- OneLetters.vue > initialize with empty letter
TypeError: Cannot read property 'config' of undefined
14 | });
15 | beforeEach(() => {
> 16 | one_letter_component = mount(OneLetter, {
| ^
17 | global: {
18 | components: {
19 | 'InputText': InputText,
at Proxy.promptText (node_modules/primevue/password/password.cjs.js:282:55)
at ReactiveEffect.run (node_modules/.pnpm/registry.npmmirror.com+@vue+reactivity@3.2.20/node_modules/@vue/reactivity/dist/reactivity.cjs.js:164:29)
at ComputedRefImpl.get value [as value] (node_modules/.pnpm/registry.npmmirror.com+@vue+reactivity@3.2.20/node_modules/@vue/reactivity/dist/reactivity.cjs.js:1075:39)
at Object.get [as promptText] (node_modules/.pnpm/registry.npmmirror.com+@vue+runtime-core@3.2.20/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:2122:30)
at Object.get (node_modules/.pnpm/registry.npmmirror.com+@vue+runtime-core@3.2.20/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5894:27)
at Proxy.mounted (node_modules/primevue/password/password.cjs.js:84:30)
at callWithErrorHandling (node_modules/.pnpm/registry.npmmirror.com+@vue+runtime-core@3.2.20/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:6599:22)
at callWithAsyncErrorHandling (node_modules/.pnpm/registry.npmmirror.com+@vue+runtime-core@3.2.20/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:6608:21)
at Array.hook.__weh.hook.__weh (node_modules/.pnpm/registry.npmmirror.com+@vue+runtime-core@3.2.20/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:1948:29)
at flushPostFlushCbs (node_modules/.pnpm/registry.npmmirror.com+@vue+runtime-core@3.2.20/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:6794:47)
at render (node_modules/.pnpm/registry.npmmirror.com+@vue+runtime-core@3.2.20/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4767:9)
at mount (node_modules/.pnpm/registry.npmmirror.com+@vue+runtime-core@3.2.20/node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:3146:25)
at Object.app.mount (node_modules/.pnpm/registry.npmmirror.com+@vue+runtime-dom@3.2.20/node_modules/@vue/runtime-dom/dist/runtime-dom.cjs.js:1521:23)
at mount (node_modules/.pnpm/registry.npmmirror.com+@vue+test-utils@2.0.0-rc.16_vue@3.2.20/node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js:7860:18)
at Object.<anonymous> (tests/unit/one_letter.spec.ts:16:32)
- OneLetters.vue > set value letter on a one-letter input
TypeError: Cannot set property 'hasOwnProperty' of undefined
14 | });
15 | beforeEach(() => {
> 16 | one_letter_component = mount(OneLetter, {
| ^
17 | global: {
18 | components: {
19 | 'InputText': InputText,
at mount (node_modules/.pnpm/registry.npmmirror.com+@vue+test-utils@2.0.0-rc.16_vue@3.2.20/node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js:7866:27)
at Object.<anonymous> (tests/unit/one_letter.spec.ts:16:32)
- OneLetters.vue > set value letter on a one-letter input
wrapper.setValue() cannot be called on INPUT-TEXT-STUB
27 | });
28 | it("set value letter on a one-letter input", async () => {
> 29 | await one_letter_component.find(".letter-input").setValue("a");
| ^
30 |
31 | expect(one_letter_component.vm.letter).toEqual("a");
32 | });
at DOMWrapper.Object.<anonymous>.DOMWrapper.setValue (node_modules/.pnpm/registry.npmmirror.com+@vue+test-utils@2.0.0-rc.16_vue@3.2.20/node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js:7389:19)
at Object.<anonymous> (tests/unit/one_letter.spec.ts:29:58)
I don’t understand why I get the error for this component, it seems to work smoothly for all other components such as Dropdown.
Thanks for your help!
And congratulations for this amazing lib, I really enjoy working with it, apart for unit test
У меня есть компонент и магазин Pinia, который содержит состояние и некоторые действия. Код отлично работает в браузере и в тестах E2E (кипарис), но не работает в модульных тестах. Я использую vue-testing-utils и vitest.
Функцию хранилища можно назвать отличной из модульного теста при нажатии кнопки, но если функция находится в смонтированном или основном скрипте, она не проходит тест.
src/components/UsersComponent.vue
<script setup>
import { onMounted } from 'vue'
import { useUsersStore } from '@/stores/users.store'
const usersStore = useUsersStore()
// usersStore.resetStatus() // <- This fails in the unit test
onMounted(() => {
usersStore.resetStatus() // <- This fails in the unit test
})
function changeStatus() {
usersStore.changeStatus() // <- This passes in the unit test
}
</script>
<template>
<div>
<p>Status: {{ usersStore.status }}</p>
<button @click="changeStatus()">Change Status</button>
</div>
</template>
src/stores/users.store.js
import { defineStore } from 'pinia'
import { usersAPI } from '@/gateways'
export const useUsersStore = defineStore({
id: 'users',
persist: true,
state: () => ({
status: 'ready',
}),
getters: {},
actions: {
resetStatus() {
this.status = 'ready'
},
changeStatus() {
this.status = 'loading'
},
},
})
src/components/tests/UsersComponent.spec.js
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import UsersComponent from '@/components/UsersComponent.vue'
import { useUsersStore } from '@/stores/users.store'
const wrapper = mount(UsersComponent, {
global: {
plugins: [createTestingPinia({ createSpy: vi.fn() })],
},
})
const usersStore = useUsersStore()
describe('UsersComponent', () => {
it('store function is called', async () => {
// arrange
const spy = vi.spyOn(usersStore, 'resetStatus')
const button = wrapper.find('button')
// act
await button.trigger('click')
// assert
expect(spy).toHaveBeenCalled()
})
})
Модульные тесты возвращают 2 разные ошибки. Первый — это журнал консоли, когда функция пытается запуститься в onMounted()
, а второй — это то, что возвращает vitest.
stderr | unknown test
[Vue warn]: Unhandled error during execution of mounted hook
at <UsersComponent ref="VTU_COMPONENT" >
at <VTUROOT>
FAIL src/components/__tests__/UsersComponent.spec.js [ src/components/__tests__/UsersComponent.spec.js ]
TypeError: usersStore.resetStatus is not a function
❯ src/components/UsersComponent.vue:16:14
16|
17| <template>
18| <div>
| ^
19| <p>Status: {{ usersStore.status }}</p>
20| <button @click="changeStatus()">Change Status</button>
Я знаю, что этот пример немного базовый и на самом деле не служит цели, но мне интересно, как я могу иметь функции хранения внутри onMounted()
(или подобных мест), не нарушая все мои модульные тесты.
Возможно, это может быть полезно для вас:
describe('UsersComponent', () => {
it('changeStatus function is called', async () => {
const wrapper = mount(UsersComponent, {
mounted: vi.fn(), // With this you mock the onMounted
global: {
plugins: [createTestingPinia({
initialState: { // Initialize the state
users: { status: 'ready' },
}
})]
}
})
// Spy the method you call...
const spy = vi.spyOn(wrapper.vm, 'changeStatus');
wrapper.vm.changeStatus()
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledTimes(1)
})
})
0
Ernesto José Jiménez Canquiz
5 Июн 2022 в 03:36