On clicking a button, I will get a dropdown with a list of items. I want to check how much time it takes for the drop down to come after clicking the button. I want to write a test using cypress for this to check performance. Intention is to check the performance of the app when the item list contains 100 thousands of values.
you can try something like this:
index2.html:
<div class="many">
</div>
<button id="manyadd">add many</button>
<script>
var prom = () => new Promise(resolve => {
var parent = document.querySelector(".many");
var e = document.createElement("div");
e.classList.add("red");
parent.append(e);
setTimeout(resolve, 1000);
})
async function createElement() {
for(var i = 0; i < 10; i++) {
await prom();
}
}
document.querySelector("#manyadd").addEventListener("click", () => createElement());
</script>
<style>
.red {
width: 100px;
height: 100px;
background-color: red;
}
</style>
and cypress code:
describe("many elements", () => {
beforeEach(() => {
cy.visit("index2.html");
})
it("wait until all elements", () => {
let t1 = null;
let t2 = null;
cy.get("#manyadd").then(b => b.click());
cy.get(".many > div").then(() => t1 = new Date());
cy
.get(".many > div", { timeout: 20000 })
.should(elements => {
expect(elements.length).to.eq(10);
t2 = new Date();
})
.then(() => cy.log(`duration: ${(t2.getTime()-t1.getTime())/1000} seconds`))
})
})
The example app code adds 10 div container and the cypress test waits until all are loaded. if you want exact times you may utilize MutationObserver but this is definitly more code than in my example :-P
Result:
Related
I've got problem with my first react app.
I've set the interval function which counts down from 10 to 0 and after the 0 is reached the interval is cleared. At least it should work like this, but when I console log the time it's always 10 (even though it renders properly in the browser - the value is getting smaller), so it never jumps to the else statement.
What should I do to fix this problem?
const {useState} = React;
const Timer = () => {
let flag = true;
const [time, setTime] = useState(10);
const handleClick = () => {
if (flag) {
setInterval(counter, 500);
}
}
const counter = () => {
if (time > 0) {
console.log(time);
setTime(time => time - 1);
} else {
console.log('out');
clearInterval(timer);
}
}
return(
<div>
<div>{time}</div>
<button className="start" onClick={handleClick}>START</button>
</div>
)
}
ReactDOM.render(
<Timer />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I managed to solve this problem. Thank you guys for trying to help :) Snippet below:
const {useState} = React;
const {useEffect} = React;
const Timer = () => {
const [flag, setFlag] = useState(false);
const [time, setTime] = useState(10);
const handleClick = () => {
setFlag(!flag);
}
useEffect(() => {
function counter () {
if (time > 0) {
setTime(time => time - 1)
}
}
if (flag) {
console.log('a');
const interval = setInterval(counter, 1000)
return () => clearInterval(interval);
}
}, [flag, time]);
return(
<div>
<div>{time}</div>
<button className="start" onClick={handleClick} >START</button>
</div>
)
}
ReactDOM.render(
<Timer />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I have list that should have 10 elements, if the list contains 11 elements i need to show scroll, the container is fixed size.
Everything is ok but how i can check that scroll is exist?
cy.get('[data-testid=list-box]')
You could get the count of elements and if the length of the list is less than or equal to 10 do some action, else check for the visibility of scrollbar. Please try below test and let me know
it('Check the length of the list', () => {
cy.get('[data-testid=list-box]')
.then(list => {
const listCount = Cypress.$(list).length;
if(listCount <= 10){
// do some action if the list count is less than 10..
}else{
cy.get('#scrollbar_Id').should('be.visible');
}
});
})
I'm doing this by measuring the height and scrollHeight with jQuery.
it("should force scroll within a large body", () => {
cy.get(".lorem-ipsum-header").click();
cy.get(".section-body")
.should("have.length", 1)
.eq(0)
.should("contain.text", "Lorem ipsum")
.then(($body) => {
cy.wrap($body).invoke("outerHeight").should("eq", 583);
cy.wrap($body).invoke("prop", "scrollHeight").should("eq", 1892);
});
});
Here I'm getting the last element. Then used cypress scrollIntoView() to scroll to that last element. And using should() to see it's visible or not.
cy.get('Selector')
.last()
.within(($element) => {
if (!$element.is(':visible')) {
cy.wrap($element).scrollIntoView();
}})
.should('be.visible');
Basically, we get the element from the jquery wrapper and then test if it is scrollable by comparing scrollWidth and actualWidth
export enum ScrollType {
scrollable='scrollable',
nonScrollable='non-scrollable',
}
export const isXScrollable = (element: HTMLElement) => {
return element.scrollWidth > element.clientWidth
};
export const isYScrollable = (element: HTMLElement) => {
return element.scrollHeight > element.clientHeight;
}
export const isScrollable = (element: HTMLElement) => {
return isXScrollable(element) || isYScrollable(element)
}
describe('Test',() => {
it('check that max of only 4 lines to be shown in the text area, and then it should add the scroll', () => {
mount(<Textarea defaultValue="Line 1" id="textbox-abc2" ></Textarea>);
const textbox = cy.get('#textbox-abc2');
textbox.type('{enter}Line 2{enter}Line 3{enter}Line 4');
textbox.then(a => {
const scrollable = isYScrollable(a[0]) ? ScrollType.scrollable: ScrollType.nonScrollable
expect(scrollable).to.eq(ScrollType.nonScrollable);
})
textbox.type('{enter}Line 5');
textbox.then(a => {
const scrollable = isYScrollable(a[0]) ? ScrollType.scrollable: ScrollType.nonScrollable
expect(scrollable).to.eq(ScrollType.scrollable);
})
})
})
Please, look at my code, I assign a new value to the variable in data and var width have value 100. After that, when animation end, i try return value to var width 100, and start animation again, but Vue does not assign new value 100 and stay 0. But if i will do this with setTimeout it's work perfect. Why is this not happening in nextTick?
link to jsfiddle
new Vue({
el: '#app',
data: {
width: 100,
time: 0
},
mounted() {
setTimeout(() => {
this.time = 5000;
this.width = 0;
setTimeout(() => {
this.rerenderBar();
}, 5100)
}, 1000)
},
methods: {
rerenderBar() {
this.time = 0;
this.width = 100;
/* this.$nextTick(() => {
this.time = 5000;
this.width = 0;
}) */
setTimeout(() => {
this.time = 5000;
this.width = 0;
}, 1500)
}
}
})
<div id="app">
<div class="progress-bar-wrap">
<div class="progress-bar" :style="{
'width': width + '%',
'transition-duration': `${time}ms`
}"></div>
</div>
</div>
My guess is that because $nextTick runs after Vue's DOM update cycle and your animations are powered by css transitions directly on the element (not handled by Vue), the $nextTick happens immediately after calling renderBar It does not wait for your animation to complete.
If you need to wait for the animation to finish, you can look into using Vue Transitions and use Javascript Hooks to reset the width of the bar when the animation finishes.
I have a function in Cypress support/index.js that is meant to get the dimensions of the cy.document outerWidth and outerHeight, then return them for future use in a test. My problem is that when the test runs and the values are compared with others the assertion says the values are NaN. I checked by console logging the value at the point of the assertion and it was empty, so I must be doing something wrong, I'm just not sure what. My function is below, any help gratefully received, thanks.
function getViewport() {
var viewport = {}
cy.document().then((doc) => {
let width = Cypress.$(doc).outerWidth()
let height = Cypress.$(doc).outerHeight()
viewport['bottom'] = height
viewport['height'] = height
viewport['left'] = 0
viewport['right'] = width
viewport['top'] = 0
viewport['width'] = width
viewport['x'] = 0
viewport['y'] = 0
}).then(() => {
return viewport
})
return viewport
}
The code that calls getViewport() is
export const getRect = (obj) => {
var rect
if (obj == 'viewport') {
rect = getViewport()
} else {
rect = getElement(obj)
if (Cypress.config('parseLayoutToInt')) { rect = parseAllToInt(rect) }
}
return rect
}
And that is called by a custom command, where subject is prevSubject and the element is the string "viewport"
Cypress.Commands.add('isInside', { prevSubject: true }, (subject, element, expected) => {
var minuend, subtrahend, diff
minuend = getRect(element)
subtrahend = getRect(subject)
diff = getRectDiff(minuend, subtrahend, expected);
expect(diff).to.deep.equal(expected);
})
Like #NoriSte said, the cy commands are asynchronous thus you can't mix them with sync code.
What you want to do is something like:
function getViewport() {
return cy.document().then( doc => {
rect = /* do something synchronous */
return rect;
});
}
Anyway, to answer the original question (in the title), there's a couple of patterns I use to store a value for later use in cypress:
wrap next commands in the then callback:
cy.document().then( doc => {
return doc.documentElement.getBoundingClientRect();
}).then( viewportRect => {
cy.doSomething(viewportRect);
cy.doSomethingElse();
});
cache to a variable and access the cached value from inside an enqueued command:
let viewportRect;
cy.document().then( doc => {
return doc.documentElement.getBoundingClientRect();
}).then( rect => viewportRect = rect );
cy.doSomething();
// this is important -- you need to access the `viewportRect`
// asynchronously, else it will be undefined at the time of access
// because it's itself assigned asynchronously in the first command'd callback
cy.then(() => {
doSomething(viewportRect);
});
Ad the actual problem in your question (if I understood it correctly), I've made a solution you can learn from:
const getRect = (selector) => {
if (selector == 'viewport') {
return cy.document().then( doc => {
return doc.documentElement.getBoundingClientRect();
});
} else if ( typeof selector === 'string' ) {
return cy.get(selector).then( $elem => {
return $elem[0].getBoundingClientRect();
});
// assume DOM elem
} else {
return cy.wrap(selector).then( elem => {
return Cypress.$(elem)[0].getBoundingClientRect();
});
}
};
const isInside = (containerRect, childRect) => {
if ( !containerRect || !childRect ) return false;
return (
childRect.top >= containerRect.top &&
childRect.bottom <= containerRect.bottom &&
childRect.left >= containerRect.left &&
childRect.right <= containerRect.right
);
};
Cypress.Commands.add('isInside', { prevSubject: true }, (child, container, expected) => {
return getRect(child).then( childRect => {
getRect(container).then( containerRect => {
expect(isInside(containerRect, childRect)).to.equal(expected);
});
});
});
describe('test', () => {
it('test', () => {
cy.document().then( doc => {
doc.body.innerHTML = `
<div class="one"></div>
<div class="two"></div>
<style>
.one, .two {
position: absolute;
}
.one {
background: rgba(255,0,0,0.3);
width: 400px;
height: 400px;
}
.two {
background: rgba(0,0,255,0.3);
width: 200px;
height: 200px;
}
</style>
`;
});
cy.get('.two').isInside('.one', true);
cy.get('.one').isInside('.two', false);
});
it('test2', () => {
cy.document().then( doc => {
doc.body.innerHTML = `
<div class="one"></div>
<div class="two"></div>
<style>
body, html { margin: 0; padding: 0 }
.one, .two {
position: absolute;
}
.one {
background: rgba(255,0,0,0.3);
width: 400px;
height: 400px;
}
.two {
background: rgba(0,0,255,0.3);
width: 200px;
height: 200px;
left: 300px;
}
</style>
`;
});
cy.get('.two').isInside('.one', false);
cy.get('.one').isInside('.two', false);
});
it('test3', () => {
cy.document().then( doc => {
doc.body.innerHTML = `
<div class="one"></div>
<style>
body, html { margin: 0; padding: 0 }
.one {
position: absolute;
background: rgba(255,0,0,0.3);
width: 400px;
height: 400px;
left: -100px;
}
</style>
`;
});
cy.get('.one').isInside('viewport', false);
});
});
Why there is a synchronous return in your getViewport function? I'm speaking about the last return viewport
function getViewport() {
var viewport = {}
cy.document().then((doc) => {
...
})
return viewport // <-- ?????
}
doing so, all the cy.document().then((doc) etc. code is useless.
I don't know if this is the problem, but I can't run your code locally because it misses a lot of functions. Could you share a "working” GitHub repo to make some more tests?
I ran into this problem as well, and opted for a solution with async/await:
function getDocument() {
return new Promise(resolve => {
cy.document().then(d => {
console.log('deeee', d);
resolve(d);
});
});
}
describe('Stuff', () => {
it('Sees the toasty character', async () => {
const document = await getDocument();
// Your test code here
});
});
Even though Cypress commands aren't really promises, you can create your own promise, and resolve it when ready. Then await that promise in your test code.
Hope it helps!
I have a ReactJS component that I want to have different behavior on a single click and on a double click.
I read this question.
<Component
onClick={this.onSingleClick}
onDoubleClick={this.onDoubleClick} />
And I tried it myself and it appears as though you cannot register both single click and double click on a ReactJS component.
I'm not sure of a good solution to this problem. I don't want to use a timer because I'm going to have 8 of these single components on my page.
Would it be a good solution to have another inner component inside this one to deal with the double click situation?
Edit:
I tried this approach but it doesn't work in the render function.
render (
let props = {};
if (doubleClick) {
props.onDoubleClick = function
} else {
props.onClick = function
}
<Component
{...props} />
);
Here is the fastest and shortest answer:
CLASS-BASED COMPONENT
class DoubleClick extends React.Component {
timer = null
onClickHandler = event => {
clearTimeout(this.timer);
if (event.detail === 1) {
this.timer = setTimeout(this.props.onClick, 200)
} else if (event.detail === 2) {
this.props.onDoubleClick()
}
}
render() {
return (
<div onClick={this.onClickHandler}>
{this.props.children}
</div>
)
}
}
FUNCTIONAL COMPONENT
const DoubleClick = ({ onClick = () => { }, onDoubleClick = () => { }, children }) => {
const timer = useRef()
const onClickHandler = event => {
clearTimeout(timer.current);
if (event.detail === 1) {
timer.current = setTimeout(onClick, 200)
} else if (event.detail === 2) {
onDoubleClick()
}
}
return (
<div onClick={onClickHandler}>
{children}
</div>
)
}
DEMO
var timer;
function onClick(event) {
clearTimeout(timer);
if (event.detail === 1) {
timer = setTimeout(() => {
console.log("SINGLE CLICK");
}, 200)
} else if (event.detail === 2) {
console.log("DOUBLE CLICK");
}
}
document.querySelector(".demo").onclick = onClick;
.demo {
padding: 20px 40px;
background-color: #eee;
user-select: none;
}
<div class="demo">
Click OR Double Click Here
</div>
I know this is an old question and i only shoot into the dark (did not test the code but i am sure enough it should work) but maybe this is of help to someone.
render() {
let clicks = [];
let timeout;
function singleClick(event) {
alert("single click");
}
function doubleClick(event) {
alert("doubleClick");
}
function clickHandler(event) {
event.preventDefault();
clicks.push(new Date().getTime());
window.clearTimeout(timeout);
timeout = window.setTimeout(() => {
if (clicks.length > 1 && clicks[clicks.length - 1] - clicks[clicks.length - 2] < 250) {
doubleClick(event.target);
} else {
singleClick(event.target);
}
}, 250);
}
return (
<a onClick={clickHandler}>
click me
</a>
);
}
I am going to test this soon and in case update or delete this answer.
The downside is without a doubt, that we have a defined "double-click speed" of 250ms, which the user needs to accomplish, so it is not a pretty solution and may prevent some persons from being able to use the double click.
Of course the single click does only work with a delay of 250ms but its not possible to do it otherwise, you have to wait for the doubleClick somehow...
All of the answers here are overcomplicated, you just need to use e.detail:
<button onClick={e => {
if (e.detail === 1) handleClick();
if (e.detail === 2) handleDoubleClick();
}}>
Click me
</button>
A simple example that I have been doing.
File: withSupportDoubleClick.js
let timer
let latestTouchTap = { time: 0, target: null }
export default function withSupportDoubleClick({ onDoubleClick = () => {}, onSingleClick = () => {} }, maxDelay = 300) {
return (event) => {
clearTimeout(timer)
const touchTap = { time: new Date().getTime(), target: event.currentTarget }
const isDoubleClick =
touchTap.target === latestTouchTap.target && touchTap.time - latestTouchTap.time < maxDelay
latestTouchTap = touchTap
timer = setTimeout(() => {
if (isDoubleClick) onDoubleClick(event)
else onSingleClick(event)
}, maxDelay)
}
}
File: YourComponent.js
import React from 'react'
import withSupportDoubleClick from './withSupportDoubleClick'
export default const YourComponent = () => {
const handleClick = withSupportDoubleClick({
onDoubleClick: (e) => {
console.log('double click', e)
},
onSingleClick: (e) => {
console.log('single click', e)
},
})
return (
<div
className="cursor-pointer"
onClick={handleClick}
onTouchStart={handleClick}
tabIndex="0"
role="button"
aria-pressed="false"
>
Your content/button...
</div>
)
}
onTouchStart start is a touch event that fires when the user touches the element.
Why do you describe these events handler inside a render function? Try this approach:
const Component = extends React.Component {
constructor(props) {
super(props);
}
handleSingleClick = () => {
console.log('single click');
}
handleDoubleClick = () => {
console.log('double click');
}
render() {
return (
<div onClick={this.handleSingleClick} onDoubleClick={this.handleDoubleClick}>
</div>
);
}
};