Skip to content

Commit a4e44aa

Browse files
authored
test(back-top): add test case for back-top (#791)
1 parent c6018b6 commit a4e44aa

File tree

4 files changed

+143
-13
lines changed

4 files changed

+143
-13
lines changed

site/test-coverage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module.exports = {
22
actionSheet: { statements: '5.26%', branches: '0%', functions: '0%', lines: '5.35%' },
33
avatar: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
4-
backTop: { statements: '11.9%', branches: '0%', functions: '0%', lines: '12.82%' },
4+
backTop: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
55
badge: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
66
button: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
77
calendar: { statements: '98.45%', branches: '95.48%', functions: '100%', lines: '99.15%' },

src/_util/dom.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { isFunction, isString } from 'lodash-es';
22

3+
export const checkWindow = () => !!(typeof window !== 'undefined' && window.document && window.document.createElement);
4+
35
// 用于判断是否可使用 dom
4-
export const canUseDocument = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
6+
export const canUseDocument = checkWindow();
57

68
/**
79
* 获取当前主题下的token色值

src/back-top/Backtop.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,12 @@ import withNativeProps, { NativeProps } from '../_util/withNativeProps';
1010
import useConfig from '../hooks/useConfig';
1111
import { TdBackTopProps } from './type';
1212
import { backTopDefaultProps } from './defaultProps';
13-
import { canUseDocument } from '../_util/dom';
13+
import { checkWindow } from '../_util/dom';
1414

1515
export type ThemeList = 'round' | 'half-round' | 'round-dark' | 'half-round-dark';
1616

1717
export type BackTopProps = TdBackTopProps & NativeProps;
1818

19-
export const defaultProps = {
20-
fixed: true,
21-
icon: 'backtop',
22-
target: (() => window) as any,
23-
text: '',
24-
theme: 'round' as ThemeList,
25-
visibilityHeight: 200,
26-
};
27-
2819
const BackTop: React.FC<BackTopProps> = (props) => {
2920
const { fixed, icon, target, text, theme, onToTop, visibilityHeight, container } = useDefaultProps(
3021
props,
@@ -37,7 +28,7 @@ const BackTop: React.FC<BackTopProps> = (props) => {
3728

3829
const getBackTopDom = () => {
3930
if (target) return target();
40-
if (canUseDocument) return window.document.documentElement;
31+
if (checkWindow()) return window.document.documentElement;
4132
return null;
4233
};
4334
backTopDom.current = getBackTopDom();
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { render, fireEvent, describe, test, expect, beforeEach, afterEach } from '@test/utils';
2+
import { vi, type Mock } from 'vitest';
3+
import React from 'react';
4+
import { AppIcon } from 'tdesign-icons-react';
5+
import { useScroll } from 'ahooks';
6+
import BackTop from '../Backtop';
7+
import { checkWindow } from '../../_util/dom';
8+
9+
vi.mock('ahooks', async () => {
10+
const actual = await vi.importActual('ahooks');
11+
return {
12+
...actual,
13+
useScroll: vi.fn(),
14+
};
15+
});
16+
17+
vi.mock('../../_util/dom', async () => {
18+
const actual = await vi.importActual('../../_util/dom');
19+
return {
20+
...actual,
21+
checkWindow: vi.fn(),
22+
};
23+
});
24+
25+
describe('BackTop', () => {
26+
beforeEach(() => {
27+
vi.useFakeTimers();
28+
});
29+
30+
afterEach(() => {
31+
vi.useRealTimers();
32+
});
33+
34+
describe('props', () => {
35+
test(':target', async () => {
36+
(checkWindow as Mock).mockReturnValue(true);
37+
document.documentElement.scrollTo = vi.fn();
38+
const { container } = render(
39+
<div>
40+
<BackTop target={() => document.querySelector('#custom-container') as HTMLElement} />
41+
<div id="custom-container"></div>
42+
</div>,
43+
);
44+
fireEvent.click(container.querySelector('.t-back-top'));
45+
expect(document.documentElement.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
46+
});
47+
48+
test(':fixed', async () => {
49+
(window.document.documentElement as any).window = window;
50+
const { container } = render(<BackTop fixed />);
51+
expect(container.querySelector('.t-back-top--fixed')).toBeTruthy();
52+
});
53+
54+
test(':icon', async () => {
55+
const { container, rerender } = render(<BackTop icon={true} />);
56+
expect(container.querySelector('.t-back-top__icon')).toBeTruthy();
57+
58+
rerender(<BackTop icon={<AppIcon />} />);
59+
expect(container.querySelector('.t-icon-app')).toBeTruthy();
60+
});
61+
62+
test(':text', async () => {
63+
const text = '回到顶部';
64+
const { queryByText } = render(<BackTop text={text} />);
65+
expect(queryByText(text)).toBeTruthy();
66+
});
67+
68+
test(':theme', async () => {
69+
const themes = ['round', 'half-round', 'round-dark', 'half-round-dark'] as const;
70+
themes.forEach((theme) => {
71+
const { container } = render(<BackTop theme={theme} />);
72+
expect(container.querySelector(`.t-back-top--${theme}`)).toBeTruthy();
73+
});
74+
});
75+
76+
test(':visibilityHeight', async () => {
77+
(useScroll as Mock).mockReturnValue({ top: 400 });
78+
const { container } = render(<BackTop visibilityHeight={200} />);
79+
80+
expect((container.querySelector('.t-back-top') as HTMLElement).style.opacity).toBe('1');
81+
});
82+
83+
test(':container', async () => {
84+
render(
85+
<div>
86+
<BackTop container={() => document.querySelector('#custom-container')} />
87+
<div id="custom-container"></div>
88+
</div>,
89+
);
90+
document.querySelector('#custom-container').scrollTo = vi.fn();
91+
fireEvent.click(document.querySelector('.t-back-top'));
92+
expect(document.querySelector('#custom-container').scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
93+
});
94+
});
95+
96+
describe('events', () => {
97+
test(':onToTop', async () => {
98+
const onToTop = vi.fn();
99+
document.documentElement.scrollTo = vi.fn();
100+
const { container } = render(<BackTop onToTop={onToTop} />);
101+
fireEvent.click(container.querySelector('.t-back-top'));
102+
expect(onToTop).toHaveBeenCalled();
103+
});
104+
});
105+
106+
describe('interaction', () => {
107+
test('should show when scrollY > visibilityHeight', async () => {
108+
(useScroll as Mock).mockReturnValue({ top: 100 });
109+
const { container, rerender } = render(<BackTop visibilityHeight={200} />);
110+
expect((container.querySelector('.t-back-top') as HTMLElement).style.opacity).toBe('0');
111+
(useScroll as Mock).mockReturnValue({ top: 400 });
112+
rerender(<BackTop visibilityHeight={200} />);
113+
expect((container.querySelector('.t-back-top') as HTMLElement).style.opacity).toBe('1');
114+
});
115+
116+
test('should scroll to top when clicked', async () => {
117+
(useScroll as Mock).mockReturnValue({ top: 400 });
118+
document.documentElement.scrollTo = vi.fn();
119+
const { container } = render(<BackTop />);
120+
fireEvent.click(container.querySelector('.t-back-top'));
121+
expect(document.documentElement.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
122+
});
123+
});
124+
125+
describe('edge cases', () => {
126+
test('should handle empty text', async () => {
127+
const { container } = render(<BackTop text="" />);
128+
expect(container.querySelector('.t-back-top__text')).toBeFalsy();
129+
});
130+
131+
test('should handle ssr', async () => {
132+
(checkWindow as Mock).mockReturnValue(false);
133+
render(<BackTop />);
134+
expect(document.querySelector('.t-back-top')).toBeTruthy();
135+
});
136+
});
137+
});

0 commit comments

Comments
 (0)