Return array from a function in cypress - javascript

getLinksByElementAttribute = async (element, attribute) => { /**#type{array}*/ let arrayHTML = [];
return new Cypress.Promise((resolve) => {
cy.get(element).each(($el, index) => {
//cy.log();
arrayHTML[index] = $el.attr(attribute);
resolve(arrayHTML);
});
cy.log(arrayHTML)
});
}
}
I am passing 'a' tag as element and 'href' as attribute and would like to have all the links on webpage which I got using arrayHTML[index] stored in an array together, any help on how to store the links together and return them as one array ?
When I currently run the code then I see the cy.log return value 'Array[52]' in log file for my weblink,means I have 52 'a' elements on my web page, any idea on how I can see actual values of 52 found elements?

That happens because of the asynchronous behavior of cypress. One solution could be to use cy.wrap() and JSON.stringify():
cy.wrap(arrayHTML)
.then((array) => cy.log(JSON.stringify(array)));
More information here: https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Mixing-Async-and-Sync-code

Related

Creating a string and converting it to JSX

I have a situation where I need to parse a string, break, rejoin and then generate jsx from it. I'm somewhat successful but I have to make it clickable as well so that I can navigate on click but the problem is onClick remains a string even after conversion.
Here's the details:
I get a string in this format:
Some Text #(username)[a_long_user_id_to_navigate_to] some more text
I first get all the mentions from text and convert it to object of usernames with their IDs.
and then:
const createMessageWithActionableMentions = (message, mentions) => {
mentions.map(mention => {
message = message.replace('#'+'['+mention.username+']'+'('+mention.id+')',`<span style="color:${Colors.colorPrimary}; cursor: pointer" onClick={props.history.push('/some-route')}>#${mention.username}</span>`)
})
return message
}
the message argument in above function contains the raw string and the mentions is an array of objects as follows:
mentions = [
{
username: 'abcd_1234',
id: 'abcd_defg_hijk'
},
{
username: 'efgh_1234',
id: 'wxyz_defg_jklm'
}
...so on
]
Here's what I do when I add this text to view:
<p dangerouslySetInnerHTML={{__html: message}}></p>
Here's what I see on webpage:
Some text #username some more text
Here's what I see on Inspector:
<span style="color:#03F29E; cursor: pointer" onclick="{props.history.push('/some-route')}">#username</span>
Now the question is how do I handle this part onclick="{props.history.push('/some-route')} to work the react way. also, am I doing it correctly or there's a better way?
You can add click handlers to those spans using Vanilla JS as following:
Assign a class to those spans, for example user-mention
const createMessageWithActionableMentions = (message, mentions) => {
mentions.map(mention => {
message = message.replace('#'+'['+mention.username+']'+'('+mention.id+')',`<span style="color:${Colors.colorPrimary}; cursor: pointer" class="user-mention">#${mention.username}</span>`)
})
return message
}
Add event listeners to those spans. If you are using function components with hooks, put it inside a useEffect like this, and make sure this effect hook is called after the spans are appended to the DOM.
useEffect(() => {
[...document.getElementsByClassName('user-mention')].forEach((element) => {
element.addEventListener('click', () => {
props.history.push('/some-route');
})
})
}, [/* Your dependencies */])
Note that if the spans change (increase/decrease in number, for example), you need to handle event listeners when they change as well. If this becomes cumbersome, you can use Event delegation
You could do it like this:
const createMessageWithActionableMentions = (message, mentions) => {
// Will return a list of JSX elements
let output = [message];
// For every mention
mentions.map(
mention=>`#[${mention.username}](${mention.id})`
).forEach(mention => {
// For every string we have output so far
output = output.map(
substring => {
// Exclude existing JSX elements or strings that do not contain the mention
if (typeof substring!=="string" || !substring.includes(mention)) return [substring]
// We know the mention exists in this specific string segment so we need to find where it is so we can cut it out
const index = substring.indexOf(mention)
// Split the string into the part before, the part after, and the JSX element
return [
substring.substring(0,index),
<span style={{color:Colors.colorPrimary; cursor: "pointer"}} onClick={()=>props.history.push('/some-route')}>{mention.username}</span>,
substring.substring(index+mention.length)
]
}
// Reduce the nested array of arrays back into a single array
).reduce((a,b)=>[...a,...b], [])
})
return output
}
No Inner html shenanigens needed. Just use it like this:
<p>{message}</p>
I figured it out, this can be solved using the array, I simply first broken the message into array of substrings:
message = message.split(' ')
Then mapped it to find mentions in those substrings and replaced the respective substr with <span>:
message.map( (substr, index) => {
if(isMention(substr))){
found = mentions.find(mention => mention.username == substr.split('[').pop().split(']')[0])
if(found) {
message[index] = <span style={{color:Colors.colorPrimary, cursor: "pointer"}}>{found.username}</span>
}
}
})
The final code
const createMessageWithActionableMentions = (message, mentions) => {
let found = ''
message = message.split(' ')
message.map( (substr, index) => {
if(isMention(substr))){
found = mentions.find(mention => mention.username == substr.split('[').pop().split(']')[0])
if(found) {
message[index] = <span style={{color:Colors.colorPrimary, cursor: "pointer"}}>{found.username}</span>
}
}
})
return message
}
Then rendered it as jsx
<p>{message}</p>

