Skip to content

Trigger events from child vue component #150

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

Closed
Aferz opened this issue Nov 3, 2017 · 14 comments
Closed

Trigger events from child vue component #150

Aferz opened this issue Nov 3, 2017 · 14 comments

Comments

@Aferz
Copy link

Aferz commented Nov 3, 2017

Hi.

I would like to be able to trigger events from children components of a given parent. Check this code out:

Parent Component (Only template):
<div class="parent">
    <child class="child-component"
      @custom-event="console.log.call(null, 'Custom Event from Child Component')" 
      @click="console.log.call(null, 'Click Event from Child Component')">
    </child>

    <div class="child-div"
      @custom-event="console.log.call(null, 'Custom Event from Native DOM Element')" 
      @click="console.log.call(null, 'Click Event from Native DOM Element')" >
    </div>
</div>
Test:
test('Testing triggers', () => {
    const wrapper = mount(Parent) // Parent Vue Component
    
    const childComponent = wrapper.find('.child-component') // Child Vue Component
    const childDiv = wrapper.find('.child-div') // Child Native DOM Element
    
    childComponent.trigger('custom-event')
    childDiv.trigger('custom-event')
    
    childComponent.trigger('click')
    childDiv.trigger('click')
})

The log generated in console is the following:

Custom Event from Native DOM Element
Click Event from Native DOM Element

As you can see, events triggered in Vue Components are not being emitted, so the parent can't catch them and act in consequence.

Is this expected? Maybe I'm doing something wrong ?
Thanks !

@Aferz Aferz changed the title Trigger events from child vue components Trigger events from child vue component Nov 3, 2017
@lmiller1990
Copy link
Member

This will not work, when you do mount it will not stub the child component, but render it's actual contents, I think. Can you post child-component?

What you probably want to do is instead of find('.child-component').trigger('custom-event'), do trigger on the actual button in the child component (maybe give it an ID or something). So:

// child component
<template>
  <button id="child" @click="$emit('custom-event')">Btn</button>
</template>
// parent
<template>
  <child-component @custom-event="something" />
</template>
// Test 
const wrapper = mount(Parent)
wrapper.find('#child').trigger('click')
// this should work (or something like this)

