Cypress how to assert that console was calledWith array of x length? - mocha.js

I'm trying to find how to assert that the console log was called with an array of a defined fixed length and I'm not sure how to do that. I'm not very familiar with Mocha/Sinon, which Cypress seems to be using.
To give a bit more of a background of my App does, it's a Queued Editing, I change a few cell and they change color, once I'm ready to click save then it shows an array of all the cells that got changed (in my particular test, 11 got changed). I know how to test the entire calledWith comparison but I just want to test the array length and nothing more (since I have random data, I can't test an equal match of data but the array length will be sufficient).
So far I have this code that works for the most part except for the array length
describe('My Sample', () => {
beforeEach(() => {
// create a console.log spy for later use
cy.window().then((win) => {
cy.spy(win.console, 'log');
});
});
it('should click on the "Save" button and expect 2 console log calls with the queue items', () => {
cy.get('[data-test=save-all-btn]').click();
cy.get('.unsaved-editable-field')
.should('have.length', 0);
cy.window().then((win) => {
expect(win.console.log).to.have.callCount(2);
expect(win.console.log).to.be.calledWith(Array[11]); // how to test array length of 11?
});
});
Also, maybe as a second question, how could we assert that this array contains a certain object property with x value? In Jest we can do it this way. I just haven't seen anything similar in Cypress, is that kind of test possible in Cypress?
// for an array
const oddArray = [1, 3, 5, 7, 9, 11, 13];
test('should start correctly', () => {
expect(oddArray).toEqual(expect.arrayContaining([1, 3, 5, 7, 9]));
});
// for an array of objects
const users = [{id: 1, name: 'Hugo'}, {id: 2, name: 'Francesco'}];
test('we should have ids 1 and 2', () => {
expect(users).toEqual(
expect.arrayContaining([
expect.objectContaining({id: 1}),
expect.objectContaining({id: 2})
])
);
});

Related

Should we avoid nested rxjs operators? One case which I cannot test

I have written the following effect in my Angular app which uses rxjs. On MyActions.myAction, I receive an object containing a property ids - an array of ids - and for each id I want to send an HTTP request via this.myApiService.getResource, which returns an Observable<Resource>. I want then to collect all results in an array, and dispatch another action passing the array.
public loadResources$: Observable<MyAction> = this.actions$.pipe(
ofType(MyActions.myAction),
switchMap(({ ids }) => from(ids).pipe(
mergeMap(id => this.myApiService.getResource(id)),
toArray()
)),
map(resources) => MyActions.resourcesLoaded({ resources } )),
);
The code above does the job, but I wonder whether I should avoid nesting two flows of reactive operators, and whether there is a better way to write that.
The reason I wonder that is that I am having problems writing a test for it. I wrote the test below but I cannot make it pass.
it('should dispatch an resourcesLoaded action with the resources', () => {
const ids = ['5f7c723832758b859bd8f866'];
const resources = [{} as Resource];
const values = {
l: MyActions.loadResources({ ids }),
t: ids[0],
o: MyActions.resourcesLoaded({ resources })
};
actions = hot('--l------------', values);
const get$ = cold(' -------t-----', values);
const expected = cold('---------o-----', values);
myApiService.getResource.withArgs(ids[0]).returns(get$);
expect(myEffects.loadResources$).toBeObservable(expected);
});
The error I get is:
Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 50, notification: Notification({ kind: 'N', value: { ....
Error: Expected $.length = 0 to equal 1.
Expected $[0] = undefined to equal Object({ frame: 50, notification: Notification({ kind: 'N', value: { ....
at <Jasmine>
at compare (http://localhost:9876/Users/jacopolanzoni/Documents/Development/myProject/node_modules/jasmine-marbles/index.js:91:1)
at <Jasmine>
but I wonder whether I should avoid nesting two flows of reactive operators, and whether there is a better way to write that
I'd say it depends on what you want to achieve, at least in this case.
of([1,2,3]).pipe(mergeAll(), switchMap(value => http.get(...)))
differs from
of([1,2,3]).pipe(switchMap(ids => from(ids).pipe(mergeMap(...))))
In the first scenario, each inner observable will be discarded by the next value(except for the last value), so only 3 will resolve.
In the second scenario, it will process all of them, because you explode the array in the inner observable(which is managed by swtichMap, so the only way its inner observable will be discarded is if a new outer value(e.g another array of ids) is emitted by the source).
A case where nesting is not necessary is:
of([1,2,3])
.pipe(
// whenever you want to explode an array,
// it does not matter which higher order operator you use
// since the operation is **synchronous**
// so, `mergeAll`, `concatAll`, `switchAll` should work the same
mergeAll(),
mergeAll(id => this.apiService.useId(id))
)
// same as
of([1,2,3])
.pipe(
mergeMap(ids => from(ids).pipe(mergeMap(id => this.apiService.useId(id))))
)
As you can see, in this case, switchMap has been replaced with mergeMap.
I have found out my test was failing because toArray was waiting for the observable returned by getResource (i.e., httpClient.get) to complete. Replacing t with (t|) fixes the test:
it('should dispatch an resourcesLoaded action with the resources', () => {
const ids = ['5f7c723832758b859bd8f866'];
const resources = [{} as Resource];
const values = {
l: MyActions.loadResources({ ids }),
t: ids[0],
o: MyActions.resourcesLoaded({ resources })
};
actions = hot('--l------------', values);
const get$ = cold(' -------(t|)-----', values);
const expected = cold('---------o-----', values);
myApiService.getResource.withArgs(ids[0]).returns(get$);
expect(myEffects.loadResources$).toBeObservable(expected);
});
Yet, the first part of my question, i.e. whether it's good practice or not to nest operators like that, still stands.

Delay for every element with RXJS

I'm using RxViz to simulate different actions that comes every 1 sec. When I try
Rx.Observable.create(obs => {
obs.next([1, 2, 3]); // or could be ['aaa', 'bbbb', 'ccc']
obs.complete();
}).delay(1000);
on https://rxviz.com
or on my own with a console.log
it keeps displaying the three number 1, 2, 3 at the same time
There's a post about this same problem, but none of the answer works for me. I'm using Rx last version 6
How can I create an observable with a delay
[EDIT] The array can contains anything like number, string or any object
If you want to delay each value (by 1 sec for example), you may do something like the following:
Rx.Observable.create(obs => {
obs.next([1, 2, 3]);
obs.complete();
})
.pipe(
// make observable to emit each element of the array (not the whole array)
mergeMap((x: [any]) => from(x)),
// delay each element by 1 sec
concatMap(x => of(x).pipe(delay(1000)))
)
.subscribe(x => console.log(x));
}
Here I did not modify the internals of the observable created by you. Instead, I just take your observable and apply appropriate operations to achieve what you seem to be expecting.
Here is my solution (very clean)
const fakeData = [1,2,3]
loadData$() {
return from(fakeData).pipe(
concatMap(item => of(item).pipe(
delay(1000)
)),
);
}
This one works by modifying a little bit #siva636's answer
Rx.Observable.create(obs => {
obs.next(1);
obs.next(2);
obs.next(3);
obs.complete();
}.concatMap(x=>Rx.Observable.of(x) .delay(1000) )
Here is a succinct way that builds on the other responses.
from([...Array(10).keys()]).pipe(
concatMap(x => of(x).pipe(delay(1000)))
).subscribe(y => console.log(y))
A more RxJs native version would be as follows.
const myInterval = rxjs.interval(1000);
myInterval.pipe(rxjs.operators.take(10)).subscribe(x => console.log(x));
Here, you emit in one observable emission the all array. [1,2,3].
You only delay that one emission by 1000 ms. But the emission is still one.
Even if we emit each value on its own, the delay function will only apply to the first emission. The others will come immediately after:
Rx.Observable.create(obs => {
var arr = [1, 2, 3];
arr.forEach(item => obs.next(item));
obs.complete();
}).delay(1000);
There is no magic in the create constructing function. If we want an emission to come every x time:
We could make an interval that emits those values (taken from learnrxjs)
import { Observable } from 'rxjs/Observable';
/*
Increment value every 1s, emit even numbers.
*/
const evenNumbers = Observable.create(function(observer) {
let value = 0;
const interval = setInterval(() => {
observer.next(value);
value++;
}, 1000);
return () => clearInterval(interval);
});
RxJS v7 supports the operator delayWhen [1], so you could write a simpler code as
import { delayWhen, interval, of } from 'rxjs';
of("John", "von", "Neumman", "János Neumann").pipe(
delayWhen((_, index) => interval(index*1000))
).subscribe(console.log);
Check out a demo on https://stackblitz.com/edit/vgibzv?file=index.ts
It works because it delays the emission of items by 0 seconds, 1000 seconds, 2000 seconds, 3000 seconds, and so on.
Another choice is the operator scan, you make the series from an interval [2].
[1] "RxJS - delayWhen." 16 Dec. 2022, https://rxjs.dev/api/operators/delayWhen
[2] "RxJS - scan." 16 Dec. 2022, rxjs.dev/api/index/function/scan

How to make rxjs non ui blocking?

I have an array of items. Each item in that array represents a row in my html table and is automatically rendered when the array of items is changing.
I now wanted to use rxjs to add some items to that table when I click a button, so I did the following:
Rx.Observable.fromEvent(this.$.button, 'click')
.switchMap(() => Rx.Observable.range(0, 10000))
.subscribe((x) => {
this.push('items', {id: '' + x, description: '' + x});
});
But this is freezing the ui until every element is pushed into the array.
How can I implement it so that the ui is not freezing and still responses to user inputs?
If you don't want range to synchronously emit the entire range of values, you can specify a scheduler.
For example:
Rx.Observable
.fromEvent(this.$.button, 'click')
.switchMap(() => Rx.Observable.range(0, 10000, Rx.Scheduler.asap))
.subscribe((x) => {
this.push('items', {id: '' + x, description: '' + x});
});

RxJs. Combining latest and once

I have a UI like this:
Where I can
Enter something in search box
Drag users from table to chart
The logic required is:
Initially chart shows some subset of all users (e.g., first 10)
When users are dragged on chart they are added to the users that already there
When filter is applied all users are removed from chart and then it is repopulated with some subset of matching users
I am trying to implement such logic with RxJs.
I have filteredUsers$ and addedUsers$ stream that produce users matching filter and dragged users correspondingly.
I need to combine them in such way:
Observable
.<OPERATOR>(filteredUsers$, addedUsers$)
.subscribe(([filteredUsers, addedUsers]) => {
// When filteredUsers$ fires:
// filteredUsers is value from stream
// addedUsers == null
// When addedUsers$ fires:
// filteredUsers is latest available value
// addedUsers is value from stream
redrawChart(/* combining users */)
});
Any ideas how I can achieve this?
Time sequence:
Filtered: - a - - - - a - ->
Added : - - b - b - - - ->
Result : - a ab - ab - a - ->
If you want the final stream to be populated only when addUsers$ fires with latest from could be a solution:
So, in your case addUsers$ could be the first stream.
You can try out the following code:
let firstObservable$ = Observable.from([1, 2, 3, 4])
.zip(Observable.interval(50), (a, b) => {
return a;
});
let secondObservable$ = Observable.from([5, 6])
.zip(
Observable.interval(70), (a, b) => {
return a;
});
firstObservable$
.withLatestFrom(secondObservable$, (f, s) => ({ a: f, b: s }))
.subscribe(x => {
console.log('result: ', x);
});
The first observable emits every 50 ms a value from the array.
The second observable every 75 ms.
The values printed are {a: 2, b: 5} {a: 3, b: 6} {a: 4, b: 6}
Because 1 was emitted before 5 we lose the pair (1,5)!
I am not clear but missing a pair from addUsers$ if the other stream has not emitted may be non-desired behavior for you.
You could overcome that if you start the second stream with an initial value and then filter out any results you don't want.
You have the combineLatest operator which basically does what you are describing. It combines two observables and gives you the latest value of both streams.
So:
--a--b-----c---
-x-----d-----p-
-combineLatest-
--a--b-b---c-c
x x d d p
This should allow you to do what you want if I understand correctly.
Here's the official doc link:
https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/combinelatest.md
Eventually I have done it by adding additional subject:
var filteredUsers$ = ... // users from filter
var addedUsers$ = ... // users dragged on chart
var usersToDraw$ = new Subject();
subscriptions:
usersToDraw$
.subscribe(usersToDraw => {
redrawChart(usersToDraw);
});
filteredUsers$
.subscribe(filteredUsers => {
usersToDraw$.next(filteredUsers);
});
Observable
.combineLatest(filteredUsers$, addedUsers$)
.filter(([filteredUsers, addedUsers]) => addedUsers != null)
.subscribe(([filteredUsers, addedUsers]) => {
// we 'clear' stream so the same users won't be added twice
addedUsers$.next(null);
usersToDraw$.next(/* combining users */);
});
UPDATE
The solution can be improved with withLatestFrom (thanks #nova)
usersToDraw$
.subscribe(usersToDraw => {
redrawChart(usersToDraw);
});
filteredUsers$
.subscribe(filteredUsers => {
usersToDraw$.next(filteredUsers);
});
addedUsers$
.withLatestFrom(filteredUsers$)
.subscribe(([addedUsers, filteredUsers]) => {
usersToDraw$.next(/* combining users */);
});

Rxjs distinct and arrays of numbers

I can't explain to myself this
const something = new Rx.BehaviorSubject([1,2,4,4])
.distinct()
.do((s) => console.log(s))
.map(list => list.length)
.filter(length => length >=2)
.subscribe(total => console.log('total:', total));
this is what I get as output
[1, 2, 4, 4]
"total:"
4
I get confused because reviewing the docs on distinct I thought it would work for numbers. My use case is a data table widget sends me events and this array tracks which row they clicked and I want to detect once a double click occurred.
updated code
const something = new Rx.BehaviorSubject([]);
something.next([1]);
console.log(something.getValue());
something.next(something.getValue().concat(2));
something.next(something.getValue().concat(3));
something.next(something.getValue().concat(4));
something.next(something.getValue().concat(4));
something
.distinct()
.subscribe(val => console.log('value:', val));
output
"value:"
[1, 2, 3, 4, 4]
You're sending a value that happens to be an array. You would see the operation of distinct if you did
const something = new Rx.BehaviorSubject([]);
something .distinct() .subscribe(val => console.log('value:', val));
something.next(1); // --> value: 1
something.next(2); // --> value: 2
something.next(1); // no output (because of distinct)
something.next(3); // --> value: 3

Resources