Unhandled error during execution of mounted hook

Version 3.0.0 Reproduction link https://codesandbox.io/s/vigorous-nash-8lxt8?file=/src/Child.vue Steps to reproduce click the increment button What is expected? this.$el should be defined inside of...

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

@kleinfreund

@LinusBorg

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.

@kleinfreund



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.

@amrnn90

@sixthgear

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.

@ajimix

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

@LinusBorg

@kleinfreund



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.

@kleinfreund



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
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

@kleinfreund

Labels

has PR

A pull request has already been submitted to solve the issue

🐞 bug

Something isn’t working

pmochine

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

vovaleontiev

nyunyu95

Same issue using Vue 2.7.x :(

VegetableChicken1024

Same issue using Vue 2.7.x

nyunyu95

I gave up with this plugin……

Emuzex

Same issue here with 2.7.x vue version

zoer98

Same issue using Vue 2.6.x

dpmango

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 :P

У меня есть компонент и магазин 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

Понравилась статья? Поделить с друзьями:
  • Ungultige gleitkommaoperation splan как исправить
  • Unexpected error json parse error type mismatch for join on calc info
  • Unfortunately the installation failed error timeout 0x0005
  • Unexpected error java lang stackoverflowerror
  • Unfortunately the installation failed error 0x0006 behringer