Mocking clientHeight and scrollHeight in React + Enzyme for test - reactjs

We have a React component called ScrollContainer than calls a prop function when its content is scrolled to the bottom.
Basically:
componentDidMount() {
const needsToScroll = this.container.clientHeight != this.container.scrollHeight
const { handleUserDidScroll } = this.props
if (needsToScroll) {
this.container.addEventListener('scroll', this.handleScroll)
} else {
handleUserDidScroll()
}
}
componentWillUnmount() {
this.container.removeEventListener('scroll', this.handleScroll)
}
handleScroll() {
const { handleUserDidScroll } = this.props
const node = this.container
if (node.scrollHeight == node.clientHeight + node.scrollTop) {
handleUserDidScroll()
}
}
this.container is set as follows in the render method:
<div ref={ container => this.container = container }>
...
</div>
I want to test this logic using Jest + Enzyme.
I need a way to force the clientHeight, scrollHeight and scrollTop properties to be values of my choosing for the test scenario.
With mount instead of shallow I can get these values but they are always 0. I have yet to find any way to set them to anything non zero. I can set the container on wrapper.instance().container = { scrollHeight: 0 } and etc, but this only modifies the test context not the actual component.
Any suggestions would be appreciated!

Jest spyOn can be used to mock getter and setter from version 22.1.0+. See jest docs
I used below code to mock implementation of document.documentElement.scrollHeight
const scrollHeightSpy = jest
.spyOn(document.documentElement, 'scrollHeight', 'get')
.mockImplementation(() => 100);
It returns 100 as scrollHeight value.

JSDOM doesn't do any actual rendering - it just simulates the DOM structure - so things like element dimensions aren't calculated as you might expect. If you grab dimensions via method calls there are ways to mock these within your test. For example:
beforeEach(() => {
Element.prototype.getBoundingClientRect = jest.fn(() => {
return { width: 100, height: 10, top: 0, left: 0, bottom: 0, right: 0 };
});
});
This obviously won't work in your example. It may be possible to override these properties on the elements and mock changes to them; but I suspect that wouldn't lead to particularly meaningful/useful tests.
See also this thread

In a scenario where you are storing the value as ref, either with createRef or useRef, you might be able to get away with mocking createRef or useRef directly:
import { useRef } from 'react';
jest.mock('react', () => ({
...jest.requireActual('react'),
useRef: jest.fn(),
}));
test('test ref', () => {
useRef.mockReturnValue(/* value of scrollHeight */);
...
});

Related

Should I be using useEffect to animate on prop value change with React Native Reanimated?

I need a view to animate in to place when the value of some props change using Reanimated in React Native
I've only been able to find animating on, for example, a user tap using an onPress function to change the value of useSharedValue values. But I can't seem to find how to animate when props change. So currently I'm using useEffect.
export const SomeComponent = ({
top,
left
}) => {
const animation = useSharedValue({
top,
left
});
useEffect(() => {
animation.value = { top, left };
}, [top, left]);
const animationStyle = useAnimatedStyle(() => {
return {
top: withTiming(animation.value.top, {
duration: 1000
}),
left: withTiming(animation.value.left, {
duration: 1000
})
};
});
}
In the above code the props can change value, should I be triggering the animation with useEffect like I am currently? Or is there a better way to do it using just Reanimated.
Interesting. This works? I haven't seen it done this way.
Yes, useEffect is a good way to trigger animations. Usually I would do it like this:
const duration = 1000;
export const SomeComponent = ({
top: topProp,
left: leftProp
}) => {
const top = useSharedValue(topProp);
const left = useSharedValue(leftProp);
useEffect(() => {
top.value = withTiming(topProp, { duration });
left.value = withTiming(leftProp, { duration });
}, [topProp, leftProp]);
const animationStyle = useAnimatedStyle(() => ({
top: top.value,
left: left.value,
}));
return <Animated.View style={animationStyle}>
//...
}
The values are split up so that they're primitives. I don't know if this affects performance - I've never used an object as a shared value before.
The styles come straight from the shared values.
The useEffect waits for props to change and then runs the animations.
I've never tried running the animations within the styles - it seems like it shouldn't work to me, but I could be wrong!

