How to reset Cypress window.location.href after test - javascript

I am testing some ui that on click updates the window.location.href. I have two tests, the first one works, but the second starts in the location set by the ui in the previous test. This is wrong and stops test two from starting on the right page.
How can I reset the window.location.href or just the Cypress browser location in general back to where it was at the beginning of the first test?
I have checked the window.location.href at the start of the second test and it looks autogenerated and so I don't think wise to try and hardcode that value into window.location.href at the start of the second test.
Looking for something I can run at afterEach.
Test
it.only('should send asynchronous analytics event after provider selection click', () => {
rewire$useFlag(() => true);
cy.location().then((location) => console.log('window !!'));
const analyticsAsyncStub = cy.stub().as('sendAnalyticsAsyncStub');
rewire$sendAnalyticsAsync(analyticsAsyncStub);
// #NOTE hacking browser detection so required provider options are availiable
cy.window().then(($window) => {
console.log('window !!', $window.location.href);
($window as any).chrome = {};
($window as any).chrome.runtime = {
sendMessage() {
'mock function';
},
};
});
mountFixtureWithProviders({ children: <ProviderSelection flagsConfig={defaultFlags} /> });
cyGetByTestId('provider--metaMask').click();
cy.get('#sendAnalyticsAsyncStub').should(
'have.been.calledWithMatch',
analyticsUtilsModule.createButtonEvent(ButtonEventName.providerSelectionPressed),
);
});
mountFixtureWithProviders function
export const mountFixtureWithProviders = ({
children,
mountInsideVisualMock = true,
setErrorLog = () => ({}),
renderErrorScreens,
}: {
children: ReactNode;
mountInsideVisualMock?: boolean;
setErrorLog?: SetErrorLog;
renderErrorScreens?: boolean;
}) => {
const RouterMockedChildren = () => <MemoryRouter>{children}.
</MemoryRouter>;
const ProvidedChildren = () =>
renderErrorScreens ? (
<DemoAppToRenderErrorMessages>
<RouterMockedChildren />
</DemoAppToRenderErrorMessages>
) : (
<LinkUiCoreContext.Provider value={{ setErrorLog,
imageResizerServiceUrl: DEV_IMAGE_RESIZER_SERVICE_URL }}>
<RouterMockedChildren />
</LinkUiCoreContext.Provider>
);
return mount(mountInsideVisualMock ? linkPageVisualMock({
children: <ProvidedChildren /> }) : <ProvidedChildren />);
};
Thank you.

