How to test V-BTN inside V-MENU? - vue.js

I'm trying to write test for my component and in this component i have a <V-MENU> and when it's activated, I'll get a form inside that component and in this form there's a <V-BTN>. so i managed to reach to buttons or items outside this menu but when i try to find my button inside menu, test will fail.
Basic Vue Component :
<v-btn
data-testid="working-button"
#click="workingbutton()"
>
Working Button
</v-btn>
<v-menu
v-model="menu"
data-testid="isMenu"
:close-on-content-click="false"
:nudge-width="200"
offset-x
>
<v-btn
data-testid="btn-one"
#click="doSomething"
>
BTN 1
</v-btn>
<v-card data-testid="card-test">
<v-text-field
v-model="country"
outlined
label="Email"/>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
data-testid="btn-two"
#click="doSomething"
>
{{ login ? 'Login' : 'Register' }}
</v-btn>
</v-card-actions>
</v-card>
</v-menu>
Test Unit :
const wrapper = mount(TestComponent, {
store,
localVue,
vuetify
})
// Passed
expect(wrapper.vm.menu).toBe(false);
expect(wrapper.find('[data-testid="working-button"]').exists()).toBe(true);
expect(wrapper.find('[data-testid="isMenu"]').exists()).toBe(true);
const menuButton = wrapper.find('[data-testid="working-button"]')
menuButton.vm.$emit('click')
expect(wrapper.vm.menu).toBe(true);
// Will Fail
expect(wrapper.find('[data-testid="btn-one"]').exists()).toBe(true);
// Will Fail
expect(wrapper.find('[data-testid="card-test"]').exists()).toBe(true);
// Will Fail
expect(wrapper.find('[data-testid="btn-two"]').exists()).toBe(true);
Above codes are a basic example which I've tried to make it short in order to don't waste your time in reading it.
So as you can see in top I've triggered Working button to set menu to true, then tried to find the button, and the reason that I've did this was that, i thought maybe menu should be true, but it wasn't and it's still fail.

Like most components that create popups or overlays, the content of Vuetify's v-menu isn't rendered within the component itself, but at the point specified in its attach property, by default the root of your Vue app.
You will have to look outside your wrapper for the rendered menu. This should do it, assuming you have createWrapper imported from vue-test-utils:
const menu = createWrapper(document.body).find('.v-menu__content');
(You could also pass the specific element that is your app root instead of document.body to be more precise.) Then you would look for everything within the menu on the menu wrapper instead of the wrapper wrapper.

Related

Vuetify how to make button that displays a v-data-table

I wanted to make a custom button that basically when clicked on would make a v-data-table pop out below it. I am basically making a data table show up when a button is clicked and using this transition
https://vuetifyjs.com/en/styles/transitions/#scale
But the transition sort of makes the table go to the right more and also has an active state with a background with opacity, basically there is a lot of built in styles that are making it hard to make a table drop down below the button as the default behavior is when you click on the transition, the item that pops up covers the button.
Below is the code that causes this
<v-menu transition="scroll-y-transition" class="scroll-button">
<template v-slot:activator="{ on, attrs }">
<v-btn v-bind="attrs" v-on="on">
Scrolling Y Transition
</v-btn>
</template>
<v-data-table> </v-data-table>
</v-menu>
Is there a better way to implement this?
I don't recommend using menu. Maybe you are looking for this kind of animation.
<v-btn #click="show = !show">
{{ show ? 'Hide' ? 'Show' }}
</v-btn>
<v-expand-transition>
<div v-show="show">
<v-table></v-table>
</div>
</v-expand-transition>
And in the script
data() {
return {
show: false
}
}
You should use the native props for v-menu to reposition the menu how you like. See this snippet: https://codepen.io/bgtor/pen/BaWdBMO .
Note the use of offset-y which allow you to offset the menu on the Y axis, and bottom which tell it to offset to the bottom. Also, nudge-left="200px" is translating the menu to the left by "200px". You can find more props for customisation on the vuetify site: https://vuetifyjs.com/en/api/v-menu/.
Other than that, I agree with #scar-2018, it seems odd to display a table in a menu.

