Skip to content

Commit 13f0dbe

Browse files
authored
test(Popover): add test for Popover (#697) (#782)
* test(Popover): add test for Popover (#697) * test(Popover): 细微修改组件实现 (#697) * test(Popover): 更新覆盖率 (#697)
1 parent 73b5f9d commit 13f0dbe

File tree

3 files changed

+350
-5
lines changed

3 files changed

+350
-5
lines changed

site/test-coverage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ module.exports = {
3939
noticeBar: { statements: '6.38%', branches: '0%', functions: '0%', lines: '6.52%' },
4040
overlay: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
4141
picker: { statements: '5.71%', branches: '0%', functions: '0%', lines: '6.28%' },
42-
popover: { statements: '5%', branches: '0%', functions: '0%', lines: '5.55%' },
42+
popover: { statements: '100%', branches: '96.55%', functions: '100%', lines: '100%' },
4343
popup: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
4444
progress: { statements: '100%', branches: '97.36%', functions: '100%', lines: '100%' },
4545
pullDownRefresh: { statements: '100%', branches: '98.43%', functions: '100%', lines: '100%' },

src/popover/Popover.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const Popover = forwardRef<PopoverExposeRef, PopoverProps>((props, ref) => {
3535
} = useDefaultProps<PopoverProps>(props, popoverDefaultProps);
3636

3737
const [currentVisible, setVisible] = useDefault(visible, defaultVisible, onVisibleChange);
38-
const [active, setActive] = useState(visible);
38+
const [active, setActive] = useState(currentVisible);
3939
const referenceRef = useRef<HTMLDivElement>(null);
4040
const popoverRef = useRef<HTMLDivElement>(null);
4141
const popperRef = useRef<ReturnType<typeof createPopper> | null>(null);
@@ -84,9 +84,9 @@ const Popover = forwardRef<PopoverExposeRef, PopoverProps>((props, ref) => {
8484

8585
const isHorizontal = horizontal.find((item) => placement.includes(item));
8686
const isEnd = placement.includes('end');
87-
const small = (a: number, b: number) => (a < b ? a : b);
87+
8888
if (isHorizontal) {
89-
const padding = isEnd ? small(width + x, popperWidth) : small(windowWidth - x, popperWidth);
89+
const padding = isEnd ? Math.min(width + x, popperWidth) : Math.min(windowWidth - x, popperWidth);
9090
return {
9191
[isEnd ? 'left' : 'right']: padding - 22,
9292
};
@@ -153,7 +153,7 @@ const Popover = forwardRef<PopoverExposeRef, PopoverProps>((props, ref) => {
153153
};
154154

155155
useEffect(() => {
156-
setVisible(visible);
156+
updateVisible(visible);
157157
// eslint-disable-next-line react-hooks/exhaustive-deps
158158
}, [visible]);
159159

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
import { render, fireEvent, waitFor, describe, test, expect, vi } from '@test/utils';
2+
import React from 'react';
3+
import Popover from '../index';
4+
5+
describe('Popover', () => {
6+
describe('props', () => {
7+
// 测试字符串内容渲染
8+
test(':content string', async () => {
9+
const { container } = render(
10+
<Popover content="String content">
11+
<button>Trigger</button>
12+
</Popover>,
13+
);
14+
15+
fireEvent.click(container.querySelector('button')!);
16+
17+
await waitFor(() => {
18+
expect(container.querySelector('.t-popover__content')).toHaveTextContent('String content');
19+
});
20+
});
21+
22+
// 测试JSX内容渲染
23+
test(':content JSX', async () => {
24+
const { container } = render(
25+
<Popover content={<div className="custom-content">JSX content</div>}>
26+
<button>Trigger</button>
27+
</Popover>,
28+
);
29+
30+
fireEvent.click(container.querySelector('button')!);
31+
32+
await waitFor(() => {
33+
expect(container.querySelector('.custom-content')).toHaveTextContent('JSX content');
34+
});
35+
});
36+
37+
// 测试空内容
38+
test(':content null', async () => {
39+
const { container } = render(
40+
<Popover content={null}>
41+
<button>Trigger</button>
42+
</Popover>,
43+
);
44+
45+
fireEvent.click(container.querySelector('button')!);
46+
47+
await waitFor(() => {
48+
expect(container.querySelector('.t-popover__content')).toHaveTextContent('');
49+
});
50+
});
51+
52+
// 测试四个方向
53+
/* eslint-disable no-await-in-loop */
54+
test(':placement', async () => {
55+
const placements = ['top', 'bottom', 'left', 'right'] as const;
56+
for (const placement of placements) {
57+
const { container, unmount } = render(
58+
<Popover content="Test" placement={placement} visible={true}>
59+
<button>Trigger</button>
60+
</Popover>,
61+
);
62+
63+
await waitFor(() => {
64+
expect(container.querySelector('.t-popover')).toHaveAttribute('data-popper-placement', placement);
65+
});
66+
67+
unmount();
68+
}
69+
});
70+
71+
// 测试复杂的方向放置
72+
test(':placement complex', async () => {
73+
const complexPlacements = [
74+
'top-left',
75+
'top-right',
76+
'bottom-left',
77+
'bottom-right',
78+
'left-top',
79+
'left-bottom',
80+
'right-top',
81+
'right-bottom',
82+
] as const;
83+
84+
for (const placement of complexPlacements) {
85+
const { container, unmount } = render(
86+
<Popover content="Test" placement={placement} visible={true}>
87+
<button>Trigger</button>
88+
</Popover>,
89+
);
90+
91+
await waitFor(() => {
92+
expect(container.querySelector('.t-popover')).toHaveAttribute('data-popper-placement', placement);
93+
});
94+
95+
unmount();
96+
}
97+
});
98+
99+
// 测试主题
100+
/* eslint-disable no-await-in-loop */
101+
test(':theme', async () => {
102+
const themes = ['dark', 'light', 'brand', 'success', 'warning', 'error'] as const;
103+
104+
for (const theme of themes) {
105+
const { container, unmount } = render(
106+
<Popover content="Test" theme={theme}>
107+
<button>Trigger</button>
108+
</Popover>,
109+
);
110+
111+
fireEvent.click(container.querySelector('button')!);
112+
113+
await waitFor(() => {
114+
expect(container.querySelector('.t-popover__content')).toHaveClass(`t-popover--${theme}`);
115+
});
116+
117+
unmount();
118+
}
119+
});
120+
121+
// 测试显示箭头
122+
/* eslint-disable no-await-in-loop */
123+
test(':showArrow true', async () => {
124+
const { container } = render(
125+
<Popover content="Test" showArrow={true}>
126+
<button>Trigger</button>
127+
</Popover>,
128+
);
129+
130+
fireEvent.click(container.querySelector('button')!);
131+
132+
await waitFor(() => {
133+
expect(container.querySelector('.t-popover__arrow')).toBeTruthy();
134+
});
135+
});
136+
137+
// 测试不显示箭头
138+
test(':showArrow false', async () => {
139+
const { container } = render(
140+
<Popover content="Test" showArrow={false}>
141+
<button>Trigger</button>
142+
</Popover>,
143+
);
144+
145+
fireEvent.click(container.querySelector('button')!);
146+
147+
await waitFor(() => {
148+
expect(container.querySelector('.t-popover__arrow')).toBeNull();
149+
});
150+
});
151+
152+
// 测试visible
153+
test(':visible', () => {
154+
const { container } = render(
155+
<Popover content="Test" visible={true}>
156+
<button>Trigger</button>
157+
</Popover>,
158+
);
159+
160+
expect(container.querySelector('.t-popover')).not.toHaveStyle('display: none');
161+
});
162+
163+
// 测试triggerElement方式触发是否有效
164+
test(':triggerElement', async () => {
165+
const { container } = render(
166+
<Popover content="Test" triggerElement={<span className="custom-trigger">Custom</span>} />,
167+
);
168+
169+
expect(container.querySelector('.custom-trigger')).toHaveTextContent('Custom');
170+
171+
fireEvent.click(container.querySelector('.custom-trigger')!);
172+
173+
await waitFor(() => {
174+
expect(container.querySelector('.t-popover')).toBeTruthy();
175+
});
176+
});
177+
178+
// 测试非受控模式
179+
test(':defaultVisible', async () => {
180+
const { container } = render(
181+
<Popover content="Test" defaultVisible={true}>
182+
<button>Trigger</button>
183+
</Popover>,
184+
);
185+
186+
await waitFor(() => {
187+
expect(container.querySelector('.t-popover')).not.toHaveStyle('display: none');
188+
});
189+
});
190+
191+
// 测试外接类
192+
test(':className', () => {
193+
const { container } = render(
194+
<Popover content="Test" className="custom-popover">
195+
<button>Trigger</button>
196+
</Popover>,
197+
);
198+
199+
expect(container.querySelector('.t-popover__wrapper')).toHaveClass('custom-popover');
200+
});
201+
202+
// 测试內联样式
203+
test(':style', () => {
204+
const { container } = render(
205+
<Popover content="Test" style={{ backgroundColor: 'red' }}>
206+
<button>Trigger</button>
207+
</Popover>,
208+
);
209+
210+
expect(container.querySelector('.t-popover__wrapper')).toHaveStyle('background-color: rgb(255, 0, 0)');
211+
});
212+
});
213+
214+
describe('events', () => {
215+
// 测试内外点击,内部点击不关闭,外部点击关闭
216+
test(':closeOnClickOutside', async () => {
217+
const { container } = render(
218+
<div>
219+
<Popover content="Test" closeOnClickOutside={true}>
220+
<button>Trigger</button>
221+
</Popover>
222+
<div className="outside">Outside</div>
223+
</div>,
224+
);
225+
226+
fireEvent.click(container.querySelector('button')!);
227+
await waitFor(() => {
228+
expect(container.querySelector('.t-popover')).toBeTruthy();
229+
});
230+
231+
fireEvent.click(container.querySelector('.outside')!);
232+
await waitFor(() => {
233+
expect(container.querySelector('.t-popover')).toHaveStyle('display: none');
234+
});
235+
});
236+
237+
// 测试closeOnClickOutside属性
238+
test(':closeOnClickOutside false', async () => {
239+
const { container } = render(
240+
<div>
241+
<Popover content="Test" closeOnClickOutside={false}>
242+
<button>Trigger</button>
243+
</Popover>
244+
<div className="outside">Outside</div>
245+
</div>,
246+
);
247+
248+
fireEvent.click(container.querySelector('button')!);
249+
await waitFor(() => {
250+
expect(container.querySelector('.t-popover')).not.toHaveStyle('display: none');
251+
});
252+
253+
fireEvent.click(container.querySelector('.outside')!);
254+
await waitFor(() => {
255+
expect(container.querySelector('.t-popover')).not.toHaveStyle('display: none');
256+
});
257+
});
258+
259+
// 测试点击内部关闭
260+
test(':closeOnClickOutside', async () => {
261+
const { container } = render(
262+
<Popover content="Test">
263+
<button>Trigger</button>
264+
</Popover>,
265+
);
266+
267+
const trigger = container.querySelector('button')!;
268+
269+
fireEvent.click(trigger);
270+
await waitFor(() => {
271+
expect(container.querySelector('.t-popover')).not.toHaveStyle('display: none');
272+
});
273+
274+
fireEvent.click(trigger);
275+
await waitFor(() => {
276+
expect(container.querySelector('.t-popover')).toHaveStyle('display: none');
277+
});
278+
});
279+
280+
// 测试vesible变化是否成功监听
281+
test(':onVisibleChange', async () => {
282+
const onVisibleChange = vi.fn();
283+
const { container } = render(
284+
<Popover content="Test" onVisibleChange={onVisibleChange}>
285+
<button>Trigger</button>
286+
</Popover>,
287+
);
288+
289+
fireEvent.click(container.querySelector('button')!);
290+
291+
await waitFor(() => {
292+
expect(onVisibleChange).toHaveBeenCalledWith(true);
293+
});
294+
});
295+
296+
// 测试组件ref暴露
297+
test(':ref functionality', () => {
298+
const ref = React.createRef<any>();
299+
render(
300+
<Popover content="Test" ref={ref}>
301+
<button>Trigger</button>
302+
</Popover>,
303+
);
304+
305+
expect(ref.current).toBeTruthy();
306+
expect(typeof ref.current.updatePopper).toBe('function');
307+
expect(() => ref.current.updatePopper()).not.toThrow();
308+
});
309+
310+
// 测试placement变化是否成功
311+
test(':placement update', async () => {
312+
const { container, rerender } = render(
313+
<Popover content="Test" placement="top" visible={true}>
314+
<button>Trigger</button>
315+
</Popover>,
316+
);
317+
318+
await waitFor(() => {
319+
expect(container.querySelector('.t-popover')).toHaveAttribute('data-popper-placement', 'top');
320+
});
321+
322+
rerender(
323+
<Popover content="Test" placement="bottom" visible={true}>
324+
<button>Trigger</button>
325+
</Popover>,
326+
);
327+
328+
await waitFor(() => {
329+
expect(container.querySelector('.t-popover')).toHaveAttribute('data-popper-placement', 'bottom');
330+
});
331+
});
332+
333+
// 测试组件卸载是否成功
334+
test(':component unmount cleanup', () => {
335+
const { container, unmount } = render(
336+
<Popover content="Test" visible={true}>
337+
<button>Trigger</button>
338+
</Popover>,
339+
);
340+
341+
expect(container.querySelector('.t-popover')).toBeTruthy();
342+
expect(() => unmount()).not.toThrow();
343+
});
344+
});
345+
});

0 commit comments

Comments
 (0)