Usually you can just do a visit in a beforeEach() to get a clean start for each test
beforeEach(() => {
cy.visit('/')
})
There's also cy.go('back') which you can run at the end of the first test.
But be aware that a fail in test 1 will then fail test 2 because the navigation won't happen - same applies to adding into afterEach().
In Cypress window is the test runner window, but you can access the app window with
cy.window().then(win => console.log(win.location.href))
or the location directly with
cy.location().then(loc => console.log(loc.href))
Don't use cy.log() for debugging, use console.log() as there are side-effects to cy.log() that may give you the wrong debugging info.
(mountFixtureWithProviders({ children: <ProviderSelection flagsConfig={{}} /> }) looks like a component test, so yes I agree that should give you a fresh start for each test.
Can you add the two tests and also mountFixtureWithProviders in the question to give the full picture please.

Related

Jest custom testing API, how to correct code frame

I'm looking to simplify my project's testing API where I am aiming for something like this:
testThing((t) => {
t(33);
t(44);
t(42);
})
Now I don't know how to get Jest to show the correct code frames for failed expect's. This is my current stab at an implementation:
const testThing = (callback: any) => {
callback((n: any) => {
test(n.toString(), () => {
expect(n).toBe(42);
});
});
};
Which results in the testThing definition to be shown for every failed test case. Here's a replit if you want to see it in action: https://replit.com/#grgr/jest-frame-issue#thing.test.js

How to use the `PerformanceNavigationTiming` API to get page load time?

I am trying to to use the PerformanceNavigationTiming API to generate a page load metric.
The MDN API document linked above says that the PerformanceEntry.duration should give me what I need because it:
[r]eturns a timestamp that is the difference between the PerformanceNavigationTiming.loadEventEnd and PerformanceEntry.startTime properties.
However, when I check this property, I get simply 0. I'm accessing this API from within a React hook that runs a useEffect function that wait for the window load event and then checks the api like so:
export const useReportPageLoadTime = () => {
useEffect(() => {
const reportTime = () => {
let navPerformance: PerformanceEntry
navPerformance = window.performance.getEntriesByType('navigation')[0]
console.log({
duration: navPerformance.duration,
blob: navPerformance.toJSON()
})
}
if (document.readyState === 'complete') {
reportTime()
return null
} else {
window.addEventListener('load', reportTime)
return () => window.removeEventListener('load', reportTime)
}
}, [])
}
As you can see there, I also call toJSON on the performance entry and indeed it shows that the values upon which duration (startTime and loadEventEnd) are both 0 as well:
Does anyone know why I am getting this value?
I was finally able to get this to work using a different method than the event listener. It certainly is logical that the data should be ready when the load event fires, but the only way I was able to get the data was to use another feature of the Performance API: the PerformanceObserver, which fires a callback when a new piece of data has become available.
Here is the code that worked for me:
export const useReportPageLoadMetrics = () => {
useEffect(() => {
const perfObserver = new PerformanceObserver((observedEntries) => {
const entry: PerformanceEntry =
observedEntries.getEntriesByType('navigation')[0]
console.log('pageload time: ', entry.duration)
})
perfObserver.observe({
type: 'navigation',
buffered: true
})
}, [])
}

NodeJS events triggering multiple times in electron-react app

I have a package (Let's say PACKAGE_A) written to do some tasks. Then it is required by PACKAGE_B. PACKAGE_A is a node script for some automation work. It has this Notifier module to create and export an EventEmitter. (The Whole project is a Monorepo)
const EventEmitter = require('events');
let myNotifier = new EventEmitter();
module.exports = myNotifier;
So in some functions in PACKAGE_A it emits event by requiring myNotifier, and also in the index.js of PACKAGE_A, I export functions (API exposed to the other packages) and the myNotifier by requiring it again.
const myNotifier = require('./myNotifier);
const func1 = () => {
// some function
return something;
}
module.exports = {func1, myNotifier}
Then I import the PACKAGE_A in PACKAGE_B and use the API functions exposed with the notifier. PACKAGE_B is an electron app with a React UI.
Below is how the program works.
I have a console output window in the electron app (React UI, UI_A). <= (keep this in mind)
When I click a button in UI_A it fires a redux action (button_action). Inside the action, a notification is sent to an event which is listened in the electron code using ipcRenderer.
ipcRenderer.send('button-clicked', data); // <= this is not the full code of the action. It's bellow.
Then in the electron code (index.js), I require another file (UI_A_COM.js which houses the code related to UI_A in electron side). The reason is code separation. Here's part of the code in index.js related to the electron.
const ui_a_com = require('./electron/UI_A_COM');
const createWindow = () => {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
},
resizable: false,
});
mainWindow.loadURL('http://localhost:3000');
const mainMenu = Menu.buildFromTemplate(menuTemplate);
ui_a_com (mainWindow);
};
Alright. Then in UI_A_COM.js, I listen to that triggered event button-clicked.
ipcMain.on('button-clicked', someFunction);
which runs the code from PACKAGE_A and return a result. So now when PACKAGE_A runs, it emits some events using myNotifier. I listen to them in the same file (UI_A_COM.js), and when those events are captured, I again send some events to React UI, which is subscribed when button_action fired.
myNotifier.on('pac_a_event_a', msg => {
mainWindow.webContents.send('ui_event_a', msg); // code in `UI_A_COM.js`
});
Here's the full code for the action. (Did not provide earlier because you'll get confused)
export const buttonAction = runs => {
return dispatch => {
ipcRenderer.send('button-clicked', data);
ipcRenderer.on('ui_event_a', (event, msg) => {
dispatch({ type: SOME_TYPE, payload: { type: msg } });
});
};
};
This will show the msg in the UI_A console.
So this is the task I'm doing. The problem is when I click the button; it works perfectly for the first time. But when I click the button on the second time, it received two messages. Then when I click the button again, three messages and it keeps growing. (but the functions in the PACKAGE_A only executes one time per button press).
Let's say the message from PACKAGE_A emitted is 'Hello there' per execution.
When I press the button 1st time a perfect result => Hello there, When I click the button again => Hello there Hello there, When I click it again => Hello there Hello there Hello there.
It's kept so on. I think my implementation of EventEmitter has some flows. So why it's happening like this? Is it EventEmitter or something else? What am I doing wrong here?
By default the electron-react boilerplate doesnt define the ipcRenderer.removeAllListeners method. So you have to first go to the main/preloads.ts file and add them :
removeListener(channel: string, func: (...args: unknown[]) => void) {
ipcRenderer.removeListener(channel, (_event, ...args) => func(...args));
},
removeAllListeners(channel: string) {
ipcRenderer.removeAllListeners(channel);
},
Then go to the renderer/preload.t.s declaration file and add the declarations too:
removeListener(
channel: string,
func: (...args: unknown[]) => void
): void;
removeAllListeners(channel: string): void;
After that make sure to clean all listeners in the cleanup function of your useEffects each time you listen to an event fired. This will prevent multiple firing.
useEffect(() => {
window.electron.ipcRenderer.on('myChannel', (event, arg) => {
// do stuffs
});
return () => {
window.electron.ipcRenderer.removeAllListeners('myChannel');
};
});
I think you should return a function that call ipcRenderer.removeAllListeners() in your component's useEffect().
Because every time you click your custom button, the ipcRenderer.on(channel, listener) is called, so you set a listener to that channel agin and agin...
Example:
useEffect(() => {
electron.ipcRenderer.on('myChannel', (event, arg) => {
dispatch({ type: arg });
});
return () => {
electron.ipcRenderer.removeAllListeners('myChannel');
};
});

