Forced to re-query element in order to fire click handler - react-testing-library

I have a test that where I assert that my API request is supplying the correct params. I am using MSW to mock the api, and I supply a spy for the request handler:
test("supplies order direction descending if header button is clicked twice", async () => {
const mockHandler = jest.fn(handler);
server.use(rest.get(`${config.apiHost}/targets`, mockHandler));
// First request to /targets happens on render
render(<Route>{(props) => <TargetList history={props.history} />}</Route>);
const button = await screen.findByRole("button", { name: "Port" });
// Second request to /targets happens on button click
userEvent.click(button);
await waitFor(() => {
expect(mockHandler).toHaveBeenCalledTimes(2);
});
// Third request to /targets SHOULD happen here but doesn't
userEvent.click(button);
await waitFor(() => {
expect(mockHandler).toHaveBeenCalledTimes(3);
const lastCall = mockHandler.mock.calls[2];
const requestArg = lastCall[0];
expect(requestArg.url.searchParams.get("orderby")).toBe("port");
expect(requestArg.url.searchParams.get("direction")).toBe("descending");
});
});
The above code doesn't work, as firing the click event a second time on the button appears to do nothing. However, if I requery for the button, then I can successfully fire the handler:
test("supplies order direction descending if header button is clicked twice", async () => {
...
const button = await screen.findByRole("button", { name: "Port" });
// Second request to /targets happens on b utton click
userEvent.click(button);
await waitFor(() => {
expect(mockHandler).toHaveBeenCalledTimes(2);
});
// Third request to /targets now works!
const button2 = await screen.findByRole("button", { name: "Port" });
userEvent.click(button2);
await waitFor(() => {
expect(mockHandler).toHaveBeenCalledTimes(3); // SUCCESS!
...
});
});
Why do I have to re-query for the same element? Am I doing something incorrectly?

I do not have complete source code but I would assume you are doing something on your click event handler that would force react to render again and in that case old element would no longer be present on DOM and hence click event handler will not be invoked.
P.S: You can use react life cycle hooks to determine if component was re-rendered.

Related

react testing, button not clicking from userevent

im trying to do a write a test where i can click a button in a react component but its not working and i don't know why. as far as i can tell this should be working. im expecting the callback to be called once.
it("should work", () => {
var mockCallback = jest.fn();
render(<button onClick={mockCallback}>hello world</button>);
act(() => {
let test = screen.getByRole("button");
console.log(test);
userEvent.click(test);
});
expect(mockCallback).toHaveBeenCalledTimes(1);
});
here is a link to a codesandbox
what am i doing wrong?
userEvent.click returns a Promise<void> as you can see here. So, to your test work you need to make your test async and await the click event:
it("should work", async () => {
var mockCallback = jest.fn();
render(<button onClick={mockCallback}>hello world</button>);
const button = screen.getByRole("button");
screen.debug();
await userEvent.click(button);
expect(mockCallback).toHaveBeenCalledTimes(1);
});
* Also, you donĀ“t need the act in this case. Check more about it here.

ionic reload a page from another page/modal

EDIT
I noticed that the subscribe event must come first before and publish get called. But it will be silly to ask user to open TabOut page every time when app start.
I do not need to always reloading the TabOut page, so I need this event sort of method to do the job. Or else could've just call the reload on ionViewDidEnter().
I have 2 Tabs and 1 modal. /TabIn, /TabOut, and /ModalIn.
The Tabs page serve as data listing which display the data from database on ionViewDidLoad().
The ModalIn page serve as data entry for the user to key in and submit data. This page resides in the TabIn page and will get called when user clicked on each of the list of data.
After successfully submit the form in the ModalIn page I want to call refresh again on the TabOut page (no matter it has been loaded before or not). I tried using events publish it is not working. Below are my code.
ModalIn .ts
let headers: any = new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }),
options: any = { "username": val, formValue },
url: any = "some_url_here";
this.http.post(url, options, headers)
.subscribe((data: any) => {
if (data.status == 'success') {
this.events.publish('shouldReloadData');
} else {
}
},
(error: any) => {
console.log(error);
});
TabOut .ts
constructor(public events: Events) {
events.subscribe('shouldReloadData', () => {
// Reload the page here
console.log("should reloadddd"); // <- This is not working
});
}
Call subscribe with this keyword inside TabOut.ts.
constructor(public events: Events) {
this.events.subscribe('shouldReloadData', () => {
});
}

priority-web-sdk: startSubForm return the parent form

