Skip to content

Commit ac1d4ed

Browse files
lockiechenRoy9102
andauthored
feat(test): 新增 Rate 相关组件测试,优化提示框逻辑与安全访问 (#754)
* feat(test): 新增 Rate 相关组件测试,优化提示框逻辑与安全访问 * test: 移除 Rate 组件拖拽时 Tips 显示值测试用例 * refactor: 重构测试用例分组,新增提示隐藏延迟常量 --------- Co-authored-by: lockiechen <[email protected]>
1 parent bef2e27 commit ac1d4ed

File tree

8 files changed

+1212
-10
lines changed

8 files changed

+1212
-10
lines changed

site/test-coverage.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module.exports = {
3333
link: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
3434
list: { statements: '8%', branches: '0%', functions: '0%', lines: '8.69%' },
3535
loading: { statements: '98.43%', branches: '96.07%', functions: '100%', lines: '98.3%' },
36-
locale: { statements: '70.37%', branches: '58.33%', functions: '83.33%', lines: '75%' },
36+
locale: { statements: '74.07%', branches: '62.5%', functions: '83.33%', lines: '75%' },
3737
message: { statements: '12.37%', branches: '0%', functions: '0%', lines: '12.76%' },
3838
navbar: { statements: '12.9%', branches: '0%', functions: '0%', lines: '13.79%' },
3939
noticeBar: { statements: '6.38%', branches: '0%', functions: '0%', lines: '6.52%' },
@@ -45,7 +45,7 @@ module.exports = {
4545
pullDownRefresh: { statements: '100%', branches: '98.43%', functions: '100%', lines: '100%' },
4646
qrcode: { statements: '100%', branches: '92.5%', functions: '100%', lines: '100%' },
4747
radio: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
48-
rate: { statements: '5.71%', branches: '0%', functions: '0%', lines: '5.71%' },
48+
rate: { statements: '99.3%', branches: '98.98%', functions: '100%', lines: '99.3%' },
4949
result: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
5050
search: { statements: '6.12%', branches: '0%', functions: '0%', lines: '6.25%' },
5151
sideBar: { statements: '54.34%', branches: '0%', functions: '33.33%', lines: '58.53%' },

src/rate/Rate.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { TdRateProps } from './type';
1313

1414
export interface RateProps extends TdRateProps, StyledProps {}
1515

16+
const TIPS_HIDE_DELAY = 300;
1617
const convertToNumber = (str: string | number, defaultValue = 0) => {
1718
const value = parseFloat(String(str));
1819
return isNaN(value) ? defaultValue : value;
@@ -133,7 +134,8 @@ const Rate = forwardRef<HTMLDivElement, RateProps>((props, ref) => {
133134
controlRef.current.enableClick = false;
134135
// 根据记录去修改数据
135136
setInnerValue(controlRef.current.currentValue);
136-
onHideTips();
137+
clearTimeout(controlRef.current.timer);
138+
controlRef.current.timer = setTimeout(onHideTips, TIPS_HIDE_DELAY) as any as number;
137139
}, [onHideTips, setInnerValue, disabled]);
138140

139141
const wrapSize = useSize(wrapRef);
@@ -196,7 +198,7 @@ const Rate = forwardRef<HTMLDivElement, RateProps>((props, ref) => {
196198
setClickTime(Date.now());
197199
setCurrentValue(value);
198200
onShowTips();
199-
controlRef.current.timer = setTimeout(onHideTips, 3000) as any as number;
201+
controlRef.current.timer = setTimeout(onHideTips, TIPS_HIDE_DELAY) as any as number;
200202
setInnerValue(value);
201203
}}
202204
/>
@@ -205,7 +207,7 @@ const Rate = forwardRef<HTMLDivElement, RateProps>((props, ref) => {
205207
</div>
206208
{showText ? <RateText texts={texts} value={isDragging ? currentValue : innerValue} /> : null}
207209
{/* 增加一个时间戳作为 key 保证每次点击的时候 组件都重新创建 防止重复利用 触发 onClickOutSide */}
208-
{tipsVisible && placement && !disabled ? (
210+
{tipsVisible && placement && !disabled && (
209211
<RateTips
210212
key={clickTime}
211213
left={tipsLeft}
@@ -254,7 +256,7 @@ const Rate = forwardRef<HTMLDivElement, RateProps>((props, ref) => {
254256
};
255257
})}
256258
/>
257-
) : null}
259+
)}
258260
</div>
259261
);
260262
});

src/rate/RateIcon.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const RateIcon = (props: Props) => {
5656
<div
5757
ref={ref}
5858
onClick={(e) => {
59+
e.stopPropagation(); // 阻止事件冒泡,防止触发 RateTips 的 clickoutside
5960
const dom = ref.current;
6061
if (!dom) {
6162
return;
@@ -71,7 +72,7 @@ export const RateIcon = (props: Props) => {
7172
[`${iconClass}--unselected`]: !isSelected || isHalf,
7273
})}
7374
>
74-
{isHalf ? (
75+
{isHalf && (
7576
<div
7677
className={cx(`${iconClass}-left`, {
7778
[`${iconClass}-left--selected`]: isSelected,
@@ -80,7 +81,7 @@ export const RateIcon = (props: Props) => {
8081
>
8182
{iconNode}
8283
</div>
83-
) : null}
84+
)}
8485

8586
{iconNode}
8687
</div>

src/rate/RateTips.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export const RateTips = (props: Props) => {
2323
const ref = useRef<HTMLDivElement | null>(null);
2424

2525
useClickAway(onClickOutside, ref);
26-
2726
return (
2827
<div
2928
ref={ref}
@@ -33,7 +32,7 @@ export const RateTips = (props: Props) => {
3332
style={{ left: `${left}px` }}
3433
>
3534
<div style={{ display: 'flex' }}>
36-
{data.map((item, index) => (
35+
{data?.map((item, index) => (
3736
<div
3837
key={index}
3938
className={cx(`${tipClass}-item`, {
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import React from 'react';
2+
import { vi } from 'vitest';
3+
import { describe, it, expect, render, fireEvent } from '@test/utils';
4+
import { RateIcon } from '../RateIcon';
5+
6+
describe('RateIcon', () => {
7+
describe('props', () => {
8+
it('should render default star icon', () => {
9+
const { container } = render(
10+
<RateIcon size={24} icon={[]} color="" isCurrent={false} isSelected={false} isHalf={false} />,
11+
);
12+
expect(container.querySelector('.t-rate__icon')).toBeInTheDocument();
13+
});
14+
15+
it(':isSelected', () => {
16+
const { container } = render(
17+
<RateIcon size={24} icon={[]} color="" isCurrent={false} isSelected={true} isHalf={false} />,
18+
);
19+
const icon = container.querySelector('.t-rate__icon');
20+
expect(icon).toHaveClass('t-rate__icon--selected');
21+
});
22+
23+
it(':isCurrent', () => {
24+
const { container } = render(
25+
<RateIcon size={24} icon={[]} color="" isCurrent={true} isSelected={false} isHalf={false} />,
26+
);
27+
const icon = container.querySelector('.t-rate__icon');
28+
expect(icon).toHaveClass('t-rate__icon--current');
29+
});
30+
31+
it(':isHalf', () => {
32+
const { container } = render(
33+
<RateIcon size={24} icon={[]} color="" isCurrent={false} isSelected={true} isHalf={true} />,
34+
);
35+
expect(container.querySelector('.t-rate__icon-left')).toBeInTheDocument();
36+
});
37+
38+
it(':color', () => {
39+
const { container } = render(
40+
<RateIcon size={24} icon={[]} color="#ff0000" isCurrent={false} isSelected={true} isHalf={false} />,
41+
);
42+
const icon = container.querySelector('.t-rate__icon');
43+
expect(icon).toHaveStyle('--td-rate-selected-color: #ff0000');
44+
});
45+
46+
it(':size', () => {
47+
const { container } = render(
48+
<RateIcon size={32} icon={[]} color="" isCurrent={false} isSelected={false} isHalf={false} />,
49+
);
50+
const icon = container.querySelector('.t-rate__icon');
51+
expect(icon).toHaveStyle('font-size: 32px');
52+
});
53+
54+
it(':icon with custom function', () => {
55+
const CustomIcon = () => <span></span>;
56+
const { container } = render(
57+
<RateIcon size={24} icon={[CustomIcon]} color="" isCurrent={false} isSelected={false} isHalf={false} />,
58+
);
59+
expect(container.querySelector('.t-rate__icon')).toBeInTheDocument();
60+
});
61+
62+
it(':icon with custom element', () => {
63+
const customIcon = <span></span>;
64+
const { container } = render(
65+
<RateIcon size={24} icon={[customIcon]} color="" isCurrent={false} isSelected={false} isHalf={false} />,
66+
);
67+
expect(container.querySelector('.t-rate__icon')).toBeInTheDocument();
68+
});
69+
70+
it(':color with array', () => {
71+
const { container } = render(
72+
<RateIcon
73+
size={24}
74+
icon={[]}
75+
color={['#ff0000', '#000000']}
76+
isCurrent={false}
77+
isSelected={true}
78+
isHalf={false}
79+
/>,
80+
);
81+
const icon = container.querySelector('.t-rate__icon');
82+
expect(icon).toHaveStyle('--td-rate-selected-color: #ff0000');
83+
expect(icon).toHaveStyle('--td-rate-unselected-color: #000000');
84+
});
85+
86+
it('should handle unselected state correctly', () => {
87+
const { container } = render(
88+
<RateIcon size={24} icon={[]} color="" isCurrent={false} isSelected={false} isHalf={false} />,
89+
);
90+
const icon = container.querySelector('.t-rate__icon');
91+
expect(icon).toHaveClass('t-rate__icon--unselected');
92+
});
93+
94+
it('should handle half selected state correctly', () => {
95+
const { container } = render(
96+
<RateIcon size={24} icon={[]} color="" isCurrent={false} isSelected={true} isHalf={true} />,
97+
);
98+
const leftIcon = container.querySelector('.t-rate__icon-left');
99+
expect(leftIcon).toHaveClass('t-rate__icon-left--selected');
100+
});
101+
102+
it('should handle half unselected state correctly', () => {
103+
const { container } = render(
104+
<RateIcon size={24} icon={[]} color="" isCurrent={false} isSelected={false} isHalf={true} />,
105+
);
106+
const leftIcon = container.querySelector('.t-rate__icon-left');
107+
expect(leftIcon).toHaveClass('t-rate__icon-left--unselected');
108+
});
109+
110+
it('should handle undefined color values', () => {
111+
const { container } = render(
112+
<RateIcon size={24} icon={[]} color={undefined} isCurrent={false} isSelected={true} isHalf={false} />,
113+
);
114+
const icon = container.querySelector('.t-rate__icon');
115+
expect(icon).toBeInTheDocument();
116+
});
117+
118+
it('should handle empty color array', () => {
119+
const { container } = render(
120+
<RateIcon size={24} icon={[]} color={[]} isCurrent={false} isSelected={true} isHalf={false} />,
121+
);
122+
const icon = container.querySelector('.t-rate__icon');
123+
expect(icon).toBeInTheDocument();
124+
});
125+
126+
it('should handle single color in array', () => {
127+
const { container } = render(
128+
<RateIcon size={24} icon={[]} color={['#ff0000']} isCurrent={false} isSelected={true} isHalf={false} />,
129+
);
130+
const icon = container.querySelector('.t-rate__icon');
131+
expect(icon).toHaveStyle('--td-rate-selected-color: #ff0000');
132+
});
133+
134+
it('should handle undefined icon values', () => {
135+
const { container } = render(
136+
<RateIcon size={24} icon={undefined} color="" isCurrent={false} isSelected={false} isHalf={false} />,
137+
);
138+
expect(container.querySelector('.t-rate__icon')).toBeInTheDocument();
139+
});
140+
141+
it('should handle null icon values', () => {
142+
const { container } = render(
143+
<RateIcon size={24} icon={[null, null]} color="" isCurrent={false} isSelected={false} isHalf={false} />,
144+
);
145+
expect(container.querySelector('.t-rate__icon')).toBeInTheDocument();
146+
});
147+
148+
it('should handle string icon values', () => {
149+
const { container } = render(
150+
<RateIcon size={24} icon={['★', '☆']} color="" isCurrent={false} isSelected={false} isHalf={false} />,
151+
);
152+
expect(container.querySelector('.t-rate__icon')).toBeInTheDocument();
153+
});
154+
155+
it(':size with zero value', () => {
156+
const { container } = render(
157+
<RateIcon size={0} icon={[]} color="" isCurrent={false} isSelected={false} isHalf={false} />,
158+
);
159+
const icon = container.querySelector('.t-rate__icon');
160+
expect(icon).toHaveStyle('font-size: 0px');
161+
});
162+
163+
it(':size with negative value', () => {
164+
const { container } = render(
165+
<RateIcon size={-10} icon={[]} color="" isCurrent={false} isSelected={false} isHalf={false} />,
166+
);
167+
const icon = container.querySelector('.t-rate__icon');
168+
expect(icon).toHaveStyle('font-size: -10px');
169+
});
170+
171+
it('should handle current and selected together', () => {
172+
const { container } = render(
173+
<RateIcon size={24} icon={[]} color="" isCurrent={true} isSelected={true} isHalf={false} />,
174+
);
175+
const icon = container.querySelector('.t-rate__icon');
176+
expect(icon).toHaveClass('t-rate__icon--current');
177+
expect(icon).toHaveClass('t-rate__icon--selected');
178+
});
179+
180+
it('should handle current and half together', () => {
181+
const { container } = render(
182+
<RateIcon size={24} icon={[]} color="" isCurrent={true} isSelected={true} isHalf={true} />,
183+
);
184+
const icon = container.querySelector('.t-rate__icon');
185+
expect(icon).toHaveClass('t-rate__icon--current');
186+
expect(container.querySelector('.t-rate__icon-left')).toBeInTheDocument();
187+
});
188+
});
189+
190+
describe('event', () => {
191+
it(':click left side', () => {
192+
const handleClick = vi.fn();
193+
const { container } = render(
194+
<RateIcon
195+
size={24}
196+
icon={[]}
197+
color=""
198+
isCurrent={false}
199+
isSelected={false}
200+
isHalf={false}
201+
onClick={handleClick}
202+
/>,
203+
);
204+
205+
const icon = container.querySelector('.t-rate__icon') as Element;
206+
const rect = {
207+
x: 100,
208+
width: 24,
209+
height: 24,
210+
y: 100,
211+
top: 100,
212+
bottom: 124,
213+
left: 100,
214+
right: 124,
215+
toJSON: () => {},
216+
};
217+
vi.spyOn(icon, 'getBoundingClientRect').mockReturnValue(rect as DOMRect);
218+
219+
fireEvent.click(icon, { clientX: 105 });
220+
expect(handleClick).toHaveBeenCalledWith('left', expect.any(Object));
221+
});
222+
223+
it(':click right side', () => {
224+
const handleClick = vi.fn();
225+
const { container } = render(
226+
<RateIcon
227+
size={24}
228+
icon={[]}
229+
color=""
230+
isCurrent={false}
231+
isSelected={false}
232+
isHalf={false}
233+
onClick={handleClick}
234+
/>,
235+
);
236+
237+
const icon = container.querySelector('.t-rate__icon') as Element;
238+
const rect = {
239+
x: 100,
240+
width: 24,
241+
height: 24,
242+
y: 100,
243+
top: 100,
244+
bottom: 124,
245+
left: 100,
246+
right: 124,
247+
toJSON: () => {},
248+
};
249+
vi.spyOn(icon, 'getBoundingClientRect').mockReturnValue(rect as DOMRect);
250+
251+
fireEvent.click(icon, { clientX: 115 });
252+
expect(handleClick).toHaveBeenCalledWith('right', expect.any(Object));
253+
});
254+
});
255+
});

0 commit comments

Comments
 (0)