How to open open card and verify the content inside then open the second card and verify the content and so on - automation

I need to open the first card and verify that everything inside matches the hashtag 'Fashion' and then do the same for the next 3 cards and then press the 'next' button and do the same for next 4 cards. how would I do it? I tried the regular way by clicking on the element.eq(0) and verifying everything inside and then cy.go('back') and so on but that's so much code duplication. how would I do it the other way?
First 4 cards:
Second 4 cards:
The CSS selector for all of them is the same [class="discover-card"]. please help:) thank you

You can use Cypress's .each() functionality to iterate through elements with the same CSS selector.
cy.get('.discover-card').each(($card, index) => {
// cy.go('back') can cause the list to become detached, so find element by index of original list.
cy.get('.discover-card').eq(index).click();
// validations after clicking the card
// unsure on this exact function, but was provided in question
cy.go('back').then(() => {
// if this is the fourth item checked, we need to press the next button.
if ((index + 1) % 4 === 0) {
cy.get('.next-btn').click(); // this selector is the next button on the carousel
}
});
});
If the data between the cards is unique, I'd advise creating a data object you can use to store the data and reference it in your test. You can do this by having each data object have a unique key equal to the text on the card, or by storing them in an array.
// unique keys
const data = { fashion: { foo: 'bar' }, beauty: { foo: 'bar2' }, ... };
// array
const data = [{ foo: 'bar' }, { foo: 'bar2' }, ...];
...
// unique keys
cy.wrap($card).should('have.attr', 'foo', data[$card.text()].foo);
// array
cy.wrap($card).should('have.attr', 'foo', data[index].foo);

If you are concerned about code duplication, put the common code in a function
const expectedData [
{ index: 1, title:'Fashion', ... } // anything you want to check
{ index: 2, title:'Beauty', ... }
]
const checkCard = (cardIndex) => {
const data = expectedData[cardIndex]
cy.get('.discover-card')
.should('contain', data.title)
.click() // open card
// test card internals
}
Cypress._.times(3, (pageNo) => { // loop 3 times between pages
Cypress._.times(4, (cardNo) => { // loop 4 times for cards
const cardIndex = ((pageNo+1) * (cardNo+1)) -1
checkCard(cardIndex)
cy.go.back() // revert to menu
})
cy.get('.next-btn').click()
})

Related

(Vue 3) Error: AG Grid: cannot get grid to draw rows when it is in the middle of drawing rows

-- Initial setup --
Create component
const ButtonAgGrid= {
template: "<button>{{ displayValue }}</button>",
setup(props) {
const displayValue = 'TEST-TEXT';
return {
displayValue,
};
},
};
Register component
<AgGridVue
:components="{
ButtonAgGrid
}"
...
/>
Pass data
const columnDefs = [
{
field: "name"
},
{
field: "button",
cellRenderer: "ButtonAgGrid",
}
]
const rowData = computed(() => {
return {
name: testReactiveValue.value ? 'test', 'test2'
}
})
And when computed "rowData" updated, agGrid send error:
Error: AG Grid: cannot get grid to draw rows when it is in the middle of drawing rows. Your code probably called a grid API method while the grid was in the render stage. To overcome this, put the API call into a timeout, e.g. instead of api.redrawRows(), call setTimeout(function() { api.redrawRows(); }, 0). To see what part of your code that caused the refresh check this stacktrace.
But if we remove cellRenderer: "ButtonAgGrid", all work good
My solution is to manually update rowData.
watchEffect(() => {
gridApi.value?.setRowData(props.rowData);
});
This one works well, but I wish it was originally

Scroll to a programmatically selected row in a v-data-table

I have a map with features on it that are also listed in a v-data-table. When the user clicks a row, the feature is highlighted on the map. When the user clicks a map feature, the corresponding grid row is selected. So I am programmatically setting the selected row like this:
selectRow(id) {
this.selected = [this.getRowFromId(id)]
},
getRowFromId(id) {
for (let site of this.sites) {
if (site.id === id) return site;
}
return []
},
Works fine with one UX problem: The table is not scrolled to the selected row.
I am using a vertically scrolling data-table with all rows in the grid rather than pagination.
Any ideas on how to programmatically scroll the data table?
Here is my solution. I found most of what I was looking for in this post: Plain JavaScript - ScrollIntoView inside Div
I had to do a couple of v-data-grid specific things to make it work. I am using a watcher in my context but you could to this anywhere you needed to:
watch: {
selectedId: function(newVal) { // watch it
// Select the row
this.selectRow(newVal)
// Scroll the item into view
// First we need to let the component finish selecting the row in the background
setTimeout( () => {
// We get the selected row (we assume only one or the first row)
const row = document.getElementsByClassName("v-data-table__selected")[0]
// Then we get the parent. We need to give the -v-data-table a ref
// and we actually take the child of the table element which
// has the scrollbar in my case.
const parent = this.$refs.detailGrid.$el.firstChild
// Finally call the scroll function
this.scrollParentToChild(parent, row )
}, 100)
}
},
methods: {
scrollParentToChild(parent, child) {
// Where is the parent on page
var parentRect = parent.getBoundingClientRect();
// What can you see?
var parentViewableArea = {
height: parent.clientHeight,
width: parent.clientWidth
};
// Where is the child
var childRect = child.getBoundingClientRect();
// Is the child viewable?
var isViewable = (childRect.top >= parentRect.top) && (childRect.top <= parentRect.top + parentViewableArea.height);
// if you can't see the child try to scroll parent
if (!isViewable) {
// scroll by offset relative to parent
parent.scrollTop = (childRect.top + parent.scrollTop) - parentRect.top - childRect.height
}
},
}