The following code starts a form and a subform, the issue is the I get the subForm instance of the parent:
await actions.loginTest();
const form = await actions.priority.formStart(this.formName,
onShowMessgeFunc, onUpdateFieldsFunc);
console.log("form", form);
if(form.subForms["SHIPTO2"]) {
const subForm = await form.startSubForm("SHIPTO2");
console.log("subform", subForm);
subForm.choose("CUSTDES", '').then(options => {
let custOptions = options.SearchLine.map(x => {return
{label:x.retval + " - " + x.string1, value: x.retval }});
this.setState({
customersShippingOptions: custOptions,
})
}).catch((err) => {
console.log("CHOOSE ERROR", err);
})
}
Output:
In order to start a subform there must be an active row in the parent form, so the subform will start for that row.
Before you start the subform you should use the getRows() function to retrieve rows and then call setActiveRow(rowIndex) to set an active row. Then you could start the subform.
Modify your code to the following:
await actions.loginTest();
const form = await actions.priority.formStart(this.formName,
onShowMessgeFunc, onUpdateFieldsFunc);
console.log("form", form);
const rows = await form.getRows();
await setActiveRow(5);
if(form.subForms["SHIPTO2"]) {
const subForm = await form.startSubForm("SHIPTO2");
console.log("subform", subForm);
...
}
By the way: I see you're using choose(), Here too there needs to be an active (subform) row,use getRows() with setActiveRow() or create a newRow() before calling choose()
If you want to pull the first line on the screen more than just use the autoRetrieveFirstRows parameter
In the function formStart
Link to the function on the Web SDK site
I personally define this value as default

Protractor not waiting for modal

I have a modal that shows up in a protractor test. When I run the test case solo, it works perfectly, but when I run it as part of the larger file (currently 10 cases, some lengthy) it slows things down and, as a result, the modal is slower to open and close. The chain effect is that, when I try to click on a button on the page, it crashes since the modal (which was supposed to be closed by now) is still there and blocking the button.
How can I properly sense when the modal is open/closed so that I can work around this?
Thanks!
(Also, this is an AngularJS application)
These are my helper functions for manual waiting:
static async forElementAbsent(element) {
return await new Promise<any>((resolve) => {
browser.driver.wait(ExpectedConditions.stalenessOf(element), 10000,
`Timed out waiting for element to be absent`).then(() => {
resolve();
});
});
}
static async forElementPresent(element) {
return await new Promise<any>((resolve) => {
browser.driver.wait(ExpectedConditions.presenceOf(element), 10000,
`Timed out waiting for element to be present`).then(() => {
resolve();
});
});
}
In our tests, we are waiting for modals to be displayed manually. We have helper functions such as
export async function forCondition(condition: () => boolean | PromiseLike<boolean>, timeout = 20000): Promise<boolean> {
try {
return await browser.wait(condition, timeout);
} catch (err) {
return false;
}
}
Given function waits for a condition to fullfill. So in your particular case you would have to find out the css selector of the modal which is displayed.
Let's say the selector is by.css('.modal.visible'), then we would have to write something like the following if we wanted to wait for the modal to be displayed:
t.it('modal should be displayed', async function() {
// wait for modal to be displayed
var result = await forCondition(function() {
return element(by.css('.modal.visible')).isDisplayed();
});
expect(result).toBeTruthy();
});

Clicking buttons after a modal event with protractor

I'm having a problem clicking on a button that is on a modal or on a page after a modal is supposed to have been closed. Here is my experience:
I have a modal i'm dealing with. I create the actions to have it displayed. I have a wait function that waits for one of the modal's items to be "clickable" (This allows it to be both present and displayed). I then verify the text on the modal.
All of the above is successful. However, when I try to click on the button to close it, I'm getting an error stating that another element on the page would be clickable. I'm thinking I need to disable angular (and CSS?) animation. Is this answer still relevant to the latest version of protractor?
How to disable animations in protractor for angular js application
The problem I have with the above answer is that typescript didn't recognize the "angular" object.
I also see this question: Click on a button in a Modal Window - Protractor But the "answer" is to sleep for 2 seconds. Not my desired solution (if at all possible).
In any case, I found a way to reproduce what I'm talking about. The following spec will load the angular materials page to the dialog demo page. It will click on the Confirm Dialog button, verify the header text on the modal, click the close button, then try to repeat the steps a second time. It fails on the second time, even though it is supposed to have waited until the button is clickable.
/**
* modal.conf.js
*/
exports.config = {
framework: 'jasmine',
specs: ['modal.spec.js'],
useAllAngular2AppRoots: false,
jasmineNodeOpts: {
'stopSpecOnExpectationFailure': true,
showColors: true
}
};
'use strict';
/**
* modal.spec.js
*/
var headerText = "Would you like to delete your debt?";
describe('Click Modal and Close test', function () {
beforeAll(function () {
browser.get('https://material.angularjs.org/latest/demo/dialog')
});
it('should click to open modal', function () {
confirmBtn.click().then(function () {
WaitForLoad(cancelBtn).then(function () {
expect(headerTextEl).toEqual(headerTextEl);
});
});
});
it('should click to close modal', function () {
cancelBtn.click().then(function () {
WaitForLoad(confirmBtn);
});
});
it('should click to open modal', function () {
confirmBtn.click().then(function () {
WaitForLoad(cancelBtn).then(function () {
expect(headerTextEl).toEqual(headerTextEl);
});
});
});
it('should click to close modal', function () {
cancelBtn.click().then(function () {
WaitForLoad(confirmBtn);
});
});
});
// region Page Objects
var confirmBtn = $('.layout-margin > button:nth-child(2)');
var headerTextEl = $('.md-title');
var cancelBtn = $('button.md-cancel-button');
// endregion
// region actions
var WaitForLoad = function (el, timeout) {
var EC = protractor.ExpectedConditions;
return browser.wait(EC.elementToBeClickable(el), timeout);
};
// endregion