Skip to content
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

lifecycle methods in child components not invoked #727

Closed
eunjae-lee opened this issue Jul 2, 2021 · 17 comments · Fixed by #743
Closed

lifecycle methods in child components not invoked #727

eunjae-lee opened this issue Jul 2, 2021 · 17 comments · Fixed by #743
Labels
bug Something isn't working

Comments

@eunjae-lee
Copy link
Contributor

eunjae-lee commented Jul 2, 2021

Lifecycle methods in child components seem to be not invoked.

  const Parent = {
    template: `
      <div>
        <p>I am a parent</p>
        <slot />
      </div>
    `,
  };
  const Child = {
    created() {
      throw new Error("error?");
      console.log("child created");
    },
    mounted() {
      console.log("child mounted");
    },
    render() {
      return "i am a child";
    },
  };

  const wrapper = mount(Parent, {
    slots: { default: Child },
  });

  console.log(wrapper.html());

https://github.com/eunjae-lee/vue-test-utils-snapshot-2/blob/master/src/Test.spec.ts#L11-L37

I expect console log from Child or even Error being thrown at created, but I don't see anything and it successfully(?) prints out the html.

However, this workaround works:

  const wrapper = mount({
    components: {Parent, Child},
    template: `
      <Parent>
        <Child />
      </Parent>
    `
  })
@standbyoneself
Copy link
Contributor

Cannot start and test your project because of many errors. Do you really need custom webpack configuration? Please use vue-cli

@eunjae-lee
Copy link
Contributor Author

eunjae-lee commented Jul 6, 2021

It's based on this but I'll try to make another repro later today. Btw, what kind of errors?

@lmiller1990 lmiller1990 added the bug Something isn't working label Jul 6, 2021
@lmiller1990
Copy link
Member

Sounds like a bug to me - this should be quite easy to repro in this repository if anyone wants to give it a go. I have to work on the Vue compat build stuff atm, but I can look into this after.

@standbyoneself
Copy link
Contributor

standbyoneself commented Jul 6, 2021

@eunjae-lee,

I have created a simple repro: https://github.com/standbyoneself/vue-slot-hooks. The only one test is in tests/unit folder

The problem is in this part of code:

src/mount.ts

