Is it possible to access an object's key inside the name portion of a .each?
let accounts =
[
{
details:
{
company_name:
"company_name",
email,
password:
"asdf",
},
find:
[
"_id",
"company_name",
"email",
"type",
],
type:
"creator"
},
{
details:
{
email,
first_name:
"first_name",
last_name:
"last_name",
password:
"asdf",
},
find:
[
"_id",
"email",
"first_name",
"last_name",
"type",
],
type:
"user"
},
]
describe.each(accounts)(
"%s", // <-- access the 'type' key, e.g. account.type
function (account)
{
// test code
}
)
Jest describe.each expects an array of arrays in the first parameter. If you pass in a 1D array, internally it will be mapped to an array of arrays (i.e. passing [1, 2, 3] as first parameter would be converted to [[1], [2], [3]]).
Each one of the arrays inside of the array is used as the data for a test suite. So, in the previous example, describe.each would generate three test suites, the first with 1 as data, the second with 2 as data and the third with 3 as data.
Now, in the test suite name, you can only format the parameters you are providing to it. In your case, you are passing to each test suite the data in each object of the accounts array. So, when you set the format specifiers in the test suite name, they will apply to the whole account object (i.e. the %s in your example will stringify your object resulting in [object Object]). Unfortunately, I don't think you can apply the format specifiers to a key of the object.
Some ideas to accomplish what you want:
Solution 1
If you use the %s formatter to compose the test suite name, the toString method of Object will be called (which by default returns [object Object]).
If you define a toString method in each of your accounts objects, that method will be used instead. So, we could add the toString method to each one of the account objects with this code (note that the toString method we are adding is returning the value for the type key):
const accounts = [{
details: {
company_name: "company_name",
email: "aa",
password: "asdf",
},
find: [ "_id", "company_name", "email", "type", ],
type: "creator"
}, {
details: {
email: 'bb',
first_name: "first_name",
last_name: "last_name",
password: "asdf",
},
find: [ "_id", "email", "first_name", "last_name", "type", ],
type: "user"
}].map(account => Object.assign(account, { toString: function() { return this.type; } }));
Now, with the %s format specifier you should see the account type in each test suite:
describe.each(accounts)(
"%s", // <-- This will cause the toString method to be called.
function (account)
{
// test code
}
)
Solution 2
You can always redefine each one of your test suite data so that the first parameter is the account type (note that now accounts is a 2D array):
let accounts = [
[
"creator",
{
details: {
company_name: "company_name",
email: "email",
password: "asdf",
},
find: [ "_id", "company_name", "email", "type", ],
type: "creator"
}
], [
"user",
{
details: {
email: "email",
first_name: "first_name",
last_name: "last_name",
password: "asdf",
},
find: [ "_id", "email", "first_name", "last_name", "type", ],
type: "user"
},
]
]
You can now use that first parameter (which is the account type) to give the test suite its name:
describe.each(accounts)(
'%s', // <-- This %s will format the first item in each test suite array.
function (accountType, account) {
// test code
}
);
Note that now your test function receives two parameters as each test suite array has two elements. The first one is the account type and the second one is the account data.
Solution 3
You can use the tagged template literal form of describe.each. With this solution you don't have to change your current definition of accounts array.
describe.each`
account
${accounts[0]}
${accounts[1]}
`('$account.type', function (account) {
// test code
});
The downside of this solution is that you have to manually append each test suite data in the template literal in a new line (i.e. if you add a new element to the accounts array you have to remember to add it in the template literal in a new line as ${accounts[2]}).
you can map your initial account array to convert each account into an array with 2 items:
the account type
the initial account element
Now, you can use the first element array in describe name
describe.each(accounts.map(account => [account.type, account]))(
'testing %s', // %s replaced by account type
(type, account) => { // note: 2 arguments now
it('details should be defined ', () => {
expect(account.details).toBeDefined();
});
},
);
As modern doc says, you can
generate unique test titles by injecting properties of test case object with $variable
So simply:
describe.each(accounts)(
"$type",
function (account) {
// tests
}
)
You can access nested object values like this: $variable.path.to.value
The same works on test.each level.
I had a similar problem with an object. I wanted to test an error message depending on http error codes, so I wrote a test object like so:
const expectedElements = {
error: {
code: 500,
title: "Problème avec l'API"
},
notFound:{
code: 404,
title: "Élement absent"
},
unauthorized:{
code: 401,
title: "Accès non autorisé"
}
};
I used Object.entries(obj) to get an array with those entries written like so: ['key','value']. I can access thoses as two parameters in the test. Here's how I wrote it:
test.each(Object.entries(expectedElements))("NoAccess show the right element for %s",(key,expectedElement)=>{
const { getByRole } = render(<NoAccess apiStatusCode={expectedElement.code}/>);
//test code
});
Now I can add cases as much as I want and I won't have to rewrite the test or create an array. I just write an new value in my expectedElements object. Bonus, I also have a descriptive test name!
Another alternative is to create a wrapper class and stick to a simple convention:
class TestCase {
constructor(value) {
this._value = value;
}
get value() {
return this._value;
}
toString() {
return JSON.stringify(this._value);
}
}
Then a test will look like this:
const testCases = accounts.map(TestCase)
describe.each(accounts)(
"%s", // <-- you can customize this in TestCase toString
function ({value: account})
{
// test code
}
)
Related
I am trying to make call from my angular service to loopback api. I have a parcelStatuses collection that contains a parcelId so i am able to include parcel collection too but I also need to check against a particular vendorId and that vendorId exists in parcel collection. I am trying to make use of scope to check against particular vendorId but i think i am not writing correct json syntax/call. Here is my function inside service
private getParcelsByFilter(
limit: number,
skip: number,
vendorId: string,
filter: string
) {
const checkFilter = {
"where": {
"and": [{"statusRepositoryId": filter}]
},
"include": [
{
"parcel": [
{
"scope": {"vendorId": vendorId}
},
"parcelStatuses",
{"customerData":"customer"}
]
}
],
"limit": limit,
"skip": skip,
}
return this._http.get<IParcel[]>(
`${environment.url}/ParcelStatuses?filter=${encodeURIComponent(JSON.stringify(checkFilter))}`
);
}
Here is my demo view of parcelStatus collection object
[{
"id":"lbh24214",
"statusRepositoryId":"3214fsad",
"parcelId":"LH21421"
}]
Demo json of parcel
[{
"id":"LHE21421",
"customerDataId":"214fdsas",
"customerId":"412dsf",
"vendorId":"123421"
}]
Please help me with writing correct call
Formatting aside, there's several issues with the query:
Unnecessary and
This line:
where: {
and: [{statusRepositoryId: filter}]
}
Can be simplified to:
where: {
statusRepositoryId: filter
}
As there is only 1 where condition, and becomes redundant.
Misuse of include and scope
include is used to include relations while scope applies filters to those relations. They can work in tandem to create a comprehensive query:
include: [
{
relation: "parcels",
scope: {
where: {vendorId: vendorId},
}
}
],
This will include the parcels relation as part of the response, while filtering the parcels relation with a where filter.
That means the final code should look similar to the following:
private getParcelsByFilter(
limit: number,
skip: number,
vendorId: string,
filter: string
) {
const checkFilter = {
where: {statusRepositoryId: filter},
include: [
{
relation: "parcels",
scope: {
where: {vendorId: vendorId},
}
}
],
limit: limit,
skip: skip,
}
return this._http.get<IParcel[]>(
`${environment.url}/ParcelStatuses?filter=${encodeURIComponent(JSON.stringify(checkFilter))}`
);
}
Further reading
Please review these resources to get a better understanding on how to use filters.
https://loopback.io/doc/en/lb4/Include-filter.html
I have a requirement; where I need to load a list of payments that were updated between a specified date/time range. And for each invoice; I need to get a list of internal id (aka invoice id) that payment has been applied to (since a payment can be applied to one or more invoices).
I tried to achieve this using a search query like this:
var paymentSearch = search.create({
type: search.Type.CUSTOMER_PAYMENT,
filters: [
['lastmodifieddate', 'within', from_datetime, to_datetime],
'and',
['appliedToTransaction.tranid', search.Operator.ISNOTEMPTY, '#NONE']
],
columns: [
'entity',
'tranid',
search.createColumn({
name: 'internalid',
join: 'appliedToTransaction'
})
]
});
for (var i = 0; i < paymentsPagedData.pageRanges.length; i++) {
var currentPage = paymentsPagedData.fetch(i);
currentPage.data.forEach(function(result) {
// TEST
var appliedToInvoiceIds = result.getValue({ name: 'internalid', join: 'appliedToTransaction' });
}
}
When I inspected the appliedToInvoiceIds, it only appears to be returning a single value, the first invoice this payment has been applied to. How can I get all of the invoice ids that the payment has been applied to?
I tried inspecting the result object (inside the forEach loop) to see what was inside and this is what I saw:
{
"recordType": "customerpayment",
"id": "25911",
"values": {
"entity": [
{
"value": "761",
"text": "COMPANY NAME INC"
}
],
"tranid": "722",
"appliedToTransaction.internalid": [
{
"value": "2676",
"text": "2676"
},
{
"value": "2658",
"text": "2658"
}
]
}
}
As you can see on the result object of the payment; it has appliedToTransaction.internalid which is an array and has two items in it. How can I retrieve these?
Is there an alternate version of result.getValue(...) available for retrieving array of items/field values via a join?
You need to return true at the end of your forEach. Otherwise it will only return one result.
{
members {
id
lastName
}
}
When I tried to get the data from members table, I can get the following responses.
{ "data": {
"members": [
{
"id": "TWVtYmVyOjE=",
"lastName": "temp"
},
{
"id": "TWVtYmVyOjI=",
"lastName": "temp2"
}
] } }
However, when I tried to update the row with 'id' where clause, the console shows error.
mutation {
updateMembers(
input: {
values: {
email: "testing#test.com"
},
where: {
id: 3
}
}
) {
affectedCount
clientMutationId
}
}
"message": "Unknown column 'NaN' in 'where clause'",
Some results from above confused me.
Why the id returned is not a numeric value? From the db, it is a number.
When I updated the record, can I use numeric id value in where clause?
I am using nodejs, apollo-client and graphql-sequelize-crud
TL;DR: check out my possibly not relay compatible PR here https://github.com/Glavin001/graphql-sequelize-crud/pull/30
Basically, the internal source code is calling the fromGlobalId API from relay-graphql, but passed a primitive value in it (e.g. your 3), causing it to return undefined. Hence I just removed the call from the source code and made a pull request.
P.S. This buggy thing which used my 2 hours to solve failed in build, I think this solution may not be consistent enough.
Please try this
mutation {
updateMembers(
input: {
values: {
email: "testing#test.com"
},
where: {
id: "3"
}
}
) {
affectedCount
clientMutationId
}
}
Using node-recurly, I can create a subscription object and pass it to recurly.subscriptions.create call:
const subscription = {
plan_code: plan.code,
currency: 'USD',
account: {
account_code: activationCode,
first_name: billingInfo.first_name,
last_name: billingInfo.last_name,
email: billingInfo.email,
billing_info: {
token_id: paymentToken,
},
},
};
I would also like to add subscription_add_ons property, which, looking at the documentation, supposed to be an array of add-ons. I tried passing it like this:
subscription_add_ons: [
{
add_on_code: shippingMethod.servicelevel_token,
unit_amount_in_cents: parseFloat(shippingMethod.amount) * 100,
},
],
The server returned an error:
Tag <subscription_add_ons> must consist only of sub-tags named
<subscription_add_on>
I attempted this:
subscription_add_ons: [
{
subscription_add_on: {
add_on_code: shippingMethod.servicelevel_token,
unit_amount_in_cents: parseFloat(shippingMethod.amount) * 100,
},
},
],
Got back this error:
What's the proper format to pass subscription add on in this scenario?
The proper format is:
subscription_add_ons: {
subscription_add_on: [{
add_on_code: shippingMethod.servicelevel_token,
unit_amount_in_cents: parseFloat(shippingMethod.amount) * 100,
}],
},
I ended up doing this which works whether you have 1 add-on or multiple add-ons. subscription_add_ons is an array which can contain 1 or more subscription add ons. I then send over the details (along with other info) in the subscription update call. This is similar to what you attempted in your original post so I'm not sure why that didn't work for you.
details.subscription_add_ons = [
{ subscription_add_on: {add_on_code: "stream", quantity: 3} },
{ subscription_add_on: {add_on_code: "hold", quantity: 2} }
];
I have a query that is generated in my Node backend - if I log it out and run it in Mongo shell then all is fine, however, if I use mongoose to do Model.find(query), then some strange property re-ordering takes place and the query breaks.
The query in question is:
{
"attributes": {
"$all": [
{
"attribute": "an id",
"value": "a value",
"deletedOn": null
},
{
"attribute": "an id again",
"value": "a value",
"deletedOn": null
}
]
}
}
However, the output from mongoose debug is:
users.find({
attributes: {
'$all': [
{
deletedOn: null,
attribute: 'an id',
value: 'a value'
},
{
deletedOn: null,
attribute: 'an id again',
value: 'a value'
}
]
}
},
{ fields: {} }
)
The only change is the shifting of the deletedOn field from last position to first position in the object. This means the query returns no results.
Are there any solutions to this issue?
Object properties in JavaScript are not ordered. You cannot ensure the order of properties on a JavaScript object and different implementations may order them differently. See this answer on a related question for some other info.
The essential key is that from the spec (ECMAScript) we get: "An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method."
There is no "solution", because this is expected behavior. So the real question is, why does the order matter to you? What are you trying to do?
Adding on the previous answer, if order is important to you, you should use array instead of objects.
for example:
"$all": [
[
{"attribute": "an id"},
{"value": "a value"},
{"deletedOn": null},
],
...etc.
]