JavaScript filter by all values of an array

Here my code and what i tried :
filterPrestationsByServiceSelected(arrayOfServices) {
console.log(arrayOfServices); // ['Repassage', 'Couture']
this.filteredPrestationsByService = this.filteredPrestations.filter(item => item.service.name.includes(arrayOfServices.values()));
},
I want to filter all items of this.filteredPrestations where the service name contains values of the arrayOfServices.
Anyone have an idea of what i can do ?
Thank's !
Remove .values() it returns an iterator which you don't need
filterPrestationsByServiceSelected(arrayOfServices) {
console.log(arrayOfServices); // ['Repassage', 'Couture']
this.filteredPrestationsByService = this.filteredPrestations.filter(item => item.service.name.includes(arrayOfServices));
}
You have to compare the items of a list with another. So you would have to have a compare each element of one data structure with another. Since you are comparing arrays you should do that way:
filterPrestationsByServiceSelected(arrayOfServices) {
console.log(arrayOfServices); // ['Repassage', 'Couture']
this.filteredPrestationsByService = this.filteredPrestations.filter(item => arrayOfServices.find(e => e === item.service.name))
},
That way you could compare the elements one by one.
Can you try this code. I think this code will work.
filterPrestationsByServiceSelected(arrayOfServices) {
console.log(arrayOfServices); // ['Repassage', 'Couture']
this.filteredPrestationsByService = this.filteredPrestations.filter(item => arrayOfServices.includes(item.service.name));
},

Updating components based on sorted array. Angular (4.3)

Working with an array of data that we want to be able to sort for display in a component, and it doesn't seem to be sorting or updating the DOM, however I have a working code sample that properly demonstrates the concept, and it should be sorting, but in the angular app, it's simply not getting sorted.
The parent component that houses the original data stores the data on an Input parameter object called Batch, and the array we're sorting is on Batch.Invoices.Results. The event from the child component is fine, and the appropriate data is confirmed to bubble to the parent component.
The function that's supposed to sort the array looks like this:
public OnInvoiceSortChange({orderValue, orderAscending}){
console.log(`Invoice Sorting has been called. Value: ${orderValue} . Ascending? ${orderAscending}`);
console.log(`Before:`);
console.log(this.BatchViewModel.Invoices.Results.map(x => x.VendorName));
const sortingArray = [...this.BatchViewModel.Invoices.Results];
if(orderAscending){
const sorted = sortingArray.sort((a, b) => a[orderValue] > b[orderValue] ? 1 : 0);
this.BatchViewModel.Invoices.Results = sorted;
console.log('Sorted');
console.log(sorted.map(x => x.VendorName));
} else {
const sorted = sortingArray.sort((a, b) => a[orderValue] < b[orderValue] ? 1 : 0);
this.BatchViewModel.Invoices.Results = sorted;
console.log(sorted.map(x => x.VendorName));
}
console.log(`After:`);
console.log(this.BatchViewModel.Invoices.Results.map(x => x.VendorName));
}
All the console logs are for debugger visibility, and the output is this:
Where in my testing file (non-angular) looks like this:(where data is a direct copy of the array from the Angular app.
const ascendingData = [...data];
const descendingData = [...data];
const sortedDescending = descendingData.sort((a, b) => a['VendorName'] < b['VendorName']? 0 : 1)
const sortedAscending = ascendingData.sort((a, b) => a['VendorName'] > b['VendorName']? 0 : 1);
const vendorListAscending = sortedAscending.map(x => x.VendorName);
const vendorListDescending = sortedDescending.map(x => x.VendorName);
console.log(vendorListDescending);
console.log(vendorListAscending);
and the output looks like this:
So I see that the sorting should work, but it's just not happening in Angular.
How can I get the array sorted, and update the DOM as well?
The function you pass to sort is wrong. It is supposed to return a negative value for "less", a positive value for "greater" or zero for "equal". If orderValue is numeric then it's easiest to just return a[orderValue] - b[orderValue], if not then just change your 0 to -1.
(By the way, name orderKey could be a bit clearer maybe?)
I don't think angular has anything to do here, but I cannot tell now why you get different results. Anyway, your sort function is invalid (it states that a equals b, but at the same time b is greater than a), I hope fixing this function helps.

Extracting specific values from an array within an object

I'm setting up a test to ensure that a faceted Solr query 'contents' are correctly displayed within a page element, using javascript.
The Solr query result, which I've named "ryanlinkstransmissionpage", is;
{ Transmission: [ 'Manual', 12104, 'Automatic', 9858 ] }
What I would like to do is extract the 'Manual' and 'Automatic' only, so I can then test that these values are displayed on a page.
However, it is more the functionality involved in this that I cannot get my head around, as I will be using this method on other Solr query results.
To possibly complicate things, this Solr query result "ryanlinkstransmissionpage" is from a dynamic 'live' Solr, so the values may change each time it's run (so there may be more or less values within this array when it's tested on the following day for example).
I've tried a few javascript commands, but to no avail.
JSON.parse(ryanlinkstransmissionpage)
JSON.stringify(ryanlinkstransmissionpage)
Object.values(ryanlinkstransmissionpage)
Any help would be greatly appreciated. Thanks.
If possible, i highyl recommend changing the transmission field to be an object, rather than an array. That will give you far greater ability to read the data within.
Ignoring that, are you looking to extract the string values and the number values that follow them? ie. "Manual" and "12104"? Or are you simply trying to assert that the string values are present on the page?
Either way, here are two possible approaches.
const ryanlinkstransmissionpage = { Transmission: [ 'Manual', 12104, 'Automatic', 9858 ] };
// Pull out the string values
const strngVals = ryanlinkstransmissionpage.Transmission.filter(val => typeof val === 'string');
// Pull out the string values and the numbers that follow
const strngNumVals = ryanlinkstransmissionpage.Transmission.reduce((keyVals, val, idx, srcArr) => {
if (typeof val === 'string') keyVals[val] = srcArr[idx + 1];
return keyVals;
}, {});
The reduce approach is not stable or robust to changes in data provided from this Solr query result you refer to, nor is it tested. #shrug
Javascript has a built in method called Array.prototype.find(() =>). If you just want to check if this value exists to ensure its on the page, you can simply do:
const ryanlinkstransmissionpage = { Transmission: [ 'Manual', 12104, 'Automatic', 9858 ] };
const manual = ryanlinkstransmissionpage.Transmission.find((ele) => ele === 'Manual'); // returns 'Manual'
const automatic = ryanlinkstransmissionpage.Transmission.find((ele) => ele === 'Automatic'); // returns 'Automatic'
console.log(automatic);
console.log(manual);
// or
const findInArray = (arr, toFind) => {
const result = arr.find((ele) => ele === toFind);
return !!result;
}
console.log(findInArray(ryanlinkstransmissionpage.Transmission, 'Automatic')); // true
console.log(findInArray(ryanlinkstransmissionpage.Transmission, 'HelloWorld')); // false
console.log(findInArray(ryanlinkstransmissionpage.Transmission, 'Manual')); // true

