I am in the process of migrating to vuetify3, and I'm using cypress (the cypress studio) for testing.
The <v-combobox> gives me a dropdown with items, but these are not easily referencable with css selectors. Cypress studio generates stuff like
cy.get('.v-overlay__content > .v-list > :nth-child(2) > .v-list-item__content > .v-list-item-title').click();
... which isn't readable for a human and will break if the dropdown list gets modified (for example, prepend a list item).
The best solution I came up with yet would be to give each list item a data-cy attribute; but how to do that properly? It seems to me that vuetify3 docs are still sparse on how to use the item slot (cf. https://next.vuetifyjs.com/en/components/combobox/); its workings seem to differ from vuetify2. By using the item slot like,
<v-combobox>
<template #item="scope">
myListItem
</template>
</v-combobox>
... the list items are not clickable any more.
Codesandbox: https://codesandbox.io/s/vuetify3-v-combobox-and-cypress-how-to-identify-list-items-gzfs71?file=/src/App.vue
EDIT Of course I could write the Cypress test code myself. But my premise is to use Cypress Studio, which CAN generate meaningful CSS selectors if possible, i. e. if there are id or data-cy attributes in the HTML code. But vuetify does not generate such. So I ask, how can I attach data-cy attributes to list items?
I think your selector is a bit more complex than needed
you can simplify to just the relevant elements
// get based on value
cy.get(".v-overlay-container .v-list-item").contains("Programming").click();
// get based on order (force: true is required if the combo box is still animating)
cy.get(".v-overlay-container .v-list-item").eq(2).click({ force: true });
example used
<template>
Click this combobox:
<v-combobox :items="items" id="combo" />
</template>
<script>
import { VCombobox } from "vuetify/components";
export default {
name: "App",
data() {
return {
items: ["Programming", "Design", "Vue", "Vuetify"],
};
},
components: { VCombobox },
};
</script>
test
describe("💩💩", () => {
beforeEach(() => {
cy.visit("http://10.0.0.248:8080/");
});
it("select with content", () => {
cy.get("#combo").click();
cy.get(".v-overlay-container .v-list-item").contains("Programming").click();
cy.get("#combo").should("have.value", "Programming");
});
it("select with order", () => {
cy.get("#combo").click();
cy.get(".v-overlay-container .v-list-item").eq(2).click({ force: true });
cy.get("#combo").should("have.value", "Vue");
});
});
Related
I've a unpredictable list of rows to delete
I simply want to click each .fa-times icon
The problem is that, after each click, the vue.js app re-render the remaining rows.
I also tried to use .each, but in this cas I got an error because element (the parent element, I think) has been detached from DOM; cypress.io suggest to use a guard to prevent this error but I've no idea of what does it mean
How to
- get a list of icons
- click on first
- survive at app rerender
- click on next
- survive at app rerender
... etch...
?
Before showing one possible solution, I'd like to preface with a recommendation that tests should be predictable. You should create a defined number of items every time so that you don't have to do hacks like these.
You can also read more on conditional testing, here: https://docs.cypress.io/guides/core-concepts/conditional-testing.html#Definition
That being said, maybe you have a valid use case (some fuzz testing perhaps?), so let's go.
What I'm doing in the following example is (1) set up a rendering/removing behavior that does what you describe happens in your app. The actual solution (2) is this: find out how many items you need to remove by querying the DOM and checking the length, and then enqueue that same number of cypress commands that query the DOM every time so that you get a fresh reference to an element.
Caveat: After each remove, I'm waiting for the element (its remove button to be precise) to not exist in DOM before continuing. If your app re-renders the rest of the items separately, after the target item is removed from DOM, you'll need to assert on something else --- such as that a different item (not the one being removed) is removed (detached) from DOM.
describe('test', () => {
it('test', () => {
// -------------------------------------------------------------------------
// (1) Mock rendering/removing logic, just for the purpose of this
// demonstration.
// -------------------------------------------------------------------------
cy.window().then( win => {
let items = ['one', 'two', 'three'];
win.remove = item => {
items = items.filter( _item => _item !== item );
setTimeout(() => {
render();
}, 100 )
};
function render () {
win.document.body.innerHTML = items.map( item => {
return `
<div class="item">
${item}
<button class="remove" onclick="remove('${item}')">Remove</button>
</div>
`;
}).join('');
}
render();
});
// -------------------------------------------------------------------------
// (2) The actual solution
// -------------------------------------------------------------------------
cy.get('.item').then( $elems => {
// using Lodash to invoke the callback N times
Cypress._.times($elems.length, () => {
cy.get('.item:first').find('.remove').click()
// ensure we wait for the element to be actually removed from DOM
// before continuing
.should('not.exist');
});
});
});
});
I am working with v-Treeview. I want to set already selected children while loading this tree view. To do this, I added items in array and used this array in v-model. But It's not working. I have following implementations:
<template>
<v-container>
<v-treeview
:items="checklist"
selectable
return-object
dense
hoverable
:v-model="selectedItemList"
/>
</v-container>
</template>
<script>
export default {
name: 'WTreeView',
props: {
// Checklist of the selected card
checklist: {
type: Array,
required: true,
},
},
data() {
return {
selectedItemList: [],
};
},
created() {
this.selectedItemList.push(this.checklist[0]);
},
};
</script>
For "checklist" I have data in the following format:
Now what is the best way to set already selected children while loading v-Treeview? Thank You.
When the vue is created, the data hasn't been filled into the controllers and neither the page is rendered.
You can consider setting the selected items in the mounted
I have been working on a similar issue for hours and finally figured out how to get it work.
First of all, v-treeview needs a separate array of ids to keep the track of selected items.
you should bind it to the value property.
Second, the timing you change the value property is critical. v-model would not load the initial values so you have to update the values after you render the page. In your code above, you set the selectedItemList in created(). But it is executed before the page is rendered. You should put it into mounted() instead.
I don't think you can achieve this "out of the box".
I recommend using the "label" slot to add your own v-checkbox and bind the v-model to the selected property on your check list item.
Something like:
<v-treeview :items="checklist">
<template v-slot:label="props">
<div class="treeCheckBox">
<v-checkbox class="treeCheckBox"
v-model="props.item.selected"
:label="props.item.name">
</v-checkbox>
</div>
</template>
</v-treeview>
Then all you need to do is ensure the "selected" property is set appropriately against the check list items when the data is bound to the tree view.
For more info on the using the named slot, see https://vuetifyjs.com/en/components/treeview and https://v2.vuejs.org/v2/guide/components-slots.html
In version 1.5x of veutify you may encounter spacing issues. I was able to fix this by applying the following styles to my check box and outer div:
.treeCheckBox{
margin-top: 0px;
max-height: 30px;
}
I'm trying to create a search input field with a history of previous searches on a dropdown, like, for example the search field in the Intellij editor.
I'm new to Vuetify and from what I can see so far it looks like Combobox is the best component to use.
I would like the dropdown to only open if you click the dropdown icon. At the moment the dropdown opens when you click in the text input field. From the documentation is looks like prop :menu-props="{openOnClick: false}" might be what I need, but it doesn't seem to work.
Can anyone give me pointer in the right direction?
https://codepen.io/damianhelme/pen/zMXJvb
<v-combobox
v-model="search"
:items="searchHistory"
label="Search"
:menu-props="{openOnClick: false}"
></v-combobox>
new Vue({
el: '#app',
data () {
return {
search: '',
searchHistory: [
'Apple',
'Banana',
'Pear'
]
}
}
})
Thanks.
EDIT:
Updated pen with custom append slot to deal with icon state:
https://codepen.io/anon/pen/KrjzRL
If you want to open (and close) the combobox only on icon-click, try this trick:
<v-combobox
:menu-props="{value: autoselectMenu}"
#click:append="toggle"
></v-combobox>
Pass custom value to menu - this indicates whether menu should be opened/closed.
Then change that value only on icon-click by using :append-icon-cb prop.
data () {
return {
autoselectMenu: false,
// ...
methods: {
toggle() {
this.autoselectMenu = !this.autoselectMenu
}
// ...
So for any other case when you want to open or close the menu, just change that custom value i.e. autoselectMenu.
codepen
I can't find a way to clear the select field in method "afterselection" which is invoked after selecting an item:
template:
<v-select
:items="adrlist"
#input="afterselection"
></v-select>
data in select:
const addressList = [{
"text":"Street 523, 2533",
"value":[1723455.7054408004,5923451.382843224]},
{"text":"Lala 3, 3455",
"value":[1723455.7054408004,5923451.382843224],
}]
vue-code:
new Vue({
el: '#app',
data () {
return {
adrlist: addressList,
}
},
methods:{
afterselection(item){
console.log(item);
//here I want to clear the select
},
},
})
Here's a code pen with this example:
codepen example
I've tried to set the v-model to null, but this is not working.
I have researched and tried for several days now, and I really can't find a solution :-/
Just for reference, you can clear a select field in vuetify with
this.$nextTick(() => {
this.selected = null
})
the important thing is the "nextTick"! otherwise it wont be rendered...
see this codepen provided by a vuetify-dev:
working codepen
I faced with the same issues. I have several v-select components in card text block and clear btn. When i click on clear i run clear func and clear all v-select items by refs.
template:
<v-card>
<v-card-title>Filters</v-card-title>
<v-card-text v-if="selectFilters.length">
<v-select
:ref="filter.id"
v-for="filter in selectFilters"
:items="filter.items"
:key="filter.id"
:label="filter.alias"
multiple
return-object
clearable
#input="selectChangeHandler($event, filter.id)"
></v-select>
</v-card-text>
<v-card-actions>
<v-btn color="primary" #click="clear">Clear</v-btn>
</v-card-actions>
</v-card>
script:
methods: {
...
clear: function() {
Object.values(this.$refs).forEach(ref => {
const vueSelect = ref[0];
vueSelect.internalValue = [];
});
},
}
you can achieve this by adding ref to the component
<v-select
ref="adrlistRef"
:items="adrlist"
#input="afterselection"
></v-select>
then use the reset method of v-select component whenever you want!
afterselection(item) {
if (item) {
console.log(item);
this.$refs['adrlistRef'].reset();
}
}
Looks like you need to bind to the select a v-model that is a computed property. Then it would be possible to change that value with the #input event.
I am currently working on making lazy-loading possible together with "more-routing" in Polymer. I already got a first working implementation of lazy loading (inspired by app-router that unfortunately does not support Polymer 1.0 completely), but I have no idea how I can pass all events from the imported child element up to my lazy-loading element.
So, in my example I want the event "hi" passed from page-login to lazy-loader and from lazy-loader upwards to the element containing lazy-loader - but without having knowledge of that specific event in lazy-loader. So I need something like "catch all events fired by child element".
That's what I have :
page-login:
ready:function() {
this.fire("hi");
},
using lazy-loader:
<lazy-loader on-hi="hi" href="../pages/page-login.html" id="login" route="login"></lazy-loader>
calling load :
document.getElementById("login").load()
Lazy-loader (reduced version):
<dom-module id="lazy-loader">
<template>
<span id="content">Loading...</span>
</template>
<script>
window.lazyLoaded = window.lazyLoaded || [];
Polymer({
is: "lazy-loader",
properties: {
href:{
type:String,
value:""
},
element:{
type:String,
value:""
}
},
load:function(finishFunc) {
if(window.lazyLoaded.indexOf(this.href)===-1) {
var that = this;
this.importHref(this.href, function(e) {
window.lazyLoaded.push(that.href);
if(!that.element) {
that.element = that.href.split('/').reverse()[0];
that.element = that.element.split(".")[0];
}
var customElement = document.createElement(that.element);
// here I need something like "customElement.on("anyEvent")"
// => that.fire("anyEvent")
Polymer.dom(that.$.content).innerHTML = "Loading done.";
Polymer.dom(that.$.content).appendChild(customElement);
Polymer.dom.flush();
that.fire("loaded");
});
}
}
});
</script>
It seems like I have to revoke my question - If I fire the event in the attached-handler, not in ready, it gets passed through automatically - at least in the current version of Polymer. Not sure if this is intended behavior, but for now the problem is solved. An answer still would be interesting, maybe for other cases (like event logging)