If you post child-component, or better yet your repo, I can add some examples of how to do this (I have done it a lot in my projects, and had the same problem, but haven't got any snippet I can share at this very moment).

Will try and put a small demo repo together later today, since I remember stuggling a little to test parent/child events.

@Aferz
Copy link
Author

Aferz commented Nov 4, 2017

Mmmh I think I Understand...

My problem is that I'm trying to do TDD and what I was attempting to do is create a Parent component that needs to react to events emitted from a yet undefined Child Component.

I wanted to define the behaviour of the Parent before the Child.

Maybe I'm not following a good strategy to do TDD as I'm used to from PHP.

Maybe if I shallow the Parent component, the Child will be stubbed and the events will be triggered?

Thanks for your help!

@lmiller1990
Copy link
Member

It looks like you are just testing if $emit works in that case. You should be testing your own code logic, not Vue itself.

Can you post the full code of what you are trying to test? There is no point to test if a component can react to a custom event, Vue already has internal tests for that.

Check out #145 , i think it's related.

@Aferz
Copy link
Author

Aferz commented Nov 4, 2017

No @lmiller1990, I don't want to test if $emit works. I want to test that my ParentComponent is able to react for events emitted for a undefined ChildComponent.

The code I put before is the full code. I mean, I don't have a real ChildComponent yet. It just something that I know I would like to wrap in its own component because it has too much logic but I would like to define in the ParentComponent.spec.js the event or api that ChildComponent would expose.

Let's try again with this snippet:

ParentComponent.vue

<template>
  <div class="ParentComponent">
    <child class="child-component" @custom="onCustom"></child>

    <p v-if="customTriggered">Triggered!</p>
  </div>
</template>

<script>
export default {
    name: 'parent-component',

    data () {
        return {
            customEmitted: false
        }
    },

    methods: {
        onCustom () {
            this.customEmitted = true
        }
    }
}
</script>

ParentComponent.spec.js

describe('ParentComponent', () => {
  test("It shows 'Triggered' when Child component allows to...", () => {
    const wrapper = mount(ParentComponent) // Maybe shallow ?
    
    const childComponent = wrapper.find('.child-component')
    childComponent.trigger('custom')
    
    expect(wrapper.html()).toContain('Triggered!')
  })
})

Note: I'm trying to test the output, not the implementation details. That's why I don't want to test wrapper.emitted() or wrapper.vm.customEmitted

Of course, I could be wrong in how to do TDD in Vue Components. Maybe ParentComponent is not responsible for making assumptions about events emitted for ChildComponent. I'm just trying to figure it out and wrap my mind around testing parent-child communication.

I hope this is clearer than before.

Thanks!

@lmiller1990
Copy link
Member

Thanks for the more detailed explanation, @Aferz .

Since you haven't defined child yet, you could make a temporary element to test with. Something like Mock.vue that contains:

<template>
  <button @click="$emit('custom')"><button>
</template>

And test with this. Then you can do an $emit on the Mock.vue component, and test like that. Basically, the same as stubbing/mocking the component out.

However, even if you do that, you are still just testing Vue. If a child component exposes an event api like you said, where custom is emitted, you would still just be testing if ParentComponent calls a method when custom is emitted - of course it does, that is what Vue's API provides.

I think what you are trying to do at the moment is:

  • "when custom event is emitted from a childcomponent (which doesn't exist yet)
  • on onCustom is called in the parent
  • customEmitted becomes true
  • "Triggered" is rendered

What you should be doing is just the last two points, which is the logic you wrote. You can be confident if a child emits an event, the parent will respond.

So should your unit test should look like this:

describe('ParentComponent', () => {
  test("It shows 'Triggered' when customEmitted is true", () => {
    const wrapper = shallow(ParentComponent) 
    
    wrapper.setData({ customEmitted: true })

    expect(wrapper.html()).toContain('Triggered!')
  })

  // or
 test("It shows 'Triggered' when onCustom is called", () => {
    const wrapper = shallow(ParentComponent) 
    
    wrapper.vm.onCustom()
  
    wrapper.vm.update() // need to call to update UI

    expect(wrapper.html()).toContain('Triggered!')
  })
})

Of course, eventually in your app you will have to test that when something (say a button) is clicked, something else in a parent component happens - this is an integration or e2e test though, since it involves multiple components interacting, which is out of the scope of vue-test-utils.

PS: your last sentence said: "I'm just trying to figure it out and wrap my mind around testing parent-child communication.". You definitely don't need to test that, but of course Vue internally does have tests for this functionality, if you are really keen you can take a look at the Vue source.

Hopefully that helps. Let me know if something isn't clear or you have more questions, we are all still learning :)

@Aferz
Copy link
Author

Aferz commented Nov 4, 2017

@lmiller1990:

Thank you very much for your so good explanation.

You cleared my mind a bit more about this topic.

Thanks!

@Aferz Aferz closed this as completed Nov 4, 2017
@pimhooghiemstra
Copy link

This topic/question is indeed very much related to #145 which I asked last week. I was also wrapping my mind around unit tests and got stuck in stuff that is already in Vue and should not be tested.

Thanks to @lmiller1990 for pointing this out and for the clarification/explanation!

And thanks to @Aferz for sharing WW's quote :-)

@felixlublasser
Copy link

Sorry for coming late to the party. I get that there's no need to test that the event from the child is received by the parent but what I would like to test is not that the parent calls a method, but that it calls the correct method. Maybe I have two child components and the parent is supposed to call different methods depending on which child fires the event. That should be part of a unit test, I think, because it's logic that is inherent to the parent component.

@samboylett
Copy link

Sorry to bring up an old issue, but I agree with @Aferz. I don't think my unit tests should be aware of potentially private functions. E.g. both of these should pass the same unit test:

<child @foo="gotFoo = true" />

// Or

<child @foo="setFoo" />

{ methods: {
  foo() {
    this.gotFoo = true;
  }
}}

Also the same applies to setValue. IMO both of these should be treated the same, but you have to unit test them differently:

<input v-model="foo" />

<my-custom-component v-model="foo" />

@VictorCazanave
Copy link
Contributor

VictorCazanave commented Oct 24, 2018

Although I understand the explanation of @lmiller1990, I agree with @felixlublasser and @samboylett.
When an event is emitted by a child component, I think calling the "right code" (event handler method, code in template...) is also part of the parent component's logic.

In the example above:

<template>
  <div class="ParentComponent">
    <child class="child-component" @custom="onCustom"></child>
    <p v-if="customTriggered">Triggered!</p>
  </div>
</template>

<script>
export default {
    name: 'parent-component',
    data () {
        return {
            customEmitted: false
        }
    },
    methods: {
        onCustom () {
            this.customEmitted = true
        }
    }
}
</script>

This test:

  test("It shows 'Triggered' when customEmitted is true", () => {
    const wrapper = shallow(ParentComponent) 
    
    wrapper.setData({ customEmitted: true })

    expect(wrapper.html()).toContain('Triggered!')
  })

doesn't mean I want to test if the v-if directive works well, but if the component's logic/output is correct. So I think it's the same reasoning with events.
Moreover since we can trigger DOM events, we have to write 2 kind of tests to test the same thing (emitted event => expected output).

Could you reconsider adding a new feature to trigger custom events?

@eddyerburgh
Copy link
Member

eddyerburgh commented Oct 24, 2018

You can emit from a child component by accessing the instance:

test("triggers correctly", () => {
  const wrapper = shallow(ParentComponent) 
  wrapper.find(Child).vm.$emit('custom')
  expect(wrapper.html()).toContain('Triggered!')
})

I don't think we should add a method to emit from a component that doesn't yet exist

@VictorCazanave
Copy link
Contributor

@eddyerburgh Thanks for the tip!
Is it mention somewhere in the documentation? I think it may be useful.
Then I agree adding a method isn't necessary 😄

@eddyerburgh
Copy link
Member

eddyerburgh commented Oct 25, 2018

There's a small code example in the common tips page, in the testing emitting events section—https://vue-test-utils.vuejs.org/guides/#common-tips. We could also add an 'emitting an event on a child component' section. Would you like to make a PR? Perhaps we could add it to the tips section, although

@VictorCazanave
Copy link
Contributor

I may not have time this week, but I think I can make a PR next week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants