请稍等 ...
×

采纳答案成功!

向帮助你的同学说点啥吧!感谢那些助人为乐的人

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 默认展开功能
    })
})

添加回答

已采纳回答

同学你好 这个问题就是 在一些单元测试中呢 我们需要 mock 一些第三库的实现,因为测试中并没有加载完全这些依赖或者有的是动画效果,这些都会影响测试的结果。

2020-10-12 18:12:06

1回答

Typescript + React 高仿 Antd 从零到一打造自己的组件库

难度高级
时长17小时
人数1126
好评度99.9%

设计,开发,测试,发布再到 CI/CD,从0到1造轮子

讲师

张轩 Web前端工程师

曾在Apple、百度担任高级前端开发工程师,是《React全栈:Redux Flux webpack Babel整合开发》该书作者,拥有丰富的Web开发经验,喜欢追寻新技术,同时致力于前端工程化,并且有大型SPA项目的架构及开发经验。

意见反馈 帮助中心 APP下载
官方微信