Test ResizeObserver and contentRect.height in TypeScript React Enzyme test

I created a simple Hook that uses ResizeObserver to run setState when an Mui Box element is resized before/after a certain point. The purpose was to apply different styles based on whether the flex component was wrapped or not.
It works in the browser, but now the problem that I have is that I have no idea how to write tests for it. I tried with Jest, Enzyme, but I cannot find a way to really run a test against it. I would like to mount the component with one width and verify that it has the proper class, then trigger the resize event and verify that the class changed. I will need to mock the ref and the height. I am searched the web for hours for a solution but haven't found anything that works.
Here is the component:
function Component {
const [isWrapped, setIsWrapped] = useState(false);
const myRef = useRef() as React.MutableRefObject<HTMLInputElement>;
React.useEffect() {
const resizeObserver = new ResizeObserver((entries) => {
if (entries[0].contentRect.height > 100) {
setIsWrapped(true);
} else {
setIsWrapped(false);
}
});
resizeObserver.observe(myRef.current);
}
return (
<Box {...{ ref: myRef }} display="flex" id="my-element" className={isWrapped ? classes.wrappedClass : classes.inlineClass}>{"someText"}</Box>
)
}
At the top of my test file I have
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}))
what I want my test to be like:
it("Test wrap/unwrap conditional styling", () => {
// mount with height 80px
let wrapper = mount(
<ReduxProvider store={store}>
<Component />
</ReduxProvider>
);
expect(toJson(wrapper)).toMatchSnapshot();
const myElement = wrapper.find("#my-element");
expect(myElement).toHaveCLass("wrappedClass");
// trigger resize with height 110
expect(myElement).toHaveCLass("inlineClass");
}

A warning is thrown because of an update to ForwardRef when testing a component animated with react-spring with #testing-library/react

I have a component animated with react-spring using the useSpring hook. Something like:
function Toggle({ handleToggle, toggled }) {
const x = useSpring({
x: toggled ? 1 : 0
});
return (
<div className={styles.switch} onToggle={handleToggle} data-testid="switch">
<animated.div
className={styles.circle}
style={{
transform: x
.interpolate({
range: [0, 0.4, 0.8, 1],
output: [0, 5, 10, 16]
})
.interpolate((x) => `translateX(${x}px)`)
}}>
</animated.div>
</div>
);
}
When testing the component a warning is thrown:
Warning: An update to ForwardRef inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/docs/test-utils.html#act
in ForwardRef (created by Toggle)
in Toggle
The code for my test is:
test("Toggle works", () => {
let toggled = false;
const handleToggle = jest.fn(() => {
toggled = true;
});
const { getByTestId, queryByTestId } = render(
<Toggle toggled={toggled} handleToggle={handleToggle}/>
);
});
How should I test components animated with react-spring using #testing-library/react?
I had this warning while testing a component that uses useSpring and <animated.div> in a react-test-renderer renderer snapshot creation:
import renderer from 'react-test-renderer';
//(...)
it('matches snapshot', () => {
const tree = renderer.create(<Component />).toJSON();
expect(tree).toMatchSnapshot();
});
//(...)
I've found inspiration in this link, and could solve it just by adding the Jest's useFakeTimers call:
import renderer from 'react-test-renderer';
//(...)
it('matches snapshot', () => {
jest.useFakeTimers(); //ADDED
const tree = renderer.create(<Component />).toJSON();
expect(tree).toMatchSnapshot();
});
//(...)
See that wrapping stuff with act, like in the referenced link, was not necessary in this case.
While researching I also found this following blog post, that explains the reasons and describes some cases in which this warning is shown, including a case when you do use jest.useFakeTimers(), the warning keeps being displayed and using act is necessary: Fix the "not wrapped in act(...)" warning

React testing library - check the existence of empty div

I'm testing a component where if ItemLength = 1, render returns null.
const { container, debug } = render(<MyComp ItemLength={1} />);
When I call debug() in my test, it shows a <div />. How do I check that the component is returning an empty div in my test?
Update
Use toBeEmptyDOMElement since toBeEmtpy has been deprecated.
You can use jest-dom's toBeEmpty:
const { container } = render(<MyComp ItemLength={1} />)
expect(container.firstChild).toBeEmpty()
The following should work as well without extending jest's expect:
const { container } = render(<MyComp ItemLength={1} />)
expect(container.firstChild).toBeNull();
Update: the new way in 2020
import { screen } from '#testing-library/react';
...
render(<MyComp ItemLength={1} />);
const child = screen.queryByTestId('data-testid-attribute-value');
expect(child).not.toBeInTheDocument();
.toHaveLength(0) should also work without jest-dom extension
const wrapper = render(<MyComp ItemLength={1}/>);
expect(wrapper.container.innerHTML).toHaveLength(0);
toBeEmpty - throw warning, you must use toBeEmptyDOMElement instead
const pageObject = cretePage(<UserResources />, appState);
expect(pageObject.noContent).toBeInTheDocument();
expect(pageObject.results).toBeEmptyDOMElement();
Since you are trying to test for empty div, one way you could try to test it is by matching node (another possible solution is number of nodes rendered)
getByText(container, (content, element) => element.tagName.toLowerCase() === 'div')
You can use js-dom's toBeEmptyDOMElement method. https://github.com/testing-library/jest-dom#tobeemptydomelement
Before you can use toBeEmptyDOMElement you will need to install jest-dom and set up jest. https://github.com/testing-library/jest-dom#usage
const { container } = render(<MyComp ItemLength={1} />)
expect(container.firstChild).toBeEmptyDOMElement()
Note: toBeEmpty method is being deprecated and its suggested to use toBeEmptyDOMElement
To extend off the previous answers; the following works and is probably a bit cleaner with no dependence on the data-testid or screen to check for roles.
import { render } from "#testing-library/react";
// Components
import { YourComponent } from "<Path>/YourComponent";
describe("YourComponent", () => {
it("component should be an empty element", () => {
const { container } = render(<YourComponent />);
expect(container).toBeEmptyDOMElement();
});
it("component should not be an empty element", () => {
const { container } = render(<YourComponent />);
expect(container).not.toBeEmptyDOMElement();
});
});

Testing Echarts react component with jest, EchartElement is null

Doing a snapchot test of a component with several nested children components, one which holds a Echart (bar-chart). When rendering in browser, EchartElement is set to the chart dom element:
<div class="echarts-for-react " _echarts_instance_="ec_1551342959315" size-sensor-id="1" style="height: 360px; -webkit-tap-highlight-color: transparent; user-select: none; position: relative; background: transparent;">
...
</div>
But running the test, its null, which makes the test fail. Do I need to mock this somehow? Doesn't sound right...All the properties it is expecting are passed in correctly also in test mode...I checked...
Is there some configuration/setup I need to make Echarts work with Jest?
Turns out that my first answer didn't work for me at the end... I kept running the test and fixing whatever it was complaining about, but there was always something else...
So with some help from a colleague, the solution was to stop React Echarts to call Echarts by mocking the function triggering the rendering.
let spy: any
beforeAll(() => {
spy = jest.spyOn(echarts, 'getInstanceByDom').mockImplementation(() => {
return {
hideLoading: jest.fn(),
setOption: jest.fn(),
showLoading: jest.fn()
}
})
})
afterAll(() => {
spy.mockRestore()
})
it('Renders show correctly', () => {
const tree = renderer
.create(
<Show />
)
.toJSON()
expect(tree).toMatchSnapshot()
})
In our case we were not interested in actually testing the canvas rendering part, so that worked for us.
Yes, I had to mock the ref object:
function createNodeMock(element: any) {
return {
getSomething: jest.fn(() => 'pizza'),
setSomething: jest.fn(),
property: null,
}
}
it('Renders show correctly', () => {
const options = { createNodeMock }
const tree = renderer
.create(
<Show/>,
options
)
.toJSON()
expect(tree).toMatchSnapshot()
})
Every time I ran into an error running the test, I went to where the method or property was called, console logged, looked what the value was while in the browser and set the data in the mockData. Maybe there is a better way of doing this, but it got it done...

Resources