How do get focus to return to input on selection with enter

Using Vue Multiselect to create an unordered list, how would I get the focus to return to the input when a user hits enter to select an item? That is:
1. user searches item
2. user hits enter
3. item is added to list (as currently happens)
4. focus is returned to input with dropdown closed so the user can type in a new entry.
Would be amazingly grateful for any help :)
I’ve tried changing close-on-select to false which returns the focus correctly but leaves the dropdown open, which is not ideal as it hides the content below.
https://jsfiddle.net/hugodesigns/rtogxe4s/?utm_source=website&utm_medium=embed&utm_campaign=rtogxe4s
new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
data: {
options: [
],
optionsProxy: [],
selectedResources: [],
showLoadingSpinner: false
},
methods: {
customLabel (option) {
return `${option.name} - ${option.version}`
},
updateSelected(value) {
value.forEach((resource) => {
// Adds selected resources to array
this.selectedResources.push(resource)
})
// Clears selected array
// This prevents the tags from being displayed
this.optionsProxy = []
},
cdnRequest(value) {
this.$http.get(`https://api.cdnjs.com/libraries?search=${value}&fields=version,description`).then((response) => {
// get body data
this.options = []
response.body.results.forEach((object) => {
this.options.push(object)
});
this.showLoadingSpinner = false
}, (response) => {
// error callback
})
},
searchQuery(value) {
this.showLoadingSpinner = true
// GET
this.cdnRequest(value)
},
removeDependency(index) {
this.selectedResources.splice(index, 1)
}
},
created() {
const value = ''
this.cdnRequest(value)
}
}).$mount('#app')
Current result is that an item is added to the list and the multiselect loses focus, so must be clicked again to enter more input.
Expected behaviour is that an item is added to the list and focus returns to the multiselect, ready to accept input, with dropdown closed.
It seems like dropdown opening on focus is a behavior was flagged as an issue for version 2. If you upgrade the package the dropdown will not automatically open on focus.
https://github.com/shentao/vue-multiselect/issues/740
You should be able to get the element to go into focus by adding a ref to the multiselect and doing the following:
this.$refs.multiselectref.$el.focus();

Iterating with v-for on dynamic item

