How to test React components inside react-responsive tags - reactjs

Inside a <MyComponent> component I am using react-responsive <MediaQuery> components to distinguish between rendering mobile and desktop content.
export class MyComponent extends React.Component {
//...
render() {
<MediaQuery query="(max-width: 600)">
<div className="inside-mobile">mobile view</div>
</MediaQuery>
}
}
I want to test the HTML inside <MyComponent>'s render() using enzyme, but can't seem to dive into the child elements of <MediaQuery>:
it('should dive into <MediaQuery>', () => {
const wrapper = mount(<Provider store={mockedStore}><MyComponent/></Provider>)
const actual = wrapper.getDOMNode().querySelectorAll(".inside-mobile")
expect(actual).to.have.length(1)
}
A console.log(wrapper.debug())shows that nothing inside <MediaQuery> is being rendered, though.
I'm guessing in a test (with no actual browser) window.width is not set which leads to the <MediaQuery> component not rendering anything.
What I want to do:
I want to be able to test <MyComponent>'s content using enzyme with react-responsive (or something similar such as react-media) to deal with mobile vs desktop viewports.
Things I've tried:
circumventing this by using enzyme's shallow with dive() instead of mount, to no avail.
using react-media's <Media> instead of react-responsive's <MediaQuery>, which seems to set window.matchMedia() to true by default. However, that's not working either.
console.log(wrapper.debug()) shows:
<MyComponent content={{...}}>
<Media query="(min-width: 600px)" defaultMatches={true} />
</MyComponent>

I found a working solution, using react-media instead of react-responsive, by mocking window.matchMedia so that matches is set to true during the test:
Create specific media components for different viewports:
const Mobile = ({ children, content }) => <Media query="(max-width: 600px)" children={children}>{matches => matches ? content : "" }</Media>;
const Desktop = ...
Use specific media component:
<MyComponent>
<Mobile content={
<div className="mobile">I'm mobile</div>
}/>
<Desktop content={...}/>
</MyComponent>
Test content per viewport:
const createMockMediaMatcher = matches => () => ({
matches,
addListener: () => {},
removeListener: () => {}
});
describe('MyComponent', () => {
beforeEach(() => {
window.matchMedia = createMockMediaMatcher(true);
});
it('should display the correct text on mobile', () => {
const wrapper = shallow(<MyComponent/>);
const mobileView = wrapper.find(Mobile).shallow().dive();
const actual = mobileView.find(".mobile").text();
expect(actual).to.equal("I'm mobile");
});
});

Related

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");
}

Why does a react component, tested with React Testing Library has issue with react-intersection-observer?

I wrote a component built with react 17.0.2 that uses react-intersection-observer 9.1.0
import { useInView } from 'react-intersection-observer'
...
const [ref, inView] = useInView({
threshold: 0.99,
root: scrollRef.current,
delay: 250,
trackVisibility: true,
onChange: (inView: boolean) => {
onChildInView(index, inView)
}
})
to detect sliding behaviours inside or outside the viewport. And the component works fine.
I wrote some unit tests to make the component safer, using #testing-library/react 12.1.4 and #testing-library/jest-dom 5.16.3.
As soon as I test just the existence or visibility of the above component with the following code
describe('method render', () => {
test('renders correctly', () => {
render(
<MyComponent
props={...}
data-testid="component-id"
>
<div />
<div />
</MyComponent>
)
const componentNode = screen.getByTestId('component-id')
expect(componentNode).toBeInTheDocument()
expect(componentNode).toBeVisible()
})
})
the testing library complains with the message error.
ReferenceError: IntersectionObserver is not defined
I tried to fix it with this suggestion of mocking the library (as linked here) written at the top of the test
const intersectionObserverMock = () => ({
observe: () => null
})
declare global {
interface Window {
IntersectionObserver: typeof IntersectionObserver
}
}
window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock);
but it did not work due to
TypeError: observer.unobserve is not a function
Suggestions? Missing something?
To fix this issue I'd recommend using mockAllIsIntersecting from test-utils.js in react-intersection-observer. This function mocks the IntersectionObserver.
e.g.
import { mockAllIsIntersecting } from 'react-intersection-observer/test-utils';
describe('method render', () => {
test('renders correctly', () => {
render(
<MyComponent
props={...}
data-testid="component-id"
>
<div />
<div />
</MyComponent>
)
mockAllIsIntersecting(true)
const componentNode = screen.getByTestId('component-id')
expect(componentNode).toBeInTheDocument()
expect(componentNode).toBeVisible()
})
})

How to add test cases for Link using jest /enzyme

