diff --git a/docs/examples/overflow.tsx b/docs/examples/overflow.tsx index d44dad6d..94a23ee5 100644 --- a/docs/examples/overflow.tsx +++ b/docs/examples/overflow.tsx @@ -5,7 +5,7 @@ import '../../assets/index.less'; const items: TabsProps['items'] = []; -for (let i = 0; i < 200; i += 1) { +for (let i = 0; i < 12; i += 1) { items.push({ key: String(i), label: `Tab ${i}`, children: `Content of ${i}` }); } diff --git a/src/TabNavList/ExtraContent.tsx b/src/TabNavList/ExtraContent.tsx new file mode 100644 index 00000000..9bbea6f9 --- /dev/null +++ b/src/TabNavList/ExtraContent.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import type { TabBarExtraPosition, TabBarExtraContent, TabBarExtraMap } from '../interface'; + +interface ExtraContentProps { + position: TabBarExtraPosition; + prefixCls: string; + extra?: TabBarExtraContent; +} + +const ExtraContent = React.forwardRef( + ({ position, prefixCls, extra }, ref) => { + if (!extra) return null; + + let content: React.ReactNode; + + // Parse extra + let assertExtra: TabBarExtraMap = {}; + if (typeof extra === 'object' && !React.isValidElement(extra)) { + assertExtra = extra as TabBarExtraMap; + } else { + assertExtra.right = extra; + } + + if (position === 'right') { + content = assertExtra.right; + } + + if (position === 'left') { + content = assertExtra.left; + } + + return content ? ( +
+ {content} +
+ ) : null; + }, +); + +if (process.env.NODE_ENV !== 'production') { + ExtraContent.displayName = 'ExtraContent'; +} + +export default ExtraContent; diff --git a/src/TabNavList/index.tsx b/src/TabNavList/index.tsx index 31f6e0e6..78924cda 100644 --- a/src/TabNavList/index.tsx +++ b/src/TabNavList/index.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { useState, useRef, useEffect } from 'react'; import classNames from 'classnames'; import raf from 'rc-util/lib/raf'; +import { useComposeRef } from 'rc-util/lib/ref'; import ResizeObserver from 'rc-resize-observer'; import useRaf, { useRafState } from '../hooks/useRaf'; import TabNode from './TabNode'; @@ -13,9 +14,8 @@ import type { EditableConfig, AnimatedConfig, OnTabScroll, - TabBarExtraPosition, TabBarExtraContent, - TabBarExtraMap, + SizeInfo, } from '../interface'; import useOffsets from '../hooks/useOffsets'; import useVisibleRange from '../hooks/useVisibleRange'; @@ -25,6 +25,8 @@ import useTouchMove from '../hooks/useTouchMove'; import useRefs from '../hooks/useRefs'; import AddButton from './AddButton'; import useSyncState from '../hooks/useSyncState'; +import { stringify } from '../util'; +import ExtraContent from './ExtraContent'; export interface TabNavListProps { id: string; @@ -49,34 +51,16 @@ export interface TabNavListProps { popupClassName?: string; } -interface ExtraContentProps { - position: TabBarExtraPosition; - prefixCls: string; - extra?: TabBarExtraContent; -} - -const ExtraContent = ({ position, prefixCls, extra }: ExtraContentProps) => { - if (!extra) return null; - - let content: React.ReactNode; - - // Parse extra - let assertExtra: TabBarExtraMap = {}; - if (extra && typeof extra === 'object' && !React.isValidElement(extra)) { - assertExtra = extra as TabBarExtraMap; - } else { - assertExtra.right = extra; - } - - if (position === 'right') { - content = assertExtra.right; - } - - if (position === 'left') { - content = assertExtra.left; - } +const getSize = (refObj: React.RefObject): SizeInfo => { + const { offsetWidth = 0, offsetHeight = 0 } = refObj.current || {}; + return [offsetWidth, offsetHeight]; +}; - return content ?
{content}
: null; +/** + * Convert `SizeInfo` to unit value. Such as [123, 456] with `top` position get `123` + */ +const getUnitValue = (size: SizeInfo, tabPositionTopOrBottom: boolean) => { + return size[tabPositionTopOrBottom ? 0 : 1]; }; function TabNavList(props: TabNavListProps, ref: React.Ref) { @@ -97,6 +81,9 @@ function TabNavList(props: TabNavListProps, ref: React.Ref) { onTabClick, onTabScroll, } = props; + const containerRef = useRef(); + const extraLeftRef = useRef(); + const extraRightRef = useRef(); const tabsWrapperRef = useRef(); const tabListRef = useRef(); const operationsRef = useRef(); @@ -116,15 +103,27 @@ function TabNavList(props: TabNavListProps, ref: React.Ref) { } }); - const [wrapperScrollWidth, setWrapperScrollWidth] = useState(0); - const [wrapperScrollHeight, setWrapperScrollHeight] = useState(0); - const [wrapperWidth, setWrapperWidth] = useState(null); - const [wrapperHeight, setWrapperHeight] = useState(null); - const [addWidth, setAddWidth] = useState(0); - const [addHeight, setAddHeight] = useState(0); + const [containerExcludeExtraSize, setContainerExcludeExtraSize] = useState([0, 0]); + const [tabContentSize, setTabContentSize] = useState([0, 0]); + const [addSize, setAddSize] = useState([0, 0]); + const [operationSize, setOperationSize] = useState([0, 0]); const [tabSizes, setTabSizes] = useRafState(new Map()); - const tabOffsets = useOffsets(tabs, tabSizes, wrapperScrollWidth); + const tabOffsets = useOffsets(tabs, tabSizes, tabContentSize[0]); + + // ========================== Unit ========================= + const containerExcludeExtraSizeValue = getUnitValue( + containerExcludeExtraSize, + tabPositionTopOrBottom, + ); + const tabContentSizeValue = getUnitValue(tabContentSize, tabPositionTopOrBottom); + const addSizeValue = getUnitValue(addSize, tabPositionTopOrBottom); + const operationSizeValue = getUnitValue(operationSize, tabPositionTopOrBottom); + + const needScroll = containerExcludeExtraSizeValue < tabContentSizeValue + addSizeValue; + const visibleTabContentValue = needScroll + ? containerExcludeExtraSizeValue - operationSizeValue + : containerExcludeExtraSizeValue - addSizeValue; // ========================== Util ========================= const operationsHiddenClassName = `${prefixCls}-nav-operations-hidden`; @@ -133,13 +132,13 @@ function TabNavList(props: TabNavListProps, ref: React.Ref) { let transformMax = 0; if (!tabPositionTopOrBottom) { - transformMin = Math.min(0, wrapperHeight - wrapperScrollHeight); + transformMin = Math.min(0, visibleTabContentValue - tabContentSizeValue); transformMax = 0; } else if (rtl) { transformMin = 0; - transformMax = Math.max(0, wrapperScrollWidth - wrapperWidth); + transformMax = Math.max(0, tabContentSizeValue - visibleTabContentValue); } else { - transformMin = Math.min(0, wrapperWidth - wrapperScrollWidth); + transformMin = Math.min(0, visibleTabContentValue - tabContentSizeValue); transformMax = 0; } @@ -174,18 +173,14 @@ function TabNavList(props: TabNavListProps, ref: React.Ref) { }); } - if (tabPositionTopOrBottom) { - // Skip scroll if place is enough - if (wrapperWidth >= wrapperScrollWidth) { - return false; - } + // Skip scroll if place is enough + if (containerExcludeExtraSizeValue >= tabContentSizeValue) { + return false; + } + if (tabPositionTopOrBottom) { doMove(setTransformLeft, offsetX); } else { - if (wrapperHeight >= wrapperScrollHeight) { - return false; - } - doMove(setTransformTop, offsetY); } @@ -206,8 +201,25 @@ function TabNavList(props: TabNavListProps, ref: React.Ref) { return clearTouchMoving; }, [lockAnimation]); + // ===================== Visible Range ===================== + // Render tab node & collect tab offset + const [visibleStart, visibleEnd] = useVisibleRange( + tabOffsets, + // Container + visibleTabContentValue, + // Transform + tabPositionTopOrBottom ? transformLeft : transformTop, + // Tabs + tabContentSizeValue, + // Add + addSizeValue, + // Operation + operationSizeValue, + { ...props, tabs }, + ); + // ========================= Scroll ======================== - function scrollToTab(key = activeKey) { + const scrollToTab = (key = activeKey) => { const tabOffset = tabOffsets.get(key) || { width: 0, height: 0, @@ -224,15 +236,15 @@ function TabNavList(props: TabNavListProps, ref: React.Ref) { if (rtl) { if (tabOffset.right < transformLeft) { newTransform = tabOffset.right; - } else if (tabOffset.right + tabOffset.width > transformLeft + wrapperWidth) { - newTransform = tabOffset.right + tabOffset.width - wrapperWidth; + } else if (tabOffset.right + tabOffset.width > transformLeft + visibleTabContentValue) { + newTransform = tabOffset.right + tabOffset.width - visibleTabContentValue; } } // LTR else if (tabOffset.left < -transformLeft) { newTransform = -tabOffset.left; - } else if (tabOffset.left + tabOffset.width > -transformLeft + wrapperWidth) { - newTransform = -(tabOffset.left + tabOffset.width - wrapperWidth); + } else if (tabOffset.left + tabOffset.width > -transformLeft + visibleTabContentValue) { + newTransform = -(tabOffset.left + tabOffset.width - visibleTabContentValue); } setTransformTop(0); @@ -243,37 +255,16 @@ function TabNavList(props: TabNavListProps, ref: React.Ref) { if (tabOffset.top < -transformTop) { newTransform = -tabOffset.top; - } else if (tabOffset.top + tabOffset.height > -transformTop + wrapperHeight) { - newTransform = -(tabOffset.top + tabOffset.height - wrapperHeight); + } else if (tabOffset.top + tabOffset.height > -transformTop + visibleTabContentValue) { + newTransform = -(tabOffset.top + tabOffset.height - visibleTabContentValue); } setTransformLeft(0); setTransformTop(alignInRange(newTransform)); } - } + }; // ========================== Tab ========================== - // Render tab node & collect tab offset - - const [visibleStart, visibleEnd] = useVisibleRange( - tabOffsets, - { - width: wrapperWidth, - height: wrapperHeight, - left: transformLeft, - top: transformTop, - }, - { - width: wrapperScrollWidth, - height: wrapperScrollHeight, - }, - { - width: addWidth, - height: addHeight, - }, - { ...props, tabs }, - ); - const tabNodeStyle: React.CSSProperties = {}; if (tabPosition === 'top' || tabPosition === 'bottom') { tabNodeStyle[rtl ? 'marginRight' : 'marginLeft'] = tabBarGutter; @@ -321,21 +312,26 @@ function TabNavList(props: TabNavListProps, ref: React.Ref) { const onListHolderResize = useRaf(() => { // Update wrapper records - const offsetWidth = tabsWrapperRef.current?.offsetWidth || 0; - const offsetHeight = tabsWrapperRef.current?.offsetHeight || 0; - const newAddWidth = innerAddButtonRef.current?.offsetWidth || 0; - const newAddHeight = innerAddButtonRef.current?.offsetHeight || 0; - - setWrapperWidth(offsetWidth); - setWrapperHeight(offsetHeight); - setAddWidth(newAddWidth); - setAddHeight(newAddHeight); - - const newWrapperScrollWidth = (tabListRef.current?.offsetWidth || 0) - newAddWidth; - const newWrapperScrollHeight = (tabListRef.current?.offsetHeight || 0) - newAddHeight; - - setWrapperScrollWidth(newWrapperScrollWidth); - setWrapperScrollHeight(newWrapperScrollHeight); + const containerSize = getSize(containerRef); + const extraLeftSize = getSize(extraLeftRef); + const extraRightSize = getSize(extraRightRef); + setContainerExcludeExtraSize([ + containerSize[0] - extraLeftSize[0] - extraRightSize[0], + containerSize[1] - extraLeftSize[1] - extraRightSize[1], + ]); + + const newAddSize = getSize(innerAddButtonRef); + setAddSize(newAddSize); + + const newOperationSize = getSize(operationsRef); + setOperationSize(newOperationSize); + + // Which includes add button size + const tabContentFullSize = getSize(tabListRef); + setTabContentSize([ + tabContentFullSize[0] - newAddSize[0], + tabContentFullSize[1] - newAddSize[1], + ]); // Update buttons records setTabSizes(() => { @@ -400,12 +396,14 @@ function TabNavList(props: TabNavListProps, ref: React.Ref) { // ========================= Effect ======================== useEffect(() => { scrollToTab(); - }, [activeKey, activeTabOffset, tabOffsets, tabPositionTopOrBottom]); + // eslint-disable-next-line + }, [activeKey, stringify(activeTabOffset), stringify(tabOffsets), tabPositionTopOrBottom]); // Should recalculate when rtl changed useEffect(() => { onListHolderResize(); - }, [rtl, tabBarGutter, activeKey, tabs.map(tab => tab.key).join('_')]); + // eslint-disable-next-line + }, [rtl]); // ========================= Render ======================== const hasDropdown = !!hiddenTabs.length; @@ -418,30 +416,30 @@ function TabNavList(props: TabNavListProps, ref: React.Ref) { if (tabPositionTopOrBottom) { if (rtl) { pingRight = transformLeft > 0; - pingLeft = transformLeft + wrapperWidth < wrapperScrollWidth; + pingLeft = transformLeft + containerExcludeExtraSizeValue < tabContentSizeValue; } else { pingLeft = transformLeft < 0; - pingRight = -transformLeft + wrapperWidth < wrapperScrollWidth; + pingRight = -transformLeft + containerExcludeExtraSizeValue < tabContentSizeValue; } } else { pingTop = transformTop < 0; - pingBottom = -transformTop + wrapperHeight < wrapperScrollHeight; + pingBottom = -transformTop + containerExcludeExtraSizeValue < tabContentSizeValue; } return ( -
{ - // No need animation when use keyboard - doLockAnimation(); - }} - > - - - + +
{ + // No need animation when use keyboard + doLockAnimation(); + }} + > + +
) {
-
- - - -
+ + + + + ); /* eslint-enable */ } diff --git a/src/hooks/useVisibleRange.ts b/src/hooks/useVisibleRange.ts index 0b8ed209..5dafe8e5 100644 --- a/src/hooks/useVisibleRange.ts +++ b/src/hooks/useVisibleRange.ts @@ -4,34 +4,29 @@ import type { TabNavListProps } from '../TabNavList'; const DEFAULT_SIZE = { width: 0, height: 0, left: 0, top: 0, right: 0 }; +export type ContainerSizeInfo = [width: number, height: number, left: number, top: number]; + export default function useVisibleRange( tabOffsets: TabOffsetMap, - containerSize: { width: number; height: number; left: number; top: number }, - tabContentNodeSize: { width: number; height: number }, - addNodeSize: { width: number; height: number }, + visibleTabContentValue: number, + transform: number, + tabContentSizeValue: number, + addNodeSizeValue: number, + operationNodeSizeValue: number, { tabs, tabPosition, rtl }: { tabs: Tab[] } & TabNavListProps, -): [number, number] { - let unit: 'width' | 'height'; +): [visibleStart: number, visibleEnd: number] { + let charUnit: 'width' | 'height'; let position: 'left' | 'top' | 'right'; let transformSize: number; if (['top', 'bottom'].includes(tabPosition)) { - unit = 'width'; + charUnit = 'width'; position = rtl ? 'right' : 'left'; - transformSize = Math.abs(containerSize.left); + transformSize = Math.abs(transform); } else { - unit = 'height'; + charUnit = 'height'; position = 'top'; - transformSize = -containerSize.top; - } - - const basicSize = containerSize[unit]; - const tabContentSize = tabContentNodeSize[unit]; - const addSize = addNodeSize[unit]; - - let mergedBasicSize = basicSize; - if (tabContentSize + addSize > basicSize && tabContentSize < basicSize) { - mergedBasicSize = basicSize - addSize; + transformSize = -transform; } return useMemo(() => { @@ -43,7 +38,7 @@ export default function useVisibleRange( let endIndex = len; for (let i = 0; i < len; i += 1) { const offset = tabOffsets.get(tabs[i].key) || DEFAULT_SIZE; - if (offset[position] + offset[unit] > transformSize + mergedBasicSize) { + if (offset[position] + offset[charUnit] > transformSize + visibleTabContentValue) { endIndex = i - 1; break; } @@ -61,8 +56,11 @@ export default function useVisibleRange( return [startIndex, endIndex]; }, [ tabOffsets, + visibleTabContentValue, + tabContentSizeValue, + addNodeSizeValue, + operationNodeSizeValue, transformSize, - mergedBasicSize, tabPosition, tabs.map(tab => tab.key).join('_'), rtl, diff --git a/src/interface.ts b/src/interface.ts index 14ef5c7b..79b33ab6 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -3,6 +3,8 @@ import type { CSSMotionProps } from 'rc-motion'; import type { TabNavListProps } from './TabNavList'; import type { TabPaneProps } from './TabPanelList/TabPane'; +export type SizeInfo = [width: number, height: number]; + export type TabSizeMap = Map< React.Key, { width: number; height: number; left: number; top: number } diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 00000000..e6ee069f --- /dev/null +++ b/src/util.ts @@ -0,0 +1,18 @@ +/** + * We trade Map as deps which may change with same value but different ref object. + * We should make it as hash for deps + * */ +export function stringify(obj: Record | Map) { + let tgt: Record; + + if (obj instanceof Map) { + tgt = {} as any; + obj.forEach((v, k) => { + tgt[k] = v; + }); + } else { + tgt = obj; + } + + return JSON.stringify(tgt); +} diff --git a/tests/common/util.tsx b/tests/common/util.tsx index 3dda597c..bbac3c29 100644 --- a/tests/common/util.tsx +++ b/tests/common/util.tsx @@ -4,43 +4,83 @@ import type { ReactWrapper } from 'enzyme'; import Tabs from '../../src'; import type { TabsProps } from '../../src/Tabs'; -export function getOffsetSizeFunc( - info: { - list?: number; - wrapper?: number; - add?: number; - operation?: number; - more?: number; - dropdown?: number; - } = {}, -) { +/** + * | container | + * | extra left | | operation | extra right | + * | | list | + * | | tab content | add | + */ + +export interface HackInfo { + container?: number; + tabNode?: number; + add?: number; + more?: number; + extra?: number; +} + +export function getOffsetSizeFunc(info: HackInfo = {}) { return function getOffsetSize() { - if (this.className.includes('rc-tabs-tab')) { - return 20; + const { container = 50, extra = 10, tabNode = 20, add = 10, more = 10 } = info; + + if (this.classList.contains('rc-tabs-nav')) { + return container; } - if (this.className.includes('rc-tabs-nav-list')) { - return info.list || 5 * 20 + 10; + + if (this.classList.contains('rc-tabs-extra-content')) { + return extra; } - if (this.className.includes('rc-tabs-nav-wrap')) { - return info.wrapper || 40; + + if (this.classList.contains('rc-tabs-nav-list')) { + return this.querySelectorAll('.rc-tabs-tab').length * tabNode + add; } - if (this.className.includes('rc-tabs-nav-add')) { - return info.add || 10; + + if (this.classList.contains('rc-tabs-tab')) { + return tabNode; } - if (this.className.includes('rc-tabs-nav-operations')) { - return info.operation || 10; + + if (this.classList.contains('rc-tabs-nav-add')) { + return more; } - if (this.className.includes('rc-tabs-nav-more')) { - return info.more || 10; + + if (this.classList.contains('rc-tabs-nav-more')) { + return more; } - if (this.className.includes('rc-tabs-dropdown')) { - return info.dropdown || 10; + + if (this.classList.contains('rc-tabs-nav-operations')) { + return this.querySelector('.rc-tabs-nav-add') ? more + add : more; } + // if (this.className.includes('rc-tabs-nav-list')) { + // return info.list || 5 * 20 + 10; + // } + // if (this.className.includes('rc-tabs-nav-add')) { + // return info.add || 10; + // } + // if (this.className.includes('rc-tabs-nav-operations')) { + // return info.operation || 10; + // } + // if (this.className.includes('rc-tabs-nav-more')) { + // return info.more || 10; + // } + // if (this.className.includes('rc-tabs-dropdown')) { + // return info.dropdown || 10; + // } + throw new Error(`className not match ${this.className}`); }; } +export function btnOffsetPosition() { + // eslint-disable-next-line @typescript-eslint/no-invalid-this + const btn = this as HTMLButtonElement; + const btnList = Array.from(btn.parentNode.childNodes).filter(ele => + (ele as HTMLElement).className.includes('rc-tabs-tab'), + ); + const index = btnList.indexOf(btn); + return 20 * index; +} + export function getTransformX(wrapper: ReactWrapper) { const { transform } = wrapper.find('.rc-tabs-nav-list').props().style; const match = transform.match(/\(([-\d.]+)px/); @@ -66,7 +106,6 @@ export function getTransformY(wrapper: ReactWrapper) { export function getTabs(props: TabsProps = null) { return ( ); } diff --git a/tests/mobile.test.tsx b/tests/mobile.test.tsx index 1bbe4eb4..538a3b5a 100644 --- a/tests/mobile.test.tsx +++ b/tests/mobile.test.tsx @@ -5,7 +5,7 @@ import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; import { act } from 'react-dom/test-utils'; import Tabs from '../src'; import type { TabsProps } from '../src'; -import { getTransformX } from './common/util'; +import { btnOffsetPosition, getOffsetSizeFunc, getTransformX } from './common/util'; describe('Tabs.Mobile', () => { const originAgent = navigator.userAgent; @@ -43,48 +43,23 @@ describe('Tabs.Mobile', () => { let btnSpy: ReturnType; let dateSpy: ReturnType; let timestamp: number = 0; - let rtl = false; beforeAll(() => { dateSpy = jest.spyOn(Date, 'now').mockImplementation(() => timestamp); - btnSpy = spyElementPrototypes(HTMLButtonElement, { + domSpy = spyElementPrototypes(HTMLElement, { + scrollIntoView: () => {}, offsetWidth: { - get: () => 20, + get: getOffsetSizeFunc(), + }, + offsetHeight: { + get: getOffsetSizeFunc(), }, offsetLeft: { - get() { - // Mock button offset - const btn = this as HTMLButtonElement; - const btnList = Array.from(btn.parentNode.childNodes).filter(ele => - (ele as HTMLElement).className.includes('rc-tabs-tab'), - ); - const index = btnList.indexOf(btn); - if (rtl) { - return 20 * (btnList.length - index - 1); - } - return 20 * index; - }, + get: btnOffsetPosition, }, - }); - - domSpy = spyElementPrototypes(HTMLDivElement, { - offsetWidth: { - get() { - if (this.className.includes('rc-tabs-tab')) { - return 20; - } - if (this.className.includes('rc-tabs-nav-list')) { - return tabCount * 20; - } - if (this.className.includes('rc-tabs-nav-wrap')) { - return 40; - } - if (this.className.includes('rc-tabs-nav-operations')) { - return 5; - } - throw new Error(`className not match ${this.className}`); - }, + offsetTop: { + get: btnOffsetPosition, }, }); }); @@ -153,10 +128,6 @@ describe('Tabs.Mobile', () => { } describe('LTR', () => { - beforeAll(() => { - rtl = false; - }); - it('slow move', () => { jest.useFakeTimers(); const wrapper = mount(getTabs({ tabPosition: 'top' })); @@ -201,10 +172,6 @@ describe('Tabs.Mobile', () => { }); describe('RTL', () => { - beforeAll(() => { - rtl = true; - }); - it('not out of the edge', () => { jest.useFakeTimers(); const wrapper = mount(getTabs({ direction: 'rtl' })); diff --git a/tests/overflow.test.tsx b/tests/overflow.test.tsx index 94491826..5eec2c1f 100644 --- a/tests/overflow.test.tsx +++ b/tests/overflow.test.tsx @@ -4,12 +4,14 @@ import KeyCode from 'rc-util/lib/KeyCode'; import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; import { act } from 'react-dom/test-utils'; import { + btnOffsetPosition, getOffsetSizeFunc, getTabs, getTransformX, getTransformY, triggerResize, } from './common/util'; +import type { HackInfo } from './common/util'; import type { TabsProps } from '../src'; import Tabs from '../src'; @@ -17,22 +19,18 @@ describe('Tabs.Overflow', () => { let domSpy: ReturnType; let holder: HTMLDivElement; - const hackOffsetInfo: { list?: number } = {}; + const hackOffsetInfo: HackInfo = {}; + + beforeEach(() => { + Object.keys(hackOffsetInfo).forEach(key => { + delete hackOffsetInfo[key]; + }); + }); beforeAll(() => { holder = document.createElement('div'); document.body.appendChild(holder); - function btnOffsetPosition() { - // eslint-disable-next-line @typescript-eslint/no-invalid-this - const btn = this as HTMLButtonElement; - const btnList = Array.from(btn.parentNode.childNodes).filter(ele => - (ele as HTMLElement).className.includes('rc-tabs-tab'), - ); - const index = btnList.indexOf(btn); - return 20 * index; - } - domSpy = spyElementPrototypes(HTMLElement, { scrollIntoView: () => {}, offsetWidth: { @@ -161,7 +159,7 @@ describe('Tabs.Overflow', () => { ['top', 'left'].forEach((tabPosition: any) => { list.forEach(({ name, x1, y1, x2, y2 }) => { - it(`should ${tabPosition} work for ${name}`, () => { + it(`should tab pos '${tabPosition}' work for ${name}`, () => { jest.useFakeTimers(); const wrapper = mount(getTabs({ tabPosition }), { attachTo: holder }); @@ -175,20 +173,20 @@ describe('Tabs.Overflow', () => { const node = wrapper.find('.rc-tabs-nav-wrap').instance() as unknown as HTMLElement; act(() => { - const touchStart = new WheelEvent('wheel', { + const wheel = new WheelEvent('wheel', { deltaX: x1, deltaY: y1, }); - node.dispatchEvent(touchStart); + node.dispatchEvent(wheel); jest.runAllTimers(); }); act(() => { - const touchStart = new WheelEvent('wheel', { + const wheel = new WheelEvent('wheel', { deltaX: x2, deltaY: y2, }); - node.dispatchEvent(touchStart); + node.dispatchEvent(wheel); jest.runAllTimers(); }); @@ -207,8 +205,6 @@ describe('Tabs.Overflow', () => { ['top', 'left'].forEach((tabPosition: any) => { it(`no need if place is enough: ${tabPosition}`, () => { - hackOffsetInfo.list = 20; - jest.useFakeTimers(); const wrapper = mount( getTabs({ @@ -230,22 +226,21 @@ describe('Tabs.Overflow', () => { // Wheel to move const node = wrapper.find('.rc-tabs-nav-wrap').instance() as unknown as HTMLElement; - const touchStart = new WheelEvent('wheel', { + const wheel = new WheelEvent('wheel', { deltaX: 20, deltaY: 20, }); - touchStart.preventDefault = jest.fn(); + wheel.preventDefault = jest.fn(); act(() => { - node.dispatchEvent(touchStart); + node.dispatchEvent(wheel); jest.runAllTimers(); }); - expect(touchStart.preventDefault).not.toHaveBeenCalled(); + expect(wheel.preventDefault).not.toHaveBeenCalled(); expect(getTransformX(wrapper)).toEqual(0); jest.useRealTimers(); - hackOffsetInfo.list = undefined; }); }); }); @@ -333,7 +328,6 @@ describe('Tabs.Overflow', () => { list.forEach(({ name, trigger }) => { it(`remove by ${name} in dropdown menu`, () => { - console.log('run here'); jest.useFakeTimers(); const onEdit = jest.fn(); const wrapper = mount(getTabs({ editable: { onEdit } })); @@ -346,8 +340,10 @@ describe('Tabs.Overflow', () => { // Click to open wrapper.find('.rc-tabs-nav-more').simulate('mouseenter'); - jest.runAllTimers(); - wrapper.update(); + act(() => { + jest.runAllTimers(); + wrapper.update(); + }); const first = wrapper.find('.rc-tabs-dropdown-menu-item-remove').first(); trigger(first); @@ -355,10 +351,12 @@ describe('Tabs.Overflow', () => { // Should be button to enable press SPACE key to trigger expect(first.instance() instanceof HTMLButtonElement).toBeTruthy(); - expect(onEdit).toHaveBeenCalledWith('remove', { - key: 'cute', - event: expect.anything(), - }); + expect(onEdit).toHaveBeenCalledWith( + 'remove', + expect.objectContaining({ + key: 'bamboo', + }), + ); wrapper.unmount(); jest.useRealTimers();