Jest: Mock a _HOC_ or _curried_ function

Given the following function:
./http.js
const http = {
refetch() {
return (component) => component;
}
}
I would like to mock the function in a test as follows:
./__tests__/someTest.js
import { refetch } from './http';
jest.mock('./http', () => {
return {
refetch: jest.fn();
}
}
refetch.mockImplementation((component) => {
// doing some stuff
})
But I'm receiving the error
TypeError: _http.refetch.mockImplementation is not a function
How can I mock the refetch function in the given example?
update:
When I modify the mock function slightly to:
jest.mock(
'../http',
() => ({ refetch: jest.fn() }),
);
I get a different error:
TypeError: (0 , _http.refetch)(...) is not a function
My guess it's something with the syntax where the curried function (or HOC function) is not mapped properly. But I don't know how to solve it.
Some of the real code I'm trying to test.
Note: The example is a bit sloppy. It works in the application. The example given is to give an idea of the workings.
./SettingsContainer
// ...some code
return (
<FormComponent
settingsFetch={settingsFetch}
settingsPutResponse={settingsPutResponse}
/>
);
}
const ConnectedSettingsContainer = refetch(
({
match: { params: { someId } },
}) => ({
settingsFetch: {
url: 'https://some-url.com/api/v1/f',
},
settingsPut: (data) => ({
settingsPutResponse: {
url: 'https://some-url.com/api/v1/p',
}
}),
}),
)(SettingsContainer);
export default ConnectedSettingsContainer;
Then in my component I am getting the settingsPutResponse via the props which react-refetch does.
I want to test if the user can re-submit a form after the server has responded once or twice with a 500 until a 204 is given back.
./FormComponent
// ...code
const FormComp = ({ settingsResponse }) => {
const [success, setSuccess] = useState(false);
useEffect(() => {
if (settingsResponse && settingsResponse.fulfilled) {
setSuccess(true);
}
}, [settingsResponse]);
if (success) {
// state of the form wil be reset
}
return (
<form>
<label htmlFor"username">
<input type="text" id="username" />
<button type="submit">Save</button>
</form>
)
};
The first question to ask yourself about mocking is "do I really need to mock this?" The most straightforward solution here is to test "component" directly instead of trying to fake out an http HOC wrapper around it.
I generally avoid trying to unit test things related to I/O. Those things are best handled with functional or integration tests. You can accomplish that by making sure that, given same props, component always renders the same output. Then, it becomes trivial to unit test component with no mocks required.
Then use functional and/or integration tests to ensure that the actual http I/O happens correctly
To more directly answer you question though, jest.fn is not a component, but React is expecting one. If you want the mock to work, you must give it a real component.
Your sample code here doesn't make sense because every part of your example is fake code. Which real code are you trying to test? I've seen gigantic test files that never actually exercize any real code - they were just testing an elaborate system of mocks. Be careful not to fall into that trap.

