Cypress Conditionnal loop - cypress

I'd like to make a conditional loop where I can execute a certain action (In this case, clicking a button) until I meet a certain condition (In this case, finding a year in a date-picker).
I have found a work around where I loop 250 time until I find the year I am looking for, but I don't like this approach. Is there a better way to do it ?
export default (placeholder, day, month, year) => {
describe("Select a date", () => {
it("Select a year", () => {
cy.get('input[data-placeholder="'+ placeholder +'"].mat-datepicker-input').parent().next().click();
cy.get('button[aria-label="Choose month and year"]').click();
cy.get('div.mat-calendar-content').then((content) => {
cy.wrap(content).as('content');
if (!(content.text().includes(year))) {
var array = Array.from({length:250},(v,k)=>k+1);
for (let i = 0; i < array.length; i++) {
cy.get('#content', {log: false}).then((tmp) => {
if (!(tmp.text().includes(year))) {
previous();
}
});
}
}
});
cy.contains(year).click();
});
});
}

You are doing a lot of complicated stuff on a control that is already tested by Angular Material library.
Why don't you just type in your date
cy.get(`input[data-placeholder="${placeholder}"]`)
.type('01-01-2022')
.blur()

Related

How would I sort first by date, then by priority?

I've figured out how to do each individually, but not sure how to prioritize one over the other.
{ "items": [ { "due": { "date": "2023-01-25"},"priority": 1}}
So I've got items.priority and items.due.date. If I want to sort by priority.due.date, I have the following, which is enabled or disabled by a switch that chooses whether or not to sort by due date, and whether or not to show it in ascending or descending order.
if (this.config.sort_by_due_date) {
items.sort((a, b) => {
if (a.due && b.due) {
if (this.config.ascending_order){
return (new Date(a.due.date)).getTime() - (new Date(b.due.date)).getTime();
}
return (new Date(b.due.date)).getTime() - (new Date(a.due.date)).getTime();
}
return 0;
});
}
I'm not sure what approach to take to first separate everything by due date, then by priority.

Enrich RxJS observable result array with data from another request

Considering the following simplified data structure:
Teacher
{
id: number,
name: string
students?: Student[] // filled with an inner second request
}
Student
{
name: string
}
TeachersResult (response of the first api request with no students inside)
{
teachers: Teacher[]
pagesCount: number // metadata for the pagination
}
My main question is how use RxJS to fill the students-property for every teacher with these two api endpoints:
GET http://localhost:1337/api/teachers
GET http://localhost:1337/api/students/{teacherId}
The first idea was to start with something like this:
getTeachersWithStudents(): Observable<TeachersResult> {
return this.apiService.getTeachers().pipe(
concatMap(teachersResult => {
const studentsObservables$: Observable<Student[]>[] = [];
teachersResult.teachers.foreach(teacher =>
studentsObservables$.push(this.apiService.getStudents(teacher.id)));
// Add students to associated teacher here? Is forkJoin() the right choice?
});
);
}
It feels complicated for me to add every students-result from the second api request to the associated teacher. This is the end result for me I want to achieve:
{
teachers: [
{
id: 1,
name: 'Uncle Bob',
students: [
{ name: 'Alice' },
{ name: 'Caren' }
]
},
{
...
}
],
pagesCount: 42
}
You may try something like this
getTeachersWithStudents(): Observable<TeachersResult> {
return this.apiService.getTeachers().pipe(
// concatMap here is right since you want to continue only after upstream
// has notifies the list of teachers
concatMap(teachersResult => {
const teachers = teachersResult.teachers;
const secondCalls = teachers.map(teacher => {
return this.apiService.getStudents(teacher.id).pipe(
// set the students into the teacher object and return the teacher
// filled with the students
map(students => {
teacher.students = students;
return teacher
})
);
})
// now you have an array of Observables for the second calls, you
// can therefore use forkJoin to execute them in parallel
return forkJoin(secondCalls).pipe(
// add the page number
map(teachersWithStudents => {
return {
teachers: teachersWithStudents,
pagesCount: teachersResult.pagesCount
}
})
)
})
);
}
In this way you are executing all the calls to get the students concurrently.
If you want to limit the concurrency rate, then you can use mergeMap in a slightly more complex stream, something like this
getTeachersWithStudents(): Observable<TeachersResult> {
return this.apiService.getTeachers().pipe(
concatMap(teachersResult => {
// transform the teachers array into a stream of teachers
const teachers = teachersResult.teachers;
return from(teachers).pipe(
// here you use mergeMap with the rate of concurrency desired
// in this example I set it to 10, which means there will be at
// most 10 requests for students on flight at the same time
mergeMap(teacher => {
return this.apiService.getStudents(teacher.id).pipe(
// set the students into the teacher object and return the teacher
// filled with the students
map(students => {
teacher.students = students;
return teacher
})
)
}, 10),
// now we want to repack all the teachers in one array using toArray
toArray(),
// here we create the response desired and return it
map(teachersWithStudents => {
return {
teachers: teachersWithStudents,
pagesCount: teachersResult.pagesCount
}
})
)
})
);
}
This stackblitz shows an example of the above 2 implementations.

Cypress - Loop looking for data and refresh if not found

I need to loop looking for an item in a table and if it's not found, click a refresh button to reload the table. I know I can't use a simple while loop due to the asynchronous nature of cypress. Is there another way to accomplish something like this.
I tried to tweak an example from another post but no luck. Here's my failed attempt.
let arry = []
for (let i = 0; i < 60; i++) { arry.push(i) }
cy.wrap(arry).each(() => {
cy.get('table[class*="MyTableClass"]').then(function($lookForTheItemInTheTable) {
if($lookForTheItemInTheTable.find("MySearchValue")) {
return true
}
else {
cy.get('a[class*="selRefreshTable"]').click()
cy.wait(2000)
}
})
})
Cypress is bundled with lodash. Instead of using a for loop, you can the _.times(). However, I wouldn't recommend this for your situation as you do not know how many times you would like to reiterate.
You'll want to use the cypress-recurse plugin and use it like this example in that repo:
import { recurse } from 'cypress-recurse'
it('gets 7 after 50 iterations or 30 seconds', () => {
recurse(
() => cy.task('randomNumber'), // actions you want to iterate
(n) => n === 7, // until this condition is satisfied
{ // options to pass along
log: true,
limit: 50, // max number of iterations
timeout: 30000, // time limit in ms
delay: 300 // delay before next iteration, ms
},
)
})
Even with the above mentioned, there may be a simplified approach to solving your problem with setting up your app to have the table always display what you are seeking on the first try.

redux-toolkit -- Type error: "unit" is read-only

I am using react-redux and redux-toolkit for this project. I want to add item to the cart. If there is already a same item in cart, just increment the unit. The error occurs at the slice, so I will just show the slice here.
const CartListSlice = createSlice({
name: 'cartItem',
initialState,
reducers: {
addToCart: (state, action) => {
let alreadyExist = false;
// get a copy of it to avoid mutating the state
let copyState = current(state.cartItem).slice();
// loop throught the cart to check if that item is already exist in the cart
copyState.map(item => {
if (item.cartItem._id === action.payload._id) {
alreadyExist = true;
item.unit += 1 // <--- Error occurs here
}
})
// If the item is not exist in the cart, put it in the cart and along with its unit
if (alreadyExist === false) {
state.cartItem.push({
cartItem: action.payload,
unit: 1
});
}
},
}
});
I get a type error telling me that unit is read-only.
How can I update the "unit" variable so that it increments whenever it is supposed to.
In React Toolkit's createSlice, you can modify the state directly and even should do so. So don't create a copy, just modify it.
In fact, this error might in some way stem from making that copy with current.
See the "Writing Reducers with Immer" documentation page on this
Meanwhile, a suggestion:
const CartListSlice = createSlice({
name: 'cartItem',
initialState,
reducers: {
addToCart: (state, action) => {
const existingItem = state.find(item => item.cartItem._id === action.payload._id)
if (existingItem) {
item.unit += 1
} else {
state.push({
cartItem: action.payload,
unit: 1
});
}
},
}
});
You don't need line:
let copyState = current(state.cartItem).slice();
Instead of copyState, just use state.cartItem.map
As #phry said, you should mutate state directly, because redux-toolkit is using immerJS in the background which takes care of mutations.

How to implement subscriptions in graphql.js?

This is a pretty simple question.
How to implement subscriptions in graphql?
I'm asking specifically for when using graphql.js constructors like below ?
I could not find a clean/simple implementation.
There is another question here, but it deals with relay.js - i don't want to unnecessarily increase the nr of external dependencies in my app.
What i have:
module.exports = function (database){
return new GraphQLSchema(
{ query: RootQuery(database)
, mutation: RootMutation(database)
, subscription: RootSubscription(database) -- i can see this in graphiql - see below
}
);
}
function RootSubscription(database){
return new GraphQLObjectType(
{ name: "RootSubscriptionType"
, fields:
{ getCounterEvery2Seconds:
{ type: new GraphQLNonNull(GraphQLInt)
, args :
{ id: { type: GraphQLString }
}
, subscribe(parent, args, context){
// this subscribe function is never called .. why?
const iterator = simpleIterator()
return iterator
}
}
}
}
)
}
I learned that i need a subscribe() which must return an iterator from this github issue.
And here is a simple async iterator. All this iterator does - is to increase and return the counter every 2 seconds. When it reaches 10 it stops.
function simpleIterator(){
return {
[ Symbol.asyncIterator ]: () => {
let i = 0
return {
next: async function(){
i++
await delay(2000)
if(i > 10){
return { done: true }
}
return {
value: i,
done: false
}
}
}
}
}
}
When i run the graphiql subscription, it returns null for some reason:
I'm piecing together code from multiple sources - wasting time and hacking it basically. Can you help me figure this one out?
Subscriptions are such a big feature, where are they properly documented? Where is that snippet of code which you just copy paste - like queries are for example - look here.
Also, i can't use an example where the schema is separate - as a string/from a file. I already created my schema as javascript constructors. Now since im trying to add subscriptions i can't just move back to using a schema as a string. Requires rewriting the entire project. Or can i actually have both? Thanks :)

Resources