function slotToFunction(slot: Slot) {
    if (typeof slot === 'object') {
      if ('render' in slot && slot.render) {
        return slot.render
      }
...

You passed your slot as an object so it simply renders without calling any lifecycle hooks.
You can be sure in this by going to and manually fix node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js file.
Find the slotToFunction function and paste it:

...
slot.mounted(); // Add this
return slot.render;
...

What is the solution? Check fix branch of my repo

  1. I have to upgrade @vue/test-utils version to rc.10 where this function implementation has changed
    function slotToFunction(slot) {
        if (typeof slot === 'object') {
            if ('render' in slot && slot.render) {
                return slot.render;
            }
            if ('template' in slot && slot.template) {
                return function () { return Vue.h(slot, props); };
            }
...
  1. Use template instead of render in your Child component and it will be working

@lmiller1990
Copy link
Member

Is there still a problem or is this fixed as of rc 10?

@eunjae-lee
Copy link
Contributor Author

eunjae-lee commented Jul 7, 2021

I didn't know about rc.10. I just upgraded the version in my reproduction and it gives me this error:

  ● lifecycle methods in child component

    TypeError: Vue.setDevtoolsHook is not a function

      31 |   };
      32 | 
    > 33 |   const wrapper = mount(Parent, {
         |                   ^
      34 |     slots: { default: Child },
      35 |   });
      36 |   console.log(wrapper.html());

      at attachEmitListener (node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js:7371:9)

CleanShot 2021-07-07 at 10 24 53

Should I create a new issue for this? Or did I do something wrong?
(What I did is just to update the version in this repo https://github.com/eunjae-lee/vue-test-utils-snapshot-2)

@standbyoneself
Copy link
Contributor

standbyoneself commented Jul 7, 2021

@eunjae-lee,

Please, upgrade a Vue version

yarn upgrade vue@next@latest

And it will work

@eunjae-lee
Copy link
Contributor Author

@standbyoneself ah thanks. from vue@3.0.0-beta.17 to 3.1.4 and now the error is gone.

However, it still doesn't work.

I also checked out this repo(https://github.com/standbyoneself/vue-slot-hooks) and updated VTU to rc.10 and this code still doesn't invoke the child's lifecycle methods.

mount(Parent, {
  slots: {
    default: Child,
  },
});

@standbyoneself
Copy link
Contributor

@eunjae-lee,

Are you on the fix branch?

Screenshot 2021-07-07 at 11 49 23

@eunjae-lee
Copy link
Contributor Author

@standbyoneself Oh I misunderstood what you said earlier. I thought you meant "either upgrade to rc.10 or use template".

Do we have to use template to make this work? It makes sense for me to have render function and still lifecycle methods getting invoked.

@standbyoneself
Copy link
Contributor

@eunjae-lee,

Since I am not a core developer I can not properly answer your question but I suppose that render() is used to render html only without calling side effects and you shouldn't have lifecycle hooks in your components with render()

@eunjae-lee
Copy link
Contributor Author

@standbyoneself Thanks for the explanation. I wasn't aware of that.

However, just to make sure, I did some tests but it's not true.

vue 2: https://codesandbox.io/s/dank-platform-tthyo?file=/src/components/Test.vue:0-136
vue 3: https://codesandbox.io/s/hungry-sid-vh44d?file=/src/components/Test.vue

Both vue versions invoke lifecycle methods. If that's the case, VTU should support too. What do you think @lmiller1990 ?

@standbyoneself
Copy link
Contributor

standbyoneself commented Jul 7, 2021

Sure, I make this supposition only in the context of using the VTU^2 and slots accordingly to the it's implementation, I did not say that you can not have lifecycle hooks inside the components with render()

@lmiller1990
Copy link
Member

lmiller1990 commented Jul 8, 2021

This should work, seems like a bug. You can use lifecycles with render. template is just compiled to render anyway. So

<template>
  <div />
</template>

<script>
export default {
  created () {} 
}
</script>

Turns into:

const { h } = require('vue')

export default {
  created() {},
  render() {
    return h('div')
  }
}

Or something like that.

Anyway, I reproduced it here: #743 for easy debugging. yarn jest tests/mountingOptions/slots.spec.ts

@lmiller1990
Copy link
Member

lmiller1990 commented Jul 8, 2021

Ok so this works fine:

  it('triggers child component lifecycles', async () => {
    const parentMounted = jest.fn()
    const childMounted = jest.fn()

    const Parent = defineComponent({
      mounted() {
        parentMounted()
      },
      render() {
        return h(this.$slots.default!)
      }
    })

    const Child = defineComponent({
      render() {
        return h('span')
      },
      mounted() {
        childMounted()
      }
    })

    const wrapper = mount(Parent, {
      global: {
        components: { Child },
      },
      slots: {
        default: Child
      }
    })

    await flushPromises()

    expect(parentMounted).toHaveBeenCalled()
    expect(childMounted).toHaveBeenCalled()
  })

This fails, though:

   const wrapper = mount(Parent, {
      global: {
        components: { Child },
      },
      slots: {
        default: `<Child />`
      }
    })

Problem is the default: Comp syntax is not working. Edit: See below post, I fixed it.

default: Child
default: '<Child />'

@lmiller1990
Copy link
Member

Problem is here https://github.com/vuejs/vue-test-utils-next/blob/6e0904c6dc9f6e17045ad69773a33926a54bc872/src/mount.ts#L265-L266

We only render .render, we need the rest of the component for the default: Child syntax.

Work around would be use one of the other syntaxes for now.

@lmiller1990
Copy link
Member

Ok, fixed it: #743

Please review, this will be in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants