React Jest snapshots wait for re-render - reactjs

I've just started working with Jest and I'm having some trouble understanding how to properly use Snapshots.
I'm currently trying to test this component.
I've successfully managed to create a snapshot, but once I go check it out it looks like this:
// Jest Snapshot v1
exports[`>>>Sidemenu --- Snapshot +++capturing Snapshot of Sidemenu 1`] = `"<div class=\\"sc-dlnjPT eFWNyw\\">Test</div>"`;
I'm suspecting this is because my component's contents only show up on the second re-render, as they need a width value, this behavior is defined in this file.
How would I tell Jest to wait for my component to re-render before creating the snapshot?
Will Enzyme even be able to get the window width value?

For async operations the tricky is waiting for loading component, or something other thing that indicates that your component finished all re-renders and is ready to be snapshoted.
Something similar to this:
test('Page rendering test.', async () => {
const pageRendering = renderer.create(<Page />);
// wait for your data to be loaded before you make your assertion.
await waitForElementToBeRemoved(screen.getByText('Loading...'));
expect(pageRendering.toJSON()).toMatchSnapshot();
});

Related

React: accessing internal operation queue of React

React collects operations(like DOM operations such 'ADD','REPLACE','REMOVE' and more) so they could execute them in batch in one shot at the end of each render.
for example, setState call inside React component is scheduled to the end of the construction of this React tree by adding this operation to the operation queue. then react will go over this queue and will decide what changes need to be made to the DOM.
React will decide whether or not to call another render based on whether or not the operation queue is empty or not.
more info on this awesome tutorial which summarizes the basics of how React works internally.
I need access to this queue to decide whether or not the current render was the last for a very custom React component. (YES maybe I can avoid it but currently, this is my requirement)
the access must be from inside of this component.
my check will be called from the lastest useEffect which is after the render ends and the DOM was updated and is the latest lifecycle event, so if the operation queue is empty there will be no more renders for sure. (here nice article explains and demonstrates the order of hook calls)
couldn't find any public API, but a workaround would also be acceptable. (forking and editing React is not a workaround)
this src file is probably the main logic to this queue. and this is the type of the actual queue. however this is the source code and this queue is not exported in the built versions of react(neither development or production build)
so, is there a way to access the internal operation queue of React?
EDIT - Warning
this is for educational purposes only - do not use it on production!
this approach is not safe based on React core team member - I've already asked. it could be safe if you plan to use a fixed version of React without upgrading later.
####### END OF EDIT #######
SOLVED!
so after many many hours digging into React codebase I finally wrote a hook that tells if any update is currently scheduled.
Note: would work for function components only, and this hook is not well tested.
you can see some internal state of React by the undocumented __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED property. this prop holds ReactCurrentOwner which is basically a reference to the current component that is being constructed.
const currentOwner = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner?.current;
currentOwner is the current component that is being constructed. this prop is available during renders only(because in effects is after render no component is being currently constructed).
but because another render can be triggered from state set from effects we should always call ower check from the latest effect
inside it .current.memoizedProps you will find a linked list of all hooks that were declared to this point on this component.
each hook holds a queue for holding scheduled updates, and inside it, there is a pending prop that tells if any update is currently scheduled for the next render.
we could run over this linked list to find out if an update is scheduled by any hook:
const wouldUpdate = (currentOwner) => {
let newObj = currentOwner?.memoizedState;
// go over the linked list of hooks and find out if there is any pending update
while (newObj && 'next' in newObj) {
newObj = newObj['next'];
if (newObj?.queue?.pending) return true;
}
return false;
};
so to summer up we could build a custom hook to check if the current render is the latest scheduled render:
const wouldUpdate = (currentOwner) => {
let newObj = currentOwner?.memoizedState;
// go over the linked list of hooks and find out if there is any pending update
while (newObj && 'next' in newObj) {
newObj = newObj['next'];
if (newObj?.queue?.pending) return true;
}
return false;
};
export const useDoesUpdateIsScheduled = () => {
// #ts-ignore
// hold the current owner ref so we could call it from effects
const currentOwner = useRef(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner?.current);
return () => wouldUpdate(currentOwner.current);
};
so many hours for so little code... ;<
Usage:
const YourComponent = (props) => {
//..
// code, hooks ,logic, effects would be here
//..
// should be could from the last useEffect
const wouldUpdate = useDoesUpdateIsScheduled();
useEffect(() => {
console.log(wouldUpdate());
});
return <div>... your jsx here ...</div>;
};
screenshot of test component on mount:
you can see that on the latest render our hook tells us there are no pending updates.
you can also call wouldUpdate from function body but take into account that updates can be scheduled from effects (means that calling it while rendering would not catch these updates)
the popular why-did-you-render also uses this undocumented __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED property to achieve it's goal.
THATS IT(actually it wasn't worth the hours, and it will probably break on various cases as this is not public API).

How to perform a server side data fetching with React Hooks

We just start moving into React Hooks from the React life-cycle and one of the things that we noticed, is that there's no straightforward way to run something similar to ComponentWillMount for loading data before sending the rendered page to the user (since useEffect is not running on SSR).
Is there any easy way supported by React to do so?
I had the same problem. I've managed to solve it with a custom hook and by rendering application twice on the server-side. The whole process looks like this:
during the first render, collect all effects to the global context,
after first render wait for all effects to resolve,
render the application for the second time with all data.
I wrote an article with examples describing this approach.
This is a direct link to the example form article on CodeSandbox.
Also, I have published an NPM package that simplifies this process - useSSE on GitHub.
This is a little tricky but one way around is to do the fetch directly in the component as in
function LifeCycle(props){
console.log("perform the fetch here as in componentwillmount")
const [number, setNumber] = useState(0)
useEffect(() => {
console.log("componentDidMount");
return () => {
console.log("componentDidUnmount");
};
}, []);
}

Testing double axios request with jest in react app

My application had a problem with calling the API 2 times in componentDidMount. I fixed it and now I wanted to make a test with Jest for this scenario.
class ResultsPage extends Component {
componentDidMount() {
// It is possible, after parent component state change to be callet again
this.props.getResults();
}
render() {
return "Some JSX";
}
}
How I can test this request. Do I need to test it in the component or to write some general test for Axios?
Unit test for ResultsPage is unable to test that. It's up to parent if some component is re-created or updated.
So if you really want to test that(I'm not sure if this provides any profit) you
Spy on componentDidMount for ResultsPage
mount() parent component
validate that spy for componentDidMount has been called only once.
const cDMspy = jest.spyOn(ResultsPage.prototype, 'componentDidMount');
mount(<SomeParent />);
expect(cDMspy).toHaveBeenCalledTimes(1);
In general case, things become harder if you expect few legal instances of ResultsPage in the same parent. Then validation for .toHaveBeenCalledTimes is not enough: it matters if we have 2 component instances with cDM called once per component or just single instance with cDM been called twice.
const cDMspy = jest.spyOn(ResultsPage.prototype, 'componentDidMount');
const wrapper = mount(<SomeParent />);
expect(wrapper.find(ResultsPage)).toHaveLength(2);
expect(cDMspy).toHaveBeenCalledTimes(2);
In case of cDM we may just check amount of component instances should be equal to calls count. But for other methods(that are not guaranteed to be called at least once per instance) we may end with creating our own tracking tool:
const callContext = [];
const original = ResultsPage.prototype.someMethod;
const spy = jest.spyOn(ResultsPage.prototype, someMethod);
spy.mockImplementation((...args) => {
callContext.push(this.props.someMeaningfullPropToIdentityComponent);
return original.call(this, ...args);
});
mount(<SomeParent />);
expect(callContext).toEqual(["id1", "id3", "id3"]);
See, it becomes much more complicated. Reasons becomes unclear, code becomes harder to maintain. Also any future refactoring like changing calls order, renaming methods supposed to be private etc breaks our test really bad.
What's alternative? You may mock things that should respond exactly N times(say mockFn.mockReturnValueOnce or any equivalent for axios mocks). Then just run your existing test cases and once something is requesting with unexpected frequency - you will know.
What's else? You may avoid focusing on that. In typical CRUD only creation should lead to dramatic results if called extra time. Everything else(deleting, updating, fetching) is more about performance then functionality. So it'd easier to test that alongside other performance-related things manually and on-demand(once there are issues with that).

Test Drop Down With React Testing Library

I am having trouble testing a drop down populated with data from an API call in React Testing Library. Below is a CodeSandbox showing the issue
https://codesandbox.io/s/mutable-sea-wtt9u
If I change App to use a hardcoded array to populate the drop down (commented out in App component), the test passes.
Thanks
When your data comes from an asynchronous fetch call, the DOM doesn't get updated synchronously, and you have to use one of the async utilities to wait for the update. This works in your case (tested in your Codesandbox):
// import `wait` directly from React Testing Library
import { render, wait } from '#testing-library/react';
...
await wait(() => {
fireEvent.change(selectElement, { target: { value: "1" } });
expect(selectElement.value).toBe("1");
});
Here's the React Testing Library docs on async utilities: https://testing-library.com/docs/dom-testing-library/api-async
EDIT: It looks like you might have changed your CodeSandbox code. Now you need to wait for the async call to be made before firing the event, since you're fetching data on mount. I've updated my answer and made sure tests pass on your current CodeSandbox.
You need to mock your fetch events. I wrote an article on how to do that. You can find it here.

React Create Portal event listening

So I am trying to migrate an existing Portal implementation from the old unstable_renderSubtreeIntoContainer to the new portal implementation.
I have an issue though, the relevant code has the following functionality:
unstable_renderSubtreeIntoContainer(
this,
this.props.children,
this.portalElement,
() => {
if (this.props.isOpen) {
this.props.onRender(this.portalElement,
this.getTargetElement());
}
callback(); //runs this.props.open() if the update ran open
},
);
Some of the open/close logic could be simplified by wrapping the component to be rendered inside an object and the appropriate callbacks could be called from there. But it seems createPortal has no callback to allow you specify when a render has or hasn't taken place. Is there anyway to synchronously or asynchronously on a createPortal call has finished rendering?

Resources