Dialog inside autocomplete - vuetify

I am trying to put a dialog inside an autocomplete component
<v-autocomplete v-model="item.selected_item"
:items="items"
item-text="name"
return-object
single-line
#change="setItemId(item)"
>
<div slot="prepend-item">
<add-item></add-item>
</div>
</v-autocomplete>
the code for component <add-item>
<v-dialog persistent v-model="dialog" width="500">
<template v-slot:activator="{ on, attrs }">
<v-btn text block v-bind="attrs" v-on="on">{{__("Add Item")}}</v-btn>
</template>
<v-card>
</v-card>
</v-dialog>
now , the dialog button actually shows up inside the autocomplete , but the problem is that the dialog automatically goes of on any mouse click ,
I noticed that this is related somehow to the autocomplete component , I tried to put the <add-item> component outside of the autocomplete and it worked fine .
how can this be solved ?
Instead of putting the whole <add-item/> inside the autocomplete, you can separate the button and the dialog itself, then control the dialog whenever the button is clicked. We can base our solution at this example. We will declare a variable dialog that will control our add-item dialog's visibility. That variable will be handled by our button inside the autocomplete.
Main Component:
<v-autocomplete
...
>
<div slot="prepend-item">
<v-btn text block #click="dialog = true">{{"Add Item"}}</v-btn>
</div>
</v-autocomplete>
<add-item v-model="dialog"/> <!-- Pass the dialog variable to the add-item component -->
export default {
data: () => ({
items: [...],
dialog: false // let's close the dialog initially.
})
}
Add Item Component:
<v-dialog persistent :value="value" width="500">
<v-card>
...
<!-- To close the dialog, we'll need to use $emit() to
let our parent know that the value had changed. -->
<v-btn #click="$emit('input', false)">Close Dialog</v-btn>
</v-card>
</v-dialog>
export default {
props: ["value"], // Pass the value of the dialog from the main component
};
Notice that we used $emit() when we want to close our dialog. We need to use that because the variable that is controlling the dialog is from the main component, and the best way to change the value from add-item is by emitting a value. More explanation here.
Here is a demo.

How do I handle the click event of a v-menu in Vuetify?

How can I check and handle the click event of a v-menu in Vuetify?
The best way is to use v-model on v-menu & watch to variable change.
HTML
<v-menu
v-model="isOpened">
</v-menu>
SCRIPT
export default({
watch:{
isOpened: function(){
// do whatever you want
}
}
})
Here is an example:
<template v-slot:activator="{ on: { click } }">
<v-btn #click="(click) (exit=!exit)" icon>
<v-icon v-if="!exit">mdi-dots-vertical</v-icon>
<v-icon v-else>mdi-close</v-icon>
</v-btn>
</template>
As you can see, if the menu is clicked the exit var is set to its opposite value and therefore the mdi-close icon is shown on the button. If the menu is clicked again, the event changing the exit variable will trigger again and the vertical dots will be shown.
I only posted this question to save the exact syntax for myself for later because I have been digging Vuetify docs for hours and finally figured out how to do it.

How to I change a v-btn's state (visually) so that it's selected/deselected in a template?

By default, Vuetify's v-btn has a "selected state", which from what I can tell, is just a darkened background. I'm using a few v-btns in a v-app-bar. One of these buttons is a Home button. When the vue app is launched (i.e. main route), I want to set the Home button as selected so that the user knows that this is the home page. Is there a simple way that I can do this from the template that I have my v-app-bar in?
<template>
<v-app-bar app fixed>
<router-link to="/">
<v-img src="/assets/my-logo.png" to="/home" class="mx-2" max-height="128" max-width="128" contain/>
</router-link>
<v-btn to="/home" tile>Home</v-btn>
<v-btn to="/another-view" tile>Another View</v-btn>
<v-btn to="/yet-another-view" tile>Yet Another View</v-btn>
</v-app-bar>
</template>
So given the markup above, how can I set the Home button as "active" or "selected" when the page opens up at the default route?
In the v-btn documentation, there's a prop called "input-value" which says that it controls the button's active state. The problem is that its type is "Any", so I'm not sure how to use it (and the documentation doesn't show anything about it). I tried setting to true/false and nothing changes.
Also, if I want to just get rid of all the buttons active states, how do I do that?
Why isn't there a simple solution like focused="true|false"?

