I'm using grommet with react to render my application's UI. I have a Layer component shown when user click a button. Everything works fine but I don't know how to test Layer's children.
I'm using enzyme, mocha and chai as test suite.
This is code the code of my component:
<Layer align="right" closer>
<Header size="large" justify="between" align="center">
<Button icon={<CloseIcon />} plain={true} onClick={ props.hide }/>
</Header>
<Section key={index} pad={{ horizontal: 'large', vertical: 'small' }}>
{ props.list }
</Section>
</Layer>
and this is my test:
const wrapper = shallow(<MyComponent />);
const layer = wrapper.find(Layer);
//this works
expect(layer).to.have.length.of(1);
const section = layer.find(Section);
//and this fails
expect(section).to.have.length.of(1);
Looking at the rendered component in the application, I found that grommet render his Layer component in this way:
<Layer><span style={{ display: 'none'}}></span></Layer>
Anyone can help me?
Thank you
If you look at the source code for the layer component, you will see that it returns the span that you posted in the render method. It's not until the component has mounted that the layer's contents will be rendered by calling the addLayer method in the Layer class.
I believe that you should use enzyme's mount function in order to test that the Layer is properly rendering its content since you will want the componentDidMount method to be called.
I finally managed to resolve this issue by mocking grommet Layer component:
jest.mock("grommet/components/Layer", () => (props) => props.children);
Related
as the title mentions I am trying to run test cases for a component in react using react testing library. After running tests when I check code coverage it changes between 66%, 84% and 100% randomly, even if no changes in the code or test file is made.
All the test cases for that component are passing just fine. what could be the issue? I cant seem to figure things out.
For building UI in components, I'm using Material UI. (just putting it out there if it may help.)
Edit
here is my my component. (I have simplified it a bit since i cannot share the whole thing)
MyComponent.js
const MyComponent = ()=>{
return<>
{/* Other components */}
<Typography
style={{ display: 'flex' }}
data-testid='delete-pop-up-opener'
{/* this handle click enables the popover*/}
onClick={(e) => handleClick(e, item?.id)}>
<img
src={ElipsesIcon}
alt='Elipses Icon'
data-testid='elipses-icon' />
</Typography>
<Popover
data-testId='popover-delete'
open={some_flag_to_on_popover}
onClose={handleClose}
onClick={(e) => e.stopPropagation()}
>
<Box
data-testid='delete-pop-up-button'
onClick={() => delteHandler(item)}>
<Typography>
<IconButton>
<DeleteOutlineIcon />
</IconButton>
</Typography>
<Typography>Delete Item</Typography>
</Box>
</Popover>
{/* Other components */}
</>
}
test snippet.
test.js
test('to see if delete button is working and deleting item', async () => {
const component = render(
<MyComponent/>
);
expect(screen.queryAllByTestId('delete-pop-up-opener')).toHaveLength(1);
const opener = screen.queryAllByTestId('delete-pop-up-opener')[0];
await waitFor(async () =>
UserEvent.click(opener), { timeout: 10000 }
);
expect(screen.queryAllByTestId('delete-pop-up-button')).toHaveLength(1);
await waitFor(async () =>
UserEvent.click(screen.queryAllByTestId('delete-pop-up-button')[0]), { timeout: 10000 });
expect(component).toMatchSnapshot();
});
note: I've also noticed that testing single test script individually works just fine. All the test cases pass, and code coverage is proper as well. But running all tests suites makes code coverage inconsistent.
I am trying to use a video in a component, but whenever a parent is rerendered (unavoidable in this situation, due to a lot of complex functionality) the video tag flashes/flickers and restarts. I have tried using useMemo and seperating into a seperate component and implementing React.memo(), shouldComponentUpdate(), and extending PureComponent (although i recognize its not recommended to use these to reliably prevent a rerender in the docs). I am thinking it some problem with the functionality of the html video player or something right now because i tried logging on the components rerender and am seeing the behaviour without any log to the console. In this visual example you can see on the top the component with this behaviour, and on the bottom another component (That has almost the same code) with a gif that uses image instead of video
let url = "url here"
const renderVideo = useMemo(
() => (
<video
src={url}
controls
muted={true}
autoPlay
height="100%"
width="100%"
style={{
borderRadius: "0.25rem",
}}
/>
),
[url, keepScale, fit]
);
return (
<Widget
{...props}
editorContent={<EditorContent />}
>
{renderVideo}
</Widget>
);
that should be the relevent code. This component is pretty simple, complex functionality is implemented in the Widget component you can see
I am trying to create a Stateful class in which you can call methods such as createHeaderButton() where after calling it would update the state and re-render with these new updates in the component.
Im using Material-UI and so most of their styling utilizes Reacts hook API which of course classes cant use. Ive tried to get around this by using;
export default withStyles(useStyles)(HeaderBar)
Which exports the class separately with the Styles(withStyles(useStyles) useStyles as the defined styles) And the class(HeaderBar). Now the only issue is that i need to access the styles in my class. Ive found a JS example online that wont work for me because of the strong typed syntax of TS. Additionally When initializing my Class component in other places i try to get the ref=(ref:any)=>{} And with that call the create button methods when i get a response from my server, Which doesnt work because of this new way of exporting the class component!
Thanks for the help, Heres my component class: https://pastebin.pl/view/944070c7
And where i try to call it: https://pastebin.com/PVxhKFHJ
My personal opinion is that you should convert HeaderBar to a function component. The reason that it needs to be a class right now is so you can use a ref to call a class method to modify the buttons. But this is not a good design to begin with. Refs should be avoided in cases where you can use props instead. In this case, you can pass down the buttons as a prop. I think the cleanest way to pass them down is by using the special children prop.
Let's create a BarButton component to externalize the rendering of each button. This is basically your this.state.barButtons.forEach callback, but we are moving it outside of the HeaderBar component to keep our code flexible since the button doesn't depend on the HeaderBar (the header bar depends on the buttons).
What is a bar button and what does it need? It needs to have a label text and a callback function which we will call on click. I also allowed it to pass through any valid props of the material-ui Button component. Note that we could have used children instead of label and that's just down to personal preference.
You defined your ButtonState as a callback which takes the HTMLButtonElement as a prop, but none of the buttons shown here use this prop at all. But I did leave this be to keep your options open so that you have the possibility of using the button in the callback if you need it. Using e.currentTarget instead of e.target gets the right type for the element.
import Button, {ButtonProps as MaterialButtonProps} from "#material-ui/core/Button";
type ButtonState = (button: HTMLButtonElement) => void;
type BarButtonProps = {
label: string;
callback: ButtonState;
} & Omit<MaterialButtonProps, 'onClick'>
const BarButton = ({ label, callback, ...props }: BarButtonProps) => {
return (
<Button
color="inherit" // place first so it can be overwritten by props
onClick={(e) => callback(e.currentTarget)}
{...props}
>
{label}
</Button>
);
};
Our HeaderBar becomes a lot simpler. We need to render the home page button, and the rest of the buttons will come from props.childen. If we define the type of HeaderBar as FunctionComponent that includes children in the props (through a PropsWithChildren<T> type which you can also use directly).
Since it's now a function component, we can get the CSS classes from a material-ui hook.
const useStyles = makeStyles({
root: {
flexGrow: 1
},
menuButton: {
marginRight: 0
},
title: {
flexGrow: 1
}
});
const HeaderBar: FunctionComponent = ({ children }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<HeaderMenu classes={classes} />
<Typography variant="h6" className={classes.title}>
<BarButton
callback={() => renderModule(<HomePage />)}
style={{ color: "white" }}
label="Sundt Memes"
/>
</Typography>
{children}
</Toolbar>
</AppBar>
</div>
);
};
Nothing up to this point has used state at all, BarButton and HeaderBar are purely for rendering. But we do need to determine whether to display "Log In" or "Log Out" based on the current login state.
I had said in my comment that the buttons would need to be stateful in the Layout component, but in fact we can just use state to store an isLoggedIn boolean flag which we get from the response of AuthVerifier (this could be made into its own hook). We decide which buttons to show based on this isLoggedIn state.
I don't know what this handle prop is all about, so I haven't optimized this at all. If this is tied to renderModule, we could use a state in Layout to store the contents, and pass down a setContents method to be called by the buttons instead of renderModule.
interface LayoutProp {
handle: ReactElement<any, any>;
}
export default function Layout(props: LayoutProp) {
// use a state to respond to an asynchronous response from AuthVerifier
// could start with a third state of null or undefined when we haven't gotten a response yet
const [isLoggedIn, setIsLoggedIn] = useState(false);
// You might want to put this inside a useEffect but I'm not sure when this
// needs to be re-run. On every re-render or just once?
AuthVerifier.verifySession((res) => setIsLoggedIn(res._isAuthenticated));
return (
<div>
<HeaderBar>
{isLoggedIn ? (
<BarButton
label="Log Out"
callback={() => new CookieManager("session").setCookie("")}
/>
) : (
<>
<BarButton
label="Log In"
callback={() => renderModule(<LogInPage />)}
/>
<BarButton
label="Sign Up"
callback={() => renderModule(<SignUpPage />)}
/>
</>
)}
</HeaderBar>
{props.handle}
</div>
);
}
I believe that this rewrite will allow you to use the material-ui styles that you want as well as improving code style, but I haven't actually been able to test it since it relies on so many other pieces of your app. So let me know if you have issues.
On small devices (phones) I want to show <Foo/> component and on large (desktops) <Bar/>, like this
function MyComp (props) {
if (isMobile)
return <Foo/>
else
return <Bar/>
}
}
I could find only 2 possible ways to implement it in Material UI :
Using Hidden component
Using withWidth HOC
Option with HOC seemed to me more correct in this case, but apparently it doesn't work correctly with SSR (but Hidden does).
So is it OK in terms of best practices to use two Hidden elems? Like this:
function MyComp (props) {
return (
<>
<Hidden mdUp implementation="css">
<Foo/>
</Hiddne>
<Hidden smDown implementation="css">
<Bar/>
</Hidden>
</>
)
}
There is no problem in using <Hidden /> like that, it just adds a <div> with the necessary CSS to show or not your component. A better approach though would be to add a CSS class directly to your <Foo /> and <Bar /> components, to avoid extra <div> in your page, like this:
function MyComp (props) {
return (
<>
<Foo className={props.classes.foo} />
<Bar className={props.classes.bar} />
</>
)
}
The implementation="css" is necessary in SSR setups because the JS implementation may "flash" the component on the page, then remove it when JS has loaded. The downside of the CSS implementation is that the element is kept in the DOM.
PS: withWidth will be deprecated.
My <Header /> component obtains class collapsed in mobile view. I want to write test that will test mobile view scenario
it('should render mobile view', () => {
const wrapper = mount(
<div style={{width: '700px'}}>
<Header content={headerData} useDOMNodeWidth={true} />
</div>
);
expect(wrapper.find('.header-component').first().hasClass('collapsed')).to.equal(true);
});
After running test I have an AssertionError, so it seems that there is a problem with rendering. I assume that render method only accepts clean react component.
Any idea how I can test it ?
Header component might be rendering mobile view based on some condition. You will have to inject that condition in Header component while/before rendering it.
For eg: I have components on web portals which shows different logos based on customer type. This customer type will be set in my appConfig before I render the component.