How to set local storage in cypress - reactjs

I have a simple test
describe('Page Test', () => {
it('button has "contact-next-disabled" class', () => {
cy.get('.contact-next-disabled')
})
})
But after running the test, cypress shows an error
34 | useEffect(() => {
35 | const _contact = getLocalStorage()
> 36 | if (!_contact.categories.length && !_contact.categoryNameEtc) {
| ^
37 | PGToaster.showDanger('Please select a category.')
38 | router.push('/contact/a-type')
39 | }
My getLocalStorage function is in a separate file from the one I am testing
export const getLocalStorage = () => {
const contact = JSON.parse(window.localStorage.getItem('contact'))
return contact
}
Which works together with my setLocalStorage function
export const setLocalStorage = (contact) => {
const _contact = getLocalStorage()
const updateContact = {
..._contact,
...contact
}
window.localStorage.setItem('contact', JSON.stringify(updateContact))
}
If I rewrite
if (!_contact.categories.length && !_contact.categoryNameEtc)
to
if (!_contact?.categories.length && !_contact?.categoryNameEtc)
The error is gone, but I would like to just set local storage in my cypress test instead of rewriting my code. How can I do this?

According to the docs of cy.clearLocalStorage()
Cypress automatically runs this command before each test to prevent state from being shared across tests. You shouldn't need to use this command unless you're using it to clear localStorage inside a single test.
You can either set up your tests using hooks:
before(() => {
// set localStorage
})
or you can use a plugin like cypress-localstorage-commands to preserve localStorage between Cypress tests.

Related

I trying to unit test my work, somehow waitfor isn't working? It's timing out

I have a unit test, I've already mocked my fetchAppList and fetchTotalAppCount but somehow when I try to render my component, but I keep getting this error:
● loads the correct number of apps
Timed out in waitFor.
11 | render(<IntegrationsPage/>);
12 |
> 13 | const appList = await waitFor(() => screen.findAllByText("Notify me when it's ready"));
| ^
14 | expect(appList).toHaveLength(10);
15 | });
at waitForWrapper (node_modules/#testing-library/dom/dist/wait-for.js:187:27)
at Object.<anonymous> (src/pages/integrations/IntegrationsPage.test.js:13:34)
This is what my test looks like:
import { render, screen, waitFor } from "#testing-library/react";
import IntegrationsPage from "./IntegrationsPage";
import apps from "../../api/apps/Apps";
import mockData from "../../api/apps/MockData";
jest.mock("../../api/apps/Apps");
test("loads the correct number of apps", async() => {
apps.fetchAppList.mockResolvedValue(mockData);
apps.fetchTotalAppCount.mockImplementation(() => mockData.length);
render(<IntegrationsPage/>);
const appList = await waitFor(() => screen.findAllByText("Notify me when it's ready"));
expect(appList).toHaveLength(10);
});
The integrationsPage is using useEffect to render a list of apps. Each app has a link that says Notify me when it's ready in another child component.
useEffect(() => {
const totalAppCount = Apps.fetchTotalAppCount()
const pageCount = calculatePageCount(totalAppCount);
Apps
.fetchAppList(INITIAL_OFFSET,ITEMS_PER_PAGE)
.then((result) => {
setCurrentPage(INITIAL_PAGE);
setTotalAppCount(totalAppCount);
setPageCount(pageCount);
setAppList(result);
});
},[]);

Test component methods on CRA

I'm working with create-react-app and I have the following component:
const Desktop = () => {
const onProjects = () => { };
return (
<Icon
title="Proyectos"
icon="projects"
onClick={onProjects}
testId="projects-testid"
/>
);
};
In which I want to test the component's onProject method to increase my coverage value:
File
% Stmts
% Branch
% Funcs
% Lines
Uncovered Line #s
index.tsx
100
100
25
100
It shows 25% cause I have many other methods, but I have decided to copy a simplified version to save space.
I used to pass those methods as props to the Desktop component, which is quite easy to test like this:
const onProjects = jest.fn();
describe("Components/Desktop", () => {
it("Calls the onClick handlers", () => {
const { getByTestId } = render(<Desktop onProjects={onProjects} />);
fireEvent.click(getByTestId("projects-testid"));
expect(onProjects).toHaveBeenCalledTimes(1);
});
});
Now that I don't want to pass those methods as props but handle them inside the Desktop component... I can't seem to find a way to test them. Is there a way to do so? Thanks in advance.

window.dispatchEvent from test code does not trigger window.addEventListener

I have below listener added for which I am trying to write test using Jest. However, it seems as though the event I'm dispatching doesn't reach my code.
window.addEventListener('message', (event) => {
if (event.data.type === 'abc') {
console.log(event.data.payload);
}
});
I have tried below 2 approaches and both of them don't seem to work. I'm unable to verify the call using the spy object I'm creating. Please refer to the code below:
const listenerSpy = jest.spyOn(window, 'addEventListener');
const data = {
type: 'abc',
payload: '',
};
const messageEvent = new MessageEvent('message', {data});
window.dispatchEvent(messageEvent);
expect(listenerSpy).toHaveBeenCalledTimes(1);
const listenerSpy = jest.spyOn(window, 'addEventListener');
const data = {
type: 'abc',
payload: '',
};
window.postMessage(data, '*');
expect(listenerSpy).toHaveBeenCalledTimes(1);
For the 1st approach, have also tried using 'new Event('message')'.
With above 2 approaches, I get the error as below:
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
102 | window.dispatchEvent(messageEvent);
103 |
> 104 | expect(listenerSpy).toHaveBeenCalledTimes(1);
| ^
I have also tried to follow different websites including below:
https://medium.com/#DavideRama/testing-global-event-listener-within-a-react-component-b9d661e59953
https://github.com/enzymejs/enzyme/issues/426
But no luck there as with typescript, I cannot follow the solution given. I have also tried to find answers on stackoverflow, but the none of solutions suggested seem to work for me.
I am new to react and got stuck with this. Any pointers on this would help.
jsdom fire the message event inside a setTimeout, see this
setTimeout(() => {
fireAnEvent("message", this, MessageEvent, { data: message });
}, 0);
For more info, see issue
So, it's asynchronous and you need to wait for the macro task scheduled by setTimeout to be finished before the test case ends.
index.ts:
export function main() {
window.addEventListener('message', (event) => {
if (event.data.type === 'abc') {
console.log(event.data.payload);
}
});
}
index.test.ts:
import { main } from './';
function flushMessageQueue(ms = 10) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
describe('71912032', () => {
test('should pass', async () => {
const logSpy = jest.spyOn(console, 'log');
main();
const data = { type: 'abc', payload: 'xyz' };
window.postMessage(data, '*');
await flushMessageQueue();
expect(logSpy).toBeCalledWith('xyz');
});
});
Test result:
PASS stackoverflow/71912032/index.test.ts
71912032
✓ should pass (41 ms)
console.log
xyz
at console.<anonymous> (node_modules/jest-mock/build/index.js:845:25)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.01 s, estimated 13 s
Also, take a look at this question React Jest: trigger 'addEventListener' 'message' from tests
Have you tried taking a look at this discussion? It seems to have a similar requirement.
Dispatch a Custom Event and test if it was correctly triggered (React TypeScript, Jest)

React - Testing function passed in as props is called via button press

I am attempting to unit test a simple component that calls a function passed into it.
It is a simple footer component with two button, cancel/save.
When save is pressed, it should call the "handleSubmit" property I have passed into it but when attempting to test with jest, I cannot get the tests to pass.
Component:
function GSFooter({
handleSubmit,
}) {
return (
<Footer>
<FooterActionsWrap>
<CancelButton className="update-btn">
{" "}
<Link to={"/invoices"}>Cancel</Link>
</CancelButton>
<button
onSubmit={e => handleSubmit(e)}
className="wp-btn update-btn"
data-testid="submit-button"
>
Save Changes
</button>
</FooterActionsWrap>
</Footer>
);
}
and the test file
let handleSubmitMock = jest.fn();
test("it should run", () => {
const {container} = render(<GSFooter
handleSubmit={handleSubmitMock}
errors={{}}
/>);
fireEvent.click(getByTestId(container, 'submit-button'));
expect(handleSubmitMock).toBeCalled();
});
output:
expect(jest.fn()).toBeCalled()
Expected number of calls: >= 1
Received number of calls: 0
36 | const submitButton = getByTestId(container, 'submit-button');
37 | fireEvent.click(submitButton);
> 38 | expect(handleSubmitMock).toBeCalled();
| ^
39 | })
40 | });
41 |
UPDATED
After discussion with #cw23, he figured out that he's using onSubmit which is only triggered with fireEvent.submit instead of fireEvent.click! This is very useful info for developers facing a similar problem.
OLD ANSWER
You should call getByTestId directly. container is usually referred to DOM elements
test("it should run", () => {
const { getByTestId } = render(<GSFooter
handleSubmit={handleSubmitMock}
errors={{}}
/>);
fireEvent.click(getByTestId('submit-button'));
expect(handleSubmitMock).toBeCalled();
});

