Looping over swiper-slides excluding cloned duplicates in cypress - vue.js

I am new to Cypress and trying to loop the swiper slides excluding cloned duplicates. I am using the index of .each() in cypress but this is not working. Below is my code
if (index != 0 && index >= 22) {
//do something
} else {
//do something
}
Below is the example snapshot of my html code:
Can anyone please suggest the logic to loop only into the original slides?

You can use the :not() pseudo-selector
cy.get('div.swiper-slide:not(.swiper-slide-duplicate)')
.should('have.length', 23) // to show loop is filtered, remove once confirmed
.each($swiperSlide => {
...
or if you prefer to check inside the loop use .not() method
cy.get('div.swiper-slide')
.each($swiperSlide => {
if ($swiperSlide.not(".swiper-slide-duplicate").length) {
} else {
}
})

You can use the jquery hasClass() method with negation to check that the class .swiper-slide-duplicate is not present.
cy.get('div.swiper-slide').each(($ele) => {
if(!$ele.hasClass(".swiper-slide-duplicate")){
//Do something when you don't have the duplicate slide
}
else {
//Do something when you have the duplicate slide
}
})
You can also reverse the check as well, first, you check the presence of the class .swiper-slide-duplicate and then in the else condition you can mention the things when you don't have the duplicate slide.
cy.get('div.swiper-slide').each(($ele) => {
if($ele.hasClass(".swiper-slide-duplicate")){
//Do Something when you have the duplicate slide
}
else {
//Do Something when you don't have the duplicate slide
}
})

You can use the jQuery .attr() method to check if the class is just swiper-slide or has both swiper-slide and swiper-slide-duplicate.
cy.get('div.swiper-slide')
.each($el => {
if($el.attr("class") === "swiper-slide") {
}
else {
}
})
Same approach for data-swiper-slide-index
cy.get('div.swiper-slide')
.each($el => {
if($el.attr("class") === "swiper-slide" && $el.attr("data-swiper-slide-index") !== "0") {
}
else {
}
})

You can use a jQuery .filter() to process each type
cy.get('div.swiper-slide')
.then($els => {
// Process non-duplicates
$els.filter('[class=["swiper-slide"]') // exact match to a single class
.each($el => {
})
// Process duplicates
$els.filter('[class*=["swiper-slide-duplicate"]') // *= means "contains"
.each($el => {
})
})
or in Cypress commands (without jQuery)
// Process non-duplicates
cy.get('div.swiper-slide')
.filter(':not(".swiper-slide-duplicate")')
.each($el => {
...
})
// Process duplicates
cy.get('div.swiper-slide')
.filter('.swiper-slide-duplicate')
.each($el => {
...
})

To loop only into the original slides, use the Cypress .not() command to exclude duplicates.
cy.get('div.swiper-slide')
.not('.swiper-slide-duplicate')
.each($el => {
...
})

Related

Cypress hangs in loop when running custom Chai assertion

I have been trying to create my own custom chai assertion (based on the Cypress recipe template: https://github.com/cypress-io/cypress-example-recipes/blob/master/examples/extending-cypress__chai-assertions/cypress/support/index.js).
What I have found with the code below is that when it is run I end up with a constant loop of WRAP, if I swap this.obj with element it then results in a constant stream of GET. I do not seem to ever progress further than getRect(first).then((actual)
If anyone could help me out I'd be very grateful.
cypress/integration/test.js
describe('testing custom chai', () => {
it('uses a custom chai helper', () => {
cy.visit('https://www.bbc.co.uk/news');
cy.get('#orb-modules > header').should('be.leftAligned', '#orb-header');
});
});
cypress/support/index.js
function getRect(selector) {
if (selector === '&document') {
return cy.document().then(doc => doc.documentElement.getBoundingClientRect());
} if (typeof selector === 'string') {
return cy.get(selector).then($elem => $elem[0].getBoundingClientRect());
}
return cy.wrap(selector).then(elem => Cypress.$(elem)[0].getBoundingClientRect());
}
function getRects(first, second) {
return getRect(first).then((actual) => {
getRect(second).then(expected => [actual, expected]);
});
}
const aligned = (_chai, utils) => {
function leftAligned(element) {
getRects(element,this.obj).then((rects) => {
this.assert(
rects[0].left === rects[1].left,
'expected #{this} to be equal',
'expected #{this} to not be equal',
this._obj,
);
});
}
_chai.Assertion.addMethod('leftAligned', leftAligned);
};
chai.use(aligned);
The basic problem is that the async commands cy.get(), cy.wrap(), cy.document() can't be used in the custom assertion. My best guess is that the auto-retry mechanism is going bananas and giving you the constant loop.
Instead, you can use Cypress.$() which is the synchronous version (essentially jquery exposed on the Cypress object).
The following seems to work ok. (I renamed getRects() param to subject, as sometimes it's a selector and sometimes it's the object passed in to .should()).
Note also this._obj instead of this.obj.
function getRect(subject) {
if (subject === '&document') {
return Cypress.$(document).context.documentElement.getBoundingClientRect();
}
if (typeof subject === 'string') { // the selector passed in to assertion
return Cypress.$(subject)[0].getBoundingClientRect();
}
if (typeof subject === 'object') { // the element from cy.get() i.e this._obj
return subject[0].getBoundingClientRect();
}
return null; // something unkown
}
function getRects(first, second) {
const actual = getRect(first)
const expected = getRect(second)
return [actual, expected];
}
const aligned = (_chai, utils) => {
function leftAligned(element) {
const rects = getRects(element, this._obj)
this.assert(
rects[0].left === rects[1].left,
'expected #{this} to be equal',
'expected #{this} to not be equal',
this._obj,
);
}
_chai.Assertion.addMethod('leftAligned', leftAligned);
};
chai.use(aligned);
I was unable to test your BBC page directly, as there's a cross-origin problem occurring
Refused to display 'https://www.bbc.com/news' in a frame because it set 'X-Frame-Options' to 'sameorigin'
but it does work with a mockup page
cypress/app/bbc-sim.html
<div id="orb-modules">
<header>
<h1>Brexit: Boris Johnson's second attempt to trigger election fails</h1>
</header>
</div>
and testing like so
it('uses a custom chai helper', () => {
cy.visit('app/bbc-sim.html')
cy.get('#orb-modules > header').should('be.leftAligned', '#orb-modules');
});

issue refer to scope of variable

how to get variable from outer layer method
trying to use a variable in outer layer in my React-Native App
updateCheckBox() {
Constants.TABS.map((item) => {//Constants.TABS is an array
AsyncStorage.getItem(item)//using item as key to fetch from AsyncStorage
.then((res) => {
if(res == 1) {
//debugged here, item was undeined. but i need setState here with item as key. How should i get item here.
this.setState({item: true}) // I need to get the item here, but it show undefined
} else {
this.setState({item:false}) // I need to get the item here, but it show undefined
}
})
})
}
// I need to get the item here, but it show undefined
You need to wrap the item in [] to use it as a key for a property. Like this:
updateCheckBox() {
Constants.TABS.map(item => {
AsyncStorage.getItem(key) //
.then((res) => {
//item is accessible here, to use item as the key to a property wrap it in []
if(res == 1) {
this.setState({[item]: true});
} else {
this.setState({[item]: false});
}
})
})
}
finally, I found there is no issue in this code, the thing is
updateCheckBox() {
Constants.TABS.map((item) => {
let key = item
AsyncStorage.getItem(key)
.then((res) => {
console.log(item, "item is here", res); //item is visible here
console.log(key) //key is all always undefined
if(res == 1) {
this.setState({item: true})
} else {
this.setState({item:false})
}
})
})
}
key is not visible in method then, which I can not explain, but all in all, my code works.

Is it good practice to use for loops to sort out data in the same function where it's fetched in Vue?

I am using fetch to get some data from an API, I convert this to JSON and want to sort it into different categories. For example tickets (which is what I'm retrieving) with the status active should be in a different array than the ones with status waiting on customer. I want to use a for loop to sort through the results. Should I do this in the same function they're fetched in?
Did a bit of googling but couldn't find a post on this.
methods: {
fetchTickets() {
fetch('/api')
.then(res => res.json())
.then(resJson => {
arrayLength = resJson.length
for(var i = 0; i < arrayLength; i++) {
if(resJson[i]['status'] === 'active') {
//do something
}
else if(resJson[i]['status'] === 'waiting on customer') {
// do something else
}
else {
// do a dance
}
}
});
},
}
So, is it okay to do the above or is it very sensitive to errors/is there a more convenient alternative?
There is a more convient alternative.
You should create two API calls.
1.) /api/activeUsers
2.) /api/waitingCustomers
Then for each API call, you can use the .filter API and return the appropiate array
fetchActiveTickets() {
fetch('/api')
.then(res => res.json())
.then(resJson => {
return resJson.filter(item => {
return item.status ==='active'
})
//do the same for waiting... i.e. resJson(item => {
//return item.status ==='waiting'
//})
}
});
},
I would recommend using .filter() rather than looping over the array to split the source into the pieces you want.
data: {
activeTickets: [],
waitingTickets: []
}
methods: {
fetchTickets() {
fetch('/api')
.then(res => res.json())
.then(resJson => {
this.activeTickets = resJson.filter(function(ticket) { return ticket.status === 'active' });
this.waitingTickets= resJson.filter(function(ticket) { return ticket.status === 'waiting on customer' });
// do things with your filters arrays...
});
},
}
Try
methods: {
async fetchTickets() {
let res = await (await fetch('/api')).json();
let active = res.filter(x=> x['status']=='active');
let waiting = res.filter(x=> x['status']=='waiting on customer');
// ... do something
},
}

Using conditional class with store getters: not updating class

I have a div with a conditional class that works well when the app is loaded, but it's not updated when the store data change.
The code in my vue component looks like this
<span class="week-day"
v-bind:class="{ complete: isDayComplete(day) }"
v-for="day in daysInWeek"
v-bind:data-day="day"
> </span>
And I have ...mapGetters(['isDayComplete']) in my computed object.
The getter looks like this
isDayComplete(state) {
return (day) => {
console.log(`called isDayComplete(${day})`)
const formattedDay = moment(day, 'DD/MM/YYYY').format('YYYY-MM-DD');
if (state.daysData[formattedDay]) {
if (state.daysData[formattedDay].meals.length > 0) {
console.log(`day ${day} is complete`);
return true;
} else {
console.log(`day ${day} is NOT complete`);
return false;
}
} else {
console.log(`no data for day ${day}`);
return false;
}
}
},
I update my meals data in a mutation
updateMeals(state, meals) {
_.forEach(meals, (meal) => {
state.daysData[meal.day].meals.push(meal);
});
}
And I have an action that commits that mutation
loadMeals({ state, commit }) {
return new Promise((resolve, reject) => {
get.meals.from.api()
.then((response) => {
commit('initDaysData');
commit('updateMeals', response.data.data);
return resolve();
})
.catch(reject);
});
}
So whenever I call loadMeals the class is not updated if one day changes its status (complete/not-complete). If I reload the page, the class is set correctly.
What am I doing wrong? Thanks!
It's a common reactivity problem. You can make deep copy (use JSON.parse(JSON.stringify())) to make data reactive:
updateMeals(state, meals) {
_.forEach(meals, (meal) => {
state.daysData[meal.day].meals.push(meal);
});
state.daysData = JSON.parse(JSON.stringify(state.daysData))
}
#ittus answer was correct. I found another way to achieve this that maybe could
help someone else.
add another mutation on the store
updateCompletedDays(state) {
const newState = [];
_.forEach(state.daysData, (currentDayData, currentDay) => {
if (currentDayData.meals.length > 0) {
newState.push(currentDay);
}
});
state.completedDays = newState;
},
commit this mutation after meals are updated
change isDayComplete getter to
isDayComplete(state) {
const formattedDay = moment(day, 'DD/MM/YYYY').format('YYYY-MM-DD');
return state.completedDays.indexOf(formattedDay) !== -1;
}
Basically when using reactivity going deep into arrays/object will not work, better have arrays of aggregated data (check also Vue.set api)

Setting validators in Angular on multiple fields

I am trying to find simple way to update the form fields with Validators. For now I do the below:
ngOnInit() {
this.form.get('licenseType').valueChanges.subscribe(value => {
this.licenseChange(value);
})
}
licenseChange(licenseValue: any) {
if (licenseValue === 2) {
this.form.get('price').setValidators([Validators.required]);
this.form.get('price').updateValueAndValidity();
this.form.get('noOfLicenses').setValidators([Validators.required]);
this.form.get('noOfLicenses').updateValueAndValidity();
this.form.get('licenseKey').setValidators([Validators.required]);
this.form.get('licenseKey').updateValueAndValidity();
this.form.get('supportNo').setValidators([Validators.required]);
this.form.get('supportNo').updateValueAndValidity();
this.form.get('purchasedFrom').setValidators([Validators.required]);
this.form.get('purchasedFrom').updateValueAndValidity();
//......others follows here
}
else {
this.form.get('price').clearValidators(); this.form.get('price').updateValueAndValidity();
this.form.get('noOfLicenses').clearValidators(); this.form.get('noOfLicenses').updateValueAndValidity();
this.form.get('licenseKey').clearValidators(); this.form.get('licenseKey').updateValueAndValidity();
this.form.get('supportNo').clearValidators(); this.form.get('supportNo').updateValueAndValidity();
this.form.get('purchasedFrom').clearValidators(); this.form.get('purchasedFrom').updateValueAndValidity();
//......others follows here
}
}
Is this the only way to add and update validators or is there any other way to achieve this. For now I am calling the updateValueAndValidity() after setting/clearing each field.
Update
Something like
licenseChange(licenseValue: any) {
if (licenseValue === 2) {
this.form.get('price').setValidators([Validators.required]);
//......others follows here
}
else{
//......
}
}
this.form.updateValueAndValidity();///only one line at the bottom setting the entire fields.
I done something similar like this
licenseChange(licenseValue: any) {
if (licenseValue === 2) {
this.updateValidation(true,this.form.get('price'));
//......others follows here
}
else {
this.updateValidation(false,this.form.get('price'));
//......others follows here
}
}
//TODO:To update formgroup validation
updateValidation(value, control: AbstractControl) {
if (value) {
control.setValidators([Validators.required]);
}else{
control.clearValidators();
}
control.updateValueAndValidity();
}
If you want to do this for all the controlls inside your form
licenseChange(licenseValue: any) {
for (const field in this.form.controls) { // 'field' is a string
const control = this.form.get(field); // 'control' is a FormControl
(licenseValue === 2) ? this.updateValidation(true,
control):this.updateValidation(fasle, control);
}
}
I did it like below:
this.form.get('licenseType').valueChanges.subscribe(value => {
this.licenseChange(value, this.form.get('price'));
//....Others
}
licenseChange(licenseValue: any, control: AbstractControl) {
licenseValue === 2 ? control.setValidators([Validators.required]) : control.clearValidators();
control.updateValueAndValidity();
}