How to test window objects using jest tests - reactjs

I am trying to test a class which returns values depending on values present in window object.
window.test = 123;
I want to mock the window object's test property to 123 value to be able to test correctly.
I tried Object.defineProperty, using global instead, but nothing seems to work. Seems like a trivial question but not able to find the answer on google or elsewhere. Please advice.

In case window exists (i.e. Jest runs with JSDOM, which is done by default) it should be:
beforeEach(() => {
window.test = 123;
});
afterEach(() => {
delete window.test;
});
In case it doesn't exist, it's:
beforeEach(() => {
global.window = { test: 123 };
});
afterEach(() => {
delete global.window;
});
Depending on whether window is used elsewhere, it may be beneficial to back it up to temporary variable in beforeEach and restore in afterEach.

Related

Component-testing #lexical/react editor: How to assert against HTML rendered by Cypress?

I want to component-test a controlled #lexical/react based input in Cypress. An example scenario:
Given I have a controlled input with initial state "Hello world"
When I click into the input
And I press "! key" + "Home key" + "Arrow right key" + "Arrow right key" + "n key"
Then input content is "Henlo world!"
How can I get the editor's text content, so I can assert against it?
I was hoping I could attach a newly-created editor instance to the rendered content to gain access to native lexical methods:
it('Works as intended', () => {
const Test: FC = () => {
const [state, setState] = useState('Hello world')
return <MyInput value={state} setValue={setState} />
}
mount(<Test />)
cy.get('[contenteditable="true"]').should(editorRoot => {
const editorHTML = editorRoot.get()[0]
const editorHtmlClone = editorHTML.cloneNode(true)
const editor = createEditor()
editor.setRootElement(editorHtmlClone as any)
editor.getEditorState().read(() => {
const textValue = $getRoot().getTextContent()
// Now I can access the editor's value
// but unfortunately it's been overwritten
})
})
})
But unfortunately this fails, because editor.setRootElement clears RootElement's contents.
I would really love to find a way to tap into Lexical's API, so I can test my functionality via the API without having to mirror it's exact implementation in my tests (for example defining exact expected html - it can change, doesn't mean much outside of Lexical and is a worse at describing my desired state).
Are there any options/workarounds to achieve this?
You can write the instance on the element or the window itself on your test environment. That's what we do for the TreeView (debugger)!
useEffect(() => {
const element = treeElementRef.current;
if (element !== null) {
element.__lexicalEditor = editor;
return () => {
element.__lexicalEditor = null;
};
}
}, [editor]);
That said, I would argue that your expectations fit a unit test more than an E2E test. An E2E test should validate the DOM, the HTML (via innerHTML) and selection (via getDOMSelection()).
If you want to learn more about how we do E2E testing you can check Lexical's.
ℹī¸ We're planning to ship a separate testing package for users to be able to leverage all the utilities we have built on top of Playwright over the time rather than having to reimplement them on their own.

Is this correct way to render according to the state data using async await?

I got multiple buttons that render different modals.
The modals may render different results according to the data provided in the state.
I got 3 state brackets I need to consider
const [cartChangeStoreShow, setCartChangeStoreShow] = useState(false);
const [orderLineId, setOrderLineId] = useState('');
const [storeId, setStoreId] = useState('');
cartChangeStoreShow is for controlling the visible state of the modal
What I want to do is I wanna change OrderLineId and storeId before rendering the component.
The data will change according to the orderlineId and storeId.
The component is like this
<CartChangeStorePopUp
visibility={cartChangeStoreShow}
setCartChangeStoreShow={setCartChangeStoreShow}
orderLineId={orderLineId}
storeId={storeId}
/>
I am calling api inside CartChangeStorePopUp component according to prop data.
So I am handing the user press button like this.
<TouchableOpacity
onPress={() => renderCartChangeStore(cartItem)}>
<Text>
Change Store
</Text>
</TouchableOpacity>
const renderCartChangeStore = async cartItem => {
try {
await setOrderLineId(cartItem.orderLineId);
await setStoreId(cartItem.storeId);
} catch (err) {
console.log(err);
} finally {
setCartChangeStoreShow(true);
}
};
the code is working now but from what I read before
Async Await doesn't work properly with setState,So I wanna know if there is potential error with the code written here
To me, it does not make sense both the async/await presence and the try/catch/finally.
Async/await is useful when the function you're calling is dealing with something like I/O, time-consuming, where you cannot do anything than "wait" for the completion. Since "to wait" might be something not desirable in a UI context, the async/await pattern helps you to keep track to the "slow function", but even leave the CPU free to serve other useful tasks.
That being said, the "setXXX" functions of React.useState are not time-consuming: no I/O or similar task involves. Hence, the async/await is not applicable.
Going further, the "setXXX" functions of React.useState throw no error on setting. They're much like setting a variable like so:
var storeId = "";
function setStoreId(value) {
storeId = value;
}
That is, the try/catch/finally is quite useless.
If you want, you might optimize the code by grouping the three variables as a single immutable object. However, that's up to your real code.
const [storeState, setStoreState] = useState({
cartChangeStoreShow: false,
storeId: "",
orderLineId: ""
});
const renderCartChangeStore = cartItem => {
setStoreState({
cartChangeStoreShow: true,
storeId: cartItem.storeId,
orderLineId: cartItem.orderLineId,
});
};
Here is a more compact way to achieve the same behavior:
const renderCartChangeStore = cartItem => {
setStoreState({
cartChangeStoreShow: true,
...cartItem,
});
};
Bear in mind that is very important that you treat the storeState as immutable. That is, never ever change a field of the object, rather create a brand new object with the new field value.
At that point, the component should be called like so:
const handleCartChangeStoreShow = value => {
setStoreState({
...storeState,
cartChangeStoreShow: value,
});
}
<CartChangeStorePopUp
visibility={storeState.cartChangeStoreShow}
setCartChangeStoreShow={handleCartChangeStoreShow}
orderLineId={storeState.orderLineId}
storeId={storeState.storeId}
/>
Notice the handler to correctly alter the storeState object. Worthwhile mention how the new value is set. First, all the current storeState is copied to a fresh new object, then the new show value is also copied on the same object. However, since that happens after, it'll have an override-effect.

How to detect if the React app is actually being viewed

So I have created an app that shows data in realtime obtaining it from devices.
However, I want to make my server not obtain data when nobody is viewing the app.
So essentially I need some way to determine whether the app is currently being viewed, regardless of if it's desktop or mobile, this includes tab is on focus where the app is opened and that is what the user is currently viewing, and there is nothing on top of the browser, so browser opened on the correct tab, but user has explorer on top of it doing something entirely different, this for my case should be false, and for mobile, the same thing including if device is locked (screen off).
The reason for trying to do that, is to reduce the load on the devices, so that data is being requested, only when there is someone to view it.
From what I have researched I found out about the focus and blur events, but I was unable to make it work, and I don't even know if that is the correct approach, but what I have tried is:
Adding event listeners to the window in the App component:
function App() {
useEffect(() => {
window.addEventListener("focus", () => { console.log("viewed")});
window.addEventListener("blur", () => { console.log("hidden")});
})
}
Adding them as props to the App component within index.js:
<App onFocus={() => {console.log("viewed")}} onBlur={() => {console.log("hidden")}}/>
Neither had any kind of effect, I didn't get either of the console outputs.
Is that even the correct approach?
I would add a socket connection to the app. Then the server would be able to know if there are at least X persons connected and act accordingly.
I would suggest you to try socket for this kind of connection tested, but since you also wanna reduce the load for the user, testing if the user is focused in the browser is the way to go.
To achieve it, I won't add this code inside the React component because of the nature of React as all of its components are rendered inside the <div id="root"></div>, other parts of the html page will still be unaffected by this mechanism. So what you probably want to do is to add the code in the index.html and use window.userFocused to pass the value into React from the index
Edit: added focus/blur script
<script>
window.addEventListener("focus", function(event)
{
console.log("window is focused");
window.userFocused = true;
}, false);
window.addEventListener("blur", function(event)
{
console.log("window is blurred");
window.userFocused = false;
}, false);
</script>
So I ended up solving it with pretty much the same code as initiallywith a few slight modifications, I still used an useEffect hook:
const onFocusFunction = () => {
// do whatever when focus is gained
};
const onBlurFunction = () => {
// do whatever when focus is lost
};
useEffect(() => {
onFocusFunction();
window.addEventListener("focus", onFocusFunction);
window.addEventListener("blur", onBlurFunction);
return () => {
onBlurFunction();
window.removeEventListener("focus", onFocusFunction);
window.removeEventListener("blur", onBlurFunction);
};
}, []);
The best way is to use document.
document.onvisibilitychange = () => {
console.log(document.hidden)
}

Modal Enzyme mount unit test: MutationObserver is not defined [duplicate]

I wrote a script with the main purpose of adding new elements to some table's cells.
The test is done with something like that:
document.body.innerHTML = `
<body>
<div id="${containerID}">
<table>
<tr id="meta-1"><td> </td></tr>
<tr id="meta-2"><td> </td></tr>
<tr id="meta-3"><td> </td></tr>
<tr id="no-meta-1"><td> </td></tr>
</table>
</div>
</body>
`;
const element = document.querySelector(`#${containerID}`);
const subject = new WPMLCFInfoHelper(containerID);
subject.addInfo();
expect(mockWPMLCFInfoInit).toHaveBeenCalledTimes(3);
mockWPMLCFInfoInit, when called, is what tells me that the element has been added to the cell.
Part of the code is using MutationObserver to call again mockWPMLCFInfoInit when a new row is added to a table:
new MutationObserver((mutations) => {
mutations.map((mutation) => {
mutation.addedNodes && Array.from(mutation.addedNodes).filter((node) => {
console.log('New row added');
return node.tagName.toLowerCase() === 'tr';
}).map((element) => WPMLCFInfoHelper.addInfo(element))
});
}).observe(metasTable, {
subtree: true,
childList: true
});
WPMLCFInfoHelper.addInfo is the real version of mockWPMLCFInfoInit (which is a mocked method, of course).
From the above test, if add something like that...
const table = element.querySelector(`table`);
var row = table.insertRow(0);
console.log('New row added'); never gets called.
To be sure, I've also tried adding the required cells in the new row.
Of course, a manual test is telling me that the code works.
Searching around, my understanding is that MutationObserver is not supported and there is no plan to support it.
Fair enough, but in this case, how can I test this part of my code? Except manually, that is :)
I know I'm late to the party here, but in my jest setup file, I simply added the following mock MutationObserver class.
global.MutationObserver = class {
constructor(callback) {}
disconnect() {}
observe(element, initObject) {}
};
This obviously won't allow you to test that the observer does what you want, but will allow the rest of your code's tests to run which is the path to a working solution.
I think a fair portion of the solution is just a mindset shift. Unit tests shouldn't determine whether MutationObserver is working properly. Assume that it is, and mock the pieces of it that your code leverages.
Simply extract your callback function so it can be tested independently; then, mock MutationObserver (as in samuraiseoul's answer) to prevent errors. Pass a mocked MutationRecord list to your callback and test that the outcome is expected.
That said, using Jest mock functions to mock MutationObserver and its observe() and disconnect() methods would at least allow you to check the number of MutationObserver instances that have been created and whether the methods have been called at expected times.
const mutationObserverMock = jest.fn(function MutationObserver(callback) {
this.observe = jest.fn();
this.disconnect = jest.fn();
// Optionally add a trigger() method to manually trigger a change
this.trigger = (mockedMutationsList) => {
callback(mockedMutationsList, this);
};
});
global.MutationObserver = mutationObserverMock;
it('your test case', () => {
// after new MutationObserver() is called in your code
expect(mutationObserverMock.mock.instances).toBe(1);
const [observerInstance] = mutationObserverMock.mock.instances;
expect(observerInstance.observe).toHaveBeenCalledTimes(1);
});
The problem is actually appears because of JSDom doesn't support MutationObserver, so you have to provide an appropriate polyfill.
Little tricky thought may not the best solution (let's use library intend for compatibility with IE9-10).
you can take opensource project like this one https://github.com/webmodules/mutation-observer which represents similar logic
import to your test file and make global
Step 1 (install this library to devDependencies)
npm install --save-dev mutation-observer
Step 2 (Import and make global)
import MutationObserver from 'mutation-observer'
global.MutationObserver = MutationObserver
test('your test case', () => {
...
})
You can use mutationobserver-shim.
Add this in setup.js
import "mutationobserver-shim"
and install
npm i -D mutationobserver-shim
Since it's not mentioned here: jsdom has supported MutationObserver for a while now.
Here's the PR implementing it https://github.com/jsdom/jsdom/pull/2398
This is a typescript rewrite of Matt's answer above.
// Test setup
const mutationObserverMock = jest
.fn<MutationObserver, [MutationCallback]>()
.mockImplementation(() => {
return {
observe: jest.fn(),
disconnect: jest.fn(),
takeRecords: jest.fn(),
};
});
global.MutationObserver = mutationObserverMock;
// Usage
new MutationObserver(() => {
console.log("lol");
}).observe(document, {});
// Test
const observerCb = mutationObserverMock.mock.calls[0][0];
observerCb([], mutationObserverMock.mock.instances[0]);
Addition for TypeScript users:
declare the module with adding a file called: mutation-observer.d.ts
/// <reference path="../../node_modules/mutation-observer" />
declare module "mutation-observer";
Then in your jest file.
import MutationObserver from 'mutation-observer'
(global as any).MutationObserver = MutationObserver
Recently I had a similar problem, where I wanted to assert on something that should be set by MutationObserver and I think I found fairly simple solution.
I made my test method async and added await new Promise(process.nextTick); just before my assertion. It puts the new promise at the end on microtask queue and holds the test execution until it is resolved. This allows for the MutationObserver callback, which was put on the microtask queue before our promise, to be executed and make changes that we expect.
So in general the test should look somewhat like this:
it('my test', async () => {
somethingThatTriggersMutationObserver();
await new Promise(process.nextTick);
expect(mock).toHaveBeenCalledTimes(3);
});

How do you test for the non-existence of an element using jest and react-testing-library?

I have a component library that I'm writing unit tests for using Jest and react-testing-library. Based on certain props or events I want to verify that certain elements aren't being rendered.
getByText, getByTestId, etc throw and error in react-testing-library if the element isn't found causing the test to fail before the expect function fires.
How do you test for something not existing in jest using react-testing-library?
From DOM Testing-library Docs - Appearance and Disappearance
Asserting elements are not present
The standard getBy methods throw an error when they can't find an element, so
if you want to make an assertion that an element is not present in the DOM,
you can use queryBy APIs instead:
const submitButton = screen.queryByText('submit')
expect(submitButton).toBeNull() // it doesn't exist
The queryAll APIs version return an array of matching nodes. The length of the
array can be useful for assertions after elements are added or removed from the
DOM.
const submitButtons = screen.queryAllByText('submit')
expect(submitButtons).toHaveLength(2) // expect 2 elements
not.toBeInTheDocument
The jest-dom utility library provides the
.toBeInTheDocument() matcher, which can be used to assert that an element is
in the body of the document, or not. This can be more meaningful than asserting
a query result is null.
import '#testing-library/jest-dom/extend-expect'
// use `queryBy` to avoid throwing an error with `getBy`
const submitButton = screen.queryByText('submit')
expect(submitButton).not.toBeInTheDocument()
Use queryBy / queryAllBy.
As you say, getBy* and getAllBy* throw an error if nothing is found.
However, the equivalent methods queryBy* and queryAllBy* instead return null or []:
queryBy
queryBy* queries return the first matching node for a query, and return null if no elements match. This is useful for asserting an element that is not present. This throws if more than one match is found (use queryAllBy instead).
queryAllBy
queryAllBy* queries return an array of all matching nodes for a query, and return an empty array ([]) if no elements match.
https://testing-library.com/docs/dom-testing-library/api-queries#queryby
So for the specific two you mentioned, you'd instead use queryByText and queryByTestId, but these work for all queries, not just those two.
getBy* throws an error when not finding an elements, so you can check for that
expect(() => getByText('your text')).toThrow('Unable to find an element');
const submitButton = screen.queryByText('submit')
expect(submitButton).toBeNull() // it doesn't exist
expect(submitButton).not.toBeNull() // it exist
You have to use queryByTestId instead of getByTestId.
Here a code example where i want to test if the component with "car" id isn't existing.
describe('And there is no car', () => {
it('Should not display car mark', () => {
const props = {
...defaultProps,
base: null,
}
const { queryByTestId } = render(
<IntlProvider locale="fr" messages={fr}>
<CarContainer{...props} />
</IntlProvider>,
);
expect(queryByTestId(/car/)).toBeNull();
});
});
Worked out for me (if you want to use getByTestId):
expect(() => getByTestId('time-label')).toThrow()
Hope this will be helpfull
this table shows why/when function errors
which functions are asynchronous
what is return statement for function
Another solution: you could also use a try/catch block
expect.assertions(1)
try {
// if the element is found, the following expect will fail the test
expect(getByTestId('your-test-id')).not.toBeVisible();
} catch (error) {
// otherwise, the expect will throw, and the following expect will pass the test
expect(true).toBeTruthy();
}
You can use react-native-testing-library "getAllByType" and then check to see if the component is null. Has the advantage of not having to set TestID, also should work with third party components
it('should contain Customer component', () => {
const component = render(<Details/>);
const customerComponent = component.getAllByType(Customer);
expect(customerComponent).not.toBeNull();
});
// check if modal can be open
const openModalBtn = await screen.findByTestId("open-modal-btn");
fireEvent.click(openModalBtn);
expect(
await screen.findByTestId(`title-modal`)
).toBeInTheDocument();
// check if modal can be close
const closeModalBtn = await screen.findByTestId(
"close-modal-btn"
);
fireEvent.click(closeModalBtn);
const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
await sleep(500);
expect(screen.queryByTestId("title-modal")).toBeNull();
I recently wrote a method to check visibility of element for a jest cucumber project.
Hope it is useful.
public async checknotVisibility(page:Page,location:string) :Promise<void>
{
const element = await page.waitForSelector(location);
expect(element).not.toBe(location);
}
don't want to bury the lead, so here's the right solution ✅
waitFor(() => queryByTestId(/car/) === null)
There are issues with all of the answers here so far...
don't use getByTestId, that'll have to wait 😴 for the timeout because it's expecting the element to eventually be there. Then it'll throw and you'll have to catch that, which is a less readable test. Finally you could have a RACE CONDITION đŸšĢ where getByTestId is evaluated before the element disappears and our test will flake.
Just using queryByTestId without waitFor is a problem if the page is changing at all and the element has not disappeared yet. RACE CONDITION đŸšĢ
deleteCarButton.click();
expect(queryByTestId(/car/)).toBeNull(); //
if expect() gets evaluated before the click handler and render completes we'll have a bad time.
The default behavior of queryByRole is to find exactly one element. If not, it throws an error. So if you catch an error, this means the current query finds 0 element
expect(
()=>screen.getByRole('button')
).toThrow()
getByRole returns 'null', if it does not find anthing
expect(screen.queryByRole('button')).toEqual((null))
findByRole runs asynchronously, so it returns a Promise. If it does not find an element, it rejects the promise. If you are using this, you need to run async callback
test("testing", async () => {
let nonExist = false;
try {
await screen.findByRole("button");
} catch (error) {
nonExist = true;
}
expect(nonExist).toEqual(true);
});

Resources