how to print a javascript object's elements

i am new to javascript and i currently have an object printed to console when i use the following code:
clickEvents: {
click:function(target) {
console.log(target);
}
}
when i view console i can see the following object:
i am banging my head against a wall to write code that takes the object and prints it to a div using the .append() method. i am extermely new to working with javascript objects, and would appreciate any help trying to tease out an object and/or print the object data.
is events the object name? would i tease out the eventDate using something like events->eventDate?
I've made this over ~15 minutes so it's imperfect; there are types and edge cases surely unaccounted for and the design of the function could be better - not to mention that performing all of this as a giant string and then setting that as HTML is likely bad practice (I'm used to React now, ha!). Regardless, this will iterate over any array or object you pass to it and print it all in a big <ul> recursively.
const targetEl = document.querySelector('.js-target')
if (!targetEl) return
// Small helper functions
const isObj = data => typeof data === 'object' && !Array.isArray(data) && data !== null
const isArr = data => Array.isArray(data)
const dataToHTML = (data, noNode = false) => {
if (isObj(data)) {
const accumulator = Object.entries(data).reduce((acc, set) => acc + `<li><strong>${set[0]}</strong>: ${dataToHTML(set[1], true)}</li>`, '')
return `<ul>${accumulator}</ul>`
}
else if (isArr(data)) {
const accumulator = data.reduce((acc, item) => acc + dataToHTML(item), '')
return `<ul>${accumulator}</ul>`
}
else return noNode ? data : `<li>${data}</li>`
}
const logHTML = dataToHTML(exampleData)
targetEl.innerHTML = logHTML
Assuming that your data/variable is named exampleData.
Any questions pop them in the comments :-)
I'm not sure if you have a div that you want to append to already, but you would do something like this ->
document.getElementById("toBeAppendedTo").innerHTML = target.events[0].eventDate; where toBeAppendedTo is the id of the div you're trying to add this text to.
append() is a jquery function, not a javascript function.
That won't have any formatting and will just be the string value 07-28-2017 in a div.

Categories

Resources