Vue.js / Vuetify / v-data-table: item from v-slot seems to be a copy and not a reference for the attribute

I'm creating a data table with Vuetify to show a list of records, where each record has a list of files to download. Then, I'm creating a button, for each table row, that when it is clicked, it should show a modal with the list of files.
The list is called tableRows and it has several objects. I provide an example below.
script
export default {
data () {
return {
tableRows: [
{
'properties': {
'date': '2015-12-19',
'title': 'LC82200632015353LGN01',
'type': 'IMAGES',
'showDownloadDialog': false
},
'provider': 'DEVELOPMENT_SEED'
},
...
],
showDownloadDialog: false // example
}
}
}
The table is built well, but I'm not capable to use the modal for each table row.
The modal example on the site works well, where I use just one variable (i.e. dialog) and I want to show just one single modal, however in my case, I have a list of objects, where each object may open a specific modal.
I've tried to put the showDownloadDialog attribute in each object from my list and bind it using v-model (v-model='props.item.properties.showDownloadDialog'), but to no avail. The modal does not open.
template
<v-data-table :items='tableRows' item-key='properties.title'>
<template v-slot:items='props'>
<tr class='tr-or-td-style-border-right'>
<td class='text-xs-left th-style-height'>
<div class='text-xs-center'>
...
<!-- Download button -->
<br>
title: {{ props.item.properties.title }}
<br>
showDownloadDialog: {{ props.item.properties.showDownloadDialog }}
<br>
<v-btn #click.stop='props.item.properties.showDownloadDialog = true' title='Show download list'>
<i class='fas fa-download'></i>
</v-btn>
<v-dialog v-model='props.item.properties.showDownloadDialog' persistent scrollable max-width="600">
<v-card>
...
<v-card-actions>
<v-btn #click='props.item.properties.showDownloadDialog = false'>
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</td>
</tr>
</template>
</v-data-table>
I've tried to print on the page the attribute props.item.properties.showDownloadDialog and it does not change when I click on the button. I believe this attribute is not reactive, because of that, its state does not change, but I don't get why it is not reactive. The props from data table seems to be a copy and not a reference for one record in my list tableRows.
example
I've already tried to add a simple flag in data () called showDownloadDialog, instead of using props.item.properties.showDownloadDialog, and it works, but it shows all the modals at the same time, not just the specific modal related to that record, unfortunately.
Would anyone know what can be happening?
Thank you in advance.
I was able to fix my problem by using Subash's help. I give the code below.
First, I've inserted a new property in my data (). I will use this property to show/close my modal and provide information to fill it.
downloadDialog: {
show: false
}
Inside data table I just let the button and I've created a method called showDownloadDialog where I pass the properties object (i.e. where the information is).
<v-btn flat icon color='black' class='v-btn-style'
#click='showDownloadDialog(props.item.properties)' title='Show download list'>
<i class='fas fa-download'></i>
</v-btn>
Outside data table, I've added v-dialog and I've bound it with downloadDialog.
In addition to it, I've created a method to close the dialog.
<v-dialog v-model='downloadDialog.show' persistent scrollable max-width="600">
<v-card>
...
<v-card-actions>
<v-btn #click='closeDownloadDialog()'>
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
Inside the showDownloadDialog I merge 'properties' into 'downloadDialog' and I open the modal, while closeDownloadDialog I just close the modal.
showDownloadDialog (properties) {
// merge 'properties' into 'downloadDialog'
Object.assign(this.downloadDialog, properties)
this.downloadDialog.show = true
},
closeDownloadDialog () {
this.downloadDialog.show = false
}
Thank you so much Subash for your help.