From 3f5cbc80eaa760c22575a791872682771864c5a5 Mon Sep 17 00:00:00 2001
From: PiePie <45820630+ImaginingMaker@users.noreply.github.com>
Date: Mon, 8 Sep 2025 20:13:30 +0800
Subject: [PATCH] test(affix): refactored the test file of the Affix component
- Refactored the test file of the Affix component
---
.../components/affix/__tests__/affix.test.tsx | 652 +++++++++++++++---
1 file changed, 557 insertions(+), 95 deletions(-)
diff --git a/packages/components/affix/__tests__/affix.test.tsx b/packages/components/affix/__tests__/affix.test.tsx
index 6f4933b7e3..ab6483b2ad 100644
--- a/packages/components/affix/__tests__/affix.test.tsx
+++ b/packages/components/affix/__tests__/affix.test.tsx
@@ -1,134 +1,596 @@
import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
-import { beforeEach, expect, it, vi } from 'vitest';
+import { beforeEach, expect, it, vi, describe } from 'vitest';
import { Affix } from '@tdesign/components/affix';
describe('Affix', () => {
- test('_______', () => {
- expect(true).toEqual(true);
- });
-
vi.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => {
cb(performance.now());
return 1;
});
- describe('Test the state of the container under the window', () => {
- const offsetTop = 10;
- const slotWidth = 100;
- const slotHeight = 20;
-
- const wrapper = mount({
- render() {
- return (
-
- hello world
-
- );
- },
+ describe('props', () => {
+ it(':offsetTop[number]', async () => {
+ const offsetTop = 10;
+ const wrapper = mount(Content);
+ await nextTick();
+ expect(wrapper.exists()).toBeTruthy();
});
- // TODO PAOPAO need refactor
- const { affixRef } = wrapper.vm.$refs as { affixRef: { affixWrapRef: any; scrollContainer: HTMLElement } };
- // 模拟 affixWrap 的位置
- vi.spyOn(affixRef.affixWrapRef, 'getBoundingClientRect').mockImplementation(() => ({
- top: 5,
- width: slotWidth,
- height: slotHeight,
- }));
-
- it('Test get container', async () => {
+
+ it(':offsetBottom[number]', async () => {
+ const offsetBottom = 50;
+ const wrapper = mount(Content);
await nextTick();
- expect(affixRef.scrollContainer).toBe(window);
+ expect(wrapper.exists()).toBeTruthy();
});
- it('Test the scrolling state', async () => {
- // 模拟滚动触发
- window.dispatchEvent(new CustomEvent('scroll'));
- expect(wrapper.find('.t-affix').classes()).toContain('t-affix');
+ it(':zIndex[number]', async () => {
+ const zIndex = 1000;
+ const wrapper = mount(Content);
+ await nextTick();
+ expect(wrapper.exists()).toBeTruthy();
});
- it('Test the position in the scroll state', () => {
- // 模拟滚动触发
- window.dispatchEvent(new CustomEvent('scroll'));
- const style = wrapper.find('.t-affix').attributes('style');
- expect(style).toBe(`top: ${offsetTop}px; width: ${slotWidth}px; height: ${slotHeight}px;`);
+ it(':container[function]', async () => {
+ const wrapper = mount({
+ methods: {
+ container(): any {
+ return (this as any).$refs?.container;
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+ await nextTick();
+ expect(wrapper.exists()).toBeTruthy();
});
- it('Test the generation of placeholder nodes', () => {
- // 模拟滚动触发
- window.dispatchEvent(new CustomEvent('scroll'));
- expect(wrapper.html()).toContain(``);
+ it(':onFixedChange[function]', async () => {
+ const onFixedChange = vi.fn();
+ const wrapper = mount(Content);
+ await nextTick();
+ expect(wrapper.exists()).toBeTruthy();
});
});
- describe('Test the specified container', () => {
- const offsetTop = 10;
- const slotWidth = 100;
- const slotHeight = 20;
- const containerTop = 100;
-
- const wrapper = mount({
- methods: {
- // TODO PAOPAO need refactor
- // @ts-ignore
- container() {
- // TODO PAOPAO need refactor
- // @ts-ignore
- return (this.$refs as any)?.container;
- },
- },
- render() {
- return (
-
- {/* // TODO PAOPAO need refactor */}
- {/* @ts-ignore */}
-
- hello world
-
-
- );
- },
- });
+ describe('events', () => {
+ it('@fixedChange', async () => {
+ const onFixedChange = vi.fn();
+ const wrapper = mount(
+
+ Content
+ ,
+ );
- // TODO PAOPAO need refactor
- const { affixRef } = wrapper.vm.$refs as {
- affixRef: { affixWrapRef: any; scrollContainer: any; handleScroll: () => void };
- };
+ // Mock getBoundingClientRect to trigger fixed state
+ const affixRef = (wrapper.vm.$refs as any).affixRef;
+ if (affixRef) {
+ vi.spyOn(affixRef.affixWrapRef, 'getBoundingClientRect').mockImplementation(() => ({
+ top: 5,
+ width: 100,
+ height: 20,
+ }));
- it('Test get container', async () => {
- await nextTick();
- expect(affixRef.scrollContainer).toBe(wrapper.vm.container());
+ // Trigger scroll to activate fixed state
+ window.dispatchEvent(new CustomEvent('scroll'));
+ await nextTick();
+
+ expect(onFixedChange).toHaveBeenCalled();
+ }
});
- // 模拟 affixWrap 的位置
- beforeEach(() => {
+ });
+
+ describe('functionality', () => {
+ describe('window container', () => {
+ const offsetTop = 10;
+ const slotWidth = 100;
+ const slotHeight = 20;
+
+ const wrapper = mount({
+ render() {
+ return (
+
+ hello world
+
+ );
+ },
+ });
+
+ const { affixRef } = wrapper.vm.$refs as { affixRef: { affixWrapRef: any; scrollContainer: HTMLElement } };
+
vi.spyOn(affixRef.affixWrapRef, 'getBoundingClientRect').mockImplementation(() => ({
top: 5,
width: slotWidth,
height: slotHeight,
}));
+
+ it('should get window as default container', async () => {
+ await nextTick();
+ expect(affixRef.scrollContainer).toBe(window);
+ });
+
+ it('should apply fixed class when scrolling', async () => {
+ window.dispatchEvent(new CustomEvent('scroll'));
+ expect(wrapper.find('.t-affix').classes()).toContain('t-affix');
+ });
+
+ it('should calculate correct position when fixed', () => {
+ window.dispatchEvent(new CustomEvent('scroll'));
+ const style = wrapper.find('.t-affix').attributes('style');
+ expect(style).toBe(`top: ${offsetTop}px; width: ${slotWidth}px; height: ${slotHeight}px;`);
+ });
+
+ it('should generate placeholder element when fixed', () => {
+ window.dispatchEvent(new CustomEvent('scroll'));
+ expect(wrapper.html()).toContain(``);
+ });
});
- it('Test the scrolling state', async () => {
- // 模拟容器滚动
- wrapper.vm.container().dispatchEvent(new CustomEvent('scroll'));
- expect(wrapper.find('.t-affix').classes()).toContain('t-affix');
+ describe('custom container', () => {
+ const offsetTop = 10;
+ const slotWidth = 100;
+ const slotHeight = 20;
+ const containerTop = 100;
+
+ const wrapper = mount({
+ methods: {
+ container(): any {
+ return (this as any).$refs?.container;
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ const { affixRef } = wrapper.vm.$refs as {
+ affixRef: { affixWrapRef: any; scrollContainer: any; handleScroll: () => void };
+ };
+
+ it('should get custom container', async () => {
+ await nextTick();
+ expect(affixRef.scrollContainer).toBe((wrapper.vm as any).container());
+ });
+
+ beforeEach(() => {
+ vi.spyOn(affixRef.affixWrapRef, 'getBoundingClientRect').mockImplementation(() => ({
+ top: 5,
+ width: slotWidth,
+ height: slotHeight,
+ }));
+ });
+
+ it('should apply fixed class when container scrolling', async () => {
+ (wrapper.vm as any).container().dispatchEvent(new CustomEvent('scroll'));
+ expect(wrapper.find('.t-affix').classes()).toContain('t-affix');
+ });
+
+ beforeEach(() => {
+ window.addEventListener('scroll', affixRef.handleScroll);
+ vi.spyOn(affixRef.scrollContainer, 'getBoundingClientRect').mockImplementation(() => ({
+ top: containerTop,
+ }));
+ });
+
+ it('should calculate correct position relative to container', () => {
+ window.dispatchEvent(new CustomEvent('scroll'));
+ const style = wrapper.find('.t-affix').attributes('style');
+ expect(style).toBe(`top: ${offsetTop + containerTop}px; width: ${slotWidth}px; height: ${slotHeight}px;`);
+ });
});
- beforeEach(() => {
- // 模拟绑定
- window.addEventListener('scroll', affixRef.handleScroll);
- // 模拟容器的位置
- vi.spyOn(affixRef.scrollContainer, 'getBoundingClientRect').mockImplementation(() => ({
- top: containerTop,
- }));
+ describe('offsetBottom', () => {
+ const offsetBottom = 50;
+ const slotWidth = 100;
+ const slotHeight = 20;
+ const containerHeight = 400;
+ const containerTop = 0;
+
+ const wrapper = mount({
+ methods: {
+ container(): any {
+ return (this as any).$refs?.container;
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ const { affixRef } = wrapper.vm.$refs as {
+ affixRef: { affixWrapRef: any; scrollContainer: any; handleScroll: () => void };
+ };
+
+ beforeEach(() => {
+ vi.spyOn(affixRef.scrollContainer, 'getBoundingClientRect').mockImplementation(() => ({
+ top: containerTop,
+ }));
+
+ Object.defineProperty(affixRef.scrollContainer, 'clientHeight', {
+ value: containerHeight,
+ writable: true,
+ });
+ });
+
+ it('should trigger when element reaches bottom boundary', async () => {
+ await nextTick();
+
+ const wrapToTop = containerHeight - offsetBottom + 10;
+ vi.spyOn(affixRef.affixWrapRef, 'getBoundingClientRect').mockImplementation(() => ({
+ top: wrapToTop,
+ width: slotWidth,
+ height: slotHeight,
+ }));
+
+ affixRef.handleScroll();
+ await nextTick();
+
+ expect(wrapper.find('.t-affix').classes()).toContain('t-affix');
+ });
+
+ it('should calculate correct bottom position', async () => {
+ await nextTick();
+
+ const wrapToTop = containerHeight - offsetBottom + 10;
+ vi.spyOn(affixRef.affixWrapRef, 'getBoundingClientRect').mockImplementation(() => ({
+ top: wrapToTop,
+ width: slotWidth,
+ height: slotHeight,
+ }));
+
+ affixRef.handleScroll();
+ await nextTick();
+
+ const expectedTop = containerTop + containerHeight - slotHeight - offsetBottom;
+ const style = wrapper.find('.t-affix').attributes('style');
+ expect(style).toBe(`top: ${expectedTop}px; width: ${slotWidth}px; height: ${slotHeight}px;`);
+ });
+
+ it('should not trigger when element is above bottom boundary', async () => {
+ const testWrapper = mount({
+ methods: {
+ container(): any {
+ return (this as any).$refs?.container;
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ const { testAffixRef } = testWrapper.vm.$refs as {
+ testAffixRef: { affixWrapRef: any; scrollContainer: any; handleScroll: () => void };
+ };
+
+ await nextTick();
+
+ vi.spyOn(testAffixRef.scrollContainer, 'getBoundingClientRect').mockImplementation(() => ({
+ top: containerTop,
+ }));
+
+ Object.defineProperty(testAffixRef.scrollContainer, 'clientHeight', {
+ value: containerHeight,
+ writable: true,
+ });
+
+ const normalPosition = 50;
+ vi.spyOn(testAffixRef.affixWrapRef, 'getBoundingClientRect').mockImplementation(() => ({
+ top: normalPosition,
+ width: slotWidth,
+ height: slotHeight,
+ }));
+
+ testAffixRef.handleScroll();
+ await nextTick();
+
+ const affixElements = testWrapper.findAll('.t-affix');
+ expect(affixElements.length).toBe(0);
+ });
+
+ it('should create placeholder element for offsetBottom', async () => {
+ await nextTick();
+
+ const wrapToTop = containerHeight - offsetBottom + 10;
+ vi.spyOn(affixRef.affixWrapRef, 'getBoundingClientRect').mockImplementation(() => ({
+ top: wrapToTop,
+ width: slotWidth,
+ height: slotHeight,
+ }));
+
+ affixRef.handleScroll();
+ await nextTick();
+
+ expect(wrapper.html()).toContain(``);
+ });
+ });
+
+ describe('event binding/unbinding', () => {
+ const offsetTop = 10;
+ const slotWidth = 100;
+ const slotHeight = 20;
+
+ it('should remove event listeners when component unmounts', async () => {
+ const testWrapper = mount({
+ methods: {
+ container(): any {
+ return (this as any).$refs?.container;
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ const testAffixRef = (testWrapper.vm as any).$refs.testAffixRef;
+ await nextTick();
+
+ expect(testAffixRef.scrollContainer).toBeDefined();
+
+ const offSpy = vi.fn();
+ vi.doMock('@tdesign/shared-utils', () => ({
+ off: offSpy,
+ on: vi.fn(),
+ getScrollContainer: vi.fn(() => testAffixRef.scrollContainer),
+ }));
+
+ const mockRAFId = 123;
+ const cancelAnimationFrameSpy = vi.spyOn(window, 'cancelAnimationFrame');
+ vi.spyOn(window, 'requestAnimationFrame').mockReturnValue(mockRAFId);
+ vi.spyOn(testAffixRef.affixWrapRef, 'getBoundingClientRect').mockImplementation(() => ({
+ top: 5,
+ width: slotWidth,
+ height: slotHeight,
+ }));
+
+ testAffixRef.handleScroll();
+ testWrapper.unmount();
+ await nextTick();
+
+ expect(cancelAnimationFrameSpy).toHaveBeenCalledWith(mockRAFId);
+ vi.restoreAllMocks();
+ });
+
+ it('should handle null scrollContainer gracefully', async () => {
+ const testWrapper = mount({
+ render() {
+ return (
+
+ test content
+
+ );
+ },
+ });
+
+ await nextTick();
+
+ const testAffixRef = (testWrapper.vm.$refs as any).affixRef;
+ if (testAffixRef) {
+ testAffixRef.scrollContainer = { value: null };
+ }
+
+ expect(() => {
+ testWrapper.unmount();
+ }).not.toThrow();
+ });
+
+ it('should handle missing rAFId gracefully', async () => {
+ const testWrapper = mount({
+ methods: {
+ container(): any {
+ return (this as any).$refs?.container;
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ const testAffixRef = (testWrapper.vm.$refs as any).testAffixRef;
+ await nextTick();
+
+ expect(testAffixRef.scrollContainer).toBeDefined();
+
+ const cancelAnimationFrameSpy = vi.spyOn(window, 'cancelAnimationFrame');
+
+ testWrapper.unmount();
+ await nextTick();
+
+ expect(cancelAnimationFrameSpy).not.toHaveBeenCalled();
+ vi.restoreAllMocks();
+ });
+
+ it('should manage isBind state correctly', async () => {
+ const testWrapper = mount({
+ methods: {
+ container(): any {
+ return (this as any).$refs?.container;
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ const testAffixRef = (testWrapper.vm.$refs as any).testAffixRef;
+ await nextTick();
+
+ expect(testAffixRef.scrollContainer).toBeDefined();
+
+ expect(() => {
+ testWrapper.unmount();
+ }).not.toThrow();
+
+ expect(() => {
+ testWrapper.unmount();
+ }).not.toThrow();
+ });
+
+ it('should handle bindScroll early return when already bound', async () => {
+ const testWrapper = mount({
+ methods: {
+ container(): any {
+ return (this as any).$refs?.container;
+ },
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ await nextTick();
+
+ const testAffixRef = (testWrapper.vm.$refs as any).testAffixRef;
+ expect(testAffixRef).toBeDefined();
+ expect(testAffixRef.scrollContainer).toBeDefined();
+
+ expect(() => {
+ testAffixRef.handleScroll();
+ }).not.toThrow();
+
+ testWrapper.unmount();
+ });
});
- it('Test the positioning after opening the binding window sliding event', () => {
- // 模拟滚动触发
- window.dispatchEvent(new CustomEvent('scroll'));
+ describe('props watchers', () => {
+ it('should trigger component update when props change', async () => {
+ const testWrapper = mount(Affix, {
+ props: {
+ offsetTop: 10,
+ offsetBottom: 10,
+ zIndex: 100,
+ },
+ slots: {
+ default: 'test content
',
+ },
+ });
+
+ await nextTick();
+ expect(testWrapper.exists()).toBe(true);
+
+ await testWrapper.setProps({ offsetTop: 20 });
+ await nextTick();
+ expect(testWrapper.exists()).toBe(true);
+
+ await testWrapper.setProps({ offsetBottom: 20 });
+ await nextTick();
+ expect(testWrapper.exists()).toBe(true);
+
+ await testWrapper.setProps({ zIndex: 200 });
+ await nextTick();
+ expect(testWrapper.exists()).toBe(true);
+
+ await testWrapper.setProps({
+ offsetTop: 30,
+ offsetBottom: 30,
+ zIndex: 300,
+ });
+ await nextTick();
+ expect(testWrapper.exists()).toBe(true);
+
+ testWrapper.unmount();
+ });
+
+ it('should handle undefined values correctly', async () => {
+ const testWrapper = mount(Affix, {
+ props: {
+ offsetTop: undefined,
+ offsetBottom: undefined,
+ zIndex: undefined,
+ },
+ slots: {
+ default: 'test content
',
+ },
+ });
+
+ await nextTick();
+ expect(testWrapper.exists()).toBe(true);
+
+ await testWrapper.setProps({ offsetTop: 10, offsetBottom: 10, zIndex: 100 });
+ await nextTick();
+ expect(testWrapper.exists()).toBe(true);
+
+ await testWrapper.setProps({ offsetTop: undefined, offsetBottom: undefined, zIndex: undefined });
+ await nextTick();
+ expect(testWrapper.exists()).toBe(true);
+
+ testWrapper.unmount();
+ });
+
+ it('should handle rapid prop changes', async () => {
+ const testWrapper = mount(Affix, {
+ props: { offsetTop: 10 },
+ slots: { default: 'test content
' },
+ });
+
+ await nextTick();
+
+ for (let i = 0; i < 3; i++) {
+ await testWrapper.setProps({ offsetTop: 10 + i * 5, zIndex: 100 + i * 10 });
+ }
+
+ await nextTick();
+ expect(testWrapper.exists()).toBe(true);
+ expect(testWrapper.find('div').exists()).toBe(true);
- const style = wrapper.find('.t-affix').attributes('style');
- expect(style).toBe(`top: ${offsetTop + containerTop}px; width: ${slotWidth}px; height: ${slotHeight}px;`);
+ testWrapper.unmount();
+ });
});
});
});