expected <h1> to contain #selectedText - javascript

What i want to do is compare that the text in class "ellipsis-2-lines text-lg text-weight-semibold" is the same as h1 on next open page
this test throws an error: expected <h1.text-h4.text-weight-semibold.q-mb-sm> to contain #selectedText
I think there is an error when using invoke function. it might not even be possible to use it that way. How to fix this error?
describe('template spec', () => {
it('visit gxpbox', () => {
cy.visit('https://gxpbox.org/')
cy.get("input[placeholder=\"Where are you going?\"]").type("Dubai")
cy.get('[class="q-item q-item-type row no-wrap q-item--clickable q-link cursor-pointer q-focusable q-hoverable items-center"]').contains('Dubai, United Arab Emirates').click()
cy.get("button[type=\"submit\"]").click();
cy.get('[class="ellipsis-2-lines text-lg text-weight-semibold"]')
.should('have.length.gt', 19)
.its('length')
.then((n) => Cypress._.random(0, n - 1))
.then((k) => {
cy.log(`picked random index ${k}`)
cy.get('[class="ellipsis-2-lines text-lg text-weight-semibold"]').eq(k).click().invoke('text').as('selectedText');
})
cy.get('h1').should("contain", "#selectedText")
})
})

You can use that syntax with plugin bahmutov/cypress-aliases.
This conveniently modifies the .should() command to accept an alias as a second parameter.
import 'cypress-aliases/commands/should'
it('visit gxpbox', () => {
cy.visit('https://gxpbox.org/')
...
cy.get('[class="ellipsis-2-lines text-lg text-weight-semibold"]')
.should('have.length.gt', 19)
.its('length')
.then((n) => Cypress._.random(0, n - 1))
.then((k) => {
cy.log(`picked random index ${k}`)
cy.get('[class="ellipsis-2-lines text-lg text-weight-semibold"]')
.eq(k).click().invoke('text').as('selectedText');
})
cy.get('h1').should("contain", "#selectedText")
otherwise you have to add another nesting level
import 'cypress-aliases/commands/should'
it('visit gxpbox', () => {
cy.visit('https://gxpbox.org/')
...
cy.get('[class="ellipsis-2-lines text-lg text-weight-semibold"]')
.should('have.length.gt', 19)
.its('length')
.then((n) => Cypress._.random(0, n - 1))
.then((k) => {
cy.log(`picked random index ${k}`)
cy.get('[class="ellipsis-2-lines text-lg text-weight-semibold"]')
.eq(k).click().invoke('text')
.then(title => {
cy.get('h1').should("contain", title)
});
})

The issue is that when using .should('contains', '#selectedText'), the second parameter is being interpreted as a string and not the aliased value. Instead, we can use the alias to compare it to the string value of the h1 element.
cy.get('h1').invoke('text').then((text) => { // get the text of the h1
cy.get('#selectedText').should('contain', text);
});

Related

How to adding values from dynamic elements and verifying the result

Similarly to this question. How add two values[1,2 elements] and verify its equal to result[3 element]
from the below HTML structure I need to add A(element) & B(element) and verify the sum is equal to A+B(element) its complex for me since A & B are dynamic and they can or cannot be present depending on the changes from the user.
I tried to use the below script.
cy.get('[data-cy="Selected in Final InterviewofferBreakUpTableBodyCellRenderer"] >span').invoke('text')
But it's yielding both the text, i only need the number.
I need help in making a test that won't fail even if only one element A or B is present.
You can do something like this:
cy.contains('span', 'Final Interview Selects')
.next()
.invoke('text')
.then((sum) => {
cy.contains('span', 'Selected - Not Offered')
.next()
.invoke('text')
.then((A) => {
cy.contains('span', 'Selected in Final Interview')
.next()
.invoke('text')
.then((B) => {
expect(+sum).to.eq(+A + +B)
})
})
})
You can do something like this if some of the elements are not visible.
const A = 0,
B = 0
cy.get('body')
.then(($body) => {
if (
'$body:not(:contains("Selected - Not Offered"))' &&
'$body:contains("Selected in Final Interview")'
) {
A = 0
cy.contains('span', 'Selected in Final Interview')
.next()
.invoke('text')
.then((num) => {
B = +num
})
}
if (
'$body:contains("Selected - Not Offered")' &&
'$body:not(:contains("Selected in Final Interview"))'
) {
B = 0
cy.contains('span', 'Selected - Not Offered')
.next()
.invoke('text')
.then((num) => {
A = +num
})
}
if (
'$body:not(:contains("Selected - Not Offered"))' &&
'$body:not(:contains("Selected in Final Interview"))'
) {
A = 0
B = 0
}
})
.then(() => {
cy.contains('span', 'Final Interview Selects')
.next()
.invoke('text')
.then((sum) => {
expect(+sum).to.eq(A + B)
})
})

Custom filter method react-table, two different match types on same column

I have a table that contains list of tld's which can be filtered via a search box or buttons for specific categories. The buttons filter based on a list of tld's while the search box should fuzzy match the tld's.
const columns = useMemo(
() => [
{
Header: 'TLD',
accessor: 'tld',
filter: multiFilter,
Cell: (props) => {
const onSale = props.row.original.sale
return (
<div>
.{props.value} {onSale ? <span className="font-bold text-purple m-left-1">Sale</span> : null}
</div>
)
},
},
Button filtering
<button autoFocus className="py-1 px-4 text-sm focus:bg-blue focus:rounded-full" onClick={() => setFilter('tld', undefined)}>All</button>
<button className="py-1 px-4 text-sm focus:bg-blue focus:rounded-full" onClick={() => setFilter('tld', sale)}>Sale</button>
Search box filtering
<input id="tld-search-box" value={searchFilterInput} onChange={handleSearchFilterChange} placeholder={'Enter a domain extension here'} className="border border-gray-300 p-2 w-[640px] rounded-full" />
For the buttons I had to make a custom filter method
function multiFilter(rows, columnIds, filterValue) {
return filterValue.length === 0
? rows
: rows.filter((row) =>
filterValue.includes(String(row.original[columnIds])),
);
}
And this is the search filter method which ends up using setFilter so it flows through the custom filter method eventually also
const handleSearchFilterChange = (e) => {
const value = e.target.value || undefined
const trimmedValue = value ? value.replace(/^\./g, '') : undefined
setFilter('tld', trimmedValue)
setSearchFilterInput(value)
}
The problem I have is since they both use the custom filter method in the end, the search box filtering is an exact match per the includes method filterValue.includes(String(row.original[columnIds])),. I want the search box to match anything that contains the value entered however and I am not sure how to do this since they both act on the column. Any advice would be greatly appreciated.
I suppose global filtering could also solve this issue but I ended up finding that the two search types were of different JS types. The buttons were type object while the search bar was string so this solved the issue:
function multiFilter(rows, columnIds, filterValue) {
return filterValue.length === 0 ? rows : rows.filter((row) => {
let match = String(row.original[columnIds])
return typeof filterValue === 'object'
? filterValue.includes(match) // Check to see if match is in array
: match.includes(filterValue) // do sub string match for strings
})
}

Why is the first element getting removed too?

So I am new to javascript and I tried making a todo list. This works well with adding elements. The issue is when I am removing some item, the first one gets removed too, why is it so? I know I am missing a small thing and this may be really basic but I am not able to find out what that is.
const App1 = () => {
const [item, updatedItem]=useState('');
const [Items, setItems]=useState([]);
function inputEvent(event) {
updatedItem(event.target.value);
}
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return[
...prev,
item
]
});
updatedItem('');
}
let key=0;
return(<>
<div className='back'>
<div className='list'>
<header>ToDo List</header>
<form onSubmit={addItem}>
<input type='text' placeholder='Add an item' value={item} onChange={inputEvent}/>
<button type='submit'>+</button>
</form>
<div className='items'>
<ol>
{Items.map((val) => <li><button id={key++} onClick={(event) => {
setItems((Items) => {
return Items.filter((val, index) => {
if(index!==Number(event.target.id)){
return index;
}
}
);
});
key=0;
}}>x</button>{val}</li>)}
</ol>
</div>
</div>
</div>
</>);
You return the index in your filter, expecting this to always be true, yet 0 (the index of the first element) is a falsy value.
Try this instead:
return Items.filter((_val, index) => index !== Number(event.target.id));
Some unrelated code-quality notes:
In React, you should always set a key prop on each element when looping through them, rather than id.
map has a second argument, index, which it passes into the callback --- you don't have to keep track of this yourself with e.g. key++ etc.
If you use map's index parameter, then you can pass that directly into your filter rather than using Number(event.target.id), which is not very idiomatic in React.
If you don't use an argument of a callback, it's a good idea to prefix it with a _ (like I've done with _val here), to make it explicit that you're not using it.
Your filter callback should return a flag. index is a number. When treated as a flag, 0 is false (more on MDN). Instead:
return Items.filter((val, index) => index !== Number(event.target.id));
However, your code is returning an array of li elements without setting key on them (see: keys), which React needs in order to manage that list properly (you should be seeing a warning about it in devtools if you're using the development version of the libs, which is best in development). You can't use the mechanism you're using now for keys when doing that, it will not work reliably (see this article linked by the React documentation). Instead, assign each Todo item a unique ID when you create it that doesn't change, and use that as the key (and as the value to look for when removing the item):
// Outside the component:
let lastId = 0;
// Inside the component:
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return [
...prev,
{text: item, id: ++lastId}
];
});
updatedItem("");
};
// Add a remove function:
const removeItem = ({currentTarget}) => {
const id = +currentTarget.getAttribute("data-id"); // Get the ID, convert string to number
setItems(items => items.filter(item => item.id !== id));
};
// When rendering:
{Items.map((item) => <li key={item.id}><button data-id={item.id} onClick={removeItem}>x</button>{item.text}</li>)}
In some cases it may be useful to use useCallback to memoize removeItem to avoid unnecessary rendering, but often that's overkill.

React Map Operator

I am trying to declare a variable "ratingVal" which is assigned a random no inside a map operator. I want to use the variable in the rating component and also display it on the screen. However I get an error
Parsing error: Unexpected token
What is the correct syntax for declaring a variable in this case?
renderProductsCardsList(products){
return products.map(
(product, i) =>
let ratingVal = Math.floor(Math.random() * 5) + 1
<Rating initialRating={ratingVal} readonly></Rating>
<div>{ratingVal}</div>
)
}
You can't have a declaration statement within an arrow function with an implicit return. In order to do so, use the explicit return syntax. Also, you cannot return multiple elements from within the map method. Wrap them within <React.Fragment>:
renderProductsCardsList(products){
return products.map(
(product, i) => {
let ratingVal = Math.floor(Math.random() * 5) + 1
return (
<React.Fragment>
<Rating initialRating={ratingVal} readonly></Rating>
<div>{ratingVal}</div>
</React.Fragment>
)
})
}
or evaluate it while assigning
renderProductsCardsList(products){
return products.map(
(product, i) =>
<React.Fragment>
<Rating initialRating={Math.floor(Math.random() * 5) + 1} readonly></Rating>
<div>{ratingVal}</div>
</React.Fragment>
)
}
Two issues :
Missing brackets after arrow => : you can only omit brackets if you do an implicit return in one line.
Floating JSX in your code : i'm not sure what you want to do. There is 2 lines of JSX floating at the end of the map method. You currently don't return anything. But i guess you want to return a Rating component.
renderProductsCardsList(products) {
return products.map(
(product, i) => {
let ratingVal = Math.floor(Math.random() * 5) + 1
return <Rating initialRating={ratingVal} readonly></Rating>
})
}
Use a function to return the product inside the map function:
function renderProductsCardsList(products) {
let rating = () => {
let ratingVal = Math.floor(Math.random() * 5) + 1
return (<>
< Rating initialRating={ratingVal} readonly ></Rating >
<div>{ratingVal}</div>
</>)
}
return products.map(product => rating())
}

React: replace links in a text

What is the proper way to replace urls in a string and render them as links with React?
Say I have a string: 'hello http://google.com world', and I want it to render like: hello http://google.com world
Ok, so this is how I done it.
class A extends React.Component {
renderText() {
let parts = this.props.text.split(re) // re is a matching regular expression
for (let i = 1; i < parts.length; i += 2) {
parts[i] = <a key={'link' + i} href={parts[i]}>{parts[i]}</a>
}
return parts
}
render() {
let text = this.renderText()
return (
<div className="some_text_class">{text}</div>
)
}
}
I ran into issues with every answer here, so I had to write my own:
// use whatever you want here
const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9#:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()#:%_\+.~#?&//=]*)/;
const renderText = txt =>
txt
.split(" ")
.map(part =>
URL_REGEX.test(part) ? <a href={part}>{part} </a> : part + " "
);
There are NPM modules to handle this. Both of these depend on linkify-it (repo)
react-linkify (repo)
<Linkify>
<div>react-linkify <span>(tasti.github.io/react-linkify/)</span></div>
<div>React component to parse links (urls, emails, etc.) in text into clickable links</div>
See examples at tasti.github.io/react-linkify/.
<footer>Contact: tasti#zakarie.com</footer>
</Linkify>
At time of writing, the current version is 1.0.0-alpha. It requires React 16. The repo has 14 open tickets and 17 open PRs. So that's not fantastic.
Version 0.2.2 allows much earlier versions but doesn't have link text decoration, etc.
react-native-hyperlink ( repo )
If you are using native (ie a phone app), it looks like the better of the two options. Code samples:
<Hyperlink linkDefault={ true }>
<Text style={ { fontSize: 15 } }>
This text will be parsed to check for clickable strings like https://github.com/obipawan/hyperlink and made clickable.
</Text>
</Hyperlink>
<Hyperlink onLongPress={ (url, text) => alert(url + ", " + text) }>
<Text style={ { fontSize: 15 } }>
This text will be parsed to check for clickable strings like https://github.com/obipawan/hyperlink and made clickable for long click.
</Text>
</Hyperlink>
<Hyperlink
linkDefault
injectViewProps={ url => ({
testID: url === 'http://link.com' ? 'id1' : 'id2' ,
style: url === 'https://link.com' ? { color: 'red' } : { color: 'blue' },
//any other props you wish to pass to the component
}) }
>
<Text>You can pass props to clickable components matched by url.
<Text>This url looks red https://link.com
</Text> and this url looks blue https://link2.com </Text>
</Hyperlink>
References
https://github.com/facebook/react-native/issues/3148
REACT - How to replace URL strings to <a> elements and rendering it properly
Try this library, it does exactly you need:
https://www.npmjs.com/package/react-process-string
An example from there:
const processString = require('react-process-string');
let config = [{
regex: /(http|https):\/\/(\S+)\.([a-z]{2,}?)(.*?)( |\,|$|\.)/gim,
fn: (key, result) => <span key={key}>
<a target="_blank" href={`${result[1]}://${result[2]}.${result[3]}${result[4]}`}>{result[2]}.{result[3]}{result[4]}</a>{result[5]}
</span>
}, {
regex: /(\S+)\.([a-z]{2,}?)(.*?)( |\,|$|\.)/gim,
fn: (key, result) => <span key={key}>
<a target="_blank" href={`http://${result[1]}.${result[2]}${result[3]}`}>{result[1]}.{result[2]}{result[3]}</a>{result[4]}
</span>
}];
let stringWithLinks = "Watch this on youtube.com";
let processed = processString(config)(stringWithLinks);
return (
<div>Hello world! {processed}</div>
);
That will replace all links with or without "http://" protocol. If you want to replace only links with protocol, remove the second object from config array.
First add <a> tag to string:
function httpHtml(content) {
const reg = /(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|&|-)+)/g;
return content.replace(reg, "<a href='$1$2'>$1$2</a>");
}
console.log(httpHtml('hello http://google.com world'))
// => hello http://google.com world
Then render string as html in react:
function MyComponent() {
return <div dangerouslySetInnerHTML={{
__html: httpHtml('hello http://google.com world')
}} />;
}
I wrote a short function to do it:
const RE_URL = /\w+:\/\/\S+/g;
function linkify(str) {
let match;
const results = [];
let lastIndex = 0;
while (match = RE_URL.exec(str)) {
const link = match[0];
if (lastIndex !== match.index) {
const text = str.substring(lastIndex, match.index);
results.push(
<span key={results.length}>{text}</span>,
);
}
results.push(
<a key={results.length} href={link} target="_blank">{link}</a>
);
lastIndex = match.index + link.length;
}
if (results.length === 0) {
return str;
}
if (lastIndex !== str.length) {
results.push(
<span key={results.length}>{str.substring(lastIndex)}</span>,
);
}
return results;
}
late to the party but here's a slightly modified version :
export const linkRenderer = (string: string):ReactNode => {
const linkExp = /^https?:\/\/[a-z0-9_./-]*$/i
return <>{
string.split(/(https?:\/\/[a-z0-9_./-]*)/gi).map((part, k) => <React.Fragment key={k}>
{part.match(linkExp) ? <a
href={part}
onFocus={(e) => { e.stopPropagation() }}
target="_blank"
rel="noreferrer"
>{part}</a>
: part}
</React.Fragment>)
}</>
}
Interesting things to note there:
it doesn't split on space or blank space so preserving existing spaces
It create less chunks by only splitting parts where link are not every word or so
the regexp passed to split must have capturing parenthesis if you want to have your links as part of the resulting array.
the noreferrer attribute is required with target blank for security reason on older browsers
Hope this help.
Based on the OP's own answer I came up with the one-liner:
{text
.split(/[-a-zA-Z0-9#:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9#:%_\+.~#?&//=]*)?/gi)
.map((part, index) => index % 2 === 0 ? part : {part}
}
for me, i managed to solve it this way
const ActiveProblem = () => {
const { activeProblem } = useProblems();
const id = new Date().toString();
const match = activeProblem.replace(
urlPattern,
(matched) => id + matched + id
);
return (
<Typography align="center" variant="body1" color="white">
{match.split(id).map((str, idx) => {
if (str.match(urlPattern))
return (
<Box component="a" href={str} key={id + idx}>
{str}
</Box>
);
return str;
})}
</Typography>
);
};
I'm using Material UI with React

Categories