Skip to content

Commit 9d11bc5

Browse files
authored
test(TabBar): add unit test (#819)
* test(TabBar): add unit test * fix(TabBar): 修复CSSTransition的findDOMNode报错问题 * feat(TabBar): 调整hooks目录结构 * test: update coverage-badge
1 parent bf7aa53 commit 9d11bc5

File tree

6 files changed

+343
-20
lines changed

6 files changed

+343
-20
lines changed

site/test-coverage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ module.exports = {
5757
swipeCell: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
5858
swiper: { statements: '57.55%', branches: '37.1%', functions: '67.6%', lines: '59.74%' },
5959
switch: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
60-
tabBar: { statements: '10%', branches: '0%', functions: '0%', lines: '10.81%' },
60+
tabBar: { statements: '100%', branches: '93.18%', functions: '100%', lines: '100%' },
6161
table: { statements: '100%', branches: '90%', functions: '100%', lines: '100%' },
6262
tabs: { statements: '43.22%', branches: '18.75%', functions: '56%', lines: '45.07%' },
6363
tag: { statements: '100%', branches: '96.87%', functions: '100%', lines: '100%' },

src/tab-bar/TabBar.tsx

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { forwardRef, memo, useMemo, useRef } from 'react';
2-
import cls from 'classnames';
2+
import classNames from 'classnames';
33
import useDefault from '../_util/useDefault';
44
import type { StyledProps } from '../common';
55
import type { TdTabBarProps } from './type';
@@ -13,9 +13,32 @@ export interface TabBarProps extends TdTabBarProps, StyledProps {}
1313

1414
const TabBar = forwardRef<HTMLDivElement, TabBarProps>((originProps, ref) => {
1515
const props = useDefaultProps(originProps, tabBarDefaultProps);
16-
const { bordered, fixed, onChange, value, defaultValue, safeAreaInsetBottom, shape, split, theme, children } = props;
16+
const {
17+
className,
18+
style,
19+
bordered,
20+
fixed,
21+
onChange,
22+
value,
23+
defaultValue,
24+
safeAreaInsetBottom,
25+
shape,
26+
split,
27+
theme,
28+
children,
29+
} = props;
1730

1831
const tabBarClass = usePrefixClass('tab-bar');
32+
const tabBarClasses = classNames(
33+
tabBarClass,
34+
className,
35+
{
36+
[`${tabBarClass}--bordered`]: bordered,
37+
[`${tabBarClass}--fixed`]: fixed,
38+
[`${tabBarClass}--safe`]: safeAreaInsetBottom,
39+
},
40+
`${tabBarClass}--${props.shape}`,
41+
);
1942
const [activeValue, onToggleActiveValue] = useDefault(value, defaultValue, onChange);
2043

2144
const defaultIndex = useRef(-1);
@@ -38,19 +61,7 @@ const TabBar = forwardRef<HTMLDivElement, TabBarProps>((originProps, ref) => {
3861
);
3962

4063
return (
41-
<div
42-
className={cls(
43-
tabBarClass,
44-
{
45-
[`${tabBarClass}--bordered`]: bordered,
46-
[`${tabBarClass}--fixed`]: fixed,
47-
[`${tabBarClass}--safe`]: safeAreaInsetBottom,
48-
},
49-
`${tabBarClass}--${props.shape}`,
50-
)}
51-
ref={ref}
52-
role="tablist"
53-
>
64+
<div className={tabBarClasses} style={style} ref={ref} role="tablist">
5465
<TabBarProvider value={memoProviderValues}>{parseTNode(children)}</TabBarProvider>
5566
</div>
5667
);

src/tab-bar/TabBarItem.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { StyledProps } from '../common';
66
import type { TdTabBarItemProps } from './type';
77
import { TabBarContext } from './TabBarContext';
88
import Badge from '../badge';
9-
import useTabBarCssTransition from './useTabBarCssTransition';
9+
import useTabBarCssTransition from './hooks/useTabBarCssTransition';
1010
import parseTNode from '../_util/parseTNode';
1111
import useDefaultProps from '../hooks/useDefaultProps';
1212
import { usePrefixClass } from '../hooks/useClass';
@@ -18,7 +18,7 @@ const defaultBadgeMaxCount = 99;
1818

1919
const TabBarItem = forwardRef<HTMLDivElement, TabBarItemProps>((originProps, ref) => {
2020
const props = useDefaultProps(originProps, {});
21-
const { subTabBar, icon, badgeProps, value, children } = props;
21+
const { subTabBar, icon, badgeProps, value, children, className, style } = props;
2222

2323
const hasSubTabBar = useMemo(() => !!subTabBar, [subTabBar]);
2424
const { defaultIndex, activeValue, updateChild, shape, split, theme, itemCount } = useContext(TabBarContext);
@@ -27,6 +27,8 @@ const TabBarItem = forwardRef<HTMLDivElement, TabBarItemProps>((originProps, ref
2727

2828
const textNode = useRef<HTMLDivElement>(null);
2929

30+
const menuRef = useRef<HTMLDivElement>(null);
31+
3032
const [iconOnly, setIconOnly] = useState(false);
3133

3234
// 组件每次 render 生成一个临时的当前组件唯一值
@@ -101,6 +103,7 @@ const TabBarItem = forwardRef<HTMLDivElement, TabBarItemProps>((originProps, ref
101103

102104
const tabItemCls = cls(
103105
tabBarItemClass,
106+
className,
104107
{
105108
[`${tabBarItemClass}--split`]: split,
106109
[`${tabBarItemClass}--text-only`]: !icon,
@@ -130,7 +133,7 @@ const TabBarItem = forwardRef<HTMLDivElement, TabBarItemProps>((originProps, ref
130133
icon && React.cloneElement(icon, { style: { fontSize: iconSize } });
131134

132135
return (
133-
<div className={tabItemCls} ref={ref}>
136+
<div className={tabItemCls} ref={ref} style={style}>
134137
<div
135138
role="tab"
136139
aria-label="TabBar"
@@ -161,7 +164,14 @@ const TabBarItem = forwardRef<HTMLDivElement, TabBarItemProps>((originProps, ref
161164
)}
162165
</div>
163166

164-
<CSSTransition timeout={200} in={showSubTabBar} classNames={transitionClsNames} mountOnEnter unmountOnExit>
167+
<CSSTransition
168+
nodeRef={menuRef}
169+
timeout={200}
170+
in={showSubTabBar}
171+
classNames={transitionClsNames}
172+
mountOnEnter
173+
unmountOnExit
174+
>
165175
<ul role="menu" className={`${tabBarItemClass}__spread`}>
166176
{subTabBar?.map((child, index) => (
167177
<div
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import React from 'react';
2+
import { describe, it, expect, render, fireEvent, vi } from '@test/utils';
3+
import { HomeIcon, AppIcon, ChatIcon, UserIcon } from 'tdesign-icons-react';
4+
import { TabBar, TabBarItem } from '..';
5+
6+
const prefix = 't';
7+
const prefixClass = `${prefix}-tab-bar-item`;
8+
const list = [
9+
{ value: 1, label: '首页', icon: <HomeIcon /> },
10+
{ value: 2, label: '应用', icon: <AppIcon /> },
11+
{ value: 3, label: '聊天', icon: <ChatIcon /> },
12+
{
13+
value: 4,
14+
label: '我的',
15+
icon: <UserIcon />,
16+
subTabBar: [
17+
{
18+
value: '4_1',
19+
label: '基本信息',
20+
},
21+
{
22+
value: '4_2',
23+
label: '个人主页',
24+
},
25+
{
26+
value: '4_3',
27+
label: '设置',
28+
},
29+
],
30+
},
31+
];
32+
33+
describe('TabBarItem', () => {
34+
describe('props', () => {
35+
it(': className', () => {
36+
const { container } = render(
37+
<TabBar>
38+
{list.map((item) => (
39+
<TabBarItem key={item.value} {...item} className="custom-class">
40+
{item.label}
41+
</TabBarItem>
42+
))}
43+
</TabBar>,
44+
);
45+
46+
expect(container.querySelectorAll('.custom-class')).toHaveLength(list.length);
47+
});
48+
49+
it(': style', () => {
50+
const { container } = render(
51+
<TabBar>
52+
{list.map((item) => (
53+
<TabBarItem key={item.value} {...item} style={{ color: '#0052d9' }}>
54+
{item.label}
55+
</TabBarItem>
56+
))}
57+
</TabBar>,
58+
);
59+
const tabBarItems = container.querySelectorAll(`.${prefixClass}`);
60+
expect(tabBarItems).toHaveLength(list.length);
61+
expect(tabBarItems[0]).toHaveStyle({ color: '#0052d9' });
62+
});
63+
64+
it(': badgeProps', () => {
65+
const { container, rerender } = render(
66+
<TabBar>
67+
{list.map((item) => (
68+
<TabBarItem key={item.value} {...item} badgeProps={{ dot: true }}>
69+
{item.label}
70+
</TabBarItem>
71+
))}
72+
</TabBar>,
73+
);
74+
expect(container.querySelectorAll(`.${prefix}-badge--dot`)).toHaveLength(list.length);
75+
76+
rerender(
77+
<TabBar>
78+
{list.map((item) => (
79+
<TabBarItem key={item.value} {...item} badgeProps={{ count: 16 }}>
80+
{item.label}
81+
</TabBarItem>
82+
))}
83+
</TabBar>,
84+
);
85+
expect(container.querySelectorAll(`.${prefix}-badge__content`)).toHaveLength(list.length);
86+
});
87+
88+
it(': badgeProps', () => {
89+
const { container, rerender } = render(
90+
<TabBar>
91+
{list.map((item) => (
92+
<TabBarItem key={item.value} {...item} badgeProps={{ dot: true }}>
93+
{item.label}
94+
</TabBarItem>
95+
))}
96+
</TabBar>,
97+
);
98+
expect(container.querySelectorAll(`.${prefix}-badge--dot`)).toHaveLength(list.length);
99+
100+
rerender(
101+
<TabBar>
102+
{list.map((item) => (
103+
<TabBarItem key={item.value} {...item} badgeProps={{ count: 16 }}>
104+
{item.label}
105+
</TabBarItem>
106+
))}
107+
</TabBar>,
108+
);
109+
expect(container.querySelectorAll(`.${prefix}-badge__content`)).toHaveLength(list.length);
110+
});
111+
112+
it(': icon', () => {
113+
const { container } = render(
114+
<TabBar>
115+
{list.map((item) => (
116+
<TabBarItem key={item.value} {...item}>
117+
{item.label}
118+
</TabBarItem>
119+
))}
120+
</TabBar>,
121+
);
122+
123+
expect(container.querySelector(`.${prefixClass}__icon`)).toBeTruthy();
124+
expect(container.querySelectorAll(`.${prefixClass}__icon`)).toHaveLength(list.length);
125+
});
126+
127+
// tabBarItem 无value属性时,通过一个递增的 defaultIndex 生成一个临时的当前组件唯一值
128+
it(': value', async () => {
129+
const { container } = render(
130+
<TabBar value={1}>
131+
{list.map((item) => (
132+
<TabBarItem key={item.value}>{item.label}</TabBarItem>
133+
))}
134+
</TabBar>,
135+
);
136+
expect(container.querySelectorAll(`.${prefixClass}`)).toHaveLength(list.length);
137+
const activeItem = container.querySelector(`.${prefixClass}__content--checked`);
138+
expect(activeItem).toBeTruthy();
139+
expect(activeItem).toHaveTextContent(list[1].label);
140+
});
141+
142+
it(': subTabBar', async () => {
143+
const onChange = vi.fn();
144+
const { container, getByText } = render(
145+
<TabBar onChange={onChange}>
146+
{list.map((item) => (
147+
<TabBarItem key={item.value} {...item}>
148+
{item.label}
149+
</TabBarItem>
150+
))}
151+
</TabBar>,
152+
);
153+
154+
const subTabBarItem = getByText('我的');
155+
fireEvent.click(subTabBarItem);
156+
expect(container.querySelector(`.${prefixClass}__spread`)).toBeTruthy();
157+
expect(onChange).toHaveBeenCalled();
158+
// expect(onChange).toHaveBeenCalledWith([4]);
159+
160+
const subTabBarMenu = container.querySelectorAll(`.${prefixClass}__spread-item`);
161+
fireEvent.click(subTabBarMenu[0]);
162+
expect(onChange).toHaveBeenCalled();
163+
// expect(onChange).toHaveBeenCalledWith([4, '4_1']);
164+
// setTimeout(() => {
165+
// expect(container.querySelector(`.${prefixClass}__spread`)).toBeFalsy();
166+
// });
167+
});
168+
});
169+
170+
describe('event', () => {
171+
it(': tabBar click', async () => {
172+
const { container, getByText } = render(
173+
<TabBar>
174+
{list.map((item) => (
175+
<TabBarItem key={item.value} {...item}>
176+
{item.label}
177+
</TabBarItem>
178+
))}
179+
</TabBar>,
180+
);
181+
182+
const subTabBarItem = getByText('我的');
183+
fireEvent.click(subTabBarItem);
184+
expect(container.querySelector(`.${prefixClass}__spread`)).toBeTruthy();
185+
fireEvent.click(subTabBarItem);
186+
expect(container.querySelector(`.${prefixClass}__spread`)).toBeTruthy();
187+
});
188+
});
189+
});

0 commit comments

Comments
 (0)