react-testing-library + material-ui error: Element type is invalid:
react-testing-library + material-ui error: Element type is invalid:
我在 React 单元测试中渲染组件时遇到错误。
我已经尝试过与酶相同的方法只对安装方法给出相同的错误。
错误信息是:
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.
The above error occurred in the component:
in div (created by Header)
in li (created by ForwardRef(ButtonBase))
in ForwardRef(ButtonBase) (created by WithStyles(ForwardRef(ButtonBase)))
in WithStyles(ForwardRef(ButtonBase)) (created by ForwardRef(ListItem))
in ForwardRef(ListItem) (created by WithStyles(ForwardRef(ListItem)))
in WithStyles(ForwardRef(ListItem)) (created by ForwardRef(MenuItem))
in ForwardRef(MenuItem) (created by WithStyles(ForwardRef(MenuItem)))
in WithStyles(ForwardRef(MenuItem)) (created by Header)
in ul (created by ForwardRef(List))
in ForwardRef(List) (created by WithStyles(ForwardRef(List)))
in WithStyles(ForwardRef(List)) (created by ForwardRef(MenuList))
in ForwardRef(MenuList) (created by ForwardRef(Menu))
in div (created by ForwardRef(Paper))
in ForwardRef(Paper) (created by WithStyles(ForwardRef(Paper)))
in WithStyles(ForwardRef(Paper)) (created by Transition)
in Transition (created by ForwardRef(Grow))
in ForwardRef(Grow) (created by Unstable_TrapFocus)
in Unstable_TrapFocus (created by ForwardRef(Modal))
in div (created by ForwardRef(Modal))
in ForwardRef(Portal) (created by ForwardRef(Modal))
in ForwardRef(Modal) (created by ForwardRef(Popover))
in ForwardRef(Popover) (created by WithStyles(ForwardRef(Popover)))
in WithStyles(ForwardRef(Popover)) (created by ForwardRef(Menu))
in ForwardRef(Menu) (created by WithStyles(ForwardRef(Menu)))
in WithStyles(ForwardRef(Menu)) (created by Header)
in div (created by Header)
in div (created by Header)
in Header (created by Login)
in div (created by Login)
in div (created by Login)
in Login
in Router
in GlobalProvider (created by WrapperComponent)
in WrapperComponent
这是代码
1. login.js
路径:src/components/login/index.js
// Login component
import React, { useState, useEffect } from 'react';
import CustomButton from '../basic-components/button';
import Textfield from '../basic-components/textfield';
import Cross from '../../images/cross-icon.svg';
import { useHistory } from 'react-router-dom';
import ErrorAlert from '../basic-components/snackbar';
import Header from '../basic-components/header';
import Cookies from 'js-cookie';
import { useGlobalState, useGlobalDispatch } from '../context/globalContext';
import './style.scss';
const Login = (props) => {
// const [email,setEmail] = useState('');
const [isCookieOn,setCookieOn] = useState(false);
const [errors,setErrors] = useState({errorSeverity:'',openSnackbar:false,msg:'',isError:false});
const history = useHistory();
const { email } = useGlobalState();
const globalDispatch = useGlobalDispatch();
const getVerificationCode = () => {
if(errors.isError || !email){
setErrors(errors=>({...errors,openSnackbar:true,errorSeverity:'error',msg:"Please enter valid email"}));
return
}
history.push('/emailverification');
}
const setSnackbar = (value) => {
setErrors(errors=>({...errors,openSnackbar:value}));
}
const onEmailChange = debounce((evt) => {
globalDispatch({type:'setEmail',payload:evt});
validateEmail(evt);
},500);
const validateEmail = (value) => {
const expression = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
if(value && !expression.test(value))
setErrors(errors=>({...errors,isError:true,msg:"Please enter valid email"}));
else
setErrors(errors=>({...errors,isError:false}));
}
const setCookie = () => {
if(!isCookieOn){
Cookies.set("CookieConsent",true);
setCookieOn(true);
}
}
useEffect(()=>{
let cookie = Cookies.get("CookieConsent");
if(cookie){
setCookieOn(cookie);
}
props.setPage(true);
return () => {
props.setPage(false);
}
},[])
return (
<div className="login">
<div className="wrapper">
<Header/>
<div className="container">
<div className='images'>
</div>
<p className='subtitle'>Welcome!</p>
<h1 className='head-text'>Please Sign In</h1>
<div className="inputs">
<Textfield
id="email"
error={errors.isError}
onChange={(e)=>onEmailChange(e.target.value)}
label="Enter your email"
errorMsg={errors.msg}
/>
<CustomButton classes="code-btn" onClick={getVerificationCode} text="Get Code"/>
</div>
</div>
</div>
{!isCookieOn && <div className='cookies'>
<div className='header'>
<span className='text'>Cookie Talk</span><img src={Cross} className='cross' onClick={setCookie} alt="cancle cookies"/>
</div>
<p className='content'>We use cookies to enhance your browsing exeprience. By continuing to browse or closing this banner, you consent to our use of cookies.</p>
</div>}
<ErrorAlert
openSnackbar={errors.openSnackbar}
msg={errors.msg}
errorSeverity={errors.errorSeverity}
setSnackbar={setSnackbar}
value={email}
/>
</div>
);
};
function debounce (fn, wait) {
let t
return function () {
clearTimeout(t)
t = setTimeout(() => fn.apply(this, arguments), wait)
}
}
export default Login;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.1/umd/react-dom.production.min.js"></script>
1. login.spec.js
路径:src/components/login/login.spec.js.
//login.spec.js
import React from 'react';
import Login from './index';
import {renderHook} from '@testing-library/react-hooks';
import { render, fireEvent } from '@testing-library/react';
describe("Login",()=>{
it("should render properly",()=>{
const wrapper = render(<GlobalProvider><Login setPage={jest.fn()}/></GlobalProvider>);
})
})
1. header.js
路径:src/components/basic-components/header/index.js
//Header component
import React, { useState, useEffect } from 'react';
import { useGlobalState,useGlobalDispatch } from '../../context/globalContext';
// import { useProfileState } from '../../context/profileContext';
import {ReactComponent as SearchIcon} from '../../../images/search-icon.svg';
import './header.scss';
import NavDrawer from '../drawer/drawer';
import {DashboardIcon} from '../../SvgComponents/dashboard';
import {SupportIcon} from '../../SvgComponents/support';
import {ProfileIcon} from '../../SvgComponents/profile';
import {SettingsIcon} from '../../SvgComponents/settings';
import {ContactsIcon} from '../../SvgComponents/mycontacts';
import {ReactComponent as SignOut} from '../../../images/signOut.svg';
import {DashboardMenuIcon} from '../../SvgComponents/dashboardmenu';
import {Link,useHistory} from 'react-router-dom';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
const Header = () => {
const { currentProfileImg,activePage,activeRoute } = useGlobalState();
const [open,setDrawer] = useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
const globalDispatch = useGlobalDispatch();
const history = useHistory();
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const updateNavigation = (route) => {
globalDispatch({type:'setActiveRoute',payload:route});
history.push(`/${route}`);
}
const toggleDrawer = () => {
setDrawer(state=>!state);
}
return (
<div className='header'>
<div className="header-container" style={activePage==='dashboard'?{borderBottom:'1px solid #E0E0E0'}:{}}>
{/* clickable navigation on arrow */}
//removed some unnecessary code
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
className='nav-desktop-menu'
>
<MenuItem onClick={handleClose}>
<div className={`menu ${activeRoute==='fellowprofile' ? 'active' :''}`} onClick={()=>updateNavigation('fellowprofile')}>
<ProfileIcon className='nav-icon' isActive={activeRoute==='fellowprofile' ? true :false}/>
<div className='nav-title'>Profile</div>
</div>
</MenuItem>
<MenuItem onClick={handleClose}>
<div className={`menu ${activeRoute==='settings' ? 'active' :''}`} onClick={()=>updateNavigation('settings')}>
<SettingsIcon className='nav-icon' isActive={activeRoute==='settings' ? true :false}/>
<div className='nav-title'>Settings</div>
</div>
</MenuItem>
<MenuItem onClick={handleClose}>
<div className='menu'>
<SignOut className='nav-icon'/>
<div className='nav-title'>Sign Out</div>
</div>
</MenuItem>
</Menu>
</div>
<NavDrawer openDrawer={open} toggleDrawer={toggleDrawer} userProfile={currentProfileImg }/>
</div>
);
};
export default Header;
这里的问题是,我试图让 MenuItem 表现得像 link 并将新路由推送到路由器历史记录中。改变前向参考有帮助。
这里的link指的是https://material-ui.com/components/buttons/#third-party-routing-library。是关于MenuItems的实现。
我在 React 单元测试中渲染组件时遇到错误。 我已经尝试过与酶相同的方法只对安装方法给出相同的错误。
错误信息是:
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.
The above error occurred in the component: in div (created by Header) in li (created by ForwardRef(ButtonBase)) in ForwardRef(ButtonBase) (created by WithStyles(ForwardRef(ButtonBase))) in WithStyles(ForwardRef(ButtonBase)) (created by ForwardRef(ListItem)) in ForwardRef(ListItem) (created by WithStyles(ForwardRef(ListItem))) in WithStyles(ForwardRef(ListItem)) (created by ForwardRef(MenuItem)) in ForwardRef(MenuItem) (created by WithStyles(ForwardRef(MenuItem))) in WithStyles(ForwardRef(MenuItem)) (created by Header) in ul (created by ForwardRef(List)) in ForwardRef(List) (created by WithStyles(ForwardRef(List))) in WithStyles(ForwardRef(List)) (created by ForwardRef(MenuList)) in ForwardRef(MenuList) (created by ForwardRef(Menu)) in div (created by ForwardRef(Paper)) in ForwardRef(Paper) (created by WithStyles(ForwardRef(Paper))) in WithStyles(ForwardRef(Paper)) (created by Transition) in Transition (created by ForwardRef(Grow)) in ForwardRef(Grow) (created by Unstable_TrapFocus) in Unstable_TrapFocus (created by ForwardRef(Modal)) in div (created by ForwardRef(Modal)) in ForwardRef(Portal) (created by ForwardRef(Modal)) in ForwardRef(Modal) (created by ForwardRef(Popover)) in ForwardRef(Popover) (created by WithStyles(ForwardRef(Popover))) in WithStyles(ForwardRef(Popover)) (created by ForwardRef(Menu)) in ForwardRef(Menu) (created by WithStyles(ForwardRef(Menu))) in WithStyles(ForwardRef(Menu)) (created by Header) in div (created by Header) in div (created by Header) in Header (created by Login) in div (created by Login) in div (created by Login) in Login in Router in GlobalProvider (created by WrapperComponent) in WrapperComponent
这是代码
1. login.js
路径:src/components/login/index.js
// Login component
import React, { useState, useEffect } from 'react';
import CustomButton from '../basic-components/button';
import Textfield from '../basic-components/textfield';
import Cross from '../../images/cross-icon.svg';
import { useHistory } from 'react-router-dom';
import ErrorAlert from '../basic-components/snackbar';
import Header from '../basic-components/header';
import Cookies from 'js-cookie';
import { useGlobalState, useGlobalDispatch } from '../context/globalContext';
import './style.scss';
const Login = (props) => {
// const [email,setEmail] = useState('');
const [isCookieOn,setCookieOn] = useState(false);
const [errors,setErrors] = useState({errorSeverity:'',openSnackbar:false,msg:'',isError:false});
const history = useHistory();
const { email } = useGlobalState();
const globalDispatch = useGlobalDispatch();
const getVerificationCode = () => {
if(errors.isError || !email){
setErrors(errors=>({...errors,openSnackbar:true,errorSeverity:'error',msg:"Please enter valid email"}));
return
}
history.push('/emailverification');
}
const setSnackbar = (value) => {
setErrors(errors=>({...errors,openSnackbar:value}));
}
const onEmailChange = debounce((evt) => {
globalDispatch({type:'setEmail',payload:evt});
validateEmail(evt);
},500);
const validateEmail = (value) => {
const expression = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/;
if(value && !expression.test(value))
setErrors(errors=>({...errors,isError:true,msg:"Please enter valid email"}));
else
setErrors(errors=>({...errors,isError:false}));
}
const setCookie = () => {
if(!isCookieOn){
Cookies.set("CookieConsent",true);
setCookieOn(true);
}
}
useEffect(()=>{
let cookie = Cookies.get("CookieConsent");
if(cookie){
setCookieOn(cookie);
}
props.setPage(true);
return () => {
props.setPage(false);
}
},[])
return (
<div className="login">
<div className="wrapper">
<Header/>
<div className="container">
<div className='images'>
</div>
<p className='subtitle'>Welcome!</p>
<h1 className='head-text'>Please Sign In</h1>
<div className="inputs">
<Textfield
id="email"
error={errors.isError}
onChange={(e)=>onEmailChange(e.target.value)}
label="Enter your email"
errorMsg={errors.msg}
/>
<CustomButton classes="code-btn" onClick={getVerificationCode} text="Get Code"/>
</div>
</div>
</div>
{!isCookieOn && <div className='cookies'>
<div className='header'>
<span className='text'>Cookie Talk</span><img src={Cross} className='cross' onClick={setCookie} alt="cancle cookies"/>
</div>
<p className='content'>We use cookies to enhance your browsing exeprience. By continuing to browse or closing this banner, you consent to our use of cookies.</p>
</div>}
<ErrorAlert
openSnackbar={errors.openSnackbar}
msg={errors.msg}
errorSeverity={errors.errorSeverity}
setSnackbar={setSnackbar}
value={email}
/>
</div>
);
};
function debounce (fn, wait) {
let t
return function () {
clearTimeout(t)
t = setTimeout(() => fn.apply(this, arguments), wait)
}
}
export default Login;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.1/umd/react-dom.production.min.js"></script>
1. login.spec.js
路径:src/components/login/login.spec.js.
//login.spec.js
import React from 'react';
import Login from './index';
import {renderHook} from '@testing-library/react-hooks';
import { render, fireEvent } from '@testing-library/react';
describe("Login",()=>{
it("should render properly",()=>{
const wrapper = render(<GlobalProvider><Login setPage={jest.fn()}/></GlobalProvider>);
})
})
1. header.js
路径:src/components/basic-components/header/index.js
//Header component
import React, { useState, useEffect } from 'react';
import { useGlobalState,useGlobalDispatch } from '../../context/globalContext';
// import { useProfileState } from '../../context/profileContext';
import {ReactComponent as SearchIcon} from '../../../images/search-icon.svg';
import './header.scss';
import NavDrawer from '../drawer/drawer';
import {DashboardIcon} from '../../SvgComponents/dashboard';
import {SupportIcon} from '../../SvgComponents/support';
import {ProfileIcon} from '../../SvgComponents/profile';
import {SettingsIcon} from '../../SvgComponents/settings';
import {ContactsIcon} from '../../SvgComponents/mycontacts';
import {ReactComponent as SignOut} from '../../../images/signOut.svg';
import {DashboardMenuIcon} from '../../SvgComponents/dashboardmenu';
import {Link,useHistory} from 'react-router-dom';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
const Header = () => {
const { currentProfileImg,activePage,activeRoute } = useGlobalState();
const [open,setDrawer] = useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
const globalDispatch = useGlobalDispatch();
const history = useHistory();
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const updateNavigation = (route) => {
globalDispatch({type:'setActiveRoute',payload:route});
history.push(`/${route}`);
}
const toggleDrawer = () => {
setDrawer(state=>!state);
}
return (
<div className='header'>
<div className="header-container" style={activePage==='dashboard'?{borderBottom:'1px solid #E0E0E0'}:{}}>
{/* clickable navigation on arrow */}
//removed some unnecessary code
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
className='nav-desktop-menu'
>
<MenuItem onClick={handleClose}>
<div className={`menu ${activeRoute==='fellowprofile' ? 'active' :''}`} onClick={()=>updateNavigation('fellowprofile')}>
<ProfileIcon className='nav-icon' isActive={activeRoute==='fellowprofile' ? true :false}/>
<div className='nav-title'>Profile</div>
</div>
</MenuItem>
<MenuItem onClick={handleClose}>
<div className={`menu ${activeRoute==='settings' ? 'active' :''}`} onClick={()=>updateNavigation('settings')}>
<SettingsIcon className='nav-icon' isActive={activeRoute==='settings' ? true :false}/>
<div className='nav-title'>Settings</div>
</div>
</MenuItem>
<MenuItem onClick={handleClose}>
<div className='menu'>
<SignOut className='nav-icon'/>
<div className='nav-title'>Sign Out</div>
</div>
</MenuItem>
</Menu>
</div>
<NavDrawer openDrawer={open} toggleDrawer={toggleDrawer} userProfile={currentProfileImg }/>
</div>
);
};
export default Header;
这里的问题是,我试图让 MenuItem 表现得像 link 并将新路由推送到路由器历史记录中。改变前向参考有帮助。
这里的link指的是https://material-ui.com/components/buttons/#third-party-routing-library。是关于MenuItems的实现。