I'm struggling to understand how I should be testing my parent/child components using the [Vue Testing Library][1].
I have a parent component called SavedCards which displays a child component (called SavedCard) for every saved card in an array i.e something like:
<script>
async function deleteCard(token: string) {
applicationStore.deleteCard(token);
}
</script>
<template>
<saved-card v-for="savedCard in getSavedCards"
#delete-card="deleteCard"
:savedCard="savedCard">
</saved-card>
</template>
The child component displays a Delete button which when clicked emits the delete-card event which (in the Parent component) calls the deleteCard function - this then results in the card being delete and removed from the list.
I would like to write a test for this but I cannot workout how!
When the test (on the parent component) runs the DOM elements of the child aren't there i.e I get something like this:
<card
class="saved-card"
data-v-2778b5d8=""
style="cursor: pointer;"
/>
So how do I get to that delete-card event in my test of the parent and make sure that after it is clicked the card disappears from view?
Can anyone offer up any advice as to how (using the Vue Testing Library) I should be writing this test?
[1]: https://testing-library.com/docs/
Related
I'm converting some components from vue 3's option API to the composition API. In this particular component I have two nested child components:
<script lang="ts" setup>
import ShiftOperation from "#/components/transformation-widgets/ShiftOperation.vue";
import RawJolt from "#/components/transformation-widgets/RawJolt.vue";
console.log([ShiftOperation, RawJolt])
...
From what I understand, if you're using the setup attribute in the script tag then all you have to do is import the component into a variable like I'm doing above and it should be available for the template without having to do anything else, like it's not like the old options api where you had to inject those components into the parent component.
Both components are imported successfully (confirmed by the console log:
When I'm rendering out this parent component I'm using the two child components to render out an array of data where I reference the children dynamically in the template based on information in each block of data that I'm iterating over:
<template>
<div class="renderer-wrapper">
<component
v-for="(block, index) in store.specBlocks"
v-bind:key="index"
:block="block"
:index="index"
:is="determineBlockComponent(block)"
#block-operation-updated="updateBlock"
>
</component>
</div>
</template>
// logic for determining the component to use:
export const determineBlockComponent = (block: JoltOperation) => {
switch (block.renderComponent) {
case 'shift':
return 'ShiftOperation'
default:
return 'RawJolt'
}
}
This worked fine in the options api version of it, but for some reason the components don't actually render. They show up in the elements tab:
But they don't show up in the view. I also added a created lifecycle hook into the child components that just console.log's out saying "created X", but those hooks don't fire.
Business logic wise nothing has changed, it's just been going from option api to composition api, so I'm assuming I'm missing some key detail.
Any ideas?
Your determineBlockComponent function should not return the string but the object of the component. Replace return 'ShiftOperation' with return ShiftOperation
I've been trying to pass data from child to parent. I'm not quite sure why what I've got right now isn't working. I should mention that the emitter gets fired but the method in the parent component doesn't seem to get called.
Children.vue:
<div #click="removeCommercials()" class="">
<u-button icon color="transparent">
<u-icon icon="switch-on" color="white"/>
</u-button>
</div>
methods: {
removeCommercials () {
this.$emit('test',{message:'HELLOOOO???'})
console.log("AAA")
}
},
Parent.vue:
<Children class="mt-4" #test="noCommercials"/>
methods: {
noCommercials(deactivate) {
console.log("?????")
this.deactivateCommercials = deactivate.message
console.log("REMOVEING COMMERCIALS")
console.log(this.deactivateCommercials)
},
},
From the console I can see the text AAA. From the vue developer tools I can also see the emitter getting fired with the correct data. But the method noCommercials in the parent component doesn't seem to trigger. Nothing from the method gets printed in the console.
From some other posts I've seen people talking about using this.$parent.$emit instead of this.$emit but that didn't do the trick for me. I've also seen others having the same problem but that's because they used the wrong child component.
I'm really confused as to why the noCommercials method does not trigger at all in the parent component. What am I doing wrong?
ADDITIONAL INFO:
I tried to trigger the same method from a different child component with $emit and it works. Now I just need to figure out why this particular child component is giving me issues.
How can I update data object of parent whenever changes happen inside v-for. I have a child component that I use inside parent component.
ParentComponent.vue
<template>
....
....
<child-component
v-for="i in count"
ref="childComponent"
:key="i"
currentPage="i" // currentPage doesn't update.
:page="i"
/>
<q-header>
{{currentPage}} // always displays default value:1
</q-header>
</template>
<script>
data () {
return {
pageCount: 10,
currentPage: 1,
}
},
How can I update currentPage of data object whenever i changes inside v-for. I have tried using watch without much luck. I don't have access to child component and can't modify it.
Much appreciated!
There is some slight confusion with how v-for is working on the child-component here. Writing currentPage="i" as a property (which should actually be v-bind:currentPage in order for the i to be interpreted as JS) will simply declare the attribute on each child-component
How can I update currentPage of data object whenever i changes inside v-for
i doesn't "change" in the traditional context of running a for loop inside of a normal JavaScript application. In Vue, your rendering logic and application logic are separate, and rightly so, because running logic as part of the rendering doesn't really make sense.
For example, let's look at how your app will render the child-component:
<!-- Vue output -->
<child-component ... currentPage="1" />
<child-component ... currentPage="2" />
<child-component ... currentPage="3" />
So let's look at separating the rendering logic from the application logic.
I realise you don't have access to child-component, but based on the context I will assume it is some kind of tabbing functionality (based on you trying to set a value for the "current page" - feel free to be more specific and I can update my answer).
We need to bridge that gap between the rendering logic and the application logic and we can do that by using events:
<child-component
v-for="i in count"
:ref="`childComponent-${i}`" // ref should be unique so add the i as part of it
:key="i"
:page="i"
v-on:click="currentPage = i" // when the user clicks this child component, the current page will be updated
/>
You may have to utilise a different event other than click but I hope this gets you closer to what you are trying to achieve. For the value of currentPage to update there has to be some kind of user input, so just find out which event makes the most sense. Maybe the child-component library you are using has custom events that are more appropriate.
you should look into Custom Events.
https://v2.vuejs.org/v2/guide/components-custom-events.html
Idea is, that whenever there is some update of your desire in child component, you can execute this.$emit(“change”), which will throw an event.
On parent side you can catch this event by #change=“myMethod” as one of the attributes.
methods: {
myMethod() {
console.log("Testing")
}
}
<child-component
v-for="i in count"
ref="childComponent"
:key="i"
currentPage="i"
:page="i"
#change=“myMethod”
/>
Let me know if that helped.
So I have a Vue2 app. I have create a component "u-button"
when i import this and use it in another component, I want to be able to add a click function to it. However at the moment it looks for a function on the u-button component rather than the component it is being used in.
so for example, in the below if i click the first button nothing happens, if i click the second button i get the console log.
<template>
<div>
<u_button #click="clicked">Click me</u_button>
<button #click="clicked">Click me</button>
</div>
</template>
<script>
import u_button from '../components/unify/u_button'
export default {
components: {
u_button
},
methods: {
clicked() {
console.log("Working!");
}
}
}
</script>
However if i add a method on the u-button component, then it calls that. So how can i get my below example to work ? The only thing I can think of is to wrap it in another div and add the click function to that. but I'm wondering if there is a better way?? I dont want to use events to do this either as that gets messy very quickly.
As you can imagine having a reusable button that when clicked always performs the same function is a bit pointless.
It's because of the nature of components for example if we had a (virtual) iframe component which had a button in it and we'd like to detect click event on it we might name the event click and listen for it in the parent component; therefore, Vue introduced a feature called event modifiers for example in Vue, We have .native modifier (you can read more about the Vue modifiers here)
Now, to make your code work, You should add a .native after #click like this:
<u_button #click.native="clicked">Click me</u_button>
By the way, it's better to develop a naming convention for yourself It'd become handy when your projects get larger.
I'm really stuck on this one.I have created a Vue (2.0) component that is made up of child components, it's all being Webpacked etc. For example, this is the parent:
<div>
<h1>This is just a title for lulz</h1>
<rowcomponent v-for="row in rows" :row-data="row"></rowcomponent>
</div>
which has a prop of rows passed to it which looks something like this:
rows: [{ sometext: "Foo"} , { sometext: "Bar" }]
So my row component looks like this:
<div>{{ this.sometext }} <button #click="deleteRow">Delete Row</button></div>
And I feel like it should be really easy to set a method on the rowcomponent that is something like this:
deleteRow() {
this.delete();
}
Do I need to $emit something to the parent with the index of the row in it to remove it? It's driving me crazy. All the examples seem to suggest it would be easy to do, but not in the case where you have child components that want to delete themselves.
Yes, the child component cannot delete itself. The reason is because the original data for rows is held in the parent component.
If the child component is allowed to delete itself, then there will be a mismatch between rows data on parent and the DOM view that got rendered.
Here is a simple jsFiddle for reference: https://jsfiddle.net/mani04/4kyzkgLu/
As you can see, there is a parent component that holds the data array, and child component that sends events via $emit to delete itself. The parent listens for delete event using v-on as follows:
<row-component
v-for="(row, index) in rows"
:row-data="row"
v-on:delete-row="deleteThisRow(index)">
</row-component>
The index property provided by v-for can be used to call the deleteThisRow method, when the delete-row event is received from the child component.