Click event inside of a Modal - test

Trying to run a test for the following code, but node can't be found .Using jest and enzyme for ReactJS
render () {
return (
this.state.permissionsLoaded ?
this.state.localPermissions[globals.UI_DATASOURCEDESIGNER] ?
this.state.datasourcePermissionsLoaded ?
this.state.allowCurrentDatasource ?
<div>
<Modal isOpen={this.state.addRequestModalOpen} style={shareModal}>
<div title="Close Window Without Saving" className="sidemodal_addnew_x" onClick={() => {this.closeAddModal()}}><FontAwesome name='xbutton' className='fa-times' /></div>
Keep getting the following error: Method “simulate” is meant to be run on 1 node. 0 found instead.
Here is what I have so far for my test:
beforeEach(() => wrapper = mount(<MemoryRouter keyLength={0}><Datasource {...baseProps} /></MemoryRouter>));
it("Test Click event on Add DataSource ", () => {
wrapper.find('Datasource').setState({
permissionsLoaded:true,
localPermissions:true,
datasourcePermissionsLoaded:true,
allowCurrentDatasource:true,
addRequestModalOpen:true
})
wrapper.update();
wrapper.find('Datasource').find('.sidemodal_addnew_x').simulate('click')
});
Here as list of my state:
permissionsLoaded: false,
datasourcePermissionsLoaded: false,
allowCurrentDatasource: false,
localPermissions:{
[globals.UI_DATASOURCEDESIGNER]:false,
}
Well it looks like you are trying to find a node which will be conditionally rendered if all of the state variables you've mentioned are true, which none of them are (you are actually setting them all to false and updating the wrapper beforehand). This means that there is no .sidemodal_addnew_x to be found that can be used to simulate a click on, hence why you get that error message.
In case you've wanted to test for the existence of that component instead, you can do the following:
expect(wrapper.find('Datasource').find('.sidemodal_addnew_x').exists()).to.equal(false);
If you do want to test the click make sure the component gets .sidemodal_addnew_x gets rendered by settings the state variables to true:
it("Test Click event on Close Window Without Saving", (done) => {
baseProps.onClick.mockClear();
wrapper.find('Datasource').setState({
permissionsLoaded:true,
localPermissions:true,
datasourcePermissionsLoaded:true,
allowCurrentDatasource:true,
addRequestModalOpen:true,
}, () => {
wrapper.update();
wrapper.find('Datasource').find('.sidemodal_addnew_x').simulate('click');
done();
});
});

Categories