Using React Context in a custom hook always returns undefined

I'm trying to create a custom hook which will eventually be packaged up on NPM and used internally on projects in the company I work for. The basic idea is that we want the package to expose a provider, which when mounted will make a request to the server that returns an array of permission strings that are then provided to the children components through context. We also want a function can which can be called within the provider which will take a string argument and return a boolean based on whether or not that string is present in the permissions array provided by context.
I was following along with this article but any time I call can from inside the provider, the context always comes back as undefined. Below is an extremely simplified version without functionality that I've been playing with to try to figure out what's going on:
useCan/src/index.js:
import React, { createContext, useContext, useEffect } from 'react';
type CanProviderProps = {children: React.ReactNode}
type Permissions = string[]
// Dummy data for fake API call
const mockPermissions: string[] = ["create", "click", "delete"]
const CanContext = createContext<Permissions | undefined>(undefined)
export const CanProvider = ({children}: CanProviderProps) => {
let permissions: Permissions | undefined
useEffect(() => {
permissions = mockPermissions
// This log displays the expected values
console.log("Mounted. Permissions: ", permissions)
}, [])
return <CanContext.Provider value={permissions}>{children}</CanContext.Provider>
}
export const can = (slug: string): boolean => {
const context = useContext(CanContext)
// This log always shows context as undefined
console.log(context)
// No functionality built to this yet. Just logging to see what's going on.
return true
}
And then the simple React app where I'm testing it out:
useCan/example/src/App.tsx:
import React from 'react'
import { CanProvider, can } from 'use-can'
const App = () => {
return (
<CanProvider>
<div>
<h1>useCan Test</h1>
{/* Again, this log always shows undefined */}
{can("post")}
</div>
</CanProvider>
)
}
export default App
Where am I going wrong here? This is my first time really using React context so I'm not sure where to pinpoint where the problem is. Any help would be appreciated. Thanks.
There are two problems with your implementation:
In your CanProvider you're reassigning the value in permissions with =. This will not trigger an update in the Provider component. I suggest using useState instead of let and =.
const [permissions, setPermissions] = React.useState<Permissions | undefined>();
useEffect(() => {
setPermissions(mockPermissions)
}, []);
This will make the Provider properly update when permissions change.
You are calling a hook from a regular function (the can function calls useContext). This violates one of the main rules of Hooks. You can learn more about it here: https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions
I suggest creating a custom hook function that gives you the can function you need.
Something like this, for example
const useCan = () => {
const context = useContext(CanContext)
return () => {
console.log(context)
return true
}
}
Then you should use your brand new hook in the root level (as per the rules of hooks) of some component that's inside your provider. For example, extracting a component for the content like so:
const Content = (): React.ReactElement => {
const can = useCan();
if(can("post")) {
return <>Yes, you can</>
}
return null;
}
export default function App() {
return (
<CanProvider>
<div>
<h1>useCan Test</h1>
<Content />
</div>
</CanProvider>
)
}
You should use state to manage permissions.
Look at the example below:
export const Provider: FC = ({ children }) => {
const [permissions, setPermissions] = useState<string[]>([]);
useEffect(() => {
// You can fetch remotely
// or do your async stuff here
retrivePermissions()
.then(setPermissions)
.catch(console.error);
}, []);
return (
<CanContext.Provider value={permissions}>{children}</CanContext.Provider>
);
};
export const useCan = () => {
const permissions = useContext(CanContext);
const can = useCallback(
(slug: string) => {
return permissions.some((p) => p === slug);
},
[permissions]
);
return { can };
};
Using useState you force the component to update the values.
You may want to read more here

Resources