import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; import React from 'react'; import { act } from 'react-dom/test-utils'; import { fireEvent, render, triggerResize, waitFakeTimer, waitFor } from '../../../tests/utils'; import type { EllipsisConfig } from '../Base'; import Base from '../Base'; jest.mock('copy-to-clipboard'); jest.mock('../../_util/styleChecker', () => ({ isStyleSupport: () => true, })); describe('Typography.Ellipsis', () => { const LINE_STR_COUNT = 20; const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); let mockRectSpy: ReturnType; let getWidthTimes = 0; let computeSpy: jest.SpyInstance; beforeAll(() => { jest.useFakeTimers(); mockRectSpy = spyElementPrototypes(HTMLElement, { offsetHeight: { get() { let html = this.innerHTML; html = html.replace(/<[^>]*>/g, ''); const lines = Math.ceil(html.length / LINE_STR_COUNT); return lines * 16; }, }, offsetWidth: { get: () => { getWidthTimes += 1; return 100; }, }, getBoundingClientRect() { let html = this.innerHTML; html = html.replace(/<[^>]*>/g, ''); const lines = Math.ceil(html.length / LINE_STR_COUNT); return { height: lines * 16 }; }, }); computeSpy = jest .spyOn(window, 'getComputedStyle') .mockImplementation(() => ({ fontSize: 12 } as unknown as CSSStyleDeclaration)); }); afterEach(() => { errorSpy.mockReset(); getWidthTimes = 0; }); afterAll(() => { jest.useRealTimers(); errorSpy.mockRestore(); mockRectSpy.mockRestore(); computeSpy.mockRestore(); }); const fullStr = 'Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light'; it('should trigger update', async () => { const ref = React.createRef(); const onEllipsis = jest.fn(); const { container, rerender, unmount } = render( {fullStr} , ); triggerResize(ref.current!); await waitFakeTimer(); expect(container.firstChild?.textContent).toEqual('Bamboo is Little ...'); expect(onEllipsis).toHaveBeenCalledWith(true); onEllipsis.mockReset(); // Second resize rerender( {fullStr} , ); expect(container.textContent).toEqual('Bamboo is Little Light Bamboo is Litt...'); expect(onEllipsis).not.toHaveBeenCalled(); // Third resize rerender( {fullStr} , ); expect(container.querySelector('p')?.textContent).toEqual(fullStr); expect(onEllipsis).toHaveBeenCalledWith(false); unmount(); }); it('support css multiple lines', async () => { const { container: wrapper } = render( {fullStr} , ); expect( wrapper.querySelectorAll('.ant-typography-ellipsis-multiple-line').length, ).toBeGreaterThan(0); expect( ( wrapper.querySelector('.ant-typography-ellipsis-multiple-line') ?.style as any )?.WebkitLineClamp, ).toEqual('2'); }); it('string with parentheses', async () => { const parenthesesStr = `Ant Design, a design language (for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team. Ant Design, a design language for background applications, is refined by Ant UED Team.`; const ref = React.createRef(); const onEllipsis = jest.fn(); const { container: wrapper, unmount } = render( {parenthesesStr} , ); triggerResize(ref.current!); await waitFakeTimer(); expect(wrapper.firstChild?.textContent).toEqual('Ant Design, a des...'); const ellipsisSpans = wrapper.querySelectorAll('span[aria-hidden]'); expect(ellipsisSpans[ellipsisSpans.length - 1].textContent).toEqual('...'); onEllipsis.mockReset(); unmount(); }); it('should middle ellipsis', async () => { const suffix = '--suffix'; const ref = React.createRef(); const { container: wrapper, unmount } = render( {fullStr} , ); triggerResize(ref.current!); await waitFakeTimer(); expect(wrapper.querySelector('p')?.textContent).toEqual('Bamboo is...--suffix'); unmount(); }); it('should front or middle ellipsis', async () => { const suffix = '--The information is very important'; const ref = React.createRef(); const { container: wrapper, rerender, unmount, } = render( {fullStr} , ); triggerResize(ref.current!); await waitFakeTimer(); expect(wrapper.querySelector('p')?.textContent).toEqual( '...--The information is very important', ); rerender( {fullStr} , ); expect(wrapper.querySelector('p')?.textContent).toEqual( 'Ba...--The information is very important', ); rerender( {fullStr} , ); expect(wrapper.querySelector('p')?.textContent).toEqual(fullStr + suffix); unmount(); }); it('connect children', async () => { const bamboo = 'Bamboo'; const is = ' is '; const ref = React.createRef(); const { container: wrapper } = render( {bamboo} {is} Little Light , ); triggerResize(ref.current!); await waitFakeTimer(); expect(wrapper.textContent).toEqual('Bamboo is Little...'); }); it('should expandable work', async () => { const onExpand = jest.fn(); const { container: wrapper } = render( {fullStr} , ); fireEvent.click(wrapper.querySelector('.ant-typography-expand')!); expect(onExpand).toHaveBeenCalled(); expect(wrapper.querySelector('p')?.textContent).toEqual(fullStr); }); it('should have custom expand style', async () => { const symbol = 'more'; const { container } = render( {fullStr} , ); expect(container.querySelector('.ant-typography-expand')?.textContent).toEqual('more'); }); describe('native css ellipsis', () => { it('can use css ellipsis', () => { const { container } = render(); expect(container.querySelector('.ant-typography-ellipsis-single-line')).toBeTruthy(); }); // https://github.com/ant-design/ant-design/issues/36786 it('Tooltip should recheck on parent visible change', () => { const originIntersectionObserver = global.IntersectionObserver; let elementChangeCallback: () => void; const observeFn = jest.fn(); const disconnectFn = jest.fn(); (global as any).IntersectionObserver = class MockIntersectionObserver { constructor(callback: () => IntersectionObserverCallback) { elementChangeCallback = callback; } observe = observeFn; disconnect = disconnectFn; }; const { container, unmount } = render(); expect(observeFn).toHaveBeenCalled(); // Hide first act(() => { elementChangeCallback?.(); }); // Trigger visible should trigger recheck getWidthTimes = 0; Object.defineProperty(container.querySelector('.ant-typography'), 'offsetParent', { get: () => document.body, }); act(() => { elementChangeCallback?.(); }); expect(getWidthTimes).toBeGreaterThan(0); unmount(); expect(disconnectFn).toHaveBeenCalled(); global.IntersectionObserver = originIntersectionObserver; }); it('should calculate padding', () => { const { container } = render( , ); expect(container.querySelector('.ant-typography-ellipsis-single-line')).toBeTruthy(); }); }); describe('should tooltip support', () => { let domSpy: ReturnType; beforeAll(() => { domSpy = spyElementPrototypes(HTMLElement, { offsetWidth: { get: () => 100, }, scrollWidth: { get: () => 200, }, }); }); afterAll(() => { domSpy.mockRestore(); }); async function getWrapper(tooltip?: EllipsisConfig['tooltip']) { const ref = React.createRef(); const wrapper = render( {fullStr} , ); triggerResize(ref.current!); await waitFakeTimer(); return wrapper; } it('boolean', async () => { const { container, baseElement } = await getWrapper(true); fireEvent.mouseEnter(container.firstChild!); await waitFor(() => { expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull(); }); }); it('customize', async () => { const { container, baseElement } = await getWrapper('Bamboo is Light'); fireEvent.mouseEnter(container.firstChild!); await waitFor(() => { expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull(); }); }); it('tooltip props', async () => { const { container, baseElement } = await getWrapper({ title: 'This is tooltip', className: 'tooltip-class-name', }); fireEvent.mouseEnter(container.firstChild!); await waitFor(() => { expect(container.querySelector('.tooltip-class-name')).toBeTruthy(); expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull(); }); }); it('tooltip title true', async () => { const { container, baseElement } = await getWrapper({ title: true, className: 'tooltip-class-name', }); fireEvent.mouseEnter(container.firstChild!); await waitFor(() => { expect(container.querySelector('.tooltip-class-name')).toBeTruthy(); expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull(); }); }); it('tooltip element', async () => { const { container, baseElement } = await getWrapper(
title
, ); fireEvent.mouseEnter(container.firstChild!); await waitFor(() => { expect(container.querySelector('.tooltip-class-name')).toBeTruthy(); expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull(); }); }); }); it('js ellipsis should show aria-label', () => { const { container: titleWrapper } = render( , ); expect(titleWrapper.querySelector('.ant-typography')?.getAttribute('aria-label')).toEqual( 'bamboo', ); const { container: tooltipWrapper } = render( , ); expect(tooltipWrapper.querySelector('.ant-typography')?.getAttribute('aria-label')).toEqual( 'little', ); }); it('should display tooltip if line clamp', async () => { mockRectSpy = spyElementPrototypes(HTMLElement, { scrollHeight: { get() { let html = this.innerHTML; html = html.replace(/<[^>]*>/g, ''); const lines = Math.ceil(html.length / LINE_STR_COUNT); return lines * 16; }, }, offsetHeight: { get: () => 32, }, offsetWidth: { get: () => 100, }, scrollWidth: { get: () => 100, }, }); const ref = React.createRef(); const { container, baseElement } = render( Ant Design, a design language for background applications, is refined by Ant UED Team. , ); triggerResize(ref.current!); await waitFakeTimer(); fireEvent.mouseEnter(container.firstChild!); await waitFor(() => { expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull(); }); mockRectSpy.mockRestore(); }); });