老师,测试代码报错了
来源:7-3 Icon 组件样式添加

再见地平线
2020-10-11
subMenu组件添加了icon过后,测试代码报错了,请问这里应该怎么处理呀
subMenu组件代码
import React, { useContext, useState } from 'react';
import classNames from 'classnames';
import { MenuContext } from './Menu';
import { MenuItemProps } from './MenuItem';
import Transition from '../Transition/Transition';
import Icon from '../Icon/Icon';
export interface SubMenuProps {
index?: string;
title: string;
className?: string;
}
const SubMenu: React.FC<SubMenuProps> = ({index, title, className, children}) => {
const context = useContext(MenuContext);
const openSubMenus = context.defaultOpenSubMenus as Array<string>; // 把 defaultOpenSubMenus 强制转化为字符串数组类型
const isOpened = (index && context.mode === 'vertical') ? openSubMenus.includes(index) : false; // 是否默认展开该 SubMenu
const [menuOpen, setOpen] = useState(isOpened); // 保存 SubMenu 的状态: 展开 or 关闭
const classes = classNames('fun-menu-item fun-submenu-item', className, {
'is-active': context.index === index,
'is-opened': menuOpen,
'is-vertical': context.mode === 'vertical'
});
const handleClick = (e: React.MouseEvent) => {
e.preventDefault();
setOpen(!menuOpen);
}
let timer: any;
const handleMouse = (e: React.MouseEvent, toggle: boolean) => {
clearTimeout(timer);
e.preventDefault();
timer = setTimeout(() => {
setOpen(toggle);
}, 300)
}
const clickEvents = context.mode === 'vertical' ? { // 竖向时,点击才能展开
onClick: handleClick
} : {}
const hoverEvents = context.mode === 'horizontal' ? { // 横向时,鼠标 hover 即可展开
onMouseEnter: (e: React.MouseEvent) => { handleMouse(e, true) },
onMouseLeave: (e: React.MouseEvent) => { handleMouse(e, false) }
} : {}
const renderChildren = () => {
const subMenuClasses = classNames('fun-submenu', {
'fun-menu-opened': menuOpen
})
const childrenComponent = React.Children.map(children, (child, i) => {
const childElement = child as React.FunctionComponentElement<MenuItemProps>;
if(childElement.type.displayName === 'MenuItem') {
return React.cloneElement(childElement, {
index: `${index}-${i}`
});
} else {
console.error('Warning: SubMenu has a child which is not a MenuItem component');
}
})
return (
<Transition
in={menuOpen} // 从无到有的过程中自动添加类名
timeout={300} // active -> down 自定义的时间段
classNames='zoom-in-top'
>
<ul className={subMenuClasses}>
{childrenComponent}
</ul>
</Transition>
)
}
return (
<li key={index} className={classes} {...hoverEvents}>
<div className='fun-submenu-title' {...clickEvents}>
{title}
<Icon icon='angle-down' className="arrow-icon"/>
</div>
{renderChildren()}
</li>
)
}
SubMenu.displayName = 'SubMenu';
export default SubMenu;
测试代码
import React from 'react'
import { render, RenderResult, fireEvent, wait } from '@testing-library/react'
import Menu, { MenuProps } from './Menu'
import MenuItem from './MenuItem'
import SubMenu from './Submenu'
const testProps: MenuProps = {
defaultIndex: '0',
onSelect: jest.fn(),
className: 'test'
}
const testVerProps: MenuProps = {
defaultIndex: '0',
mode: 'vertical',
defaultOpenSubMenus: ['4']
}
const generateMenu = (props: MenuProps) => {
return (
<Menu {...props}>
<MenuItem>
active
</MenuItem>
<MenuItem disabled>
disabled
</MenuItem>
<MenuItem>
xyz
</MenuItem>
<SubMenu title='dropdown'>
<MenuItem>
drop1
</MenuItem>
</SubMenu>
<SubMenu title='opened'>
<MenuItem>
opened1
</MenuItem>
</SubMenu>
</Menu>
)
}
const createStyleFile = () => { // 自定义 css 文件,测试 subMenu
const cssFile: string = `
.fun-submenu {
display: none;
}
.fun-submenu.fun-menu-opened {
display: block;
}
`;
const style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = cssFile;
return style;
}
let wrapper: RenderResult, wrapper2: RenderResult, menuElement: HTMLElement, activeElement: HTMLElement, disabledElement: HTMLElement;
// 单元测试的好处就在于,每次开发了新的功能,在运行测试代码时可以检测之前的某些功能是否被影响,减少对之前业务功能的影响
describe('test Menu and MenuItem component in default(horizontal) mode', () => {
beforeEach(() => { // 在开始的时候获得 wrapper 对象,避免在每个 it 里面重复获取
wrapper = render(generateMenu(testProps));
wrapper.container.append(createStyleFile()); // 插入自定义的 css 文件
menuElement= wrapper.getByTestId('test-menu'); // 通过指定的 data-testid 获得元素
activeElement = wrapper.getByText('active');
// activeElement 也可以通过 wrapper.container.querySelector('.is-active') 获得
// 不推荐这样获得的原因是 jest 官方推荐通过内容来获得元素,而不是通过 ID 或者 类名
disabledElement = wrapper.getByText('disabled');
})
it('should render correct Menu and MenuItem based on default props' , () => {
expect(menuElement).toBeInTheDocument();
expect(menuElement).toHaveClass('fun-menu test');
expect(menuElement.querySelectorAll(':scope > li').length).toEqual(5); // 使用 :scope > li 获得第一级 li
expect(activeElement).toHaveClass('fun-menu-item is-active');
expect(disabledElement).toHaveClass('fun-menu-item is-disabled');
})
it('click items should change active and call the right callback', () => {
const thirdItem = wrapper.getByText('xyz');
fireEvent.click(thirdItem);
expect(thirdItem).toHaveClass('is-active'); // 被点击之后即有 is-active 类名
expect(activeElement).not.toHaveClass('is-active'); // 原来的 is-active 类名消失
expect(testProps.onSelect).toHaveBeenCalledWith('2');
fireEvent.click(disabledElement);
expect(disabledElement).not.toHaveClass('is-active');
expect(testProps.onSelect).not.toHaveBeenCalledWith('1'); // 点击 disabled 的 MenuItem ,onSelect 方法不被调用
})
it('should show dropdown items when hover on subMenu', async () => {
// queryBytext 与 getByText 的区别在于 queryByText 返回的是一个 HTMLElment 类型 或者 null
expect(wrapper.queryByText('drop1')).not.toBeVisible(); // 最开始的时候 subMenu 没有展开,css 属性为 display: none,因此不存在 "drop1"
const dropdownElement = wrapper.getByText('dropdown');
fireEvent.mouseEnter(dropdownElement); // 模拟鼠标移入的 hover 效果,移入后 subMenu 展开
// 因为在 subMenu 中使用了 setTimeout 进行了一个延迟操作(异步),这里使用 async await 搭配 wait 方法来进行测试
await wait(() => {
expect(wrapper.queryByText('drop1')).toBeVisible();
})
fireEvent.click(wrapper.getByText('drop1'));
expect(testProps.onSelect).toHaveBeenCalledWith('3-0');
fireEvent.mouseLeave(dropdownElement); // 模拟鼠标移开
await wait(() => {
expect(wrapper.queryByText('drop1')).not.toBeVisible();
})
})
})
describe('test Menu and MenuItem component in vertical mode', () => {
beforeEach(() => {
wrapper2 = render(generateMenu(testVerProps));
wrapper2.container.append(createStyleFile());
})
it('should render vertical mode when mode is set to vertical', () => {
const menuElement = wrapper.getByTestId('test-menu');
expect(menuElement).toHaveClass('fun-menu-vertical'); // 通过 test-id 获取 menu 元素
})
it('should show dropdown items when click on subMenu for vertical mode', () => {
expect(wrapper2.queryByText('drop1')).not.toBeVisible();
fireEvent.click(wrapper2.getByText('dropdown')); // 竖向 subMenu 被点击才能展开
expect(wrapper2.queryByText('drop1')).toBeVisible();
})
it('should show subMenu dropdown when defaultOpenSubMenus contains SubMenu index', () => {
expect(wrapper2.queryByText('opened1')).toBeVisible(); // 测试竖向 subMenu 默认展开功能
})
})
写回答
2回答
-
同学你好 这个问题就是 在一些单元测试中呢 我们需要 mock 一些第三库的实现,因为测试中并没有加载完全这些依赖或者有的是动画效果,这些都会影响测试的结果。
112020-10-14 -
再见地平线
提问者
2020-10-12
看了老师代码:
jest.mock('../Icon/icon', () => {
return () => {
return <i className="fa" />
}
})
jest.mock('react-transition-group', () => {
return {
CSSTransition: (props: any) => {
return props.children
}
}
})
,解决了,但是还是不是太清楚
00
相似问题