I'm trying to iterate through a db object I fetch during created(), I get the values in a console.log but the v-for template part remains empty. My sub-question is : is this good practice ? I'm quite new to Vue and my searches on this issue make me think it's a lifecycle issue.
Thanks for the help.
TEMPLATE PART :
.content(v-for="(content, key, index) in contents")
h3 {{key}}
.line
| {{getValue(content)}} // this is blank
METHODS PART:
getValue(value) {
PagesService.fetchDataWrap({
id: value
}).then((response) => {
const test = response.data.values[0].value
console.log(test) //this is working and gives the right value
return test
})
},
getPage() {
PagesService.fetchPage({
id: this.$route.params.page
}).then((response) => {
this.name = response.data.result.name
this.langs = response.data.result.langs
this.project = response.data.result.parent
this.contents = response.data.result.values
})
this.getProject()
}
console.log(this.contents) result :
{__ob__: Observer}
footer: "5a29943b719236225dce6191"
header: "5a29a9f080568b2484b31ee1"
which is the values I want to send when v-for iterates on contents so the getValue can process it to fetch corresponding values
I wouldn't recommend attempting to output the value of an asynchronous method. It's highly unlikely that it will work correctly.
Instead, populate your contents array / object fully during the created hook. For example, this can replace the contents hash value with whatever comes back from fetchDataWrap...
getPage () {
PagesService.fetchPage({
id: this.$route.params.page
}).then(response => {
this.name = response.data.result.name
this.langs = response.data.result.langs
this.project = response.data.result.parent
let contents = response.data.result.values
Promise.all(Object.keys(contents).map(key => {
// map each key to a "fetchDataWrap" promise
return PageService.fetchDataWrap({
id: contents[key]
}).then(res => {
// replace the hash with the resolved value
contents[key] = res.data.values[0].value
})
}).then(() => {
// all done, assign the data property
this.contents = contents
})
})
}
Then you can trust that the content has been loaded for rendering
.content(v-for="(content, key, index) in contents")
h3 {{key}}
.line
| {{content}}

Lists and Components not updating after data change - (VueJS + VueX)

A question about best practice (or even a go-to practice)
I have a list (ex. To-do list). My actual approach is:
On my parent component, I populate my 'store.todos' array. Using a
getter, I get all the To-do's and iterate on a list using a v-for
loop.
Every item is a Component, and I send the to-do item as a prop.
Inside this component, I have logic to update the "done" flag. And this element display a checkbox based on the "state" of the flag. When it does that, it do an action to the db and updates the store state.
Should I instead:
Have each list-item to have a getter, and only send the ID down the child-component?
Everything works fine, but if I add a new item to the to-do list, this item is not updated when I mark it as completed. I wonder if this issue is because I use a prop and not a getter inside the child component
Code:
store:
const state = {
tasks: []
}
const mutations = {
CLEAR_TASKS (state) {
state.tasks = [];
},
SET_TASKS (state, tasks) {
state.tasks = tasks;
},
ADD_TASK (state, payload) {
// if the payload has an index, it replaces that object, if not, pushes a new task to the array
if(payload.index){
state.currentSpaceTasks[payload.index] = payload.task;
// (1) Without this two lines, the item doesn't update
state.tasks.push('');
state.tasks.pop();
}
else{
state.tasks.push(payload.task);
}
},
SET_TASK_COMPLETION (state, task){
let index = state.tasks.findIndex(obj => obj.id == task.id);
state.tasks[index].completed_at = task.completed_at;
}
}
const getters = {
(...)
getTasks: (state) => (parentId) => {
if (parentId) {
return state.tasks.filter(task => task.parent_id == parentId );
} else {
return state.tasks.filter(task => !task.parent_id );
}
}
(...)
}
const actions = {
(...)
/*
* Add a new Task
* 1st commit add a Temp Task, second updates the first one with real information (Optimistic UI - or a wannabe version of it)
*/
addTask({ commit, state }, task ) {
commit('ADD_TASK',{
task
});
let iNewTask = state.currentSpaceTasks.length - 1;
axios.post('/spaces/'+state.route.params.spaceId+'/tasks',task).then(
response => {
let newTask = response.data;
commit('ADD_TASK',{
task: newTask,
index: iNewTask
});
},
error => {
alert(error.response.data);
});
},
markTaskCompleted({ commit, dispatch, state }, task ){
console.log(task.completed_at);
commit('SET_TASK_COMPLETION', task);
dispatch('updateTask', { id: task.id, field: 'completed', value: task.completed_at } ).then(
response => {
commit('SET_TASK_COMPLETION', response.data);
},
error => {
task.completed_at = !task.completed_at;
commit('SET_TASK_COMPLETION', task);
});
},
updateTask({ commit, state }, data ) {
return new Promise((resolve, reject) => {
axios.patch('/spaces/'+state.route.params.spaceId+'/tasks/'+ data.id, data).then(
response => {
resolve(response.data);
},
error => {
reject(error);
});
})
}
}
And basically this is my Parent and Child Components:
Task List component (it loads the tasks from the Getters)
(...)
<task :task = 'item' v-for = "(item, index) in tasks(parentId)" :key = 'item.id"></task>
(...)
The task component display a "checkbox"(using Fontawesome). And changes between checked/unchecked depending on the completed_at being set/true.
This procedure works fine:
Access Task list
Mark one existing item as done - checkbox is checked
This procedure fails
Add a new task (It fires the add task, which firstly adds a 'temporary' item, and after the return of the ajax, updates it with real information (id, etc..). While it doesn't have the id, the task displays a loading instead of the checkbox, and after it updates it shows the checkbox - this works!
Check the newly added task - it does send the request, it updates the item and DB. But checkbox is not updated :(
After digging between Vue.js docs I could fix it.
Vue.js and Vuex does not extend reactivity to properties that were not on the original object.
To add new items in an array for example, you have to do this:
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
More info here:
https://v2.vuejs.org/v2/guide/reactivity.html
and here: https://v2.vuejs.org/v2/guide/list.html#Caveats
At first it only solved part of the issue. I do not need the "hack" used after pushing an item into the array (push and pop an empty object to force the list to reload)
But having this in mind now, I checked the object returned by the server, and although on the getTasks, the list has all the fields, including the completed_at, after saving a new item, it was only returning the fields that were set (completed_at is null when created). That means that Vue.js was not tracking this property.
I added the property to be returned by the server side (Laravel, btw), and now everything works fine!
If anybody has a point about my code other than this, feel free to add :)
Thanks guys