I am trying to write some test cases using jest and enzyme,I am unable to add test cases for Link and Please suggest if any more test cases needs to be added
these is styled component, and when should we spy /mock function
displayErr.tsx
export const DisplayErr = React.memo<Props>(({ text, SearchLink }) => (
<Section>
<h1>{text}</h1>
{!SearchLink && <Link to={getPath('SEARCH')}>some text</Link>}
</Section>
));
displayErr.test.tsx
describe('Error Msg', () => {
it('styled component', () => {
const output = mount(<DisplayErr text="hello world" SearchLink={true} />);
const link = output.find(Link).find({ to: '/Search' });
expect(output.find(Section).text()).toEqual('hello world');
//expect(link).toBe('<div class="link">Login</div>');//error
});
});
Here Link will render based on the SearchLink props. As you are passing it as true, it wont be available in the DOM. Pass SearchLink=false and execute the unit tests.
describe('Error Msg', () => {
it('styled component', () => {
const output = mount(<DisplayErr text="hello world" SearchLink=false/>)
expect(output.find(Link).props().to).toEqual('/search');
});
Let me know if you are facing the same issue.

Tooltip delay on hover with RXJS

I'm trying to add tooltip delay (300msemphasized text) using rxjs (without setTimeout()). My goal is to have this logic inside of TooltipPopover component which will be later be reused and delay will be passed (if needed) as a prop.
I'm not sure how can I add "delay" logic inside of TooltipPopover component using rxjs?
Portal.js
const Portal = ({ children }) => {
const mount = document.getElementById("portal-root");
const el = document.createElement("div");
useEffect(() => {
mount.appendChild(el);
return () => mount.removeChild(el);
}, [el, mount]);
return createPortal(children, el);
};
export default Portal;
TooltipPopover.js
import React from "react";
const TooltipPopover = ({ delay??? }) => {
return (
<div className="ant-popover-title">Title</div>
<div className="ant-popover-inner-content">{children}</div>
);
};
App.js
const App = () => {
return (
<Portal>
<TooltipPopover>
<div>
Content...
</div>
</TooltipPopover>
</Portal>
);
};
Then, I'm rendering TooltipPopover in different places:
ReactDOM.render(<TooltipPopover delay={1000}>
<SomeChildComponent/>
</TooltipPopover>, rootEl)
Here would be my approach:
mouseenter$.pipe(
// by default, the tooltip is not shown
startWith(CLOSE_TOOLTIP),
switchMap(
() => concat(timer(300), NEVER).pipe(
mapTo(SHOW_TOOLTIP),
takeUntil(mouseleave$),
endWith(CLOSE_TOOLTIP),
),
),
distinctUntilChanged(),
)
I'm not very familiar with best practices in React with RxJS, but this would be my reasoning. So, the flow would be this:
on mouseenter$, start the timer. concat(timer(300), NEVER) is used because although after 300ms the tooltip should be shown, we only want to hide it when mouseleave$ emits.
after 300ms, the tooltip is shown and will be closed mouseleave$
if mouseleave$ emits before 300ms pass, the CLOSE_TOOLTIP will emit, but you could avoid(I think) unnecessary re-renders with the help of distinctUntilChanged

React testing library: Test styles (specifically background image)

I'm building a React app with TypeScript. I do my component tests with react-testing-library.
I'm buildilng a parallax component for my landing page.
The component is passed the image via props and sets it via JSS as a background image:
<div
className={parallaxClasses}
style={{
backgroundImage: "url(" + image + ")",
...this.state
}}
>
{children}
</div>
Here is the unit test that I wrote:
import React from "react";
import { cleanup, render } from "react-testing-library";
import Parallax, { OwnProps } from "./Parallax";
afterEach(cleanup);
const createTestProps = (props?: object): OwnProps => ({
children: null,
filter: "primary",
image: require("../../assets/images/bridge.jpg"),
...props
});
describe("Parallax", () => {
const props = createTestProps();
const { getByText } = render(<Parallax {...props} />);
describe("rendering", () => {
test("it renders the image", () => {
expect(getByText(props.image)).toBeDefined();
});
});
});
But it fails saying:
● Parallax › rendering › it renders the image
Unable to find an element with the text: bridge.jpg. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
<body>
<div>
<div
class="Parallax-parallax-3 Parallax-primaryColor-4"
style="background-image: url(bridge.jpg); transform: translate3d(0,0px,0);"
/>
</div>
</body>
16 | describe("rendering", () => {
17 | test("it renders the image", () => {
> 18 | expect(getByText(props.image)).toBeDefined();
| ^
19 | });
20 | });
21 | });
at getElementError (node_modules/dom-testing-library/dist/query-helpers.js:30:10)
at getAllByText (node_modules/dom-testing-library/dist/queries.js:336:45)
at firstResultOrNull (node_modules/dom-testing-library/dist/query-helpers.js:38:30)
at getByText (node_modules/dom-testing-library/dist/queries.js:346:42)
at Object.getByText (src/components/Parallax/Parallax.test.tsx:18:14)
How can I test that the image is being set as a background image correctly with Jest and react-testing-library?
getByText won't find the image or its CSS. What it does is to look for a DOM node with the text you specified.
In your case, I would add a data-testid parameter to your background (<div data-testid="background">) and find the component using getByTestId.
After that you can test like this:
expect(getByTestId('background')).toHaveStyle(`background-image: url(${props.image})`)
Make sure you install #testing-library/jest-dom in order to have toHaveStyle.
If you want to avoid adding data-testid to your component, you can use container from react-testing-library.
const {container} = render(<Parallax {...props})/>
expect(container.firstChild).toHaveStyle(`background-image: url(${props.image})`)
This solution makes sense for your component test, since you are testing the background-image of the root node. However, keep in mind this note from the docs:
If you find yourself using container to query for rendered elements then you should reconsider! The other queries are designed to be more resiliant to changes that will be made to the component you're testing. Avoid using container to query for elements!
in addition to toHaveStyle JsDOM Matcher, you can use also style property which is available to the current dom element
Element DOM API
expect(getByTestId('background').style.backgroundImage).toEqual(`url(${props.image})`)
also, you can use another jestDOM matcher
toHaveAttribute Matcher
expect(getByTestId('background')).toHaveAttribute('style',`background-image: url(${props.image})`)
Simple solution for testing Component css with react-testing-library. this is helpful for me it is working perfect.
test('Should attach background color if user
provide color from props', () => {
render(<ProfilePic name="Any Name" color="red" data-
testid="profile"/>);
//or can fetch the specific element from the component
const profile = screen.getByTestId('profile');
const profilePicElem = document.getElementsByClassName(
profile.className,
);
const style = window.getComputedStyle(profilePicElem[0]);
//Assertion
expect(style.backgroundColor).toBe('red');
});

Resources