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 ( +
+ Content +
+ ); + }, + }); + 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 ( +
+ +
hello world
+
+
+ ); + }, + }); + + 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 ( +
+ +
hello world
+
+
+ ); + }, + }); + + 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 ( +
+ +
hello world
+
+
+ ); + }, + }); + + 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 ( +
+ +
test content
+
+
+ ); + }, + }); + + 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 ( +
+ +
test content
+
+
+ ); + }, + }); + + 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 ( +
+ +
test content
+
+
+ ); + }, + }); + + 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 ( +
+ +
test content
+
+
+ ); + }, + }); + + 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(); + }); }); }); });