I would like to do sequential HTTP calls and merge the responses into one and unique observable.
Problem is: the way I am doing it seems to be wrong. Indeed, I would like to merge the data from the second request into the first one, but the way I did it seems to replace the result of the first one by the result of the seconde one.
Here it is the first request called:
[{vehicule_id:123, statistics:{speed:60,rotation:38}},...]
The second one:
[{vehicule_id:123, name:"name",description:"description},...]
And the result I would like to get:
[{vehicule_id:123, statistics:{speed:60,rotation:38}, name:"name",description:"description},...]
Important thing to know: the second request needs a vehicule_id provided in the response of the first one.
With my code, the response of the second call replace the result of the first one instead of merging them.
Here it is my code:
getUserVehiculesInfo(user_id: number, language: string): void {
this._data.getUserVehiculesInfo(user_id, language)
.pipe(
map(res => res.data[user_id]),
switchMap(vehicules => forkJoin(vehicules.map(vehicule => this._data.getVehiculesInfo(tank.vehicule_id, language)))),
tap(console.log)
)
.subscribe();
}
Once you have the vehiculesInfo (the result of the forkJoin, you simply need to combine them with the vehicules:
switchMap(
vehicules => forkJoin(...).pipe(
map(infos => combineVehiculesAndInfos(vehicules, infos))
)
)
Related
https://docs.cypress.io/api/commands/wait - cy.wait() is used to wait for an arbitrary number of milliseconds.
What's the difference in the following two snippets
// snippet-1
cy.wait(10*1000);
// other code
// snippet - 2
cy.wait(10*1000).then(()=>{
// other code
})
I tried to run the following code to test how it is working and i got the output mentioned in the below image.
cy.wait(10 * 1000).then(() =>
cy.log("This is inside wait()" + new Date().toLocaleTimeString())
);
cy.log("This is after wait()" + new Date().toLocaleTimeString());
These are the log results:
From the above image, it looks like we should add our code inside .then because that will be executed after cy.wait . I also have seen people writing their test cases just like snippet-1
Which one is correct and which one we should use?
If you want a better log, use a custom command to make a version that defers evaluating the value.
By providing a callback, the new Date().toLocaleTimeString() expression evaluates when you expect it to.
Cypress.Commands.add('logLazy', (thingToLog) => {
if (typeof thingToLog === 'function') {
cy.log(thingToLog())
} else {
cy.log(thingToLog)
}
})
cy.wait(3_000).then(() =>
cy.log("This is inside wait()" + new Date().toLocaleTimeString())
);
cy.log("This is after wait()" + new Date().toLocaleTimeString());
cy.wait(3_000).then(() =>
cy.logLazy("This is LAZY LOG inside wait()" + new Date().toLocaleTimeString())
);
cy.logLazy(() => "This is LAZY LOG after wait()" + new Date().toLocaleTimeString());
Cypress commands use the current value of variables and resolve function values when enqueuing commands. So, in your example, the commands are enqueued as .wait() and then the outer .log(). The value of the date string is calculated when the command is enqueued. The inner .log() is enqueued within the .then(), and thus has a later date string.
Either snippet is fine to use, but be aware of how the subsequent commands in your test will react. Are you manipulating some variable during your .wait(), and you would not want the original value to be used for your Cypress command? Probably a good idea to place that command within a .then() or some other Cypress command. Are you just interacting with the website and need to wait 10 seconds? You probably don't need to worry about placing commands inside .then().
I have set up a number of intercepts in the body of my tests. Here they are, pasted from the Cypress log, with the alias added
cy:intercept ➟ // alias: getRecipesSku(971520)
Method: GET
Matcher: "https://wpsite.com/wp-json/wp/v2/posts?tags[]=6287&_fields**"
Mocked Response: [{ ... }]
cy:intercept ➟ // alias: getRecipesSku(971520,971310)
Method: GET
Matcher: "https://wpsite.com/wp-json/wp/v2/posts?tags[]=6287&tags[]=6289&_fields**"
Mocked Response: [{ ... }]
Our application's tests also mocks a number of routes by default, (coming from an apiClient.initialize) including this one below. FWIW, this is defined earlier than those above:
cy:intercept ➟ // alias: getJustcookRecipes
Method: GET
Matcher: "https://wpsite.com/wp-json/**"
Mocked Response: [{ ... }]
My test code sets up those first two intercepts, and ultimately calls the routes. Here is the code, heavily abridged:
it('refreshes the recipes when switching protein tabs', () => {
apiClient.initialize()
/* do lots of other stuff; load up the page, perform other tests, etc */
// call a function that sets up the intercepts. you can see from the cypress output
// that the intercepts are created correctly, so I don't feel I need to include the code here.
interceptJustCook({ skus: [beefCuts[0].id] }, [beefCut1Recipe])
interceptJustCook({ skus: [beefCuts[0].id, beefCuts[1].id] }, twoBeefRecipes)
// [#1] select 1 item;
// calls route associated with intercept "getRecipesSku(971520)"
page.click.checkboxWithSku(beefCuts[0].id)
/* assert against that call */
// [#2] select 2nd item (now 2 items are selected);
// calls route associated with intercept "getRecipesSku(971520, 971310)"
page.click.checkboxWithSku(beefCuts[1].id)
In the Cypress logs, the first call (marked by comment #1) is intercepted correctly:
cy:fetch ➟ (getRecipesSku(971520)) STUBBED
GET https://wpsite.com/wp-json/wp/v2/posts?tags[]=6287&_fields=jetpack_featured_media_url,title.rendered,excerpt.rendered,link,id&per_page=100&page=1&orderby=date
However, the second call (marked by comment #2) is intercepted by the wrong route mocker:
cy:fetch ➟ (getJustCookRecipes) STUBBED
GET https://wpsite.com/wp-json/wp/v2/posts?tags[]=6287&tags[]=6289&_fields=jetpack_featured_media_url,title.rendered,excerpt.rendered,link,id&per_page=100&page=1&orderby=date
You can see for yourself that the URL called at #2 does indeed match the getRecipesSku(971520, 971310) intercept, but it is caught by the getJustcookRecipes intercept. Now, I suppose the URL for that latter intercept would catch my second custom intercept. But it would also, in the same way, catch my first custom intercept, but that first one works.
(update:) I tried commenting out the place in the code where the getJustcookRecipes intercept is created so that it doesn't exist. Now, the call that should hit getRecipesSku(971520,971310) isn't being mocked at all! I checked and the mocked and called urls are a match.
Why is this going wrong and how do I fix it?
Something in the glob pattern for the 2nd intercept #getRecipesSku(971520,971310) is refusing to match.
It's probably not worth while analyzing what exactly (you may not be able to fix it in glob), but switching to a regex will match.
See regex101.com online test
cy.intercept(/wpsite\.com\/wp-json\/wp\/v2\/posts\?tags\[]=6287&tags\[]=6289&_fields/, {})
.as('getRecipesSku(971520,971310)')
The request query string may be badly formed
Looking at the docs for URLSearchParams, the implication is that the query string should be key/value pairs.
But the 2nd request has two identical keys using tags[] as the key.
It looks as if the correct format would be /wp/v2/posts?tags=[6287,6289] since the square brackets don't have a lot of meaning otherwise.
It may be that the server is handling the format tags[]=6287&tags[]=6289, but Cypress is probably not. If you run the following intercept you see the query object has only one tags[] key and it's the last one in the URL.
cy.intercept({
method: 'GET',
pathname: '/wp-json/wp/v2/posts',
},
(req) => {
console.log('url', req.url)
console.log('query', req.query) // Proxy {tags[]: '6289', ...
}
)
#Paolo was definitely on the right track. The WP API we're consuming uses tags[]=1,tags=[2] instead of tags[1,2] so that's correct, but some research into globs showed me that brackets are special. Why the first bracket isn't messing it up but the second one is, I'm not sure and I kind of don't care.
I thought about switching to regex, but instead I was able to escape the offender in the glob without switching to regex and needing to escape aaaallll the slashes in the url:
const interceptRecipes = (
{ category, skus }: RecipeFilterArgs,
recipes: Recipe[]
) => {
let queryString = ''
if (category) queryString = `categories[]=${CATEGORY_MAP[category]}`
if (skus) {
const tagIdMap = SkuToTagIdMap as Record<number, { tagId: number }>
// brackets break the glob pattern, so we need to escape them
queryString = skus.map((sku) => `tags\\[]=${tagIdMap[sku].tagId}`).join('&')
}
const url = `${baseUrl}?${queryString}${otherParams}`
const alias = category ? `get${category}Recipes` : `getRecipesSku(${skus})`
cy.intercept('GET', url, recipes).as(alias)
}
I am experimenting with lodash sorting. I got the lodash to sort the object list although the sorted results are stuck in wrapper. If I use .value(), then I get unsorted keys output.
var testData = {c:1,b:2,a:3}
var sortedKeys = _(testData).keys().sortBy(key => {return testData.key});
console.log(sortedKeys);
will produce:
LodashWrapper {__wrapped__: {…}, __actions__: Array(2), __chain__: false, __index__: 0, __values__: undefined}
__actions__:(2) [{…}, {…}]
__chain__:false
__index__:0
__values__:undefined
__wrapped__:
a:3
b:2
c:1
__proto__:Object
__proto__:lodash
What is it that I am missing in order to get sorted object list out of lodash wrapper.
When you do testData.key, I'm pretty confident that you actually mean to be doing testData[key].
That alone allows the method to work properly i.e. return an array of Object keys sorted by values. Note that you still have to call .value() if you'd like to unwrap the lodash object.
If there's something else you're expecting, please clarify.
const testData = {c:1,b:2,a:0}
const sortedKeys = _(testData).keys().sortBy(key => {return testData[key]})
/* can do without the return like the below as well */
// const sortedKeys = _(testData).keys().sortBy(key => testData[key])
console.log(sortedKeys.value())
// produces ['a','c','b']
If you want the key and value pair, try the below.
_(obj).toPairs().sortBy(0).value()
There are few things that are happening here which I think are important to note:
First you are starting your sorting statement with the short notation for the lodash _.chain method which allows the results of one operation to flow into the next one. This is similar to how _.flow works in lodash/fp.
Chaining requires the last operation in the chain to end with values() in order to get your actual result from the lodash wrapper. So if you did:
_(testData).keys().sortBy(key => {return testData.key}).values(); // OR
_.chian(testData).keys().sortBy(key => {return testData.key}).values();
You would get some results.
Second issue is that in your flow you get the keys of the objects but then you are not actually sorting by them. To do this you need something like this:
var testData = {c:1, b:2, a:3}
console.log(_.chain(testData).keys().sortBy().value());
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
The difference here is in the sort function where you already have the keys as `[ 'c', 'b', 'a' ] so you just need to sort them. They are not objects anymore but simply strings.
Hope this helps.
I want an observable to be delayed depending on its value. For example:
of(someBool).pipe(delay(1000))
skip delay when someBool is false, but wait a sec when it's true.
You can use delayWhen for that:
of(someBool).pipe(
delayWhen(val => val ? interval(1000) : of(undefined))
)
Side note, according to the docs using empty() instead of of() should IMHO work, but doesn't appear to. I believe this might be a bug. I have reported it.
Example. Suppose you are implementing a login page and you want to wait until you get some token to put it in another Observable or Cookies. Then you can wait a value of your Observable (someBool here I used this.authService.isLoggedIn). So you can do something like this:
return this.authService.isLoggedIn
.pipe(
delayWhen(loggedIn => loggedIn ? interval(0) : interval(10000)),
);
And when a state of isLoggedIn is changed a user will be logged in.
Note: In future versions, empty notifiers will no longer re-emit the source value on the output observable.
someBool$.pipe(
delayWhen(val => val ? timer(1000) : timer(0))
)
This works:
this.http.get('/doesntexist1')
.finally(() => console.log('finally1'))
.subscribe(() => { });
But this doesn't:
const obs = this.http.get('/doesntexist2');
obs.finally(() => console.log('finally2'))
obs.subscribe(() => { });
Both URL produce a 404.
I run both and I only see "finally1" being displayed in the console, any idea why?
The difference is that in the second example the .finally is not in the same stream as the .subscribe since you aren't chaining them.
Your first example creates this stream:
get -> finally -> subscribe
Your second example creates two branches:
get -> finally
get -> subscribe
The finally wont be executed without a subscription after it.
If you want to build a stream then you need to chain the operators by using the result of the previous operator. It isn't like an in-place operator.