How to set window size in jest test?
grantspilsbury opened this issue · comments
How should I setup a test where a different component is rendered depending on screen size?
This is the component:
<Media query={`(max-width: ${theme.screenSizes.sm.max}px)`}>
{matches => (matches ? this.renderMobileView() : this.renderDesktopView())}
</Media>
It only executes renderDesktopView
but I'd like to also test this.renderMobileView()
.
I've added this to my test global.window.innerWidth = 300;
with no success
adding
global.window.dispatchEvent(new Event('resize'));
after the innerWidth set also doesn't work
to note, I'm using enzyme's shallow()
also, maybe a separate issue, I'm not sure, but whenever I try to render a component with in it, I see a console error:
React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
The tests here manually mock the window.matchMedia
API. Would something like that work for you?
window.matchMedia = {
matches: true, // <-- Set according to what you want to test
addListener: () => {},
removeListener: () => {}
}
...run your test
I wasn't able to test if that works or not. The Media element just won't render and it gives the error I posted above. Below is the code I tried:
import * as React from 'react';
import * as Enzyme from 'enzyme';
import * as Adapter from 'enzyme-adapter-react-16';
import Media from 'react-media';
Enzyme.configure({ adapter: new Adapter() });
const { mount } = Enzyme;
describe('Component', () => {
it('should render the dang thing', () => {
const component = mount(
<div>
<Media query="(max-width: 721px)">
{ matches => matches ? (
<div>This matches</div>
) : <div>This doesn't match</div> }
</Media>
</div>
);
expect(component).toMatchSnapshot();
});
});
And the error I receive when trying to run the test:
Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports
@ethanova Your snipped actually looks good to me, so I'm guessing it's a setup issue.
Did you try import * as Media from 'react-media'
?
@edorivai That helped me get in the right direction. Should it always be used as * as Media
or is it just for testing that its needed? It works fine as just import Media
for actual usage. Should the readme be updated to reflect all this somehow?
Working test:
import * as Media from 'react-media';
describe('Component monster test rally', () => {
it('should render the dang thing', () => {
window.matchMedia = (matches: string) => ({
matches: true, // <-- Set according to what you want to test
addListener: () => {},
removeListener: () => {},
media: ''
});
const component = mount(
<div>
<Media targetWindow={window} query="(max-width: 721px)">
{ matches => matches ? (
<div>This matches</div>
) : <div>This doesn't match</div> }
</Media>
</div>
);
expect(component).toMatchSnapshot();
});
it('should render the dang thing with the non-match', () => {
window.matchMedia = (matches: string) => ({
matches: false, // <-- Set according to what you want to test
addListener: () => {},
removeListener: () => {},
media: ''
});
const component = mount(
<div>
<Media targetWindow={window} query="(max-width: 721px)">
{ matches => matches ? (
<div>This matches</div>
) : <div>This doesn't match</div> }
</Media>
</div>
);
expect(component).toMatchSnapshot();
});
});```
I guess it depends on your setup. If you're using "plain es6" you shouldn't need the * as
. Maybe if you share a minimal reproduction repo I can help you out further, but just from your test file I cannot see why you'd need the * as
.
Ah dang, whatever the setup is for this project makes it so that the test requires * as Media
but when changed in actual components it breaks the React App, so I can't keep the import at * as Media
. Which means that I can't keep the test 😢
Is it a typescript project?
In that case take a look at the allowSyntheticDefaultImports
flag which you should be able to set in your tsconfig.json
Looks like that might have broken some other things TypeError: Adapter is not a constructor
3 | import * as Adapter from 'enzyme-adapter-react-16';
4 |
5 | Enzyme.configure({ adapter: new Adapter() });```
Yeah, if you set that flag, you won't need any * as
anymore 😄 :
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
@ethanova It seems to me that your issues are not react-media
specific anymore, so I'm going to close this issue.
@grantspilsbury Please let me know if the proposed solution doesn't work for you!
@edorivai I am experiencing another issue, close to this one, maybe you can help me out.
I have component which has two <Media>
components, one for mobile media query and another for desktop.
When I test it with jest, using your snippet above (matches: true,
) it renders both components, obviously. How can I test, for example, mobile view, so only one component, the one for mobile query, is rendered? I tried solution with setting window.innerWidth
and then triggering resize
event, but it doesn't help.
I found a solution, maybe not the prettiest one, but it works. Method matchMedia
takes media-query string as a parameter, so I am just matching it to the one I want to test:
global.matchMedia = media => ({
addListener: () => {},
removeListener: () => {},
matches: media === '(min-width: 545px)',
});
Hi Khrystyna,
WHat is media in matches: media === '(min-width: 545px)',.
Thank you :)
@Neelimaordre It's on the first line of the snippet; the argument passed into the matchMedia
function.
@Khrystyna basically overwrites the original window.matchMedia
with a hardcoded one, to define that the media query only matches when the query is equal to 'min-width: 545px'
.
@Khrystyna can you send a screenshot of how you solved it using the matchMedia function?
I found a solution, maybe not the prettiest one, but it works. Method
matchMedia
takes media-query string as a parameter, so I am just matching it to the one I want to test:global.matchMedia = media => ({ addListener: () => {}, removeListener: () => {}, matches: media === '(min-width: 545px)', });
thanks! worked perfectly! new stuff learned ;)