diff --git a/packages/components/affix/__tests__/affix.test.tsx b/packages/components/affix/__tests__/affix.test.tsx index 80664da6fb..38f4ab4f4f 100644 --- a/packages/components/affix/__tests__/affix.test.tsx +++ b/packages/components/affix/__tests__/affix.test.tsx @@ -3,7 +3,7 @@ import { describe, mockTimeout, render, vi } from '@test/utils'; import Affix from '../index'; -describe('Affix 组件测试', () => { +describe('Affix', () => { const mockFn = vi.spyOn(HTMLDivElement.prototype, 'getBoundingClientRect'); const mockScrollTo = async (top: number) => { mockFn.mockImplementation(() => ({ @@ -23,130 +23,133 @@ describe('Affix 组件测试', () => { await mockScrollTo(0); }); - test('render perfectly', async () => { - const { queryByText } = render( - -
固钉
-
, - ); - - expect(queryByText('固钉')).toBeInTheDocument(); - }); - - test('className', async () => { - const { container } = render( - -
固钉
-
, - ); - - const affixElement = container.querySelector('.custom-class-name'); - expect(affixElement).not.toBeNull(); - expect(affixElement?.className).toContain('custom-class-name'); - }); - - test('style', async () => { - const { container } = render( - -
固钉
-
, - ); - const affixElement = container.querySelector('.custom-class-name'); - expect(affixElement).not.toBeNull(); - expect((affixElement as HTMLElement)?.style.background).toBe('red'); - }); - - test('content', async () => { - const Children = () =>
固钉
; - const { queryByText } = render(} />); - expect(queryByText('固钉')).toBeInTheDocument(); - }); - - test('offsetTop trigger onFixedChange and zIndex', async () => { - const onFixedChangeMock = vi.fn(); - - const { getByText } = render( - -
固钉
-
, - ); - // 默认 - expect(onFixedChangeMock).toHaveBeenCalledTimes(0); - expect(getByText('固钉').parentNode).not.toHaveClass('t-affix'); - expect(getByText('固钉').parentElement?.style.zIndex).toBe(''); - - // offsetTop - await mockScrollTo(30); - await mockScrollTo(10); - await mockTimeout(() => false, 200); - expect(onFixedChangeMock).toHaveBeenCalledTimes(1); - - expect(getByText('固钉').parentNode).toHaveClass('t-affix'); - expect(getByText('固钉').parentElement?.style.zIndex).toBe('2'); - }); - - test('offsetBottom trigger onFixedChange and zIndex', async () => { - const onFixedChangeMock = vi.fn(); - - const { getByText } = render( - -
固钉
-
, - ); - // 默认 - expect(onFixedChangeMock).toHaveBeenCalledTimes(0); - expect(getByText('固钉').parentNode).not.toHaveClass('t-affix'); - expect(getByText('固钉').parentElement?.style.zIndex).toBe(''); - - const { innerHeight } = window; - mockFn.mockImplementation(() => ({ - top: innerHeight - 10, - bottom: innerHeight, - left: 0, - right: 0, - height: 10, - width: 0, - x: 0, - y: 0, - toJSON: () => ({}), - })); - await mockTimeout(() => false, 200); - expect(onFixedChangeMock).toHaveBeenCalledTimes(1); - - expect(getByText('固钉').parentNode).toHaveClass('t-affix'); - expect(getByText('固钉').parentElement?.style.zIndex).toBe('2'); + describe('props', () => { + test('className', async () => { + const { container } = render( + +
固钉
+
, + ); + + const affixElement = container.querySelector('.custom-class-name'); + expect(affixElement).not.toBeNull(); + expect(affixElement?.className).toContain('custom-class-name'); + }); + + test('style', async () => { + const { container } = render( + +
固钉
+
, + ); + const affixElement = container.querySelector('.custom-class-name'); + expect(affixElement).not.toBeNull(); + expect((affixElement as HTMLElement)?.style.background).toBe('red'); + }); + test('content', async () => { + const Children = () =>
固钉
; + const { queryByText } = render(} />); + expect(queryByText('固钉')).toBeInTheDocument(); + }); }); - test('offsetTop and offsetBottom trigger onFixedChange and zIndex', async () => { - const onFixedChangeMock = vi.fn(); - - const { getByText } = render( - -
固钉
-
, - ); - // 默认 - expect(onFixedChangeMock).toHaveBeenCalledTimes(0); - expect(getByText('固钉').parentNode).not.toHaveClass('t-affix'); - expect(getByText('固钉').parentElement?.style.zIndex).toBe(''); - - // offsetTop - await mockScrollTo(30); - await mockScrollTo(10); - await mockTimeout(() => false, 200); - expect(onFixedChangeMock).toHaveBeenCalledTimes(1); - - // offsetBottom - const isWindow = typeof window !== 'undefined' && window.innerHeight !== undefined; - const { clientHeight } = document.documentElement; - const { innerHeight } = window; - await mockScrollTo((isWindow ? innerHeight : clientHeight) - 40); - await mockScrollTo(isWindow ? innerHeight : clientHeight); - await mockTimeout(() => false, 200); - - expect(onFixedChangeMock).toHaveBeenCalledTimes(1); - - expect(getByText('固钉').parentNode).toHaveClass('t-affix'); - expect(getByText('固钉').parentElement?.style.zIndex).toBe('2'); + describe('scenarios', () => { + test('render perfectly', async () => { + const { queryByText } = render( + +
固钉
+
, + ); + + expect(queryByText('固钉')).toBeInTheDocument(); + }); + + test('offsetTop trigger onFixedChange and zIndex', async () => { + const onFixedChangeMock = vi.fn(); + + const { getByText } = render( + +
固钉
+
, + ); + // 默认 + expect(onFixedChangeMock).toHaveBeenCalledTimes(0); + expect(getByText('固钉').parentNode).not.toHaveClass('t-affix'); + expect(getByText('固钉').parentElement?.style.zIndex).toBe(''); + + // offsetTop + await mockScrollTo(30); + await mockScrollTo(10); + await mockTimeout(() => false, 200); + expect(onFixedChangeMock).toHaveBeenCalledTimes(1); + + expect(getByText('固钉').parentNode).toHaveClass('t-affix'); + expect(getByText('固钉').parentElement?.style.zIndex).toBe('2'); + }); + + test('offsetBottom trigger onFixedChange and zIndex', async () => { + const onFixedChangeMock = vi.fn(); + + const { getByText } = render( + +
固钉
+
, + ); + // 默认 + expect(onFixedChangeMock).toHaveBeenCalledTimes(0); + expect(getByText('固钉').parentNode).not.toHaveClass('t-affix'); + expect(getByText('固钉').parentElement?.style.zIndex).toBe(''); + + const { innerHeight } = window; + mockFn.mockImplementation(() => ({ + top: innerHeight - 10, + bottom: innerHeight, + left: 0, + right: 0, + height: 10, + width: 0, + x: 0, + y: 0, + toJSON: () => ({}), + })); + await mockTimeout(() => false, 200); + expect(onFixedChangeMock).toHaveBeenCalledTimes(1); + + expect(getByText('固钉').parentNode).toHaveClass('t-affix'); + expect(getByText('固钉').parentElement?.style.zIndex).toBe('2'); + }); + + test('offsetTop and offsetBottom trigger onFixedChange and zIndex', async () => { + const onFixedChangeMock = vi.fn(); + + const { getByText } = render( + +
固钉
+
, + ); + // 默认 + expect(onFixedChangeMock).toHaveBeenCalledTimes(0); + expect(getByText('固钉').parentNode).not.toHaveClass('t-affix'); + expect(getByText('固钉').parentElement?.style.zIndex).toBe(''); + + // offsetTop + await mockScrollTo(30); + await mockScrollTo(10); + await mockTimeout(() => false, 200); + expect(onFixedChangeMock).toHaveBeenCalledTimes(1); + + // offsetBottom + const isWindow = typeof window !== 'undefined' && window.innerHeight !== undefined; + const { clientHeight } = document.documentElement; + const { innerHeight } = window; + await mockScrollTo((isWindow ? innerHeight : clientHeight) - 40); + await mockScrollTo(isWindow ? innerHeight : clientHeight); + await mockTimeout(() => false, 200); + + expect(onFixedChangeMock).toHaveBeenCalledTimes(1); + + expect(getByText('固钉').parentNode).toHaveClass('t-affix'); + expect(getByText('固钉').parentElement?.style.zIndex).toBe('2'); + }); }); }); diff --git a/packages/components/alert/__tests__/alert.test.tsx b/packages/components/alert/__tests__/alert.test.tsx index 6eef227c79..9df099b74c 100644 --- a/packages/components/alert/__tests__/alert.test.tsx +++ b/packages/components/alert/__tests__/alert.test.tsx @@ -3,153 +3,156 @@ import { act, fireEvent, mockTimeout, render, vi, waitFor } from '@test/utils'; import Alert from '../Alert'; -describe('Alert 组件测试', () => { +describe('Alert', () => { const testId = 'alert-test-id'; const text = 'Alert内容'; - test('Alert 关闭操作', async () => { - const onClose = vi.fn(); - const onClosed = vi.fn(); - - const { queryByTestId, container, queryByText } = render( - {text}} - onClose={onClose} - onClosed={onClosed} - operation={test content} - />, - ); - - expect(container.querySelector('.t-alert--closing')).not.toBeInTheDocument(); - expect(container.querySelector('#operation-test')).not.toBeNull(); - expect(container.querySelector('#operation-test')).toBeInTheDocument(); - expect(queryByText('title content')).not.toBeNull(); - expect(queryByText('title content')).toBeInTheDocument(); - expect(container.querySelector('.t-alert--error')).not.toBeNull(); - expect(container.querySelector('.t-alert--error')).toBeInTheDocument(); - - const element = queryByTestId(testId); - if (element) { - // 在 react 18.3.1之后 可以在 react 中导出 act,且在18.3.1 之后应该在react中使用 act - act(() => fireEvent.click(element)); - } else { - throw new Error(`Element with testId ${testId} not found`); - } - expect(container.querySelector('.t-alert--closing')).toBeInTheDocument(); - expect(onClose).toHaveBeenCalledTimes(1); - - await mockTimeout(() => expect(onClosed).toHaveBeenCalledTimes(1), 300); - }); - - test('custom close icon render', () => { - const { queryByTestId } = render( - {text}} />, - ); - - expect(queryByTestId(testId)).not.toBeNull(); - expect(queryByTestId(testId)).toBeInTheDocument(); - }); - - test('default close icon render', () => { - const { container } = render(); + describe('props', () => { + test('default close icon render', () => { + const { container } = render(); - expect(container.querySelector('.t-icon-close')).not.toBeNull(); - expect(container.querySelector('.t-icon-close')).toBeInTheDocument(); - }); + expect(container.querySelector('.t-icon-close')).not.toBeNull(); + expect(container.querySelector('.t-icon-close')).toBeInTheDocument(); + }); - test('custom icon render', () => { - const { queryByText } = render(custom icon} title="title content" />); + test('theme icon render', () => { + const { container } = render(); - expect(queryByText('custom icon')).not.toBeNull(); - expect(queryByText('custom icon')).toBeInTheDocument(); - }); + expect(container.querySelector('.t-icon-check-circle-filled')).not.toBeNull(); + expect(container.querySelector('.t-icon-check-circle-filled')).toBeInTheDocument(); + }); - test('theme icon render', () => { - const { container } = render(); + test('default theme icon render', () => { + const { container } = render(); - expect(container.querySelector('.t-icon-check-circle-filled')).not.toBeNull(); - expect(container.querySelector('.t-icon-check-circle-filled')).toBeInTheDocument(); - }); + expect(container.querySelector('.t-icon-info-circle-filled')).not.toBeNull(); + expect(container.querySelector('.t-icon-info-circle-filled')).toBeInTheDocument(); + }); - test('default theme icon render', () => { - const { container } = render(); + test('maxLine', () => { + const { container } = render(); - expect(container.querySelector('.t-icon-info-circle-filled')).not.toBeNull(); - expect(container.querySelector('.t-icon-info-circle-filled')).toBeInTheDocument(); - }); + expect(container.querySelector('.t-alert__collapse')).toBeNull(); + expect(container.querySelector('.t-alert__collapse')).not.toBeInTheDocument(); + }); - test('maxLine', () => { - const { container } = render(); + test('className', () => { + const { container } = render(); + expect(container.querySelector('.custom-class')).not.toBeNull(); + }); - expect(container.querySelector('.t-alert__collapse')).toBeNull(); - expect(container.querySelector('.t-alert__collapse')).not.toBeInTheDocument(); - }); + test('style', () => { + const { container } = render(); + const element = container.querySelector('.t-alert') as HTMLElement; + expect(element?.style.color).toBe('red'); + }); + test('custom close icon render', () => { + const { queryByTestId } = render( + {text}} />, + ); - test('message not collapsed', () => { - const message = [ -
{text}
, -
{text}
, -
- {text} -
, - ]; - const { container } = render(); - const { container: container1 } = render(); - - expect(container.querySelector('.t-alert__collapse')).toBeNull(); - expect(container.querySelector('.t-alert__collapse')).not.toBeInTheDocument(); - expect(container1.querySelector('.t-alert__collapse')).toBeNull(); - expect(container1.querySelector('.t-alert__collapse')).not.toBeInTheDocument(); - }); + expect(queryByTestId(testId)).not.toBeNull(); + expect(queryByTestId(testId)).toBeInTheDocument(); + }); - test('Alert 展开收起操作', async () => { - const massage = [ -
{text}
, -
{text}
, -
- {text} -
, - ]; - const { queryByText, queryByTestId } = render(); - - expect(queryByText('展开更多')).not.toBeNull(); - expect(queryByText('展开更多')).toBeInTheDocument(); - const element = await waitFor(() => queryByTestId(testId)); - expect(element).toBeNull(); - - const btn = await waitFor(() => queryByText('展开更多')); - if (btn) { - act(() => fireEvent.click(btn)); - } else { - throw new Error(`Button with text '展开更多' not found`); - } - - expect(queryByText('收起')).not.toBeNull(); - expect(queryByText('收起')).toBeInTheDocument(); - const element1 = await waitFor(() => queryByTestId(testId)); - expect(element1).not.toBeNull(); - - const btn1 = await waitFor(() => queryByText('收起')); - if (btn1) { - act(() => fireEvent.click(btn1)); - } else { - throw new Error(`Button with text '收起' not found`); - } - const element3 = await waitFor(() => queryByTestId(testId)); - expect(element3).toBeNull(); - }); + test('custom icon render', () => { + const { queryByText } = render(custom icon} title="title content" />); - test('className', () => { - const { container } = render(); - expect(container.querySelector('.custom-class')).not.toBeNull(); + expect(queryByText('custom icon')).not.toBeNull(); + expect(queryByText('custom icon')).toBeInTheDocument(); + }); }); - test('style', () => { - const { container } = render(); - const element = container.querySelector('.t-alert') as HTMLElement; - expect(element?.style.color).toBe('red'); + describe('scenarios', () => { + test('Alert 关闭操作', async () => { + const onClose = vi.fn(); + const onClosed = vi.fn(); + + const { queryByTestId, container, queryByText } = render( + {text}} + onClose={onClose} + onClosed={onClosed} + operation={test content} + />, + ); + + expect(container.querySelector('.t-alert--closing')).not.toBeInTheDocument(); + expect(container.querySelector('#operation-test')).not.toBeNull(); + expect(container.querySelector('#operation-test')).toBeInTheDocument(); + expect(queryByText('title content')).not.toBeNull(); + expect(queryByText('title content')).toBeInTheDocument(); + expect(container.querySelector('.t-alert--error')).not.toBeNull(); + expect(container.querySelector('.t-alert--error')).toBeInTheDocument(); + + const element = queryByTestId(testId); + if (element) { + // 在 react 18.3.1之后 可以在 react 中导出 act,且在18.3.1 之后应该在react中使用 act + act(() => fireEvent.click(element)); + } else { + throw new Error(`Element with testId ${testId} not found`); + } + expect(container.querySelector('.t-alert--closing')).toBeInTheDocument(); + expect(onClose).toHaveBeenCalledTimes(1); + + await mockTimeout(() => expect(onClosed).toHaveBeenCalledTimes(1), 300); + }); + + test('message not collapsed', () => { + const message = [ +
{text}
, +
{text}
, +
+ {text} +
, + ]; + const { container } = render(); + const { container: container1 } = render(); + + expect(container.querySelector('.t-alert__collapse')).toBeNull(); + expect(container.querySelector('.t-alert__collapse')).not.toBeInTheDocument(); + expect(container1.querySelector('.t-alert__collapse')).toBeNull(); + expect(container1.querySelector('.t-alert__collapse')).not.toBeInTheDocument(); + }); + + test('Alert 展开收起操作', async () => { + const massage = [ +
{text}
, +
{text}
, +
+ {text} +
, + ]; + const { queryByText, queryByTestId } = render(); + + expect(queryByText('展开更多')).not.toBeNull(); + expect(queryByText('展开更多')).toBeInTheDocument(); + const element = await waitFor(() => queryByTestId(testId)); + expect(element).toBeNull(); + + const btn = await waitFor(() => queryByText('展开更多')); + if (btn) { + act(() => fireEvent.click(btn)); + } else { + throw new Error(`Button with text '展开更多' not found`); + } + + expect(queryByText('收起')).not.toBeNull(); + expect(queryByText('收起')).toBeInTheDocument(); + const element1 = await waitFor(() => queryByTestId(testId)); + expect(element1).not.toBeNull(); + + const btn1 = await waitFor(() => queryByText('收起')); + if (btn1) { + act(() => fireEvent.click(btn1)); + } else { + throw new Error(`Button with text '收起' not found`); + } + const element3 = await waitFor(() => queryByTestId(testId)); + expect(element3).toBeNull(); + }); }); }); diff --git a/packages/components/anchor/__tests__/anchor.test.tsx b/packages/components/anchor/__tests__/anchor.test.tsx index c118f8b10d..a7dc60d048 100644 --- a/packages/components/anchor/__tests__/anchor.test.tsx +++ b/packages/components/anchor/__tests__/anchor.test.tsx @@ -6,73 +6,81 @@ import Anchor from '../Anchor'; const { AnchorItem, AnchorTarget } = Anchor; -describe('Anchor 组件测试', () => { - const distance = 0; - const childTestID = 'childTestID'; +const distance = 0; +const childTestID = 'childTestID'; - test('Anchor 工具函数', () => { - const wrapper = render( - - - , - ); +describe('Anchor', () => { + describe('events', () => { + test('render Anchor click', async () => { + const fn = vi.fn(); + const wrapper = render( +
+ + + + + +
+
, + ); + const anchorItem = wrapper.getByTestId(childTestID); + fireEvent.click(anchorItem.firstChild); + expect(fn).toHaveBeenCalledTimes(1); + }); + }); - // target is element - const domDistance = getScroll(wrapper.getByTestId(childTestID), true); - expect(domDistance).toEqual(distance); + describe('scenarios', () => { + test('Anchor 工具函数', () => { + const wrapper = render( + + + , + ); - // target is document - const documentDistance = getScroll(document, true); - expect(documentDistance).toEqual(distance); + // target is element + const domDistance = getScroll(wrapper.getByTestId(childTestID), true); + expect(domDistance).toEqual(distance); - // target is window - const windowDistance = getScroll(window, true); - expect(windowDistance).toEqual(distance); - }); + // target is document + const documentDistance = getScroll(document, true); + expect(documentDistance).toEqual(distance); - test('render AnchorItem links', async () => { - render( - - - - , - ); - expect(document.querySelector('.t-is-active')).toBe(null); - expect(document.querySelector('a[href="#test-a"]')).not.toBe(null); - expect(document.querySelector('a[href="#test-b"]')).not.toBe(null); - }); + // target is window + const windowDistance = getScroll(window, true); + expect(windowDistance).toEqual(distance); + }); - test('render Anchor click', async () => { - const fn = vi.fn(); - const wrapper = render( -
- + test('render AnchorItem links', async () => { + render( + - - -
-
, - ); - const anchorItem = wrapper.getByTestId(childTestID); - fireEvent.click(anchorItem.firstChild); - expect(fn).toHaveBeenCalledTimes(1); + , + ); + expect(document.querySelector('.t-is-active')).toBe(null); + expect(document.querySelector('a[href="#test-a"]')).not.toBe(null); + expect(document.querySelector('a[href="#test-b"]')).not.toBe(null); + }); }); +}); - test('render AnchorTarget', async () => { - const wrapper = render( -
- - 基础锚点 - -
, - ); - const { container } = wrapper; - const anchorItem = wrapper.getByTestId(childTestID); - expect(anchorItem).not.toBeNull(); - fireEvent.mouseDown(container.querySelector('#default')); - const icon = container.querySelector('.t-anchor__copy'); - fireEvent.click(icon); - expect(icon).not.toBeNull(); +describe('AnchorTarget', () => { + describe('scenarios', () => { + test('render AnchorTarget', async () => { + const wrapper = render( +
+ + 基础锚点 + +
, + ); + const { container } = wrapper; + const anchorItem = wrapper.getByTestId(childTestID); + expect(anchorItem).not.toBeNull(); + fireEvent.mouseDown(container.querySelector('#default')); + const icon = container.querySelector('.t-anchor__copy'); + fireEvent.click(icon); + expect(icon).not.toBeNull(); + }); }); }); diff --git a/packages/components/auto-complete/__tests__/auto-complete.test.tsx b/packages/components/auto-complete/__tests__/auto-complete.test.tsx new file mode 100644 index 0000000000..da48e49233 --- /dev/null +++ b/packages/components/auto-complete/__tests__/auto-complete.test.tsx @@ -0,0 +1,366 @@ +import React from 'react'; +import { fireEvent, mockDelay, render, simulateKeydownEvent, vi } from '@test/utils'; + +import { AutoComplete } from '..'; + +function getNormalAutoCompleteMount(props: Record = {}, events: Record = {}) { + const options = [ + 'FirstKeyword', + { + // 自定义选项 + label: () =>
TNode SecondKeyword
, + // 用于搜索的纯文本 + text: 'SecondKeyword', + }, + 'ThirdKeyword', + { + label: 'READONLY_KEYWORD', + }, + { + text: 'DISABLED_KEYWORD', + }, + ]; + return render(); +} + +function getOptionSlotAutoCompleteMount(props: Record = {}, events: Record = {}) { + const options = [ + 'First', + { + label:
First Keyword
, + text: 'First Keyword', + }, + ]; + return render(); +} + +describe('AutoComplete', () => { + describe('props', () => { + test(`autofocus is equal to false`, () => { + const { container } = render(); + const domWrapper = container.querySelector('input'); + expect(domWrapper.getAttribute('autofocus')).toBeNull(); + }); + test(`autofocus is equal to true`, () => { + const { container } = render(); + const domWrapper = container.querySelector('input'); + expect(domWrapper.getAttribute('autofocus')).toBeDefined(); + }); + + test('clearable: show clear icon on mouse enter', async () => { + const { container } = getNormalAutoCompleteMount({ + value: 'Default Keyword', + clearable: true, + }); + fireEvent.mouseEnter(container.querySelector('.t-input')); + await mockDelay(); + expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); + }); + test('clearable: expect trigger clear and change events after clear icon has been clicked', async () => { + const onClearFn1 = vi.fn(); + const onChangeFn1 = vi.fn(); + const { container } = getNormalAutoCompleteMount( + { value: 'Default Keyword', clearable: true }, + { onClear: onClearFn1, onChange: onChangeFn1 }, + ); + fireEvent.mouseEnter(container.querySelector('.t-input')); + await mockDelay(); + expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); + fireEvent.click(container.querySelector('.t-input__suffix-clear')); + expect(onClearFn1).toHaveBeenCalled(1); + expect(onClearFn1.mock.calls[0][0].e.stopPropagation).toBeTruthy(); + expect(onClearFn1.mock.calls[0][0].e.type).toBe('click'); + expect(onChangeFn1).toHaveBeenCalled(1); + expect(onChangeFn1.mock.calls[0][0]).toBe(''); + expect(onChangeFn1.mock.calls[0][1].e.stopPropagation).toBeTruthy(); + expect(onChangeFn1.mock.calls[0][1].e.type).toBe('click'); + }); + + test('disabled', () => { + // disabled default value is + const wrapper1 = render(); + const container1 = wrapper1.container.querySelector('.t-input'); + expect(container1.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); + // disabled = true + const wrapper2 = render(); + const container2 = wrapper2.container.querySelector('.t-input'); + expect(container2).toHaveClass('t-is-disabled'); + // disabled = false + const wrapper3 = render(); + const container3 = wrapper3.container.querySelector('.t-input'); + expect(container3.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); + }); + + test('filter', () => { + const { container } = getNormalAutoCompleteMount({ + filter: (filterWords, option) => option.text.includes('Second'), + }); + fireEvent.focus(container.querySelector('input')); + const tSelectOptionDom = document.querySelectorAll('.t-select-option'); + expect(tSelectOptionDom.length).toBe(1); + }); + + test('filterable', () => { + const { container } = getNormalAutoCompleteMount({ + value: 'First', + filterable: true, + }); + fireEvent.focus(container.querySelector('input')); + const tSelectOptionDom = document.querySelectorAll('.t-select-option'); + expect(tSelectOptionDom.length).toBe(1); + }); + + test('highlightKeyword', () => { + const { container } = getNormalAutoCompleteMount({ + value: 'Second', + highlightKeyword: true, + }); + fireEvent.focus(container.querySelector('input')); + const tSelectOptionDom = document.querySelectorAll('.t-select-option'); + expect(tSelectOptionDom.length).toBe(1); + }); + + test('options: option.label could be defined to any element', () => { + const { container } = getNormalAutoCompleteMount(); + fireEvent.focus(container.querySelector('input')); + const customNodeDom = document.querySelector('.custom-node'); + expect(customNodeDom).toBeTruthy(); + }); + test('options: 5 options should exist', () => { + const { container } = getNormalAutoCompleteMount(); + fireEvent.focus(container.querySelector('input')); + const tSelectOptionDom = document.querySelectorAll('.t-select-option'); + expect(tSelectOptionDom.length).toBe(5); + }); + test('options: expect empty options with no panel', () => { + const { container } = render( + , + ); + fireEvent.focus(container.querySelector('input')); + const emptyOptionsClassNameTAutocompletePanelDom = document.querySelectorAll( + '.empty-options-class-name .t-autocomplete__panel', + ); + expect(emptyOptionsClassNameTAutocompletePanelDom.length).toBe(0); + }); + test('options: define one option', () => { + const { container } = getOptionSlotAutoCompleteMount({ + popupProps: { overlayClassName: 'option-slot-class-name' }, + }); + fireEvent.focus(container.querySelector('input')); + const optionSlotClassNameCustomSlotOptionDom = document.querySelector( + '.option-slot-class-name .custom-slot-option', + ); + expect(optionSlotClassNameCustomSlotOptionDom.textContent).toBe('First Keyword'); + }); + + test(`placeholder is equal to 'type keyword to search'`, () => { + const { container } = render(); + const domWrapper = container.querySelector('input'); + expect(domWrapper.getAttribute('placeholder')).toBe('type keyword to search'); + }); + + test('popupProps overlayClassName', () => { + const { container } = getNormalAutoCompleteMount({ + popupProps: { overlayClassName: 'custom-class-name' }, + }); + fireEvent.focus(container.querySelector('input')); + const customClassNameDom = document.querySelector('.custom-class-name'); + expect(customClassNameDom).toBeTruthy(); + }); + test('popupProps overlayInnerClassName', () => { + const { container } = getNormalAutoCompleteMount({ + popupProps: { overlayInnerClassName: 'custom-class-name' }, + }); + fireEvent.focus(container.querySelector('input')); + const customClassNameDom = document.querySelector('.custom-class-name'); + expect(customClassNameDom).toBeTruthy(); + }); + + test('readOnly', () => { + // readonly default value is + const wrapper1 = getNormalAutoCompleteMount(); + const container1 = wrapper1.container.querySelector('.t-input'); + expect(container1.querySelector(`.${'t-is-readonly'}`)).toBeFalsy(); + // readonly = true + const wrapper2 = getNormalAutoCompleteMount({ readOnly: true }); + const container2 = wrapper2.container.querySelector('.t-input'); + expect(container2).toHaveClass('t-is-readonly'); + // readonly = false + const wrapper3 = getNormalAutoCompleteMount({ readOnly: false }); + const container3 = wrapper3.container.querySelector('.t-input'); + expect(container3.querySelector(`.${'t-is-readonly'}`)).toBeFalsy(); + }); + + const sizeClassNameList = ['t-size-s', { 't-size-m': false }, 't-size-l']; + (['small', 'medium', 'large'] as const).forEach((item, index) => { + test(`size is equal to ${item}`, () => { + const wrapper = getNormalAutoCompleteMount({ size: item }); + const container = wrapper.container.querySelector('.t-input'); + if (typeof sizeClassNameList[index] === 'string') { + expect(container).toHaveClass(sizeClassNameList[index]); + } else if (typeof sizeClassNameList[index] === 'object') { + const classNameKey = Object.keys(sizeClassNameList[index])[0]; + expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); + } + }); + }); + + const statusClassNameList = [{ 't-is-default': false }, 't-is-success', 't-is-warning', 't-is-error']; + (['default', 'success', 'warning', 'error'] as const).forEach((item, index) => { + test(`status is equal to ${item}`, () => { + const wrapper = getNormalAutoCompleteMount({ status: item }); + const container = wrapper.container.querySelector('.t-input'); + if (typeof statusClassNameList[index] === 'string') { + expect(container).toHaveClass(statusClassNameList[index]); + } else if (typeof statusClassNameList[index] === 'object') { + const classNameKey = Object.keys(statusClassNameList[index])[0]; + expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); + } + }); + }); + + test('tips is equal this is a tip', () => { + const { container } = render(); + expect(container.querySelector('.t-input__tips')).toBeTruthy(); + }); + + test(`value is equal to 'DefaultKeyword'`, () => { + const { container } = render(); + const domWrapper = container.querySelector('input'); + expect(domWrapper.value).toBe('DefaultKeyword'); + }); + test('children', () => { + const { container } = render( + + TNode + , + ); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + + test('panelBottomContent', () => { + const { container } = render( + TNode}>, + ); + fireEvent.focus(container.querySelector('input')); + const customNodeDom = document.querySelector('.custom-node'); + expect(customNodeDom).toBeTruthy(); + const tPopupDom = document.querySelector('.t-popup'); + expect(tPopupDom).toBeTruthy(); + }); + + test('panelTopContent', () => { + const { container } = render( + TNode}>, + ); + fireEvent.focus(container.querySelector('input')); + const customNodeDom = document.querySelector('.custom-node'); + expect(customNodeDom).toBeTruthy(); + const tPopupDom = document.querySelector('.t-popup'); + expect(tPopupDom).toBeTruthy(); + }); + + test('triggerElement', () => { + const { container } = render( + TNode}>, + ); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + }); + + describe('events', () => { + test('blur', async () => { + const onFocusFn = vi.fn(); + const onBlurFn1 = vi.fn(); + const { container } = getNormalAutoCompleteMount({}, { onFocus: onFocusFn, onBlur: onBlurFn1 }); + fireEvent.focus(container.querySelector('input')); + expect(onFocusFn).toHaveBeenCalled(1); + expect(onFocusFn.mock.calls[0][0].e.type).toBe('focus'); + fireEvent.blur(container.querySelector('input')); + await mockDelay(300); + expect(onBlurFn1).toHaveBeenCalled(1); + expect(onBlurFn1.mock.calls[0][0].e.type).toBe('blur'); + }); + + test('compositionend', () => { + const onCompositionendFn = vi.fn(); + const { container } = render(); + fireEvent.compositionEnd(container.querySelector('input')); + expect(onCompositionendFn).toHaveBeenCalled(1); + expect(onCompositionendFn.mock.calls[0][0].e.type).toBe('compositionend'); + }); + + test('compositionstart', () => { + const onCompositionstartFn = vi.fn(); + const { container } = render(); + fireEvent.compositionStart(container.querySelector('input')); + expect(onCompositionstartFn).toHaveBeenCalled(1); + expect(onCompositionstartFn.mock.calls[0][0].e.type).toBe('compositionstart'); + }); + + test('enter', () => { + const onEnterFn1 = vi.fn(); + const { container } = getNormalAutoCompleteMount({}, { onEnter: onEnterFn1 }); + fireEvent.focus(container.querySelector('input')); + fireEvent.keyDown(container.querySelector('input'), { + key: 'Enter', + code: 'Enter', + charCode: 13, + }); + expect(onEnterFn1).toHaveBeenCalled(1); + expect(onEnterFn1.mock.calls[0][0].e.type).toBe('keydown'); + expect(/Enter/i.test(onEnterFn1.mock.calls[0][0].e.key)).toBeTruthy(); + }); + + test('focus', () => { + const onFocusFn = vi.fn(); + const { container } = getNormalAutoCompleteMount({}, { onFocus: onFocusFn }); + fireEvent.focus(container.querySelector('input')); + expect(container.querySelector('.t-is-focused')).toBeTruthy(); + expect(onFocusFn).toHaveBeenCalled(1); + expect(onFocusFn.mock.calls[0][0].e.type).toBe('focus'); + }); + + test('select', () => { + const onSelectFn1 = vi.fn(); + const { container } = getNormalAutoCompleteMount( + { popupProps: { overlayClassName: 'select-event-class-name' } }, + { onSelect: onSelectFn1 }, + ); + fireEvent.focus(container.querySelector('input')); + fireEvent.click(document.querySelector('.select-event-class-name .t-select-option')); + expect(onSelectFn1).toHaveBeenCalled(1); + expect(onSelectFn1.mock.calls[0][0]).toBe('FirstKeyword'); + expect(onSelectFn1.mock.calls[0][1].e.type).toBe('click'); + }); + test('select: keyboard operations: ArrowDown & ArrowUp & Enter', async () => { + const onSelectFn6 = vi.fn(); + const { container } = getNormalAutoCompleteMount({}, { onSelect: onSelectFn6 }); + fireEvent.focus(container.querySelector('input')); + simulateKeydownEvent(document, 'ArrowDown'); + await mockDelay(10); + const domWrapper1 = document.querySelector('.t-select-option:first-child'); + expect(domWrapper1).toHaveClass('t-select-option--hover'); + simulateKeydownEvent(document, 'ArrowDown'); + await mockDelay(10); + const domWrapper2 = document.querySelector('.t-select-option:nth-child(2)'); + expect(domWrapper2).toHaveClass('t-select-option--hover'); + simulateKeydownEvent(document, 'ArrowUp'); + await mockDelay(10); + const domWrapper3 = document.querySelector('.t-select-option:first-child'); + expect(domWrapper3).toHaveClass('t-select-option--hover'); + simulateKeydownEvent(document, 'ArrowUp'); + await mockDelay(10); + const domWrapper4 = document.querySelector('.t-select-option:nth-child(5)'); + expect(domWrapper4).toHaveClass('t-select-option--hover'); + simulateKeydownEvent(document, 'ArrowDown'); + await mockDelay(10); + const domWrapper5 = document.querySelector('.t-select-option:first-child'); + expect(domWrapper5).toHaveClass('t-select-option--hover'); + simulateKeydownEvent(document, 'Enter'); + expect(onSelectFn6).toHaveBeenCalled(1); + expect(onSelectFn6.mock.calls[0][0]).toBe('FirstKeyword'); + expect(onSelectFn6.mock.calls[0][1].e.type).toBe('keydown'); + }); + }); +}); diff --git a/packages/components/auto-complete/__tests__/mount.jsx b/packages/components/auto-complete/__tests__/mount.jsx deleted file mode 100644 index 4d19448be5..0000000000 --- a/packages/components/auto-complete/__tests__/mount.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { render } from '@test/utils'; - -export function getNormalAutoCompleteMount(AutoComplete, props, events) { - const options = [ - 'FirstKeyword', - { - // 自定义选项 - label: () =>
TNode SecondKeyword
, - // 用于搜索的纯文本 - text: 'SecondKeyword', - }, - 'ThirdKeyword', - { - label: 'READONLY_KEYWORD', - }, - { - text: 'DISABLED_KEYWORD', - }, - ]; - return render(); -} - -export function getOptionSlotAutoCompleteMount(AutoComplete, props, events) { - const options = ['First', { label:
First Keyword
, text: 'First Keyword' }]; - return render(); -} diff --git a/packages/components/auto-complete/__tests__/vitest-auto-complete.test.jsx b/packages/components/auto-complete/__tests__/vitest-auto-complete.test.jsx deleted file mode 100644 index 59b4af2314..0000000000 --- a/packages/components/auto-complete/__tests__/vitest-auto-complete.test.jsx +++ /dev/null @@ -1,327 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * 该文件由脚本自动生成,如需修改请联系 PMC - * This file generated by scripts of tdesign-api. `npm run api:docs AutoComplete React(PC) vitest,finalProject` - * If you need to modify this file, contact PMC first please. - */ -import React from 'react'; -import { fireEvent, vi, render, mockDelay, simulateKeydownEvent } from '@test/utils'; -import { AutoComplete } from '..'; -import { getNormalAutoCompleteMount, getOptionSlotAutoCompleteMount } from './mount'; - -describe('AutoComplete Component', () => { - it(`props.autofocus is equal to false`, () => { - const { container } = render(); - const domWrapper = container.querySelector('input'); - expect(domWrapper.getAttribute('autofocus')).toBeNull(); - }); - it(`props.autofocus is equal to true`, () => { - const { container } = render(); - const domWrapper = container.querySelector('input'); - expect(domWrapper.getAttribute('autofocus')).toBeDefined(); - }); - - it('props.children works fine', () => { - const { container } = render( - - TNode - , - ); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - it('props.clearable: show clear icon on mouse enter', async () => { - const { container } = getNormalAutoCompleteMount(AutoComplete, { value: 'Default Keyword', clearable: true }); - fireEvent.mouseEnter(container.querySelector('.t-input')); - await mockDelay(); - expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); - }); - it('props.clearable: expect trigger clear and change events after clear icon has been clicked', async () => { - const onClearFn1 = vi.fn(); - const onChangeFn1 = vi.fn(); - const { container } = getNormalAutoCompleteMount( - AutoComplete, - { value: 'Default Keyword', clearable: true }, - { onClear: onClearFn1, onChange: onChangeFn1 }, - ); - fireEvent.mouseEnter(container.querySelector('.t-input')); - await mockDelay(); - expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); - fireEvent.click(container.querySelector('.t-input__suffix-clear')); - expect(onClearFn1).toHaveBeenCalled(1); - expect(onClearFn1.mock.calls[0][0].e.stopPropagation).toBeTruthy(); - expect(onClearFn1.mock.calls[0][0].e.type).toBe('click'); - expect(onChangeFn1).toHaveBeenCalled(1); - expect(onChangeFn1.mock.calls[0][0]).toBe(''); - expect(onChangeFn1.mock.calls[0][1].e.stopPropagation).toBeTruthy(); - expect(onChangeFn1.mock.calls[0][1].e.type).toBe('click'); - }); - - it('props.disabled works fine', () => { - // disabled default value is - const wrapper1 = render(); - const container1 = wrapper1.container.querySelector('.t-input'); - expect(container1.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - // disabled = true - const wrapper2 = render(); - const container2 = wrapper2.container.querySelector('.t-input'); - expect(container2).toHaveClass('t-is-disabled'); - // disabled = false - const wrapper3 = render(); - const container3 = wrapper3.container.querySelector('.t-input'); - expect(container3.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - }); - - it('props.filter works fine', () => { - const { container } = getNormalAutoCompleteMount(AutoComplete, { - filter: (filterWords, option) => option.text.includes('Second'), - }); - fireEvent.focus(container.querySelector('input')); - const tSelectOptionDom = document.querySelectorAll('.t-select-option'); - expect(tSelectOptionDom.length).toBe(1); - }); - - it('props.filterable works fine', () => { - const { container } = getNormalAutoCompleteMount(AutoComplete, { value: 'First', filterable: true }); - fireEvent.focus(container.querySelector('input')); - const tSelectOptionDom = document.querySelectorAll('.t-select-option'); - expect(tSelectOptionDom.length).toBe(1); - }); - - it('props.highlightKeyword works fine', () => { - const { container } = getNormalAutoCompleteMount(AutoComplete, { value: 'Second', highlightKeyword: true }); - fireEvent.focus(container.querySelector('input')); - const tSelectOptionDom = document.querySelectorAll('.t-select-option'); - expect(tSelectOptionDom.length).toBe(1); - }); - - it('props.options: option.label could be defined to any element', () => { - const { container } = getNormalAutoCompleteMount(AutoComplete); - fireEvent.focus(container.querySelector('input')); - const customNodeDom = document.querySelector('.custom-node'); - expect(customNodeDom).toBeTruthy(); - }); - it('props.options: 5 options should exist', () => { - const { container } = getNormalAutoCompleteMount(AutoComplete); - fireEvent.focus(container.querySelector('input')); - const tSelectOptionDom = document.querySelectorAll('.t-select-option'); - expect(tSelectOptionDom.length).toBe(5); - }); - it('props.options: expect empty options with no panel', () => { - const { container } = render( - , - ); - fireEvent.focus(container.querySelector('input')); - const emptyOptionsClassNameTAutocompletePanelDom = document.querySelectorAll( - '.empty-options-class-name .t-autocomplete__panel', - ); - expect(emptyOptionsClassNameTAutocompletePanelDom.length).toBe(0); - }); - it('props.options: define one option', () => { - const { container } = getOptionSlotAutoCompleteMount(AutoComplete, { - popupProps: { overlayClassName: 'option-slot-class-name' }, - }); - fireEvent.focus(container.querySelector('input')); - const optionSlotClassNameCustomSlotOptionDom = document.querySelector( - '.option-slot-class-name .custom-slot-option', - ); - expect(optionSlotClassNameCustomSlotOptionDom.textContent).toBe('First Keyword'); - }); - - it('props.panelBottomContent works fine', () => { - const { container } = render( - TNode}>, - ); - fireEvent.focus(container.querySelector('input')); - const customNodeDom = document.querySelector('.custom-node'); - expect(customNodeDom).toBeTruthy(); - const tPopupDom = document.querySelector('.t-popup'); - expect(tPopupDom).toBeTruthy(); - }); - - it('props.panelTopContent works fine', () => { - const { container } = render( - TNode}>, - ); - fireEvent.focus(container.querySelector('input')); - const customNodeDom = document.querySelector('.custom-node'); - expect(customNodeDom).toBeTruthy(); - const tPopupDom = document.querySelector('.t-popup'); - expect(tPopupDom).toBeTruthy(); - }); - - it(`props.placeholder is equal to 'type keyword to search'`, () => { - const { container } = render(); - const domWrapper = container.querySelector('input'); - expect(domWrapper.getAttribute('placeholder')).toBe('type keyword to search'); - }); - - it('props.popupProps works fine', () => { - const { container } = getNormalAutoCompleteMount(AutoComplete, { - popupProps: { overlayClassName: 'custom-class-name' }, - }); - fireEvent.focus(container.querySelector('input')); - const customClassNameDom = document.querySelector('.custom-class-name'); - expect(customClassNameDom).toBeTruthy(); - }); - it('props.popupProps works fine', () => { - const { container } = getNormalAutoCompleteMount(AutoComplete, { - popupProps: { overlayInnerClassName: 'custom-class-name' }, - }); - fireEvent.focus(container.querySelector('input')); - const customClassNameDom = document.querySelector('.custom-class-name'); - expect(customClassNameDom).toBeTruthy(); - }); - - it('props.readOnly works fine', () => { - // readonly default value is - const wrapper1 = getNormalAutoCompleteMount(AutoComplete); - const container1 = wrapper1.container.querySelector('.t-input'); - expect(container1.querySelector(`.${'t-is-readonly'}`)).toBeFalsy(); - // readonly = true - const wrapper2 = getNormalAutoCompleteMount(AutoComplete, { readOnly: true }); - const container2 = wrapper2.container.querySelector('.t-input'); - expect(container2).toHaveClass('t-is-readonly'); - // readonly = false - const wrapper3 = getNormalAutoCompleteMount(AutoComplete, { readOnly: false }); - const container3 = wrapper3.container.querySelector('.t-input'); - expect(container3.querySelector(`.${'t-is-readonly'}`)).toBeFalsy(); - }); - - const sizeClassNameList = ['t-size-s', { 't-size-m': false }, 't-size-l']; - ['small', 'medium', 'large'].forEach((item, index) => { - it(`props.size is equal to ${item}`, () => { - const wrapper = getNormalAutoCompleteMount(AutoComplete, { size: item }); - const container = wrapper.container.querySelector('.t-input'); - if (typeof sizeClassNameList[index] === 'string') { - expect(container).toHaveClass(sizeClassNameList[index]); - } else if (typeof sizeClassNameList[index] === 'object') { - const classNameKey = Object.keys(sizeClassNameList[index])[0]; - expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); - } - }); - }); - - const statusClassNameList = [{ 't-is-default': false }, 't-is-success', 't-is-warning', 't-is-error']; - ['default', 'success', 'warning', 'error'].forEach((item, index) => { - it(`props.status is equal to ${item}`, () => { - const wrapper = getNormalAutoCompleteMount(AutoComplete, { status: item }); - const container = wrapper.container.querySelector('.t-input'); - if (typeof statusClassNameList[index] === 'string') { - expect(container).toHaveClass(statusClassNameList[index]); - } else if (typeof statusClassNameList[index] === 'object') { - const classNameKey = Object.keys(statusClassNameList[index])[0]; - expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); - } - }); - }); - - it('props.tips is equal this is a tip', () => { - const { container } = render(); - expect(container.querySelector('.t-input__tips')).toBeTruthy(); - }); - - it('props.triggerElement works fine', () => { - const { container } = render( - TNode}>, - ); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - it(`props.value is equal to 'DefaultKeyword'`, () => { - const { container } = render(); - const domWrapper = container.querySelector('input'); - expect(domWrapper.value).toBe('DefaultKeyword'); - }); - - it('events.blur works fine', async () => { - const onFocusFn = vi.fn(); - const onBlurFn1 = vi.fn(); - const { container } = getNormalAutoCompleteMount(AutoComplete, {}, { onFocus: onFocusFn, onBlur: onBlurFn1 }); - fireEvent.focus(container.querySelector('input')); - expect(onFocusFn).toHaveBeenCalled(1); - expect(onFocusFn.mock.calls[0][0].e.type).toBe('focus'); - fireEvent.blur(container.querySelector('input')); - await mockDelay(300); - expect(onBlurFn1).toHaveBeenCalled(1); - expect(onBlurFn1.mock.calls[0][0].e.type).toBe('blur'); - }); - - it('events.compositionend works fine', () => { - const onCompositionendFn = vi.fn(); - const { container } = render(); - fireEvent.compositionEnd(container.querySelector('input')); - expect(onCompositionendFn).toHaveBeenCalled(1); - expect(onCompositionendFn.mock.calls[0][0].e.type).toBe('compositionend'); - }); - - it('events.compositionstart works fine', () => { - const onCompositionstartFn = vi.fn(); - const { container } = render(); - fireEvent.compositionStart(container.querySelector('input')); - expect(onCompositionstartFn).toHaveBeenCalled(1); - expect(onCompositionstartFn.mock.calls[0][0].e.type).toBe('compositionstart'); - }); - - it('events.enter works fine', () => { - const onEnterFn1 = vi.fn(); - const { container } = getNormalAutoCompleteMount(AutoComplete, {}, { onEnter: onEnterFn1 }); - fireEvent.focus(container.querySelector('input')); - fireEvent.keyDown(container.querySelector('input'), { key: 'Enter', code: 'Enter', charCode: 13 }); - expect(onEnterFn1).toHaveBeenCalled(1); - expect(onEnterFn1.mock.calls[0][0].e.type).toBe('keydown'); - expect(/Enter/i.test(onEnterFn1.mock.calls[0][0].e.key)).toBeTruthy(); - }); - - it('events.focus works fine', () => { - const onFocusFn = vi.fn(); - const { container } = getNormalAutoCompleteMount(AutoComplete, {}, { onFocus: onFocusFn }); - fireEvent.focus(container.querySelector('input')); - expect(container.querySelector('.t-is-focused')).toBeTruthy(); - expect(onFocusFn).toHaveBeenCalled(1); - expect(onFocusFn.mock.calls[0][0].e.type).toBe('focus'); - }); - - it('events.select works fine', () => { - const onSelectFn1 = vi.fn(); - const { container } = getNormalAutoCompleteMount( - AutoComplete, - { popupProps: { overlayClassName: 'select-event-class-name' } }, - { onSelect: onSelectFn1 }, - ); - fireEvent.focus(container.querySelector('input')); - fireEvent.click(document.querySelector('.select-event-class-name .t-select-option')); - expect(onSelectFn1).toHaveBeenCalled(1); - expect(onSelectFn1.mock.calls[0][0]).toBe('FirstKeyword'); - expect(onSelectFn1.mock.calls[0][1].e.type).toBe('click'); - }); - it('events.select: keyboard operations: ArrowDown & ArrowUp & Enter', async () => { - const onSelectFn6 = vi.fn(); - const { container } = getNormalAutoCompleteMount(AutoComplete, {}, { onSelect: onSelectFn6 }); - fireEvent.focus(container.querySelector('input')); - simulateKeydownEvent(document, 'ArrowDown'); - await mockDelay(10); - const domWrapper1 = document.querySelector('.t-select-option:first-child'); - expect(domWrapper1).toHaveClass('t-select-option--hover'); - simulateKeydownEvent(document, 'ArrowDown'); - await mockDelay(10); - const domWrapper2 = document.querySelector('.t-select-option:nth-child(2)'); - expect(domWrapper2).toHaveClass('t-select-option--hover'); - simulateKeydownEvent(document, 'ArrowUp'); - await mockDelay(10); - const domWrapper3 = document.querySelector('.t-select-option:first-child'); - expect(domWrapper3).toHaveClass('t-select-option--hover'); - simulateKeydownEvent(document, 'ArrowUp'); - await mockDelay(10); - const domWrapper4 = document.querySelector('.t-select-option:nth-child(5)'); - expect(domWrapper4).toHaveClass('t-select-option--hover'); - simulateKeydownEvent(document, 'ArrowDown'); - await mockDelay(10); - const domWrapper5 = document.querySelector('.t-select-option:first-child'); - expect(domWrapper5).toHaveClass('t-select-option--hover'); - simulateKeydownEvent(document, 'Enter'); - expect(onSelectFn6).toHaveBeenCalled(1); - expect(onSelectFn6.mock.calls[0][0]).toBe('FirstKeyword'); - expect(onSelectFn6.mock.calls[0][1].e.type).toBe('keydown'); - }); -}); diff --git a/packages/components/avatar/__tests__/__snapshots__/vitest-avatar.test.jsx.snap b/packages/components/avatar/__tests__/__snapshots__/avatar-group.test.tsx.snap similarity index 74% rename from packages/components/avatar/__tests__/__snapshots__/vitest-avatar.test.jsx.snap rename to packages/components/avatar/__tests__/__snapshots__/avatar-group.test.tsx.snap index 137f8bd971..d041b2651a 100644 --- a/packages/components/avatar/__tests__/__snapshots__/vitest-avatar.test.jsx.snap +++ b/packages/components/avatar/__tests__/__snapshots__/avatar-group.test.tsx.snap @@ -1,66 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Avatar Component > props.children works fine 1`] = ` -
-
- - - TNode - - -
-
-`; - -exports[`Avatar Component > props.content works fine 1`] = ` -
-
- - - TNode - - -
-
-`; - -exports[`Avatar Component > props.shape is equal to circle 1`] = ` -
-
- -
-
-`; - -exports[`Avatar Component > props.shape is equal to round 1`] = ` -
-
- -
-
-`; - -exports[`AvatarGroup Component > props.cascading is equal to left-up 1`] = ` +exports[`AvatarGroup > props > cascading is equal to left-up 1`] = `
props.cascading is equal to left-up 1`] = `
`; -exports[`AvatarGroup Component > props.cascading is equal to right-up 1`] = ` +exports[`AvatarGroup > props > cascading is equal to right-up 1`] = `
props.cascading is equal to right-up 1`] = `
`; -exports[`AvatarGroup Component > props.collapseAvatar works fine 1`] = ` +exports[`AvatarGroup > props > collapseAvatar 1`] = `
props.collapseAvatar works fine 1`] = ` props.collapseAvatar works fine 1`] = ` props.collapseAvatar works fine 1`] = ` d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r5-overlap-fill1)" + mask="url(#t-icon-image-instance-r4-overlap-fill1)" /> props > children 1`] = ` +
+
+ + + TNode + + +
+
+`; + +exports[`Avatar > props > content 1`] = ` +
+
+ + + TNode + + +
+
+`; + +exports[`Avatar > props > shape is equal to circle 1`] = ` +
+
+ +
+
+`; + +exports[`Avatar > props > shape is equal to round 1`] = ` +
+
+ +
+
+`; diff --git a/packages/components/avatar/__tests__/avatar-group.test.tsx b/packages/components/avatar/__tests__/avatar-group.test.tsx new file mode 100644 index 0000000000..ffbf640f8a --- /dev/null +++ b/packages/components/avatar/__tests__/avatar-group.test.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { render } from '@test/utils'; + +import { Avatar, AvatarGroup } from '..'; + +import type { TdAvatarGroupProps } from '../type'; + +function getAvatarGroupDefaultMount(props?: Partial, events?: Record) { + return render( + + + W + } /> + TD + ME + , + ); +} + +describe('AvatarGroup', () => { + describe('props', () => { + const cascadingClassNameList = ['t-avatar--offset-left', 't-avatar--offset-right']; + (['left-up', 'right-up'] as TdAvatarGroupProps['cascading'][]).forEach((item, index) => { + test(`cascading is equal to ${item}`, () => { + const { container } = render(); + expect(container.firstChild).toHaveClass(cascadingClassNameList[index]); + expect(container).toMatchSnapshot(); + }); + }); + + test('max', () => { + const { container } = getAvatarGroupDefaultMount({ max: 3 }); + expect(container.querySelectorAll('.t-avatar').length).toBe(4); + expect(container.querySelectorAll('.t-avatar__collapse').length).toBe(1); + expect(container.querySelector('.t-avatar__collapse > span').textContent).toBe('+2'); + }); + + test(`size is equal to small`, () => { + const { container } = getAvatarGroupDefaultMount({ size: 'small' }); + const domWrapper = container.querySelector('.t-avatar'); + expect(domWrapper).toHaveClass('t-size-s'); + const domWrapper1 = container.querySelector('.t-avatar:nth-child(5)'); + expect(domWrapper1).toHaveClass('t-size-l'); + }); + + test(`size is equal to large`, () => { + const { container } = getAvatarGroupDefaultMount({ size: 'large' }); + const domWrapper = container.querySelector('.t-avatar'); + expect(domWrapper).toHaveClass('t-size-l'); + const domWrapper1 = container.querySelector('.t-avatar:nth-child(4)'); + expect(domWrapper1).toHaveClass('t-size-s'); + }); + + test(`size is equal to 120px`, () => { + const { container } = getAvatarGroupDefaultMount({ size: '120px' }); + const domWrapper = container.querySelector('.t-avatar') as HTMLElement; + expect(domWrapper.style.width).toBe('120px'); + expect(domWrapper.style.height).toBe('120px'); + expect(domWrapper.style.fontSize).toBe('60px'); + }); + test('collapseAvatar', () => { + const { container } = getAvatarGroupDefaultMount({ + collapseAvatar: TNode, + max: 3, + }); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/components/avatar/__tests__/avatar.test.tsx b/packages/components/avatar/__tests__/avatar.test.tsx index 7a697873d5..a47af945f9 100644 --- a/packages/components/avatar/__tests__/avatar.test.tsx +++ b/packages/components/avatar/__tests__/avatar.test.tsx @@ -1,40 +1,131 @@ import React from 'react'; import { mockDelay, render, simulateImageEvent, vi } from '@test/utils'; -import Avatar from '../Avatar'; - -describe('Avatar 组件测试', () => { - test('Avatar 默认 DOM 结构', async () => { - const imageSrc = 'https://tdesign.gtimg.com/site/avatar.jpg'; - const wrapper = render(); - const image: any = wrapper.getByAltText('test-avatar'); - expect(image.src).toBe(imageSrc); - }); +import { Avatar } from '..'; - test('Avatar 初始化 Size', async () => { - const imageSrc = 'https://tdesign.gtimg.com/site/avatar.jpg'; - const wrapper = render(); - const image = wrapper.container.querySelector('.t-image__wrapper'); - expect(image).toHaveStyle('width: 50px'); - }); +import type { TdAvatarProps } from '../type'; + +const imageSrc = 'https://tdesign.gtimg.com/site/avatar.jpg'; + +describe('Avatar', () => { + describe('props', () => { + test('alt', () => { + const wrapper = render(); + const container = wrapper.container.querySelector('img'); + expect(container.getAttribute('alt')).toBe('Avatar'); + }); + + test('hideOnLoadFailed', async () => { + const { container } = render( + , + ); + const imgDom = container.querySelector('img'); + simulateImageEvent(imgDom, 'error'); + await mockDelay(300); + expect(container.querySelector('.t-image')).toBeFalsy(); + }); + + test(`image is equal to https://tdesign.tencent.com/`, () => { + const { container } = render(); + const domWrapper = container.querySelector('img'); + expect(domWrapper.getAttribute('src')).toBe('https://tdesign.tencent.com/'); + }); + + ['circle', 'round'].forEach((item) => { + test(`shape is equal to ${item}`, () => { + const { container } = render(); + expect(container.firstChild).toHaveClass(`t-avatar--${item}`); + expect(container).toMatchSnapshot(); + }); + }); - test('Avatar 初始化 Shape', async () => { - const { container } = render(); - expect(container.firstChild).toHaveClass('t-avatar--round'); + const sizeClassNameMap = { + small: 't-size-s', + medium: 't-size-m', + large: 't-size-l', + }; + Object.entries(sizeClassNameMap).forEach(([enumValue, expectedClassName]) => { + test(`size is equal to ${enumValue}`, () => { + let propValue: any = { true: true, false: false }[enumValue]; + propValue = propValue === undefined ? enumValue : propValue; + const { container } = render(); + expect(container.firstChild).toHaveClass(expectedClassName); + }); + }); + + test(`size is equal to 120px`, () => { + const { container } = render(); + const domWrapper = container.firstChild as HTMLElement; + expect(domWrapper.style.width).toBe('120px'); + expect(domWrapper.style.height).toBe('120px'); + expect(domWrapper.style.fontSize).toBe('60px'); + }); + + test('Avatar 初始化 Size', async () => { + const wrapper = render(); + const image = wrapper.container.querySelector('.t-image__wrapper'); + expect(image).toHaveStyle('width: 50px'); + }); + + test('Avatar 初始化 Shape', async () => { + const { container } = render(); + expect(container.firstChild).toHaveClass('t-avatar--round'); + }); + test('children', () => { + const { container } = render( + + TNode + , + ); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('content', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('icon', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container.querySelector('.t-avatar__icon')).toBeTruthy(); + }); }); - test('Avatar 初始化 Dom', async () => { - const { container } = render(); - expect(container.firstChild).toBeInTheDocument(); + describe('events', () => { + test('error', async () => { + const onErrorFn = vi.fn(); + const { container } = render(); + const imgDom = container.querySelector('img'); + simulateImageEvent(imgDom, 'error'); + await mockDelay(300); + expect(onErrorFn).toHaveBeenCalled(); + expect(onErrorFn.mock.calls[0][0].e.type).toBe('error'); + }); + + test('Avatar onError 回调', async () => { + const mockOnErrorFn = vi.fn(); + const { container } = render(); + const imageDom = container.querySelector('img'); + simulateImageEvent(imageDom, 'error'); + await mockDelay(300); + expect(mockOnErrorFn).toHaveBeenCalled(); + expect(mockOnErrorFn.mock.calls[0][0].e.type).toBe('error'); + }); }); - test('Avatar onError 回调', async () => { - const mockOnErrorFn = vi.fn(); - const { container } = render(); - const imageDom = container.querySelector('img'); - simulateImageEvent(imageDom, 'error'); - await mockDelay(300); - expect(mockOnErrorFn).toHaveBeenCalled(); - expect(mockOnErrorFn.mock.calls[0][0].e.type).toBe('error'); + describe('scenarios', () => { + test('Avatar 默认 DOM 结构', async () => { + const wrapper = render(); + const image: any = wrapper.getByAltText('test-avatar'); + expect(image.src).toBe(imageSrc); + }); + + test('Avatar 初始化 Dom', async () => { + const { container } = render(); + expect(container.firstChild).toBeInTheDocument(); + }); }); }); diff --git a/packages/components/avatar/__tests__/mount.jsx b/packages/components/avatar/__tests__/mount.jsx deleted file mode 100644 index c67879db71..0000000000 --- a/packages/components/avatar/__tests__/mount.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import { render } from '@test/utils'; -import { Avatar } from '..'; - -// Avatar 数量变化会影响测试用例结果 -export function getAvatarGroupDefaultMount(AvatarGroup, props, events) { - return render( - - - W - } /> - TD - ME - - ); -} - -export default getAvatarGroupDefaultMount; diff --git a/packages/components/avatar/__tests__/vitest-avatar.test.jsx b/packages/components/avatar/__tests__/vitest-avatar.test.jsx deleted file mode 100644 index 206935472c..0000000000 --- a/packages/components/avatar/__tests__/vitest-avatar.test.jsx +++ /dev/null @@ -1,140 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * 该文件由脚本自动生成,如需修改请联系 PMC - * This file generated by scripts of tdesign-api. `npm run api:docs Avatar React(PC) vitest,finalProject` - * If you need to modify this file, contact PMC first please. - */ -import React from 'react'; -import { fireEvent, vi, render, mockDelay, simulateImageEvent } from '@test/utils'; -import { Avatar, AvatarGroup } from '..'; -import { getAvatarGroupDefaultMount } from './mount'; - -describe('Avatar Component', () => { - it('props.alt works fine', () => { - const wrapper = render(); - const container = wrapper.container.querySelector('img'); - expect(container.getAttribute('alt')).toBe('Avatar'); - }); - - it('props.children works fine', () => { - const { container } = render( - - TNode - , - ); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.content works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.hideOnLoadFailed works fine', async () => { - const { container } = render(); - const imgDom = container.querySelector('img'); - simulateImageEvent(imgDom, 'error'); - await mockDelay(300); - expect(container.querySelector('.t-image')).toBeFalsy(); - }); - - it('props.icon works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container.querySelector('.t-avatar__icon')).toBeTruthy(); - }); - - it(`props.image is equal to https://tdesign.tencent.com/`, () => { - const { container } = render(); - const domWrapper = container.querySelector('img'); - expect(domWrapper.getAttribute('src')).toBe('https://tdesign.tencent.com/'); - }); - - ['circle', 'round'].forEach((item) => { - it(`props.shape is equal to ${item}`, () => { - const { container } = render(); - expect(container.firstChild).toHaveClass(`t-avatar--${item}`); - expect(container).toMatchSnapshot(); - }); - }); - - const sizeClassNameMap = { small: 't-size-s', medium: 't-size-m', large: 't-size-l' }; - Object.entries(sizeClassNameMap).forEach(([enumValue, expectedClassName]) => { - it(`props.size is equal to ${enumValue}`, () => { - let propValue = { true: true, false: false }[enumValue]; - propValue = propValue === undefined ? enumValue : propValue; - const { container } = render(); - expect(container.firstChild).toHaveClass(expectedClassName); - }); - }); - - it(`props.size is equal to 120px`, () => { - const { container } = render(); - const domWrapper = container.firstChild; - expect(domWrapper.style.width).toBe('120px'); - expect(domWrapper.style.height).toBe('120px'); - expect(domWrapper.style.fontSize).toBe('60px'); - }); - - it('events.error works fine', async () => { - const onErrorFn = vi.fn(); - const { container } = render(); - const imgDom = container.querySelector('img'); - simulateImageEvent(imgDom, 'error'); - await mockDelay(300); - expect(onErrorFn).toHaveBeenCalled(); - expect(onErrorFn.mock.calls[0][0].e.type).toBe('error'); - }); -}); - -describe('AvatarGroup Component', () => { - const cascadingClassNameList = ['t-avatar--offset-left', 't-avatar--offset-right']; - ['left-up', 'right-up'].forEach((item, index) => { - it(`props.cascading is equal to ${item}`, () => { - const { container } = render(); - expect(container.firstChild).toHaveClass(cascadingClassNameList[index]); - expect(container).toMatchSnapshot(); - }); - }); - - it('props.collapseAvatar works fine', () => { - const { container } = getAvatarGroupDefaultMount(AvatarGroup, { - collapseAvatar: TNode, - max: 3, - }); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.max works fine. `{".t-avatar":4,".t-avatar__collapse":1,".t-avatar__collapse > span":{"text":"+2"}}` should exist', () => { - const { container } = getAvatarGroupDefaultMount(AvatarGroup, { max: 3 }); - expect(container.querySelectorAll('.t-avatar').length).toBe(4); - expect(container.querySelectorAll('.t-avatar__collapse').length).toBe(1); - expect(container.querySelector('.t-avatar__collapse > span').textContent).toBe('+2'); - }); - - it(`props.size is equal to small`, () => { - const { container } = getAvatarGroupDefaultMount(AvatarGroup, { size: 'small' }); - const domWrapper = container.querySelector('.t-avatar'); - expect(domWrapper).toHaveClass('t-size-s'); - const domWrapper1 = container.querySelector('.t-avatar:nth-child(5)'); - expect(domWrapper1).toHaveClass('t-size-l'); - }); - it(`props.size is equal to large`, () => { - const { container } = getAvatarGroupDefaultMount(AvatarGroup, { size: 'large' }); - const domWrapper = container.querySelector('.t-avatar'); - expect(domWrapper).toHaveClass('t-size-l'); - const domWrapper1 = container.querySelector('.t-avatar:nth-child(4)'); - expect(domWrapper1).toHaveClass('t-size-s'); - }); - - it(`props.size is equal to 120px`, () => { - const { container } = getAvatarGroupDefaultMount(AvatarGroup, { size: '120px' }); - const domWrapper = container.querySelector('.t-avatar'); - expect(domWrapper.style.width).toBe('120px'); - expect(domWrapper.style.height).toBe('120px'); - expect(domWrapper.style.fontSize).toBe('60px'); - }); -}); diff --git a/packages/components/back-top/__tests__/__snapshots__/vitest-back-top.test.jsx.snap b/packages/components/back-top/__tests__/__snapshots__/back-top.test.tsx.snap similarity index 83% rename from packages/components/back-top/__tests__/__snapshots__/vitest-back-top.test.jsx.snap rename to packages/components/back-top/__tests__/__snapshots__/back-top.test.tsx.snap index 779cb936ee..7c04528c04 100644 --- a/packages/components/back-top/__tests__/__snapshots__/vitest-back-top.test.jsx.snap +++ b/packages/components/back-top/__tests__/__snapshots__/back-top.test.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`BackTop Component > props.children works fine 1`] = ` +exports[`BackTop > props > children 1`] = `
`; -exports[`BackTop Component > props.content works fine 1`] = ` +exports[`BackTop > props > content 1`] = `
`; -exports[`BackTop Component > props.default works fine 1`] = ` +exports[`BackTop > props > default 1`] = `
`; -exports[`BackTop Component > props.shape is equal to circle 1`] = ` +exports[`BackTop > props > shape is equal to circle 1`] = `
`; -exports[`BackTop Component > props.shape is equal to square 1`] = ` +exports[`BackTop > props > shape is equal to square 1`] = `
`; -exports[`BackTop Component > props.size is equal to medium 1`] = ` +exports[`BackTop > props > size is equal to medium 1`] = `
`; -exports[`BackTop Component > props.size is equal to small 1`] = ` +exports[`BackTop > props > size is equal to small 1`] = `
`; -exports[`BackTop Component > props.theme is equal to dark 1`] = ` +exports[`BackTop > props > theme is equal to dark 1`] = `
`; -exports[`BackTop Component > props.theme is equal to light 1`] = ` +exports[`BackTop > props > theme is equal to light 1`] = `
`; -exports[`BackTop Component > props.theme is equal to primary 1`] = ` +exports[`BackTop > props > theme is equal to primary 1`] = `
`; -exports[`Button Component > props.block works fine 2`] = ` +exports[`Button > props > block 2`] = `
`; -exports[`Button Component > props.children works fine 1`] = ` +exports[`Button > props > children 1`] = `
`; -exports[`Button Component > props.content works fine 1`] = ` +exports[`Button > props > content 1`] = `
`; -exports[`Button Component > props.disabled works fine 1`] = ` +exports[`Button > props > disabled 1`] = `
props.disabled works fine 1`] = `
`; -exports[`Button Component > props.disabled works fine 2`] = ` +exports[`Button > props > disabled 2`] = `
`; -exports[`Button Component > props.ghost works fine 1`] = ` +exports[`Button > props > ghost 1`] = `
`; -exports[`Button Component > props.ghost works fine 2`] = ` +exports[`Button > props > ghost 2`] = `
`; -exports[`Button Component > props.href works fine 1`] = ` +exports[`Button > props > href 1`] = ` `; -exports[`Button Component > props.loading works fine 1`] = ` +exports[`Button > props > loading 1`] = `
`; -exports[`Button Component > props.loading works fine 2`] = ` +exports[`Button > props > loading 2`] = `
`; -exports[`Button Component > props.loading: Button contains element \`.t-loading\` 1`] = ` +exports[`Button > props > loading contains .t-loading 1`] = `
`; -exports[`Button Component > props.shape is equal to circle 1`] = ` +exports[`Button > props > shape is equal to circle 1`] = `
`; -exports[`Button Component > props.shape is equal to rectangle 1`] = ` +exports[`Button > props > shape is equal to rectangle 1`] = `
`; -exports[`Button Component > props.shape is equal to round 1`] = ` +exports[`Button > props > shape is equal to round 1`] = `
`; -exports[`Button Component > props.shape is equal to square 1`] = ` +exports[`Button > props > shape is equal to square 1`] = `
`; -exports[`Button Component > props.size is equal to large 1`] = ` +exports[`Button > props > size is equal to large 1`] = `
`; -exports[`Button Component > props.size is equal to medium 1`] = ` +exports[`Button > props > size is equal to medium 1`] = `
`; -exports[`Button Component > props.size is equal to small 1`] = ` +exports[`Button > props > size is equal to small 1`] = `
`; -exports[`Button Component > props.tag is equal to a 1`] = ` +exports[`Button > props > tag is equal to a 1`] = `
`; -exports[`Button Component > props.tag is equal to button 1`] = ` +exports[`Button > props > tag is equal to button 1`] = `
`; -exports[`Button Component > props.tag is equal to div 1`] = ` +exports[`Button > props > tag is equal to div 1`] = `
props.tag is equal to div 1`] = `
`; -exports[`Button Component > props.theme is equal to danger 1`] = ` +exports[`Button > props > theme is equal to danger 1`] = `
`; -exports[`Button Component > props.theme is equal to default 1`] = ` +exports[`Button > props > theme is equal to default 1`] = `
`; -exports[`Button Component > props.theme is equal to primary 1`] = ` +exports[`Button > props > theme is equal to primary 1`] = `
`; -exports[`Button Component > props.theme is equal to success 1`] = ` +exports[`Button > props > theme is equal to success 1`] = `
`; -exports[`Button Component > props.theme is equal to warning 1`] = ` +exports[`Button > props > theme is equal to warning 1`] = `
`; -exports[`Button Component > props.variant is equal to base 1`] = ` +exports[`Button > props > variant is equal to base 1`] = `
`; -exports[`Button Component > props.variant is equal to dashed 1`] = ` +exports[`Button > props > variant is equal to dashed 1`] = `
`; -exports[`Button Component > props.variant is equal to outline 1`] = ` +exports[`Button > props > variant is equal to outline 1`] = `
`; -exports[`Button Component > props.variant is equal to text 1`] = ` +exports[`Button > props > variant is equal to text 1`] = `
); + expect(container1.querySelector(`.${'t-size-full-width'}`)).toBeFalsy(); + // block = true + const { container: container2 } = render(); + expect(container2.firstChild).toHaveClass('t-size-full-width'); + expect(container2).toMatchSnapshot(); + // block = false + const { container: container3 } = render(); + expect(container3.querySelector(`.${'t-size-full-width'}`)).toBeFalsy(); + expect(container3).toMatchSnapshot(); + }); + + test('disabled', () => { + // disabled default value is false + const { container: container1 } = render(); + expect(container1.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); + // disabled = true + const { container: container2 } = render(); + expect(container2.firstChild).toHaveClass('t-is-disabled'); + expect(container2).toMatchSnapshot(); + // disabled = false + const { container: container3 } = render(); + expect(container3.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); + expect(container3).toMatchSnapshot(); + }); + + test('ghost', () => { + // ghost default value is false + const { container: container1 } = render(); + expect(container1.querySelector(`.${'t-button--ghost'}`)).toBeFalsy(); + // ghost = true + const { container: container2 } = render(); + expect(container2.firstChild).toHaveClass('t-button--ghost'); + expect(container2).toMatchSnapshot(); + // ghost = false + const { container: container3 } = render(); + expect(container3.querySelector(`.${'t-button--ghost'}`)).toBeFalsy(); + expect(container3).toMatchSnapshot(); + }); + + test('href', () => { + const { container } = render(); + expect((container.firstChild as HTMLElement).getAttribute('href')).toBe('https://tdesign.tencent.com/'); + expect(container).toMatchSnapshot(); + }); + + test('loading', () => { + // loading default value is false + const { container: container1 } = render(); + expect(container1.querySelector(`.${'t-is-loading'}`)).toBeFalsy(); + // loading = true + const { container: container2 } = render(); + expect(container2.firstChild).toHaveClass('t-is-loading'); + expect(container2).toMatchSnapshot(); + // loading = false + const { container: container3 } = render(); + expect(container3.querySelector(`.${'t-is-loading'}`)).toBeFalsy(); + expect(container3).toMatchSnapshot(); + }); + + test('loading contains .t-loading', () => { + // loading default value is false + const { container } = render(); + expect(container.querySelector('.t-loading')).toBeFalsy(); + // loading = false + const { container: container1 } = render(); + expect(container1.querySelector('.t-loading')).toBeFalsy(); + // loading = true + const { container: container2 } = render(); + expect(container2.querySelector('.t-loading')).toBeTruthy(); + expect(container2).toMatchSnapshot(); + }); + + const shapeClassNameList: Array> = [ + { 't-button--shape-rectangle': false }, + 't-button--shape-square', + 't-button--shape-round', + 't-button--shape-circle', + ]; + (['rectangle', 'square', 'round', 'circle'] as TdButtonProps['shape'][]).forEach((item, index) => { + test(`shape is equal to ${item}`, () => { + const { container } = render(); + if (typeof shapeClassNameList[index] === 'string') { + expect(container.firstChild).toHaveClass(shapeClassNameList[index]); + } else if (typeof shapeClassNameList[index] === 'object') { + const classNameKey = Object.keys(shapeClassNameList[index])[0]; + expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); + } + expect(container).toMatchSnapshot(); + }); + }); + + const sizeClassNameList: Array> = ['t-size-s', { 't-size-m': false }, 't-size-l']; + (['small', 'medium', 'large'] as TdButtonProps['size'][]).forEach((item, index) => { + test(`size is equal to ${item}`, () => { + const { container } = render(); + if (typeof sizeClassNameList[index] === 'string') { + expect(container.firstChild).toHaveClass(sizeClassNameList[index]); + } else if (typeof sizeClassNameList[index] === 'object') { + const classNameKey = Object.keys(sizeClassNameList[index])[0]; + expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); + } + expect(container).toMatchSnapshot(); + }); + }); + + const tagExpectedDom = ['button', 'a', 'div']; + (['button', 'a', 'div'] as TdButtonProps['tag'][]).forEach((item, index) => { + test(`tag is equal to ${item}`, () => { + const { container } = render(); + expect(container.querySelector(tagExpectedDom[index])).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + }); + + (['default', 'primary', 'danger', 'warning', 'success'] as TdButtonProps['theme'][]).forEach((item) => { + test(`theme is equal to ${item}`, () => { + const { container } = render(); + expect(container.firstChild).toHaveClass(`t-button--theme-${item}`); + expect(container).toMatchSnapshot(); + }); + }); + + const attributeValues = ['submit', 'reset', 'button']; + (['submit', 'reset', 'button'] as TdButtonProps['type'][]).forEach((item, index) => { + test(`type is equal to ${item}`, () => { + const { container } = render(); + expect((container.firstChild as HTMLElement).getAttribute('type')).toBe(attributeValues[index]); + }); + }); + + (['base', 'outline', 'dashed', 'text'] as TdButtonProps['variant'][]).forEach((item) => { + test(`variant is equal to ${item}`, () => { + const { container } = render(); + expect(container.firstChild).toHaveClass(`t-button--variant-${item}`); + expect(container).toMatchSnapshot(); + }); + }); + test('children', () => { + const { container } = render( + , + ); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('content', () => { + const { container } = render(); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('icon', () => { + const { container } = render(); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + + test('suffix', () => { + const { container } = render(); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + }); + + describe('events', () => { + test('click', () => { + const fn = vi.fn(); + const { container } = render(); + fireEvent.click(container.firstChild); + expect(fn).toHaveBeenCalled(); + expect(fn.mock.calls[0][0].stopPropagation).toBeTruthy(); + expect(fn.mock.calls[0][0].type).toBe('click'); + }); + }); +}); diff --git a/packages/components/button/__tests__/vitest-button.test.jsx b/packages/components/button/__tests__/vitest-button.test.jsx deleted file mode 100644 index d4e7bba212..0000000000 --- a/packages/components/button/__tests__/vitest-button.test.jsx +++ /dev/null @@ -1,187 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * 该文件由脚本自动生成,如需修改请联系 PMC - * This file generated by scripts of tdesign-api. `npm run api:docs Button React(PC) vitest,finalProject` - * If you need to modify this file, contact PMC first please. - */ -import React from 'react'; -import { fireEvent, vi, render } from '@test/utils'; -import { Button } from '..'; - -describe('Button Component', () => { - it('props.block works fine', () => { - // block default value is false - const { container: container1 } = render(); - expect(container1.querySelector(`.${'t-size-full-width'}`)).toBeFalsy(); - // block = true - const { container: container2 } = render(); - expect(container2.firstChild).toHaveClass('t-size-full-width'); - expect(container2).toMatchSnapshot(); - // block = false - const { container: container3 } = render(); - expect(container3.querySelector(`.${'t-size-full-width'}`)).toBeFalsy(); - expect(container3).toMatchSnapshot(); - }); - - it('props.children works fine', () => { - const { container } = render( - , - ); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.content works fine', () => { - const { container } = render(); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.disabled works fine', () => { - // disabled default value is false - const { container: container1 } = render(); - expect(container1.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - // disabled = true - const { container: container2 } = render(); - expect(container2.firstChild).toHaveClass('t-is-disabled'); - expect(container2).toMatchSnapshot(); - // disabled = false - const { container: container3 } = render(); - expect(container3.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - expect(container3).toMatchSnapshot(); - }); - - it('props.ghost works fine', () => { - // ghost default value is false - const { container: container1 } = render(); - expect(container1.querySelector(`.${'t-button--ghost'}`)).toBeFalsy(); - // ghost = true - const { container: container2 } = render(); - expect(container2.firstChild).toHaveClass('t-button--ghost'); - expect(container2).toMatchSnapshot(); - // ghost = false - const { container: container3 } = render(); - expect(container3.querySelector(`.${'t-button--ghost'}`)).toBeFalsy(); - expect(container3).toMatchSnapshot(); - }); - - it('props.href works fine', () => { - const { container } = render(); - expect(container.firstChild.getAttribute('href')).toBe('https://tdesign.tencent.com/'); - expect(container).toMatchSnapshot(); - }); - - it('props.icon works fine', () => { - const { container } = render(); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - it('props.loading works fine', () => { - // loading default value is false - const { container: container1 } = render(); - expect(container1.querySelector(`.${'t-is-loading'}`)).toBeFalsy(); - // loading = true - const { container: container2 } = render(); - expect(container2.firstChild).toHaveClass('t-is-loading'); - expect(container2).toMatchSnapshot(); - // loading = false - const { container: container3 } = render(); - expect(container3.querySelector(`.${'t-is-loading'}`)).toBeFalsy(); - expect(container3).toMatchSnapshot(); - }); - - it('props.loading: Button contains element `.t-loading`', () => { - // loading default value is false - const { container } = render(); - expect(container.querySelector('.t-loading')).toBeFalsy(); - // loading = false - const { container: container1 } = render(); - expect(container1.querySelector('.t-loading')).toBeFalsy(); - // loading = true - const { container: container2 } = render(); - expect(container2.querySelector('.t-loading')).toBeTruthy(); - expect(container2).toMatchSnapshot(); - }); - - const shapeClassNameList = [ - { 't-button--shape-rectangle': false }, - 't-button--shape-square', - 't-button--shape-round', - 't-button--shape-circle', - ]; - ['rectangle', 'square', 'round', 'circle'].forEach((item, index) => { - it(`props.shape is equal to ${item}`, () => { - const { container } = render(); - if (typeof shapeClassNameList[index] === 'string') { - expect(container.firstChild).toHaveClass(shapeClassNameList[index]); - } else if (typeof shapeClassNameList[index] === 'object') { - const classNameKey = Object.keys(shapeClassNameList[index])[0]; - expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); - } - expect(container).toMatchSnapshot(); - }); - }); - - const sizeClassNameList = ['t-size-s', { 't-size-m': false }, 't-size-l']; - ['small', 'medium', 'large'].forEach((item, index) => { - it(`props.size is equal to ${item}`, () => { - const { container } = render(); - if (typeof sizeClassNameList[index] === 'string') { - expect(container.firstChild).toHaveClass(sizeClassNameList[index]); - } else if (typeof sizeClassNameList[index] === 'object') { - const classNameKey = Object.keys(sizeClassNameList[index])[0]; - expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); - } - expect(container).toMatchSnapshot(); - }); - }); - - it('props.suffix works fine', () => { - const { container } = render(); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - const tagExpectedDom = ['button', 'a', 'div']; - ['button', 'a', 'div'].forEach((item, index) => { - it(`props.tag is equal to ${item}`, () => { - const { container } = render(); - expect(container.querySelector(tagExpectedDom[index])).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - }); - - ['default', 'primary', 'danger', 'warning', 'success'].forEach((item) => { - it(`props.theme is equal to ${item}`, () => { - const { container } = render(); - expect(container.firstChild).toHaveClass(`t-button--theme-${item}`); - expect(container).toMatchSnapshot(); - }); - }); - - const attributeValues = ['submit', 'reset', 'button']; - ['submit', 'reset', 'button'].forEach((item, index) => { - it(`props.type is equal to ${item}`, () => { - const { container } = render(); - expect(container.firstChild.getAttribute('type')).toBe(attributeValues[index]); - }); - }); - - ['base', 'outline', 'dashed', 'text'].forEach((item) => { - it(`props.variant is equal to ${item}`, () => { - const { container } = render(); - expect(container.firstChild).toHaveClass(`t-button--variant-${item}`); - expect(container).toMatchSnapshot(); - }); - }); - - it('events.click works fine', () => { - const fn = vi.fn(); - const { container } = render(); - fireEvent.click(container.firstChild); - expect(fn).toHaveBeenCalled(); - expect(fn.mock.calls[0][0].stopPropagation).toBeTruthy(); - expect(fn.mock.calls[0][0].type).toBe('click'); - }); -}); diff --git a/packages/components/calendar/__tests__/calendar.test.tsx b/packages/components/calendar/__tests__/calendar.test.tsx index df29bcc33e..dc2962dcb9 100644 --- a/packages/components/calendar/__tests__/calendar.test.tsx +++ b/packages/components/calendar/__tests__/calendar.test.tsx @@ -6,10 +6,8 @@ import Calendar from '../Calendar'; // 固定时间,当使用 new Date() 时,返回固定时间,防止“当前时间”的副作用影响,导致 snapshot 变更,mockdate 插件见 https://github.com/boblauer/MockDate MockDate.set('2021-08-27'); -// every component needs four parts: props/events/slots/functions. -describe('Calendar测试', () => { - // test props api - describe(':props', () => { +describe('Calendar', () => { + describe('props', () => { test(':theme', () => { const { container } = render(); expect(container.children[0].classList.contains('t-calendar--card')).toBeTruthy(); @@ -20,7 +18,9 @@ describe('Calendar测试', () => { const tableCell = getAllByText(/^0/i); expect(tableCell[0]).toBeInTheDocument(); }); + }); + describe('events', () => { test('click 单击单元格', async () => { const mockFn = vi.fn(); const { container } = render(); diff --git a/packages/components/card/__tests__/card.test.tsx b/packages/components/card/__tests__/card.test.tsx index 8170adf818..83e55c68a4 100644 --- a/packages/components/card/__tests__/card.test.tsx +++ b/packages/components/card/__tests__/card.test.tsx @@ -8,100 +8,107 @@ const cover = 'https://tdesign.gtimg.com/site/source/card-demo.png'; const avatar = 'https://tdesign.gtimg.com/site/avatar.jpg'; describe('Card', () => { - test('title', () => { - const { container } = render(); - expect(container.querySelector('.t-card__title')).toBeInTheDocument(); - expect(container.querySelector('.t-card__title').innerHTML).toBe('标题'); - }); - - test('subtitle', () => { - const { container } = render(); - expect(container.querySelector('.t-card__subtitle')).toBeInTheDocument(); - expect(container.querySelector('.t-card__subtitle').innerHTML).toBe('副标题'); - }); + describe('props', () => { + test('title', () => { + const { container } = render(); + expect(container.querySelector('.t-card__title')).toBeInTheDocument(); + expect(container.querySelector('.t-card__title').innerHTML).toBe('标题'); + }); - test('description', () => { - const { container } = render(); - expect(container.querySelector('.t-card__description')).toBeInTheDocument(); - expect(container.querySelector('.t-card__description').innerHTML).toBe('这是一段描述'); - }); + test('subtitle', () => { + const { container } = render(); + expect(container.querySelector('.t-card__subtitle')).toBeInTheDocument(); + expect(container.querySelector('.t-card__subtitle').innerHTML).toBe('副标题'); + }); - test('avatar', () => { - const { container } = render(}>); - expect(container.querySelector('.t-avatar')).toBeInTheDocument(); - expect(container.querySelector('.t-card__avatar')).toBeInTheDocument(); - }); + test('description', () => { + const { container } = render(); + expect(container.querySelector('.t-card__description')).toBeInTheDocument(); + expect(container.querySelector('.t-card__description').innerHTML).toBe('这是一段描述'); + }); - test('cover', () => { - const { container } = render(); - expect(container.querySelector('.t-card__cover')).toBeInTheDocument(); - expect(container.querySelector('img').getAttribute('src')).toBe(cover); - }); + test('cover', () => { + const { container } = render(); + expect(container.querySelector('.t-card__cover')).toBeInTheDocument(); + expect(container.querySelector('img').getAttribute('src')).toBe(cover); + }); - test('header', () => { - const { container } = render(); - expect(container.querySelector('.t-card__header')).toBeInTheDocument(); - }); + test('header', () => { + const { container } = render(); + expect(container.querySelector('.t-card__header')).toBeInTheDocument(); + }); - test('headerClassName and headerStyle', () => { - const { container } = render( - , - ); - const headerEle = container.querySelector('.t-card__header'); - expect(headerEle).toHaveClass('test-header-class-name'); - expect(window.getComputedStyle(headerEle).height).toBe(`100px`); - }); + test('loading', () => { + const { container } = render(); + expect(container.querySelector('.t-loading')).toBeInTheDocument(); + }); + test('custom loading props', () => { + const customProps = { + text: 'TDesign努力加载中...', + }; + const { container } = render( + , + ); + expect(container.querySelector('.t-loading')).toBeInTheDocument(); + expect(container.querySelector('.t-loading__text').textContent).toBe('TDesign努力加载中...'); + }); + test('avatar', () => { + const { container } = render(}>); + expect(container.querySelector('.t-avatar')).toBeInTheDocument(); + expect(container.querySelector('.t-card__avatar')).toBeInTheDocument(); + }); - test('footer', () => { - const { container } = render(底部
}>); - expect(container.querySelector('.t-card__footer')).toBeInTheDocument(); - expect(container.querySelector('.t-card__footer').textContent).toBe('底部'); - }); + test('footer', () => { + const { container } = render(底部
}>); + expect(container.querySelector('.t-card__footer')).toBeInTheDocument(); + expect(container.querySelector('.t-card__footer').textContent).toBe('底部'); + }); - test('footerClassName and footerStyle', () => { - const { container } = render( - 底部
}>, - ); - const footerEle = container.querySelector('.t-card__footer'); - expect(footerEle).toHaveClass('test-footer-class-name'); - expect(window.getComputedStyle(footerEle).height).toBe(`100px`); + test('actions', () => { + const { container } = render(操作
}>); + expect(container.querySelector('.t-card__actions')).toBeInTheDocument(); + expect(container.querySelector('.t-card__actions').textContent).toBe('操作'); + }); }); - test('bodyClassName and bodyStyle', () => { - const { container } = render( - -
body
-
, - ); - const bodyEle = container.querySelector('.test-body-class-name'); - expect(bodyEle).not.toBeNull(); - expect(window.getComputedStyle(bodyEle).height).toBe(`100px`); - }); + describe('scenarios', () => { + test('headerClassName and headerStyle', () => { + const { container } = render( + , + ); + const headerEle = container.querySelector('.t-card__header'); + expect(headerEle).toHaveClass('test-header-class-name'); + expect(window.getComputedStyle(headerEle).height).toBe(`100px`); + }); - test('actions', () => { - const { container } = render(操作
}>); - expect(container.querySelector('.t-card__actions')).toBeInTheDocument(); - expect(container.querySelector('.t-card__actions').textContent).toBe('操作'); - }); + test('footerClassName and footerStyle', () => { + const { container } = render( + 底部
} + >, + ); + const footerEle = container.querySelector('.t-card__footer'); + expect(footerEle).toHaveClass('test-footer-class-name'); + expect(window.getComputedStyle(footerEle).height).toBe(`100px`); + }); - test('loading', () => { - const { container } = render(); - expect(container.querySelector('.t-loading')).toBeInTheDocument(); - }); - test('custom loading props', () => { - const customProps = { - text: 'TDesign努力加载中...', - }; - const { container } = render( - , - ); - expect(container.querySelector('.t-loading')).toBeInTheDocument(); - expect(container.querySelector('.t-loading__text').textContent).toBe('TDesign努力加载中...'); + test('bodyClassName and bodyStyle', () => { + const { container } = render( + +
body
+
, + ); + const bodyEle = container.querySelector('.test-body-class-name'); + expect(bodyEle).not.toBeNull(); + expect(window.getComputedStyle(bodyEle).height).toBe(`100px`); + }); }); }); diff --git a/packages/components/cascader/__tests__/cascader-panel.test.tsx b/packages/components/cascader/__tests__/cascader-panel.test.tsx new file mode 100644 index 0000000000..31836f80df --- /dev/null +++ b/packages/components/cascader/__tests__/cascader-panel.test.tsx @@ -0,0 +1,126 @@ +import React, { useState } from 'react'; +import { fireEvent, render } from '@test/utils'; + +import { CascaderPanel } from '../index'; + +const options = [ + { + children: [ + { + label: '子选项一', + value: '1.1', + }, + { + label: '子选项二', + value: '1.2', + }, + ], + label: '选项一', + value: '1', + }, + { + children: [ + { + label: '子选项一', + value: '2.1', + }, + { + label: '子选项二', + value: '2.2', + }, + ], + label: '选项二', + value: '2', + }, +]; + +describe('CascaderPanel', () => { + describe('props', () => { + test('Panel组件 trigger 为 hover 时,应该正确展开下一层级', () => { + const { container, getByText } = render(); + expect(container.querySelectorAll('.t-cascader__menu').length).toBe(1); + fireEvent.mouseEnter(getByText('选项一')); + expect(getByText('子选项一')).toBeInTheDocument(); + expect(container.querySelectorAll('.t-cascader__menu').length).toBe(2); + }); + + test('Panel组件 multiple 测试', () => { + const { getByText, getByTitle } = render(); + fireEvent.click(getByText('选项一')); + expect(getByTitle('子选项一')).not.toHaveClass('t-is-checked'); + // 选中一个子选项,父级应为 indeterminate 状态 + fireEvent.click(getByText('子选项一')); + expect(getByTitle('选项一')).toHaveClass('t-is-indeterminate'); + expect(getByTitle('子选项一')).toHaveClass('t-is-checked'); + // 选中全部子选项,父级应该为 checked 状态 + fireEvent.click(getByText('子选项二')); + expect(getByTitle('选项一')).toHaveClass('t-is-checked'); + expect(getByTitle('子选项二')).toHaveClass('t-is-checked'); + }); + }); + + describe('scenarios', () => { + test('CascaderPanel 基础测试', () => { + const btnText = 'change'; + const TestComponent = () => { + const [value, setValue] = useState(); + const [multiple, setMultiple] = useState(false); + const onChange = (nextValue) => { + setValue(nextValue); + }; + const onClick = () => { + setMultiple(!multiple); + }; + return ( + <> + + + + ); + }; + + const { container, getByText } = render(); + expect(getByText('选项一')).toBeInTheDocument(); + expect(container.querySelectorAll('.t-cascader__menu').length).toBe(1); + // 首次展开子选项会插入新的 ul + fireEvent.click(getByText('选项一')); + expect(getByText('子选项一')).toBeInTheDocument(); + expect(container.querySelectorAll('.t-cascader__menu').length).toBe(2); + // multiple 测试,存在 checkbox + expect(container.querySelector('input')).toBeNull(); + fireEvent.click(getByText(btnText)); + expect(container.querySelector('input')).toBeInTheDocument(); + }); + + test('Panel组件 multiple 时设置 max 测试', async () => { + const btnText = 'set max 1'; + const TestComponent = () => { + const [max, setMax] = useState(3); + return ( + <> + + + + ); + }; + const { getByText, getByTitle } = render(); + // 所选内容不超过 max ,都可选中 + fireEvent.click(getByTitle('选项一').querySelector('input')); + expect(getByTitle('选项一')).toHaveClass('t-is-checked'); + expect(getByTitle('子选项一')).toHaveClass('t-is-checked'); + expect(getByTitle('子选项二')).toHaveClass('t-is-checked'); + // 清空所有选中 + fireEvent.click(getByTitle('选项一').querySelector('input')); + // 设置 max 为 1,点击父级无法选中任何节点 + fireEvent.click(getByText(btnText)); + fireEvent.click(getByTitle('选项一')); + fireEvent.click(getByTitle('选项一').querySelector('input')); + expect(getByTitle('选项一')).not.toHaveClass('t-is-checked'); + expect(getByTitle('子选项一')).not.toHaveClass('t-is-checked'); + // 选中一个子节点后所有节点为 disabled + fireEvent.click(getByTitle('子选项一')); + expect(getByTitle('子选项一')).toHaveClass('t-is-checked'); + expect(getByTitle('选项二')).toHaveClass('t-is-disabled'); + }); + }); +}); diff --git a/packages/components/cascader/__tests__/cascader.test.tsx b/packages/components/cascader/__tests__/cascader.test.tsx index 90dad6c926..b4c5b6737c 100644 --- a/packages/components/cascader/__tests__/cascader.test.tsx +++ b/packages/components/cascader/__tests__/cascader.test.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { fireEvent, mockDelay, mockTimeout, render, userEvent, vi } from '@test/utils'; import Tag from '../../tag'; -import Cascader, { CascaderPanel } from '../index'; +import Cascader from '../index'; const options = [ { @@ -80,503 +80,425 @@ const optionsData = [ const popupSelector = '.t-popup'; // TODO -describe('Cascader 组件测试', () => { - test('设置 checkStrictly 时每项应该可各自选中或取消', async () => { - const btnText = 'change'; - const TestComponent = () => { - const [strict, setStrict] = useState(false); - return ( - <> - - - - ); - }; - const { getByText } = render(); - // 默认值为 false - fireEvent.click(document.querySelector('input')); - fireEvent.click(getByText('选项一')); - expect(getByText('子选项一')).toHaveTextContent('子选项一'); - fireEvent.click(getByText('子选项一')); - expect(document.querySelector('.t-input__inner')).toHaveValue('选项一 / 子选项一'); - // 设置值为 true,可直接选择选项一 - await mockTimeout(() => fireEvent.click(getByText(btnText))); - fireEvent.click(document.querySelector('input')); - fireEvent.click(getByText('选项一')); - expect(document.querySelector('.t-input__inner')).toHaveValue('选项一'); - }); - - test('onBlur 测试,应该正确触发', async () => { - const onBlur = vi.fn(); - const { container } = render(); - // 模拟 blur 时触发 onBlur - fireEvent.click(container.querySelector('.t-input')); - await mockDelay(100); - fireEvent.mouseDown(document); - expect(onBlur).toHaveBeenCalled(); - }); - - test('selectInputProps.onInputChange 测试,应该正确触发', async () => { - const selectInputProps = { - onInputChange: () => 1, - }; - const enterText = 'test'; - const spy = vi.spyOn(selectInputProps, 'onInputChange'); - render(); - // 模拟用户键盘输入 "test" ,一共会触发四次 onInputChange - await userEvent.type(document.querySelector('input'), enterText); - await mockTimeout(() => expect(spy).toHaveBeenCalledTimes(enterText.length)); - }); - - test('selectInputProps.onTagChange 测试,应该正确触发且页面展示正常', async () => { - const selectInputProps = { - onTagChange: () => 1, - allowInput: true, - }; - const TextComponent = (props) => { - const [value, setValue] = useState(['1.1', '1.2']); - const onChange = (val) => { - setValue(val); +describe('Cascader', () => { + describe('props', () => { + test('设置 checkStrictly 时每项应该可各自选中或取消', async () => { + const btnText = 'change'; + const TestComponent = () => { + const [strict, setStrict] = useState(false); + return ( + <> + + + + ); }; + const { getByText } = render(); + // 默认值为 false + fireEvent.click(document.querySelector('input')); + fireEvent.click(getByText('选项一')); + expect(getByText('子选项一')).toHaveTextContent('子选项一'); + fireEvent.click(getByText('子选项一')); + expect(document.querySelector('.t-input__inner')).toHaveValue('选项一 / 子选项一'); + // 设置值为 true,可直接选择选项一 + await mockTimeout(() => fireEvent.click(getByText(btnText))); + fireEvent.click(document.querySelector('input')); + fireEvent.click(getByText('选项一')); + expect(document.querySelector('.t-input__inner')).toHaveValue('选项一'); + }); - return ( - + test('filterable 测试', async () => { + const placeholder = 'filter'; + const filterContent = '子选项一'; + const { getByPlaceholderText, getByText } = render( + , ); - }; - - const spy = vi.spyOn(selectInputProps, 'onTagChange'); - const { container } = render(); - const wrapper = container.querySelector('.t-input__prefix'); - // 初始时有两个 tag - expect(wrapper.querySelectorAll('.t-tag').length).toBe(2); - fireEvent.click(container.querySelector('.t-icon-close')); - expect(spy).toHaveBeenCalledTimes(1); - fireEvent.click(container.querySelector('.t-icon-close')); - // 删除两次后清空全部 tag,触发两次 onTagChange - expect(wrapper.querySelectorAll('.t-tag').length).toBe(0); - expect(spy).toHaveBeenCalledTimes(2); - // trigger 为 'enter' 时不执行,调用次数依然为 2 - const input = container.querySelector('input'); - await userEvent.type(input, 'abc{enter}'); - expect(input).toHaveValue('abc'); - expect(spy).toHaveBeenCalledTimes(2); - }); - - test('selectInputProps.onClear 测试,应该正确触发且页面展示正常', () => { - const selectInputProps = { - onClear: () => 1, - }; - const TextComponent = (props) => { - const [value, setValue] = useState(['1.1', '1.2']); - const onChange = (val) => { - setValue(val); - }; + // 搜索 子选项一 ,共有两个结果,成功匹配的内容应该高亮 + fireEvent.focus(getByPlaceholderText(placeholder)); + await userEvent.type(getByPlaceholderText(placeholder), filterContent); + await mockTimeout(() => + expect(document.querySelector(popupSelector).querySelectorAll('.t-cascader__item-label--filter').length).toBe( + 2, + ), + ); + // 清空搜索项,无匹配任何高亮内容 + await userEvent.type(getByPlaceholderText(placeholder), '{backspace}{backspace}{backspace}{backspace}'); + await mockTimeout(() => + expect(document.querySelector(popupSelector).querySelectorAll('.t-cascader__item-label--filter').length).toBe( + 0, + ), + ); + // 匹配不到任何内容 + await userEvent.type(getByPlaceholderText(placeholder), 'null'); + await mockTimeout(() => expect(getByText('暂无数据')).toBeInTheDocument()); + }); + test('render label', async () => { + const label = '单选:'; + const { getByText } = render(); + expect(getByText(label)).toBeInTheDocument(); + }); - return ( + test('render single valueDisplay', async () => { + const SingleValueDisplay = ({ value, selectedOptions }: any) => + value && ( +
+ + {selectedOptions?.[0]?.label} + ({value}) +
+ ); + const { container } = render( } clearable - /> + >, ); - }; - const spy = vi.spyOn(selectInputProps, 'onClear'); - const { container } = render(); - const wrapper = container.querySelector('.t-input__prefix'); - fireEvent.mouseOver(wrapper); - fireEvent.click(container.querySelector('.t-input__suffix').querySelector('.t-icon')); - expect(wrapper.querySelectorAll('.t-tag').length).toBe(0); - expect(spy).toHaveBeenCalled(); - }); + expect(container.querySelector('.valueDisplay')).toBeInTheDocument(); + }); - test('selectInputProps.onPopupVisibleChange 测试,应该正确触发', async () => { - const selectInputProps = { - onPopupVisibleChange: () => 1, - }; - const placeholderId = 'placeholder-id'; - const btnText = 'change'; - const TextComponent = (props) => { - const [disabled, setDisabled] = useState(false); - const onClick = () => { - setDisabled(!disabled); - }; - return ( - <> - - - + test('render multiple valueDisplay', async () => { + const MultipleValueDisplay = ({ value, selectedOptions, onClose }: any) => + value && value.length + ? selectedOptions.map((option, index) => ( + onClose(index)}> + + {option.label} + ({option.value}) + + )) + : null; + const { container } = render( + } + multiple + clearable + >, ); - }; - const spy = vi.spyOn(selectInputProps, 'onPopupVisibleChange'); - const { getByPlaceholderText, getByText } = render(); - // 展开收起一共触发 onPopupVisibleChange 2次 - fireEvent.click(getByPlaceholderText(placeholderId)); - expect(spy).toHaveBeenCalled(); - fireEvent.click(getByPlaceholderText(placeholderId)); - expect(spy).toHaveBeenCalledTimes(2); - await mockTimeout(() => - expect(document.querySelector(popupSelector)).toHaveStyle({ - display: 'none', - }), - ); - - // disabled 不会展开 popup,且不执行 onPopupVisibleChange - fireEvent.click(getByText(btnText)); - fireEvent.click(getByPlaceholderText(placeholderId)); - expect(document.querySelector(popupSelector)).toHaveStyle({ - display: 'none', + const optionsValue = container.querySelectorAll('.options-value'); + expect(optionsValue[0]).toHaveTextContent('(1.3)'); + expect(optionsValue[1]).toHaveTextContent('(2.1)'); + expect(optionsValue[2]).toHaveTextContent('(2.2)'); + fireEvent.click(container.querySelector('.t-input')); + await mockDelay(); + expect(document.querySelectorAll('.t-is-checked')).toHaveLength(2); + expect(document.querySelectorAll('.t-is-checked')[1].children[0]).toHaveAttribute('checked'); }); - expect(spy).toHaveBeenCalledTimes(2); - }); - test('filterable 测试', async () => { - const placeholder = 'filter'; - const filterContent = '子选项一'; - const { getByPlaceholderText, getByText } = render( - , - ); - // 搜索 子选项一 ,共有两个结果,成功匹配的内容应该高亮 - fireEvent.focus(getByPlaceholderText(placeholder)); - await userEvent.type(getByPlaceholderText(placeholder), filterContent); - await mockTimeout(() => - expect(document.querySelector(popupSelector).querySelectorAll('.t-cascader__item-label--filter').length).toBe(2), - ); - // 清空搜索项,无匹配任何高亮内容 - await userEvent.type(getByPlaceholderText(placeholder), '{backspace}{backspace}{backspace}{backspace}'); - await mockTimeout(() => - expect(document.querySelector(popupSelector).querySelectorAll('.t-cascader__item-label--filter').length).toBe(0), - ); - // 匹配不到任何内容 - await userEvent.type(getByPlaceholderText(placeholder), 'null'); - await mockTimeout(() => expect(getByText('暂无数据')).toBeInTheDocument()); - }); + test('render panelTopContent', async () => { + const panelTopContent = 'panelTopContent'; + const { getByText } = render( + , + ); - test('value is zero should be selected', async () => { - const popupSelector = '.t-popup'; - const labelText = 'my value is 0 that typeof number'; - const TestComponent = () => { - const [value, setValue] = useState(''); - const options = [ - { - value: 0, - label: labelText, - }, - ]; - const onChange = (value) => { - setValue(value); - }; + await fireEvent.click(document.querySelector('input')); + expect(getByText(panelTopContent)).toBeInTheDocument(); + }); - return ; - }; - const { getByText } = render(); - fireEvent.click(document.querySelector('input')); - expect(document.querySelector(popupSelector)).toHaveTextContent(labelText); - await mockTimeout(() => fireEvent.click(getByText(labelText))); - expect(document.querySelector('.t-input__inner')).toHaveValue(labelText); - }); + test('render panelBottomContent', async () => { + const panelBottomContent = 'panelBottomContent'; + const { getByText } = render( + , + ); - test('multiple Clicking the clear all button only triggers onChange once', async () => { - const onChange = vi.fn(); - const onRemove = vi.fn(); - const { container } = render( - , - ); - fireEvent.mouseEnter(container.querySelector('.t-input')); - await mockDelay(); - // 点击清除按钮只触发一次onChange,不触发onRemove - expect(container.querySelector('.t-tag-input__suffix-clear')).toBeTruthy(); - fireEvent.click(container.querySelector('.t-tag-input__suffix-clear')); - expect(onChange).toHaveBeenCalledTimes(1); - expect(onRemove).toHaveBeenCalledTimes(0); - // 点击tag onRemove,触发一次onRemove,触发两次onChange - fireEvent.click(container.querySelectorAll('.t-tag__icon-close')[0]); - expect(onRemove).toHaveBeenCalledTimes(1); - expect(onChange).toHaveBeenCalledTimes(2); + await fireEvent.click(document.querySelector('input')); + expect(getByText(panelBottomContent)).toBeInTheDocument(); + }); }); - test('render label', async () => { - const label = '单选:'; - const { getByText } = render(); - expect(getByText(label)).toBeInTheDocument(); - }); + describe('events', () => { + test('onBlur 测试,应该正确触发', async () => { + const onBlur = vi.fn(); + const { container } = render(); + // 模拟 blur 时触发 onBlur + fireEvent.click(container.querySelector('.t-input')); + await mockDelay(100); + fireEvent.mouseDown(document); + expect(onBlur).toHaveBeenCalled(); + }); - test('render single valueDisplay', async () => { - const SingleValueDisplay = ({ value, selectedOptions }: any) => - value && ( -
- - {selectedOptions?.[0]?.label} - ({value}) -
- ); - const { container } = render( - } - clearable - >, - ); - expect(container.querySelector('.valueDisplay')).toBeInTheDocument(); - }); + test('selectInputProps.onInputChange 测试,应该正确触发', async () => { + const selectInputProps = { + onInputChange: () => 1, + }; + const enterText = 'test'; + const spy = vi.spyOn(selectInputProps, 'onInputChange'); + render(); + // 模拟用户键盘输入 "test" ,一共会触发四次 onInputChange + await userEvent.type(document.querySelector('input'), enterText); + await mockTimeout(() => expect(spy).toHaveBeenCalledTimes(enterText.length)); + }); - test('render multiple valueDisplay', async () => { - const MultipleValueDisplay = ({ value, selectedOptions, onClose }: any) => - value && value.length - ? selectedOptions.map((option, index) => ( - onClose(index)}> - - {option.label} - ({option.value}) - - )) - : null; - const { container } = render( - } - multiple - clearable - >, - ); - const optionsValue = container.querySelectorAll('.options-value'); - expect(optionsValue[0]).toHaveTextContent('(1.3)'); - expect(optionsValue[1]).toHaveTextContent('(2.1)'); - expect(optionsValue[2]).toHaveTextContent('(2.2)'); - fireEvent.click(container.querySelector('.t-input')); - await mockDelay(); - expect(document.querySelectorAll('.t-is-checked')).toHaveLength(2); - expect(document.querySelectorAll('.t-is-checked')[1].children[0]).toHaveAttribute('checked'); - }); + test('selectInputProps.onTagChange 测试,应该正确触发且页面展示正常', async () => { + const selectInputProps = { + onTagChange: () => 1, + allowInput: true, + }; + const TextComponent = (props) => { + const [value, setValue] = useState(['1.1', '1.2']); + const onChange = (val) => { + setValue(val); + }; - test('render panelTopContent', async () => { - const panelTopContent = 'panelTopContent'; - const { getByText } = render( - , - ); + return ( + + ); + }; - await fireEvent.click(document.querySelector('input')); - expect(getByText(panelTopContent)).toBeInTheDocument(); - }); + const spy = vi.spyOn(selectInputProps, 'onTagChange'); + const { container } = render(); + const wrapper = container.querySelector('.t-input__prefix'); + // 初始时有两个 tag + expect(wrapper.querySelectorAll('.t-tag').length).toBe(2); + fireEvent.click(container.querySelector('.t-icon-close')); + expect(spy).toHaveBeenCalledTimes(1); + fireEvent.click(container.querySelector('.t-icon-close')); + // 删除两次后清空全部 tag,触发两次 onTagChange + expect(wrapper.querySelectorAll('.t-tag').length).toBe(0); + expect(spy).toHaveBeenCalledTimes(2); + // trigger 为 'enter' 时不执行,调用次数依然为 2 + const input = container.querySelector('input'); + await userEvent.type(input, 'abc{enter}'); + expect(input).toHaveValue('abc'); + expect(spy).toHaveBeenCalledTimes(2); + }); - test('render panelBottomContent', async () => { - const panelBottomContent = 'panelBottomContent'; - const { getByText } = render( - , - ); + test('selectInputProps.onClear 测试,应该正确触发且页面展示正常', () => { + const selectInputProps = { + onClear: () => 1, + }; + const TextComponent = (props) => { + const [value, setValue] = useState(['1.1', '1.2']); + const onChange = (val) => { + setValue(val); + }; - await fireEvent.click(document.querySelector('input')); - expect(getByText(panelBottomContent)).toBeInTheDocument(); - }); -}); + return ( + + ); + }; + const spy = vi.spyOn(selectInputProps, 'onClear'); + const { container } = render(); + const wrapper = container.querySelector('.t-input__prefix'); + fireEvent.mouseOver(wrapper); + fireEvent.click(container.querySelector('.t-input__suffix').querySelector('.t-icon')); + expect(wrapper.querySelectorAll('.t-tag').length).toBe(0); + expect(spy).toHaveBeenCalled(); + }); -describe('Cascader Panel 组件测试', () => { - test('CascaderPanel 基础测试', () => { - const btnText = 'change'; - const TestComponent = () => { - const [value, setValue] = useState(); - const [multiple, setMultiple] = useState(false); - const onChange = (value) => { - setValue(value); + test('selectInputProps.onPopupVisibleChange 测试,应该正确触发', async () => { + const selectInputProps = { + onPopupVisibleChange: () => 1, }; - const onClick = () => { - setMultiple(!multiple); + const placeholderId = 'placeholder-id'; + const btnText = 'change'; + const TextComponent = (props) => { + const [disabled, setDisabled] = useState(false); + const onClick = () => { + setDisabled(!disabled); + }; + return ( + <> + + + + ); }; - return ( - <> - - - + const spy = vi.spyOn(selectInputProps, 'onPopupVisibleChange'); + const { getByPlaceholderText, getByText } = render(); + // 展开收起一共触发 onPopupVisibleChange 2次 + fireEvent.click(getByPlaceholderText(placeholderId)); + expect(spy).toHaveBeenCalled(); + fireEvent.click(getByPlaceholderText(placeholderId)); + expect(spy).toHaveBeenCalledTimes(2); + await mockTimeout(() => + expect(document.querySelector(popupSelector)).toHaveStyle({ + display: 'none', + }), ); - }; - - const { container, getByText } = render(); - expect(getByText('选项一')).toBeInTheDocument(); - expect(container.querySelectorAll('.t-cascader__menu').length).toBe(1); - // 首次展开子选项会插入新的 ul - fireEvent.click(getByText('选项一')); - expect(getByText('子选项一')).toBeInTheDocument(); - expect(container.querySelectorAll('.t-cascader__menu').length).toBe(2); - // multiple 测试,存在 checkbox - expect(container.querySelector('input')).toBeNull(); - fireEvent.click(getByText(btnText)); - expect(container.querySelector('input')).toBeInTheDocument(); - }); - test('Panel组件 trigger 为 hover 时,应该正确展开下一层级', () => { - const { container, getByText } = render(); - expect(container.querySelectorAll('.t-cascader__menu').length).toBe(1); - fireEvent.mouseEnter(getByText('选项一')); - expect(getByText('子选项一')).toBeInTheDocument(); - expect(container.querySelectorAll('.t-cascader__menu').length).toBe(2); + // disabled 不会展开 popup,且不执行 onPopupVisibleChange + fireEvent.click(getByText(btnText)); + fireEvent.click(getByPlaceholderText(placeholderId)); + expect(document.querySelector(popupSelector)).toHaveStyle({ + display: 'none', + }); + expect(spy).toHaveBeenCalledTimes(2); + }); }); - test('Panel组件 multiple 测试', () => { - const { getByText, getByTitle } = render(); - fireEvent.click(getByText('选项一')); - expect(getByTitle('子选项一')).not.toHaveClass('t-is-checked'); - // 选中一个子选项,父级应为 indeterminate 状态 - fireEvent.click(getByText('子选项一')); - expect(getByTitle('选项一')).toHaveClass('t-is-indeterminate'); - expect(getByTitle('子选项一')).toHaveClass('t-is-checked'); - // 选中全部子选项,父级应该为 checked 状态 - fireEvent.click(getByText('子选项二')); - expect(getByTitle('选项一')).toHaveClass('t-is-checked'); - expect(getByTitle('子选项二')).toHaveClass('t-is-checked'); - }); + describe('scenarios', () => { + test('value is zero should be selected', async () => { + const popupSelector = '.t-popup'; + const labelText = 'my value is 0 that typeof number'; + const TestComponent = () => { + const [value, setValue] = useState(''); + const options = [ + { + value: 0, + label: labelText, + }, + ]; + const onChange = (value) => { + setValue(value); + }; + + return ; + }; + const { getByText } = render(); + fireEvent.click(document.querySelector('input')); + expect(document.querySelector(popupSelector)).toHaveTextContent(labelText); + await mockTimeout(() => fireEvent.click(getByText(labelText))); + expect(document.querySelector('.t-input__inner')).toHaveValue(labelText); + }); - test('Panel组件 multiple 时设置 max 测试', async () => { - const btnText = 'set max 1'; - const TestComponent = () => { - const [max, setMax] = useState(3); - return ( - <> - - - + test('multiple Clicking the clear all button only triggers onChange once', async () => { + const onChange = vi.fn(); + const onRemove = vi.fn(); + const { container } = render( + , ); - }; - const { getByText, getByTitle } = render(); - // 所选内容不超过 max ,都可选中 - fireEvent.click(getByTitle('选项一').querySelector('input')); - expect(getByTitle('选项一')).toHaveClass('t-is-checked'); - expect(getByTitle('子选项一')).toHaveClass('t-is-checked'); - expect(getByTitle('子选项二')).toHaveClass('t-is-checked'); - // 清空所有选中 - fireEvent.click(getByTitle('选项一').querySelector('input')); - // 设置 max 为 1,点击父级无法选中任何节点 - fireEvent.click(getByText(btnText)); - fireEvent.click(getByTitle('选项一')); - fireEvent.click(getByTitle('选项一').querySelector('input')); - expect(getByTitle('选项一')).not.toHaveClass('t-is-checked'); - expect(getByTitle('子选项一')).not.toHaveClass('t-is-checked'); - // 选中一个子节点后所有节点为 disabled - fireEvent.click(getByTitle('子选项一')); - expect(getByTitle('子选项一')).toHaveClass('t-is-checked'); - expect(getByTitle('选项二')).toHaveClass('t-is-disabled'); - }); + fireEvent.mouseEnter(container.querySelector('.t-input')); + await mockDelay(); + // 点击清除按钮只触发一次onChange,不触发onRemove + expect(container.querySelector('.t-tag-input__suffix-clear')).toBeTruthy(); + fireEvent.click(container.querySelector('.t-tag-input__suffix-clear')); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onRemove).toHaveBeenCalledTimes(0); + // 点击tag onRemove,触发一次onRemove,触发两次onChange + fireEvent.click(container.querySelectorAll('.t-tag__icon-close')[0]); + expect(onRemove).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledTimes(2); + }); - test('multiple 应按用户勾选顺序保留 value', async () => { - const onChange = vi.fn(); - const TestComponent = () => { - const [value, setValue] = useState([]); - return ( - { - onChange(val, ctx); - setValue(val as any[]); - }} - /> + test('multiple 应按用户勾选顺序保留 value', async () => { + const onChange = vi.fn(); + const TestComponent = () => { + const [value, setValue] = useState([]); + return ( + { + onChange(val, ctx); + setValue(val as any[]); + }} + /> + ); + }; + const { getByText } = render(); + fireEvent.click(document.querySelector('input')); + await mockDelay(); + + // 切到「选项二」面板,勾选 2.1 + fireEvent.click(getByText('选项二')); + fireEvent.click(document.querySelector('.t-checkbox input[name="2.1"]')); + expect(onChange).toHaveBeenLastCalledWith(['2.1'], expect.any(Object)); + + // 切到「选项一」面板,勾选 1.1(最新点击的项应排在末尾) + fireEvent.click(getByText('选项一')); + fireEvent.click(document.querySelector('.t-checkbox input[name="1.1"]')); + expect(onChange).toHaveBeenLastCalledWith(['2.1', '1.1'], expect.any(Object)); + + // 继续切到「选项二」勾选 2.2 + fireEvent.click(getByText('选项二')); + fireEvent.click(document.querySelector('.t-checkbox input[name="2.2"]')); + expect(onChange).toHaveBeenLastCalledWith(['2.1', '1.1', '2.2'], expect.any(Object)); + + // 取消勾选 2.1,已有项的相对顺序应保留 + fireEvent.click(document.querySelector('.t-checkbox input[name="2.1"]')); + expect(onChange).toHaveBeenLastCalledWith(['1.1', '2.2'], expect.any(Object)); + }); + + test('multiple + valueType=full 应按用户勾选顺序保留 value', async () => { + const onChange = vi.fn(); + const TestComponent = () => { + const [value, setValue] = useState([]); + return ( + { + onChange(val, ctx); + setValue(val as any[]); + }} + /> + ); + }; + const { getByText } = render(); + fireEvent.click(document.querySelector('input')); + await mockDelay(); + + fireEvent.click(getByText('选项二')); + fireEvent.click(document.querySelector('.t-checkbox input[name="2.1"]')); + expect(onChange).toHaveBeenLastCalledWith([['2', '2.1']], expect.any(Object)); + + fireEvent.click(getByText('选项一')); + fireEvent.click(document.querySelector('.t-checkbox input[name="1.1"]')); + expect(onChange).toHaveBeenLastCalledWith( + [ + ['2', '2.1'], + ['1', '1.1'], + ], + expect.any(Object), ); - }; - const { getByText } = render(); - fireEvent.click(document.querySelector('input')); - await mockDelay(); - - // 切到「选项二」面板,勾选 2.1 - fireEvent.click(getByText('选项二')); - fireEvent.click(document.querySelector('.t-checkbox input[name="2.1"]')); - expect(onChange).toHaveBeenLastCalledWith(['2.1'], expect.any(Object)); - - // 切到「选项一」面板,勾选 1.1(最新点击的项应排在末尾) - fireEvent.click(getByText('选项一')); - fireEvent.click(document.querySelector('.t-checkbox input[name="1.1"]')); - expect(onChange).toHaveBeenLastCalledWith(['2.1', '1.1'], expect.any(Object)); - - // 继续切到「选项二」勾选 2.2 - fireEvent.click(getByText('选项二')); - fireEvent.click(document.querySelector('.t-checkbox input[name="2.2"]')); - expect(onChange).toHaveBeenLastCalledWith(['2.1', '1.1', '2.2'], expect.any(Object)); - - // 取消勾选 2.1,已有项的相对顺序应保留 - fireEvent.click(document.querySelector('.t-checkbox input[name="2.1"]')); - expect(onChange).toHaveBeenLastCalledWith(['1.1', '2.2'], expect.any(Object)); - }); - test('multiple + valueType=full 应按用户勾选顺序保留 value', async () => { - const onChange = vi.fn(); - const TestComponent = () => { - const [value, setValue] = useState([]); - return ( - { - onChange(val, ctx); - setValue(val as any[]); - }} - /> + fireEvent.click(getByText('选项二')); + fireEvent.click(document.querySelector('.t-checkbox input[name="2.2"]')); + expect(onChange).toHaveBeenLastCalledWith( + [ + ['2', '2.1'], + ['1', '1.1'], + ['2', '2.2'], + ], + expect.any(Object), ); - }; - const { getByText } = render(); - fireEvent.click(document.querySelector('input')); - await mockDelay(); - - fireEvent.click(getByText('选项二')); - fireEvent.click(document.querySelector('.t-checkbox input[name="2.1"]')); - expect(onChange).toHaveBeenLastCalledWith([['2', '2.1']], expect.any(Object)); - - fireEvent.click(getByText('选项一')); - fireEvent.click(document.querySelector('.t-checkbox input[name="1.1"]')); - expect(onChange).toHaveBeenLastCalledWith( - [ - ['2', '2.1'], - ['1', '1.1'], - ], - expect.any(Object), - ); - - fireEvent.click(getByText('选项二')); - fireEvent.click(document.querySelector('.t-checkbox input[name="2.2"]')); - expect(onChange).toHaveBeenLastCalledWith( - [ - ['2', '2.1'], - ['1', '1.1'], - ['2', '2.2'], - ], - expect.any(Object), - ); + }); }); }); diff --git a/packages/components/checkbox/__tests__/checkbox-group.test.tsx b/packages/components/checkbox/__tests__/checkbox-group.test.tsx new file mode 100644 index 0000000000..5d45d8bcd5 --- /dev/null +++ b/packages/components/checkbox/__tests__/checkbox-group.test.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { fireEvent, render, vi } from '@test/utils'; + +import Checkbox from '../Checkbox'; + +describe('CheckboxGroup', () => { + describe('props', () => { + test('value', () => { + const { container } = render( + + 广州 + + 深圳 + + , + ); + expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); + }); + + test('value is string', () => { + const { container } = render(); + expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); + }); + + test('value is number', () => { + const { container } = render(); + expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); + }); + + test('defaultValue', () => { + const { container } = render( + + 广州 + + 深圳 + + , + ); + expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); + }); + }); + + describe('events', () => { + test('onChange', () => { + const fn = vi.fn(); + const { container } = render( + + 广州 + + 深圳 + + , + ); + fireEvent.click(container.firstChild.firstChild); + expect(fn).toHaveBeenCalledTimes(1); + }); + }); + + describe('scenarios', () => { + test('option', () => { + const { container } = render( + , + ); + fireEvent.click(container.firstChild.lastChild); + expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); + const options = container.firstChild.childNodes; + // 0 is rendered as string '0' + expect(options.item(4).textContent).toBe('0'); + }); + + test('max', () => { + const { container } = render( + + 广州 + 深圳 + 北京 + , + ); + fireEvent.click(container.firstChild.firstChild); + expect(container.firstChild.lastChild).toHaveClass('t-is-disabled'); + }); + }); +}); diff --git a/packages/components/checkbox/__tests__/checkbox.test.tsx b/packages/components/checkbox/__tests__/checkbox.test.tsx index 2645000fee..b67f24df3c 100644 --- a/packages/components/checkbox/__tests__/checkbox.test.tsx +++ b/packages/components/checkbox/__tests__/checkbox.test.tsx @@ -4,123 +4,45 @@ import { fireEvent, render, vi } from '@test/utils'; import Checkbox from '../Checkbox'; describe('Checkbox', () => { - test('checked & children', () => { - const { container, queryByText } = render(选中项); - expect(container.firstChild).toHaveClass('t-checkbox', 't-is-checked'); - expect(queryByText('选中项')).toBeInTheDocument(); - }); - - test('defaultChecked', () => { - const { container } = render(); - expect(container.firstChild).toHaveClass('t-checkbox', 't-is-checked'); - }); - - test('disabled', () => { - const fn = vi.fn(); - const { container } = render(); - expect(container.firstChild).toHaveClass('t-is-disabled'); - fireEvent.click(container.firstChild); - expect(fn).toHaveBeenCalledTimes(0); - }); - - test('indeterminate', () => { - const { container } = render(); - expect(container.firstChild).toHaveClass('t-is-indeterminate'); - }); - - test('label', () => { - const { queryByText } = render(); - expect(queryByText('选中项')).toBeInTheDocument(); - }); - - test('onChange', () => { - const fn = vi.fn(); - const { container } = render(); - fireEvent.click(container.firstChild); - expect(fn).toHaveBeenCalledTimes(0); - }); -}); - -describe('CheckboxGroup', () => { - test('value', () => { - const { container } = render( - - 广州 - - 深圳 - - , - ); - expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); - }); - - test('onChange', () => { - const fn = vi.fn(); - const { container } = render( - - 广州 - - 深圳 - - , - ); - fireEvent.click(container.firstChild.firstChild); - expect(fn).toHaveBeenCalledTimes(1); - }); - - test('option', () => { - const { container } = render( - , - ); - fireEvent.click(container.firstChild.lastChild); - expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); - const options = container.firstChild.childNodes; - // 0 is rendered as string '0' - expect(options.item(4).textContent).toBe('0'); - }); - - test('value is string', () => { - const { container } = render(); - expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); - }); - - test('value is number', () => { - const { container } = render(); - expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); - }); - - test('defaultValue', () => { - const { container } = render( - - 广州 - - 深圳 - - , - ); - expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); - }); - - test('max', () => { - const { container } = render( - - 广州 - 深圳 - 北京 - , - ); - fireEvent.click(container.firstChild.firstChild); - expect(container.firstChild.lastChild).toHaveClass('t-is-disabled'); + describe('props', () => { + test('defaultChecked', () => { + const { container } = render(); + expect(container.firstChild).toHaveClass('t-checkbox', 't-is-checked'); + }); + + test('indeterminate', () => { + const { container } = render(); + expect(container.firstChild).toHaveClass('t-is-indeterminate'); + }); + + test('label', () => { + const { queryByText } = render(); + expect(queryByText('选中项')).toBeInTheDocument(); + }); + }); + + describe('events', () => { + test('onChange', () => { + const fn = vi.fn(); + const { container } = render(); + fireEvent.click(container.firstChild); + expect(fn).toHaveBeenCalledTimes(0); + }); + }); + + describe('scenarios', () => { + test('checked & children', () => { + const { container, queryByText } = render(选中项); + expect(container.firstChild).toHaveClass('t-checkbox', 't-is-checked'); + expect(queryByText('选中项')).toBeInTheDocument(); + }); + + test('disabled', () => { + const fn = vi.fn(); + const { container } = render(); + expect(container.firstChild).toHaveClass('t-is-disabled'); + fireEvent.click(container.firstChild); + expect(fn).toHaveBeenCalledTimes(0); + }); }); }); diff --git a/packages/components/collapse/__tests__/collapse.test.tsx b/packages/components/collapse/__tests__/collapse.test.tsx index ae26bf85a4..6d90e65760 100644 --- a/packages/components/collapse/__tests__/collapse.test.tsx +++ b/packages/components/collapse/__tests__/collapse.test.tsx @@ -19,35 +19,37 @@ const CollapseTestComponent = (props: TestComponentProps) => ( ); -describe('Collapse Unit Test', () => { - // 渲染测试 - test('create', async () => { - const { container, queryByText } = render(); - expect(container.firstElementChild.classList.contains('t-collapse')).toBeTruthy(); - expect(queryByText(panelContent)).toBeInTheDocument(); - }); - - // 测试点击事件 - test('click event', async () => { - const CollapseTest = () => ( - - - 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 - - - ); - const { container } = render(); - - // 获取 element - const element = container.querySelector('.t-collapse-panel__header'); - expect(element).not.toBeNull(); - - // 默认展开,点击关闭 - fireEvent.click(element); - expect(document.querySelector('.t-collapse-panel__body--collapsed')).toBeNull(); - - // 当前关闭,点击展开 - fireEvent.click(element); - expect(document.querySelector('.t-collapse-panel__body--collapsed')).not.toBeNull(); +describe('Collapse', () => { + describe('scenarios', () => { + // 渲染测试 + test('create', async () => { + const { container, queryByText } = render(); + expect(container.firstElementChild.classList.contains('t-collapse')).toBeTruthy(); + expect(queryByText(panelContent)).toBeInTheDocument(); + }); + + // 测试点击事件 + test('click event', async () => { + const CollapseTest = () => ( + + + 这部分是每个折叠面板折叠或展开的内容,可根据不同业务或用户的使用诉求,进行自定义填充。可以是纯文本、图文、子列表等内容形式。 + + + ); + const { container } = render(); + + // 获取 element + const element = container.querySelector('.t-collapse-panel__header'); + expect(element).not.toBeNull(); + + // 默认展开,点击关闭 + fireEvent.click(element); + expect(document.querySelector('.t-collapse-panel__body--collapsed')).toBeNull(); + + // 当前关闭,点击展开 + fireEvent.click(element); + expect(document.querySelector('.t-collapse-panel__body--collapsed')).not.toBeNull(); + }); }); }); diff --git a/packages/components/color-picker/__tests__/color-picker-panel.test.tsx b/packages/components/color-picker/__tests__/color-picker-panel.test.tsx new file mode 100644 index 0000000000..ab2f2ecfca --- /dev/null +++ b/packages/components/color-picker/__tests__/color-picker-panel.test.tsx @@ -0,0 +1,98 @@ +import React, { useState } from 'react'; +import { fireEvent, render, vi } from '@test/utils'; + +import ColorPickerPanel from '../ColorPickerPanel'; + +describe('ColorPickerPanel', () => { + describe('scenarios', () => { + test('ColorPickerPanel 测试', () => { + const { queryByText } = render(); + expect(queryByText('最近使用颜色')).toBeInTheDocument(); + }); + + test('渐变颜色更新 测试', async () => { + const { container } = render( + , + ); + + // 点击第二个渐变点 + fireEvent.click(container.querySelectorAll('li.t-color-picker__thumb')[1]); + + // 点击 Saturation 板上的点,并模拟鼠标移动 + const thumb = container.querySelector('span.t-color-picker__thumb'); + fireEvent.mouseDown(thumb); + fireEvent.mouseMove(thumb, { clientX: 50, clientY: 50 }); + fireEvent.mouseUp(thumb); + + // 获取移动后的色值 + const { color } = getComputedStyle(thumb); + + const rgb2Rgba = (rgb: string) => { + const rgba = rgb.replace('rgb', 'rgba').replace(')', ', 1)'); + return rgba; + }; + + // 检查 CSS Input 生成的新渐变色 + expect(container.querySelectorAll('.t-input__inner')[2]).toHaveValue( + `linear-gradient(45deg,rgb(79, 172, 255) 0%,${rgb2Rgba(color)} 100%)`, + ); + }); + + test('enableAlpha 开启透明通道', () => { + const btnText = 'changeAlpha'; + const [defaultValue, changeValue] = ['rgba(0, 82, 217, 1)', 'rgba(0, 82, 217, 0.32)']; + const TestComponent = () => { + const [value, setValue] = useState(defaultValue); + const onClick = () => { + setValue(changeValue); + }; + return ( + <> + + + + ); + }; + const { container, getByText } = render(); + expect(container.querySelector('.t-color-picker__alpha')).toBeInTheDocument(); + expect(container.querySelector('.t-color-picker__sliders-preview-inner')).toHaveStyle({ + background: defaultValue, + }); + fireEvent.click(getByText(btnText)); + expect(container.querySelector('.t-color-picker__sliders-preview-inner')).toHaveStyle({ + background: changeValue, + }); + }); + + test('最近使用颜色 recentColors 测试', async () => { + const onRecentColorsChange = vi.fn(); + const TestComponent = () => { + const value = '#0052d9'; + return ; + }; + const { container } = render(); + + // 点击添加,当前颜色成功加入且为激活状态,且删除按钮出现 + fireEvent.click(container.querySelector('.t-icon-add')); + expect(container.querySelector('.t-is-active')).toBeInTheDocument(); + expect(container.querySelector('.t-color-picker__swatches--item__inner')).toHaveStyle({ + background: 'rgb(0, 82, 217)', + }); + // 不能重复添加同个颜色值 + fireEvent.click(container.querySelector('.t-icon-add')); + expect( + container.querySelector('.t-color-picker__swatches--items').querySelectorAll('.t-color-picker__swatches--item') + .length, + ).toBe(1); + expect(container.querySelector('.t-icon-delete')).toBeInTheDocument(); + // 删除当前激活的颜色 + fireEvent.click(container.querySelector('.t-icon-delete')); + expect(container.querySelector('.t-icon-delete')).toBeNull(); + expect(container.querySelector('.t-is-active')).toBeNull(); + expect(onRecentColorsChange).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/components/color-picker/__tests__/color-picker.test.tsx b/packages/components/color-picker/__tests__/color-picker.test.tsx index 733d026989..9469fd8c72 100644 --- a/packages/components/color-picker/__tests__/color-picker.test.tsx +++ b/packages/components/color-picker/__tests__/color-picker.test.tsx @@ -1,140 +1,56 @@ -import React, { useState } from 'react'; -import { fireEvent, mockTimeout, render, userEvent, vi } from '@test/utils'; +import React from 'react'; +import { fireEvent, mockTimeout, render, userEvent } from '@test/utils'; import ColorPicker from '../ColorPicker'; -import ColorPickerPanel from '../ColorPickerPanel'; -describe('ColorPicker 组件测试', () => { +describe('ColorPicker', () => { const user = userEvent.setup(); - test('ColorPicker Trigger 测试', async () => { - const { container } = render(); - expect(container.querySelector('.t-input__inner')).toHaveValue('#0052d9'); - // 测试 input 改变值失焦时触发 onChange 改变颜色值 - fireEvent.focus(container.querySelector('.t-input__inner')); - await user.type(container.querySelector('.t-input__inner'), '{end}{backspace}8'); - fireEvent.blur(container.querySelector('.t-input__inner')); - await mockTimeout(() => expect(container.querySelector('.t-input__inner')).toHaveValue('#0052d8')); - // 测试颜色格式正确展示(不传入 format 时默认为 RGB) - fireEvent.click(container); - fireEvent.click(document.querySelector('.t-color-picker__swatches--item')); - expect(container.querySelector('.t-input__inner')).toHaveValue('rgb(236, 242, 254)'); - }); - - test('ColorPicker 预设颜色切换 测试', () => { - const { container } = render( - , - ); - fireEvent.click(container.querySelector('.t-input')); - expect(document.querySelector('.t-color-picker__gradient')).toBeInTheDocument(); - // 点击系统预设颜色的第一个 - fireEvent.click(document.querySelector('.t-color-picker__swatches--item')); - // 渐变模式下切换为对应的纯色 - expect(container.querySelector('.t-input__inner')).toHaveValue('rgba(236, 242, 254, 1)'); - }); - - test(':disabled 测试', () => { - const { container } = render(); - expect(container.querySelector('.t-input__inner')).toBeDisabled(); - }); - test('饱和度选择测试', () => { - const defaultValue = '#fff'; - const { container } = render(); - fireEvent.click(container.querySelector('.t-input__inner')); - expect(container.querySelector('.t-input__inner')).toHaveValue(defaultValue); - const wrapper = document.querySelector('.t-color-picker__saturation'); - fireEvent.mouseDown(wrapper); - fireEvent.mouseMove(wrapper, { deltaX: 232, deltaY: 160 }); - expect(container.querySelector('.t-input__inner')).toHaveValue('rgb(0, 0, 0)'); - fireEvent.mouseMove(wrapper, { deltaX: -232, deltaY: -160 }); - expect(container.querySelector('.t-input__inner')).toHaveValue('rgb(255, 255, 255)'); - }); -}); - -describe('ColorPickerPanel 组件测试', () => { - test('ColorPickerPanel 测试', () => { - const { queryByText } = render(); - expect(queryByText('最近使用颜色')).toBeInTheDocument(); + describe('props', () => { + test(':disabled 测试', () => { + const { container } = render(); + expect(container.querySelector('.t-input__inner')).toBeDisabled(); + }); }); - test('渐变颜色更新 测试', async () => { - const { container } = render( - , - ); - - // 点击第二个渐变点 - fireEvent.click(container.querySelectorAll('li.t-color-picker__thumb')[1]); - - // 点击 Saturation 板上的点,并模拟鼠标移动 - const thumb = container.querySelector('span.t-color-picker__thumb'); - fireEvent.mouseDown(thumb); - fireEvent.mouseMove(thumb, { clientX: 50, clientY: 50 }); - fireEvent.mouseUp(thumb); - - // 获取移动后的色值 - const { color } = getComputedStyle(thumb); - - const rgb2Rgba = (rgb: string) => { - const rgba = rgb.replace('rgb', 'rgba').replace(')', ', 1)'); - return rgba; - }; - - // 检查 CSS Input 生成的新渐变色 - expect(container.querySelectorAll('.t-input__inner')[2]).toHaveValue( - `linear-gradient(45deg,rgb(79, 172, 255) 0%,${rgb2Rgba(color)} 100%)`, - ); - }); + describe('scenarios', () => { + test('ColorPicker Trigger 测试', async () => { + const { container } = render(); + expect(container.querySelector('.t-input__inner')).toHaveValue('#0052d9'); + // 测试 input 改变值失焦时触发 onChange 改变颜色值 + fireEvent.focus(container.querySelector('.t-input__inner')); + await user.type(container.querySelector('.t-input__inner'), '{end}{backspace}8'); + fireEvent.blur(container.querySelector('.t-input__inner')); + await mockTimeout(() => expect(container.querySelector('.t-input__inner')).toHaveValue('#0052d8')); + // 测试颜色格式正确展示(不传入 format 时默认为 RGB) + fireEvent.click(container); + fireEvent.click(document.querySelector('.t-color-picker__swatches--item')); + expect(container.querySelector('.t-input__inner')).toHaveValue('rgb(236, 242, 254)'); + }); - test('enableAlpha 开启透明通道', () => { - const btnText = 'changeAlpha'; - const [defaultValue, changeValue] = ['rgba(0, 82, 217, 1)', 'rgba(0, 82, 217, 0.32)']; - const TestComponent = () => { - const [value, setValue] = useState(defaultValue); - const onClick = () => { - setValue(changeValue); - }; - return ( - <> - - - + test('ColorPicker 预设颜色切换 测试', () => { + const { container } = render( + , ); - }; - const { container, getByText } = render(); - expect(container.querySelector('.t-color-picker__alpha')).toBeInTheDocument(); - expect(container.querySelector('.t-color-picker__sliders-preview-inner')).toHaveStyle({ background: defaultValue }); - fireEvent.click(getByText(btnText)); - expect(container.querySelector('.t-color-picker__sliders-preview-inner')).toHaveStyle({ background: changeValue }); - }); - - test('最近使用颜色 recentColors 测试', async () => { - const onRecentColorsChange = vi.fn(); - const TestComponent = () => { - const value = '#0052d9'; - return ; - }; - const { container } = render(); + fireEvent.click(container.querySelector('.t-input')); + expect(document.querySelector('.t-color-picker__gradient')).toBeInTheDocument(); + // 点击系统预设颜色的第一个 + fireEvent.click(document.querySelector('.t-color-picker__swatches--item')); + // 渐变模式下切换为对应的纯色 + expect(container.querySelector('.t-input__inner')).toHaveValue('rgba(236, 242, 254, 1)'); + }); - // 点击添加,当前颜色成功加入且为激活状态,且删除按钮出现 - fireEvent.click(container.querySelector('.t-icon-add')); - expect(container.querySelector('.t-is-active')).toBeInTheDocument(); - expect(container.querySelector('.t-color-picker__swatches--item__inner')).toHaveStyle({ - background: 'rgb(0, 82, 217)', + test('饱和度选择测试', () => { + const defaultValue = '#fff'; + const { container } = render(); + fireEvent.click(container.querySelector('.t-input__inner')); + expect(container.querySelector('.t-input__inner')).toHaveValue(defaultValue); + const wrapper = document.querySelector('.t-color-picker__saturation'); + fireEvent.mouseDown(wrapper); + fireEvent.mouseMove(wrapper, { deltaX: 232, deltaY: 160 }); + expect(container.querySelector('.t-input__inner')).toHaveValue('rgb(0, 0, 0)'); + fireEvent.mouseMove(wrapper, { deltaX: -232, deltaY: -160 }); + expect(container.querySelector('.t-input__inner')).toHaveValue('rgb(255, 255, 255)'); }); - // 不能重复添加同个颜色值 - fireEvent.click(container.querySelector('.t-icon-add')); - expect( - container.querySelector('.t-color-picker__swatches--items').querySelectorAll('.t-color-picker__swatches--item') - .length, - ).toBe(1); - expect(container.querySelector('.t-icon-delete')).toBeInTheDocument(); - // 删除当前激活的颜色 - fireEvent.click(container.querySelector('.t-icon-delete')); - expect(container.querySelector('.t-icon-delete')).toBeNull(); - expect(container.querySelector('.t-is-active')).toBeNull(); - expect(onRecentColorsChange).toHaveBeenCalled(); }); }); diff --git a/packages/components/comment/__tests__/comment.test.tsx b/packages/components/comment/__tests__/comment.test.tsx index 3062b19a73..bea29c4299 100644 --- a/packages/components/comment/__tests__/comment.test.tsx +++ b/packages/components/comment/__tests__/comment.test.tsx @@ -4,32 +4,32 @@ import { render } from '@test/utils'; import Comment from '../Comment'; describe('Comment', () => { - test('author', () => { - const { container } = render(); - expect(container.querySelector('.t-comment__author > .t-comment__name')).toContainHTML('评论作者名'); - }); + describe('props', () => { + test('author', () => { + const { container } = render(); + expect(container.querySelector('.t-comment__author > .t-comment__name')).toContainHTML('评论作者名'); + }); - test('datetime', () => { - const { container } = render(); - expect(container.querySelector('.t-comment__author > .t-comment__time')).toContainHTML('今天16:38'); - }); + test('datetime', () => { + const { container } = render(); + expect(container.querySelector('.t-comment__author > .t-comment__time')).toContainHTML('今天16:38'); + }); - test('content', () => { - const { container } = render(); - expect(container.querySelector('.t-comment__detail')).toContainHTML('评论内容'); - }); + test('content', () => { + const { container } = render(); + expect(container.querySelector('.t-comment__detail')).toContainHTML('评论内容'); + }); - test('reply', () => { - const { container } = render(); - expect(container.querySelector('.t-comment__reply')).toContainHTML('回复内容'); - }); + test('reply', () => { + const { container } = render(); + expect(container.querySelector('.t-comment__reply')).toContainHTML('回复内容'); + }); - test('quote', () => { - const { container } = render(); - expect(container.querySelector('.t-comment__quote')).toContainHTML('引用内容'); - }); + test('quote', () => { + const { container } = render(); + expect(container.querySelector('.t-comment__quote')).toContainHTML('引用内容'); + }); - describe('avatar', () => { test('avatar is url string', () => { const { container } = render(); expect(container.querySelector('.t-comment__avatar-image')).toHaveAttribute( @@ -37,7 +37,6 @@ describe('Comment', () => { 'https://tdesign.gtimg.com/site/images/list-icon.png', ); }); - test('avatar is custom element', () => { const { container } = render( { expect(container.querySelector('.t-comment__avatar-image')).toBeNull(); expect(container.querySelector('.t-comment__avatar > img')).toHaveClass('custom-image'); }); - }); - test('actions with ReactNode items', () => { - const actions = [ - - 6 - , - - 回复 - , - ]; + test('actions with ReactNode items', () => { + const actions = [ + + 6 + , + + 回复 + , + ]; - const { container } = render(); - expect(container.querySelector('.t-comment__actions')).toBeInTheDocument(); - expect(container.querySelectorAll('.t-comment__actions .t-button')).toHaveLength(2); - }); + const { container } = render(); + expect(container.querySelector('.t-comment__actions')).toBeInTheDocument(); + expect(container.querySelectorAll('.t-comment__actions .t-button')).toHaveLength(2); + }); - test('actions with a single ReactNode', () => { - const { container } = render(操作}>); - expect(container.querySelectorAll('.t-comment__actions .t-button')).toHaveLength(1); - expect(container.querySelector('.single-action')).toHaveTextContent('操作'); - }); + test('actions with a single ReactNode', () => { + const { container } = render(操作}>); + expect(container.querySelectorAll('.t-comment__actions .t-button')).toHaveLength(1); + expect(container.querySelector('.single-action')).toHaveTextContent('操作'); + }); - test('actions with a Fragment TNode', () => { - const actions = ( - <> - - - 6 - - - - 回复 - - - ); + test('actions with a Fragment TNode', () => { + const actions = ( + <> + + + 6 + + + + 回复 + + + ); - const { container } = render(); - const buttons = container.querySelectorAll('.t-comment__actions .t-button'); - expect(buttons).toHaveLength(2); - expect(buttons[0]).toContainElement(container.querySelector('.thumb-up-icon')); - expect(buttons[0]).toHaveTextContent('6'); - expect(buttons[1]).toContainElement(container.querySelector('.chat-icon')); - expect(buttons[1]).toHaveTextContent('回复'); + const { container } = render(); + const buttons = container.querySelectorAll('.t-comment__actions .t-button'); + expect(buttons).toHaveLength(2); + expect(buttons[0]).toContainElement(container.querySelector('.thumb-up-icon')); + expect(buttons[0]).toHaveTextContent('6'); + expect(buttons[1]).toContainElement(container.querySelector('.chat-icon')); + expect(buttons[1]).toHaveTextContent('回复'); + }); }); }); diff --git a/packages/components/date-picker/__tests__/date-picker-panel.test.tsx b/packages/components/date-picker/__tests__/date-picker-panel.test.tsx index 70d65ad191..0e9c2f84e2 100644 --- a/packages/components/date-picker/__tests__/date-picker-panel.test.tsx +++ b/packages/components/date-picker/__tests__/date-picker-panel.test.tsx @@ -23,141 +23,149 @@ describe('DatePickerPanel', () => { vi.useRealTimers(); }); - test('value props', async () => { - const { container } = render(); - expect(container.querySelector('.t-date-picker__cell--active')).toBeInTheDocument(); - expect(container.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('14'); - - const { container: container2 } = render(); - expect(container2.querySelector('.t-date-picker__cell--active')).toBeInTheDocument(); - expect(container2.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('12'); + describe('props', () => { + test('value props', async () => { + const { container } = render(); + expect(container.querySelector('.t-date-picker__cell--active')).toBeInTheDocument(); + expect(container.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('14'); + + const { container: container2 } = render(); + expect(container2.querySelector('.t-date-picker__cell--active')).toBeInTheDocument(); + expect(container2.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('12'); + }); + + test('disableDate props', async () => { + const { container: container1 } = render( + , + ); + expect(container1.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); + expect(container1.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('30'); + + const { container: container2 } = render( + , + ); + expect(container2.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); + expect(container2.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('2'); + + const { container: container3 } = render( + , + ); + expect(container3.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); + expect(container3.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('28'); + }); + + test('enableTimePicker props', async () => { + const { container } = render(); + expect(container.querySelector('.t-time-picker__panel')).toBeInTheDocument(); + }); + + test('firstDayOfWeek props', async () => { + const { container } = render(); + expect(container.querySelector('.t-date-picker__table table thead tr').firstChild).toHaveTextContent('二'); + }); + + test('format props', async () => { + const { container } = render(); + expect(container.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('2'); + }); + + test('mode props', async () => { + const { container: container1 } = render(); + expect(container1.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('3'); + + const { container: container2 } = render(); + expect(container2.querySelector('.t-date-picker__table-week-row--active')).toBeInTheDocument(); + expect(container2.querySelector('.t-date-picker__table-week-row--active td').firstChild).toHaveTextContent('36'); + + const { container: container3 } = render(); + expect(container3.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('11 月'); + + const { container: container4 } = render(); + expect(container4.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('2024'); + + // const { container: container5 } = render(); + // expect(container5.querySelector('.t-date-picker__cell--active')).toHaveTextContent('二季度'); + }); + + test('disableTime', async () => { + const { container } = render( + , + ); + + expect(container.querySelector('.t-date-picker__cell--active').firstChild.firstChild).toHaveTextContent('26'); + expect(container.querySelectorAll('.t-time-picker__panel-body-scroll')[0].children).toHaveLength(17); + }); }); - test('disableDate props', async () => { - const { container: container1 } = render( - , - ); - expect(container1.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); - expect(container1.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('30'); - - const { container: container2 } = render( - , - ); - expect(container2.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); - expect(container2.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('2'); - - const { container: container3 } = render( - , - ); - expect(container3.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); - expect(container3.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('28'); + describe('events', () => { + test('cell handleClick', async () => { + const fn = vi.fn(); + const { container } = render(); + + fireEvent.click(container.querySelector('.t-date-picker__cell')); + expect(fn).toHaveBeenCalledTimes(1); + }); + + test('onJumperClick', async () => { + const fn = vi.fn(); + const { container } = render(); + + const jumperPrev = container.querySelector('.t-pagination-mini__prev'); + fireEvent.click(jumperPrev); + expect(fn).toHaveBeenCalledTimes(1); + + const jumperNext = container.querySelector('.t-pagination-mini__next'); + fireEvent.click(jumperNext); + expect(fn).toHaveBeenCalledTimes(2); + + const jumperCurrent = container.querySelector('.t-pagination-mini__current'); + fireEvent.click(jumperCurrent); + }); + + test('onPresetClick', async () => { + const fn = vi.fn(); + const { getByText } = render( + , + ); + + const christmasBtn = getByText('圣诞节'); + fireEvent.click(christmasBtn); + expect(fn).toHaveBeenCalledTimes(1); + }); }); - test('enableTimePicker props', async () => { - const { container } = render(); - expect(container.querySelector('.t-time-picker__panel')).toBeInTheDocument(); - }); - - test('firstDayOfWeek props', async () => { - const { container } = render(); - expect(container.querySelector('.t-date-picker__table table thead tr').firstChild).toHaveTextContent('二'); - }); - - test('format props', async () => { - const { container } = render(); - expect(container.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('2'); - }); - - test('mode props', async () => { - const { container: container1 } = render(); - expect(container1.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('3'); - - const { container: container2 } = render(); - expect(container2.querySelector('.t-date-picker__table-week-row--active')).toBeInTheDocument(); - expect(container2.querySelector('.t-date-picker__table-week-row--active td').firstChild).toHaveTextContent('36'); - - const { container: container3 } = render(); - expect(container3.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('11 月'); - - const { container: container4 } = render(); - expect(container4.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('2024'); - - // const { container: container5 } = render(); - // expect(container5.querySelector('.t-date-picker__cell--active')).toHaveTextContent('二季度'); - }); - - test('presets、presetsPlacement props', async () => { - const { container } = render(); - expect(container.querySelector('.t-date-picker__presets')).toBeInTheDocument(); - expect(container.querySelector('.t-date-picker__presets').firstChild.firstChild).toHaveTextContent('今天'); - expect(container.querySelector('.t-date-picker__presets').parentNode).toHaveClass('t-date-picker__footer--left'); - }); - - test('cell handleClick', async () => { - const fn = vi.fn(); - const { container } = render(); - - fireEvent.click(container.querySelector('.t-date-picker__cell')); - expect(fn).toHaveBeenCalledTimes(1); - }); - - test('onJumperClick', async () => { - const fn = vi.fn(); - const { container } = render(); - - const jumperPrev = container.querySelector('.t-pagination-mini__prev'); - fireEvent.click(jumperPrev); - expect(fn).toHaveBeenCalledTimes(1); - - const jumperNext = container.querySelector('.t-pagination-mini__next'); - fireEvent.click(jumperNext); - expect(fn).toHaveBeenCalledTimes(2); - - const jumperCurrent = container.querySelector('.t-pagination-mini__current'); - fireEvent.click(jumperCurrent); - }); - - test('onTimePickerChange & onConfirmClick', async () => { - const { container, getByText } = render(); - - expect(container.querySelector('.t-date-picker__panel-time')).toBeTruthy(); - expect(getByText('确定')).toBeTruthy(); - }); - - test('onPresetClick', async () => { - const fn = vi.fn(); - const { getByText } = render(); - - const christmasBtn = getByText('圣诞节'); - fireEvent.click(christmasBtn); - expect(fn).toHaveBeenCalledTimes(1); - }); - - test('onYearChange & onMonthChange', async () => { - const { container } = render(); - - const yearSelect = container.querySelector('.t-date-picker__header-controller-year'); - fireEvent.click(yearSelect); - const monthSelect = container.querySelector('.t-date-picker__header-controller-month'); - fireEvent.click(monthSelect); - }); - - test('disableTime', async () => { - const { container } = render( - , - ); - - expect(container.querySelector('.t-date-picker__cell--active').firstChild.firstChild).toHaveTextContent('26'); - expect(container.querySelectorAll('.t-time-picker__panel-body-scroll')[0].children).toHaveLength(17); + describe('scenarios', () => { + test('presets、presetsPlacement props', async () => { + const { container } = render(); + expect(container.querySelector('.t-date-picker__presets')).toBeInTheDocument(); + expect(container.querySelector('.t-date-picker__presets').firstChild.firstChild).toHaveTextContent('今天'); + expect(container.querySelector('.t-date-picker__presets').parentNode).toHaveClass('t-date-picker__footer--left'); + }); + + test('onTimePickerChange & onConfirmClick', async () => { + const { container, getByText } = render(); + + expect(container.querySelector('.t-date-picker__panel-time')).toBeTruthy(); + expect(getByText('确定')).toBeTruthy(); + }); + + test('onYearChange & onMonthChange', async () => { + const { container } = render(); + + const yearSelect = container.querySelector('.t-date-picker__header-controller-year'); + fireEvent.click(yearSelect); + const monthSelect = container.querySelector('.t-date-picker__header-controller-month'); + fireEvent.click(monthSelect); + }); }); }); diff --git a/packages/components/date-picker/__tests__/date-picker.test.tsx b/packages/components/date-picker/__tests__/date-picker.test.tsx index d13d09ba51..f92aa41740 100644 --- a/packages/components/date-picker/__tests__/date-picker.test.tsx +++ b/packages/components/date-picker/__tests__/date-picker.test.tsx @@ -26,360 +26,375 @@ describe('DatePicker', () => { vi.useRealTimers(); }); - test('clearable', async () => { - const { container } = render(); - // 模拟鼠标进入 - fireEvent.mouseEnter(container.querySelector('input')); - const clearElement = await waitFor(() => document.querySelector('.t-input__suffix-clear')); - expect(clearElement).not.toBeNull(); - fireEvent.click(clearElement); - }); - - test('disableDate', async () => { - const disabledDate = (date: DateValue) => dayjs().isBefore(date); - const { container: container1 } = render(); - fireEvent.mouseDown(container1.querySelector('input')); - const { container: container2 } = render( dayjs(date).day() === 6} />); - fireEvent.mouseDown(container2.querySelector('input')); - - const { container: container3 } = render( - , - ); - fireEvent.mouseDown(container3.querySelector('input')); - const { container: container4 } = render(); - fireEvent.mouseDown(container4.querySelector('input')); - - const { container: container5 } = render( - , - ); - fireEvent.mouseDown(container5.querySelector('input')); - - const disabledEle = await waitFor(() => document.querySelector('.t-date-picker__cell--disabled')); - expect(disabledEle).not.toBeNull(); - }); - - test('disabled', () => { - const { container } = render(); - const inputElement = container.querySelector('.t-input__inner'); - expect((inputElement as HTMLInputElement).disabled).toBe(true); - }); - - test('enableTimePicker', async () => { - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); - const panelElement = await waitFor(() => document.querySelector('.t-date-picker__panel-time')); - expect(panelElement).toBeInTheDocument(); - }); - - test('firstDayOfWeek', async () => { - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); - const weekElement = await waitFor(() => document.querySelector('.t-date-picker__table table thead tr th')); - expect(weekElement).toHaveTextContent('三'); - }); + describe('props', () => { + test('clearable', async () => { + const { container } = render(); + // 模拟鼠标进入 + fireEvent.mouseEnter(container.querySelector('input')); + const clearElement = await waitFor(() => document.querySelector('.t-input__suffix-clear')); + expect(clearElement).not.toBeNull(); + fireEvent.click(clearElement); + }); - test('defaultValue value format', async () => { - const { container: container1 } = render(); - const inputElement1 = container1.querySelector('.t-input__inner'); - expect((inputElement1 as HTMLInputElement).value).toEqual('2022-09-14'); + test('disableDate', async () => { + const disabledDate = (date: DateValue) => dayjs().isBefore(date); + const { container: container1 } = render(); + fireEvent.mouseDown(container1.querySelector('input')); + const { container: container2 } = render( dayjs(date).day() === 6} />); + fireEvent.mouseDown(container2.querySelector('input')); + + const { container: container3 } = render( + , + ); + fireEvent.mouseDown(container3.querySelector('input')); + const { container: container4 } = render(); + fireEvent.mouseDown(container4.querySelector('input')); + + const { container: container5 } = render( + , + ); + fireEvent.mouseDown(container5.querySelector('input')); + + const disabledEle = await waitFor(() => document.querySelector('.t-date-picker__cell--disabled')); + expect(disabledEle).not.toBeNull(); + }); - const { container: container2 } = render(); - const inputElement2 = container2.querySelector('.t-input__inner'); - expect((inputElement2 as HTMLInputElement).value).toEqual('2022-09-14'); - }); + test('disabled', () => { + const { container } = render(); + const inputElement = container.querySelector('.t-input__inner'); + expect((inputElement as HTMLInputElement).disabled).toBe(true); + }); - test('inputProps', async () => { - const { container } = render(); - const inputWrapElement = container.querySelector('.t-input__inner'); - expect((inputWrapElement as HTMLInputElement).value).toEqual('test-inputProps'); - }); + test('enableTimePicker', async () => { + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); + const panelElement = await waitFor(() => document.querySelector('.t-date-picker__panel-time')); + expect(panelElement).toBeInTheDocument(); + }); - test('mode week', async () => { - const { container: container1 } = render(); - fireEvent.mouseDown(container1.querySelector('input')); - const weekEle = await waitFor(() => document.querySelector('.t-date-picker__panel-week')); - expect(weekEle).not.toBeNull(); - }); + test('firstDayOfWeek', async () => { + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); + const weekElement = await waitFor(() => document.querySelector('.t-date-picker__table table thead tr th')); + expect(weekElement).toHaveTextContent('三'); + }); - test('mode month', async () => { - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); - const pickerTable = await waitFor(() => document.querySelector('tbody')); - expect(pickerTable.firstChild.firstChild.firstChild).toHaveTextContent('1 月'); - }); + test('inputProps', async () => { + const { container } = render(); + const inputWrapElement = container.querySelector('.t-input__inner'); + expect((inputWrapElement as HTMLInputElement).value).toEqual('test-inputProps'); + }); - test('mode quarter', async () => { - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); - const pickerTable = await waitFor(() => document.querySelector('.t-date-picker__table')); - expect(pickerTable).toHaveTextContent('一季度'); - }); + test('mode week', async () => { + const { container: container1 } = render(); + fireEvent.mouseDown(container1.querySelector('input')); + const weekEle = await waitFor(() => document.querySelector('.t-date-picker__panel-week')); + expect(weekEle).not.toBeNull(); + }); - test('mode year', async () => { - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); - const weekEle = await waitFor(() => document.querySelector('.t-date-picker__panel-year')); - expect(weekEle).not.toBeNull(); - }); + test('mode month', async () => { + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); + const pickerTable = await waitFor(() => document.querySelector('tbody')); + expect(pickerTable.firstChild.firstChild.firstChild).toHaveTextContent('1 月'); + }); - test('placeholder', () => { - const wrapper = render(); - expect(wrapper.container.querySelector('input')?.placeholder).toEqual('请选择日期'); - }); + test('mode quarter', async () => { + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); + const pickerTable = await waitFor(() => document.querySelector('.t-date-picker__table')); + expect(pickerTable).toHaveTextContent('一季度'); + }); - test('popupProps', async () => { - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); - const popupArrow = await waitFor(() => document.querySelector('.t-popup__arrow')); - expect(popupArrow).toBeInTheDocument(); - }); + test('mode year', async () => { + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); + const weekEle = await waitFor(() => document.querySelector('.t-date-picker__panel-year')); + expect(weekEle).not.toBeNull(); + }); - test('prefixIcon suffixIcon', async () => { - const { container } = render(} suffixIcon={} />); - const iconBrowse = container.querySelector('.t-icon-browse'); - const lockOn = container.querySelector('.t-icon-lock-on'); + test('placeholder', () => { + const wrapper = render(); + expect(wrapper.container.querySelector('input')?.placeholder).toEqual('请选择日期'); + }); - expect(iconBrowse).toBeInTheDocument(); - expect(lockOn).toBeInTheDocument(); - }); + test('popupProps', async () => { + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); + const popupArrow = await waitFor(() => document.querySelector('.t-popup__arrow')); + expect(popupArrow).toBeInTheDocument(); + }); - test('presets bottom top', async () => { - const presets = { - 昨天: dayjs().subtract(1, 'day').format('YYYY-MM-DD'), - }; - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); - const table = await waitFor(() => document.querySelector('table')); - fireEvent.mouseEnter(table); - const pickerPresets = await waitFor(() => document.querySelector('.t-date-picker__presets')); - expect(pickerPresets).toHaveTextContent('昨天'); - }); + test('presets bottom top', async () => { + const presets = { + 昨天: dayjs().subtract(1, 'day').format('YYYY-MM-DD'), + }; + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); + const table = await waitFor(() => document.querySelector('table')); + fireEvent.mouseEnter(table); + const pickerPresets = await waitFor(() => document.querySelector('.t-date-picker__presets')); + expect(pickerPresets).toHaveTextContent('昨天'); + }); - test('status', async () => { - const { container } = render(); - const tInput = container.querySelector('.t-input'); - expect(tInput.classList.contains('t-is-warning')).toBeTruthy(); - }); + test('status', async () => { + const { container } = render(); + const tInput = container.querySelector('.t-input'); + expect(tInput.classList.contains('t-is-warning')).toBeTruthy(); + }); - test('timePickerProps', async () => { - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); - const panelElement = await waitFor(() => document.querySelector('.t-date-picker__panel-time .t-is-current')); - expect(panelElement).toHaveTextContent('13'); - }); + test('timePickerProps', async () => { + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); + const panelElement = await waitFor(() => document.querySelector('.t-date-picker__panel-time .t-is-current')); + expect(panelElement).toHaveTextContent('13'); + }); - test('tips', async () => { - const tips = 'test-tips'; - const { queryByText } = render(); - expect(queryByText(tips)).toBeInTheDocument(); - }); + test('tips', async () => { + const tips = 'test-tips'; + const { queryByText } = render(); + expect(queryByText(tips)).toBeInTheDocument(); + }); - test('label', async () => { - const label = 'test-label'; - const { container } = render(); - const prefix = container.querySelector('.t-input__prefix'); - expect(prefix).toBeTruthy(); - expect(prefix).toHaveTextContent(label); - }); + test('disableTime', async () => { + const { container } = render( + , + ); - test('onBlur onFocus', async () => { - const blurFn = vi.fn(); - const focusFn = vi.fn(); + fireEvent.mouseDown(container.querySelector('input')); - const { container } = render(); - const InputDom = container.querySelector('.t-input__inner'); - fireEvent.focus(InputDom); - expect(focusFn).toHaveBeenCalledTimes(1); - fireEvent.blur(InputDom); - expect(blurFn).toHaveBeenCalledTimes(1); - }); + await waitFor(() => { + expect(document.querySelector('.t-date-picker__cell--active')?.firstChild?.firstChild).toHaveTextContent('26'); + expect(document.querySelectorAll('.t-time-picker__panel-body-scroll')[0].children).toHaveLength(17); + }); + }); + test('prefixIcon suffixIcon', async () => { + const { container } = render(} suffixIcon={} />); + const iconBrowse = container.querySelector('.t-icon-browse'); + const lockOn = container.querySelector('.t-icon-lock-on'); - test('onChange onPick', async () => { - const changeFn = vi.fn(); - const pickFn = vi.fn(); - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); + expect(iconBrowse).toBeInTheDocument(); + expect(lockOn).toBeInTheDocument(); + }); - const firstDay = document.querySelector('.t-date-picker__cell--first-day-of-month'); - fireEvent.click(firstDay); - expect(changeFn).toHaveBeenCalledTimes(1); - expect(pickFn).toHaveBeenCalledTimes(1); + test('label', async () => { + const label = 'test-label'; + const { container } = render(); + const prefix = container.querySelector('.t-input__prefix'); + expect(prefix).toBeTruthy(); + expect(prefix).toHaveTextContent(label); + }); }); - test('onChange onPick', async () => { - const changeFn = vi.fn(); - const pickFn = vi.fn(); - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); + describe('events', () => { + test('onBlur onFocus', async () => { + const blurFn = vi.fn(); + const focusFn = vi.fn(); - const firstDay = await waitFor(() => document.querySelector('.t-date-picker__cell--first-day-of-month')); - fireEvent.click(firstDay); - expect(changeFn).toHaveBeenCalledTimes(1); - expect(pickFn).toHaveBeenCalledTimes(1); - }); - - test('onYearChange', async () => { - const yearChangeFn = vi.fn(); - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); + const { container } = render(); + const InputDom = container.querySelector('.t-input__inner'); + fireEvent.focus(InputDom); + expect(focusFn).toHaveBeenCalledTimes(1); + fireEvent.blur(InputDom); + expect(blurFn).toHaveBeenCalledTimes(1); + }); - const yearInput = await waitFor(() => - document.querySelector('.t-popup .t-date-picker__header-controller-year .t-input'), - ); - fireEvent.click(yearInput); + test('onChange onPick', async () => { + const changeFn = vi.fn(); + const pickFn = vi.fn(); + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); - const yearOptionNode = await waitFor(() => document.querySelector('.t-popup .t-select-option span[title="2021"]')); + const firstDay = document.querySelector('.t-date-picker__cell--first-day-of-month'); + fireEvent.click(firstDay); + expect(changeFn).toHaveBeenCalledTimes(1); + expect(pickFn).toHaveBeenCalledTimes(1); + }); - fireEvent.click(yearOptionNode); - expect(yearChangeFn).toHaveBeenCalledTimes(1); - }); + test('onChange onPick', async () => { + const changeFn = vi.fn(); + const pickFn = vi.fn(); + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); - test('onMonthChange', async () => { - const monthChangeFn = vi.fn(); - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); + const firstDay = await waitFor(() => document.querySelector('.t-date-picker__cell--first-day-of-month')); + fireEvent.click(firstDay); + expect(changeFn).toHaveBeenCalledTimes(1); + expect(pickFn).toHaveBeenCalledTimes(1); + }); - // 先模拟点击某个过去的年份,保证不会在以后因为处于当前的年份和月份,导致 onMonthChange 事件不触发 - const yearInput = await waitFor(() => - document.querySelector('.t-popup .t-date-picker__header-controller-year .t-input'), - ); - fireEvent.click(yearInput); - const yearOptionNode = await waitFor(() => document.querySelector('.t-popup .t-select-option span[title="2021"]')); - fireEvent.click(yearOptionNode); + test('onYearChange', async () => { + const yearChangeFn = vi.fn(); + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); - const monthInput = await waitFor(() => - document.querySelector('.t-popup .t-date-picker__header-controller-month .t-input'), - ); - fireEvent.click(monthInput); + const yearInput = await waitFor(() => + document.querySelector('.t-popup .t-date-picker__header-controller-year .t-input'), + ); + fireEvent.click(yearInput); - const monthOptionNode = await waitFor(() => document.querySelector('.t-popup .t-select-option span[title="1 月"]')); + const yearOptionNode = await waitFor(() => + document.querySelector('.t-popup .t-select-option span[title="2021"]'), + ); - fireEvent.click(monthOptionNode); - expect(monthChangeFn).toHaveBeenCalledTimes(1); - }); + fireEvent.click(yearOptionNode); + expect(yearChangeFn).toHaveBeenCalledTimes(1); + }); - test('panel select month and year', async () => { - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); - - const panelEle = await waitFor(() => document.querySelector('.t-date-picker__panel-date')); - - const monthPanel = panelEle.querySelector('.t-date-picker__header-controller-month .t-input'); - fireEvent.click(monthPanel); - const monthPopup = await waitFor(() => document.querySelector('.t-select__list')); - fireEvent.click(monthPopup.firstChild); - - const monthInput = await waitFor(() => - document.querySelector('.t-date-picker__header-controller-month .t-input__inner'), - ); - expect((monthInput as HTMLInputElement).value).toEqual('1 月'); - - const yearPanel = panelEle.querySelector('.t-date-picker__header-controller-year .t-input'); - fireEvent.click(yearPanel); - - const yearPopup = await waitFor(() => - document.querySelector('.t-date-picker__header-controller-year .t-select__list .t-is-selected'), - ); - fireEvent.click(yearPopup.nextElementSibling); - const yearInput = await waitFor(() => - document.querySelector('.t-date-picker__header-controller-year .t-input__inner'), - ); - expect((yearInput as HTMLInputElement).value).toEqual('2023'); + test('onMonthChange', async () => { + const monthChangeFn = vi.fn(); + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); + + // 先模拟点击某个过去的年份,保证不会在以后因为处于当前的年份和月份,导致 onMonthChange 事件不触发 + const yearInput = await waitFor(() => + document.querySelector('.t-popup .t-date-picker__header-controller-year .t-input'), + ); + fireEvent.click(yearInput); + const yearOptionNode = await waitFor(() => + document.querySelector('.t-popup .t-select-option span[title="2021"]'), + ); + fireEvent.click(yearOptionNode); + + const monthInput = await waitFor(() => + document.querySelector('.t-popup .t-date-picker__header-controller-month .t-input'), + ); + fireEvent.click(monthInput); + + const monthOptionNode = await waitFor(() => + document.querySelector('.t-popup .t-select-option span[title="1 月"]'), + ); + + fireEvent.click(monthOptionNode); + expect(monthChangeFn).toHaveBeenCalledTimes(1); + }); }); - test('panel select year, popup scroll', async () => { - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); + describe('scenarios', () => { + test('defaultValue value format', async () => { + const { container: container1 } = render(); + const inputElement1 = container1.querySelector('.t-input__inner'); + expect((inputElement1 as HTMLInputElement).value).toEqual('2022-09-14'); - const panelEle = await waitFor(() => document.querySelector('.t-date-picker__panel-date')); - const yearPanel = panelEle.querySelector('.t-date-picker__header-controller-year .t-input'); - fireEvent.click(yearPanel); - const popupContent = panelEle.querySelector('.t-date-picker__header-controller-year-popup .t-popup__content'); - fireEvent.scroll(popupContent, { target: { scrollY: 100, scrollTop: 0 } }); - }); + const { container: container2 } = render(); + const inputElement2 = container2.querySelector('.t-input__inner'); + expect((inputElement2 as HTMLInputElement).value).toEqual('2022-09-14'); + }); - test('pure trigger onChange onPick', async () => { - const defaultValue = '2022-09-14 20:08:08'; - const { container } = render( - , - ); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.change(inputEle, { target: { value: '2022-09-16 20:08:08' } }); - expect((inputEle as HTMLInputElement).value).toEqual('2022-09-16 20:08:08'); - }); + test('panel select month and year', async () => { + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); + + const panelEle = await waitFor(() => document.querySelector('.t-date-picker__panel-date')); + + const monthPanel = panelEle.querySelector('.t-date-picker__header-controller-month .t-input'); + fireEvent.click(monthPanel); + const monthPopup = await waitFor(() => document.querySelector('.t-select__list')); + fireEvent.click(monthPopup.firstChild); + + const monthInput = await waitFor(() => + document.querySelector('.t-date-picker__header-controller-month .t-input__inner'), + ); + expect((monthInput as HTMLInputElement).value).toEqual('1 月'); + + const yearPanel = panelEle.querySelector('.t-date-picker__header-controller-year .t-input'); + fireEvent.click(yearPanel); + + const yearPopup = await waitFor(() => + document.querySelector('.t-date-picker__header-controller-year .t-select__list .t-is-selected'), + ); + fireEvent.click(yearPopup.nextElementSibling); + const yearInput = await waitFor(() => + document.querySelector('.t-date-picker__header-controller-year .t-input__inner'), + ); + expect((yearInput as HTMLInputElement).value).toEqual('2023'); + }); - test('cell handleMouseEnter', async () => { - const { container, getByText } = render(); - const inputEle = container.querySelector('.t-input__inner') as HTMLInputElement; - fireEvent.mouseDown(inputEle); + test('panel select year, popup scroll', async () => { + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); + + const panelEle = await waitFor(() => document.querySelector('.t-date-picker__panel-date')); + const yearPanel = panelEle.querySelector('.t-date-picker__header-controller-year .t-input'); + fireEvent.click(yearPanel); + const popupContent = panelEle.querySelector('.t-date-picker__header-controller-year-popup .t-popup__content'); + fireEvent.scroll(popupContent, { + target: { scrollY: 100, scrollTop: 0 }, + }); + }); - const cellBtn = getByText('25'); - fireEvent.mouseEnter(cellBtn); - expect(inputEle.value).toEqual('2022-08-25'); - fireEvent.mouseLeave(cellBtn); - expect(inputEle.value).not.toEqual('2022-08-25'); - }); + test('pure trigger onChange onPick', async () => { + const defaultValue = '2022-09-14 20:08:08'; + const { container } = render( + , + ); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.change(inputEle, { target: { value: '2022-09-16 20:08:08' } }); + expect((inputEle as HTMLInputElement).value).toEqual('2022-09-16 20:08:08'); + }); - test('cell handleClick', async () => { - const { container, getByText } = render(); - const inputEle = container.querySelector('.t-input__inner') as HTMLInputElement; - fireEvent.mouseDown(inputEle); + test('cell handleMouseEnter', async () => { + const { container, getByText } = render(); + const inputEle = container.querySelector('.t-input__inner') as HTMLInputElement; + fireEvent.mouseDown(inputEle); - const cellBtn = getByText('25'); - fireEvent.click(cellBtn); - expect(inputEle.value).toEqual('2022-08-25'); - }); - - test('onJumperClick', async () => { - const { container, getByText } = render(); - const inputEle = container.querySelector('.t-input__inner') as HTMLInputElement; - fireEvent.mouseDown(inputEle); + const cellBtn = getByText('25'); + fireEvent.mouseEnter(cellBtn); + expect(inputEle.value).toEqual('2022-08-25'); + fireEvent.mouseLeave(cellBtn); + expect(inputEle.value).not.toEqual('2022-08-25'); + }); - const jumperPrev = await waitFor(() => document.querySelector('.t-pagination-mini__prev')); - fireEvent.click(jumperPrev); - const cellBtn = getByText('25'); - fireEvent.click(cellBtn); - expect(inputEle.value).toEqual('2022-07-25'); - }); + test('cell handleClick', async () => { + const { container, getByText } = render(); + const inputEle = container.querySelector('.t-input__inner') as HTMLInputElement; + fireEvent.mouseDown(inputEle); - test('onTimePickerChange & onConfirmClick', async () => { - const { container, getByText } = render(); - fireEvent.mouseDown(container.querySelector('input')); + const cellBtn = getByText('25'); + fireEvent.click(cellBtn); + expect(inputEle.value).toEqual('2022-08-25'); + }); - const timePickerItem = await waitFor(() => document.querySelector('.t-time-picker__panel-body-scroll-item')); - fireEvent.click(timePickerItem); - const confirmBtn = getByText('确定'); - fireEvent.click(confirmBtn); - }); + test('onJumperClick', async () => { + const { container, getByText } = render(); + const inputEle = container.querySelector('.t-input__inner') as HTMLInputElement; + fireEvent.mouseDown(inputEle); - test('onPresetClick', async () => { - const { container, getByText } = render(); - const inputEle = container.querySelector('.t-date-picker .t-input__inner'); - fireEvent.mouseDown(inputEle); + const jumperPrev = await waitFor(() => document.querySelector('.t-pagination-mini__prev')); + fireEvent.click(jumperPrev); + const cellBtn = getByText('25'); + fireEvent.click(cellBtn); + expect(inputEle.value).toEqual('2022-07-25'); + }); - const christmasBtn = await waitFor(() => getByText('圣诞节')); - fireEvent.click(christmasBtn); - expect((inputEle as HTMLInputElement).value).toEqual('2023-12-25'); - }); + test('onTimePickerChange & onConfirmClick', async () => { + const { container, getByText } = render(); + fireEvent.mouseDown(container.querySelector('input')); - test('disableTime', async () => { - const { container } = render(); + const timePickerItem = await waitFor(() => document.querySelector('.t-time-picker__panel-body-scroll-item')); + fireEvent.click(timePickerItem); + const confirmBtn = getByText('确定'); + fireEvent.click(confirmBtn); + }); - fireEvent.mouseDown(container.querySelector('input')); + test('onPresetClick', async () => { + const { container, getByText } = render(); + const inputEle = container.querySelector('.t-date-picker .t-input__inner'); + fireEvent.mouseDown(inputEle); - await waitFor(() => { - expect(document.querySelector('.t-date-picker__cell--active')?.firstChild?.firstChild).toHaveTextContent('26'); - expect(document.querySelectorAll('.t-time-picker__panel-body-scroll')[0].children).toHaveLength(17); + const christmasBtn = await waitFor(() => getByText('圣诞节')); + fireEvent.click(christmasBtn); + expect((inputEle as HTMLInputElement).value).toEqual('2023-12-25'); }); }); }); diff --git a/packages/components/date-picker/__tests__/date-range-picker-panel.test.tsx b/packages/components/date-picker/__tests__/date-range-picker-panel.test.tsx index 27fe63441d..5afcbcb3c0 100644 --- a/packages/components/date-picker/__tests__/date-range-picker-panel.test.tsx +++ b/packages/components/date-picker/__tests__/date-range-picker-panel.test.tsx @@ -14,218 +14,226 @@ describe('DateRangePickerPanel', () => { vi.useRealTimers(); }); - test('value props', async () => { - const { container } = render(); - expect(container.querySelector('.t-date-picker__cell--active')).toBeInTheDocument(); - expect(container.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('14'); - - const { container: container2 } = render(); - expect(container2.querySelector('.t-date-picker__cell--active')).toBeInTheDocument(); - expect(container2.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('12'); - }); - - test('disableDate props', async () => { - const { container: container1 } = render( - , - ); - expect(container1.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); - expect(container1.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('30'); - - const { container: container2 } = render( - , - ); - expect(container2.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); - expect(container2.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('2'); - - const { container: container3 } = render( - , - ); - expect(container3.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); - expect(container3.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('28'); - }); - - test('enableTimePicker props', async () => { - const { container } = render(); - expect(container.querySelector('.t-time-picker__panel')).toBeInTheDocument(); - }); - - test('firstDayOfWeek props', async () => { - const { container } = render(); - expect(container.querySelector('.t-date-picker__table table thead tr').firstChild).toHaveTextContent('二'); - }); - - test('format props', async () => { - const { container } = render( - , - ); - expect(container.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('2'); - }); - - test('mode props', async () => { - const { container: container1 } = render( - , - ); - expect(container1.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('3'); - - const { container: container2 } = render( - , - ); - expect(container2.querySelector('.t-date-picker__table-week-row--active')).toBeInTheDocument(); - expect(container2.querySelector('.t-date-picker__table-week-row--active td').firstChild).toHaveTextContent('36'); - - const { container: container3 } = render( - , - ); - expect(container3.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('11 月'); - - const { container: container4 } = render(); - expect(container4.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('2024'); - - // const { container: container5 } = render(); - // expect(container5.querySelector('.t-date-picker__cell--active')).toHaveTextContent('二季度'); - }); - - test('presets props', async () => { - const { container } = render(); - expect(container.querySelector('.t-date-picker__presets')).toBeInTheDocument(); - expect(container.querySelector('.t-date-picker__presets').firstChild.firstChild).toHaveTextContent('今天'); - }); - - test('presets、presetsPlacement props', async () => { - const { container } = render( - , - ); - expect(container.querySelector('.t-date-picker__presets')).toBeInTheDocument(); - expect(container.querySelector('.t-date-picker__presets').firstChild.firstChild).toHaveTextContent('今天'); - expect(container.querySelector('.t-date-picker__presets').parentNode).toHaveClass('t-date-picker__footer--left'); - }); - - test('cell handleClick', async () => { - const fn = vi.fn(); - const { container } = render(); - - fireEvent.click(container.querySelector('.t-date-picker__cell')); - fireEvent.click(container.querySelector('.t-date-picker__cell')); - expect(fn).toHaveBeenCalledTimes(1); - }); - - test('onJumperClick', async () => { - const fn = vi.fn(); - const { container } = render(); - - const jumperPrev = container.querySelector('.t-pagination-mini__prev'); - fireEvent.click(jumperPrev); - expect(fn).toHaveBeenCalledTimes(1); - }); - - test('onTimePickerChange & onConfirmClick', async () => { - const { container, getByText } = render(); - - expect(container.querySelector('.t-date-picker__panel-time')).toBeTruthy(); - expect(getByText('确定')).toBeTruthy(); - }); - - test('onPresetClick', async () => { - const fn = vi.fn(); - const { getByText } = render( - , - ); - - const christmasBtn = getByText('圣诞节'); - fireEvent.click(christmasBtn); - expect(fn).toHaveBeenCalledTimes(1); - }); - - test('onYearChange & onMonthChange', async () => { - const { container } = render(); - - const yearSelect = container.querySelector('.t-date-picker__header-controller-year'); - fireEvent.click(yearSelect); - const monthSelect = container.querySelector('.t-date-picker__header-controller-month'); - fireEvent.click(monthSelect); - }); - - test('cell handleMouseEnter', async () => { - const { container } = render(); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.mouseDown(inputEle); - - fireEvent.mouseEnter(document.querySelector('.t-date-picker__cell-inner')); - fireEvent.mouseLeave(document.querySelector('.t-date-picker__cell-inner')); - }); - - test('cell handleClick', async () => { - const { container } = render(); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.mouseDown(inputEle); - - fireEvent.click(document.querySelector('.t-date-picker__cell-inner')); - fireEvent.click(document.querySelector('.t-date-picker__cell-inner')); - }); - - test('onJumperClick', async () => { - const { container } = render(); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.mouseDown(inputEle); - - const jumperPrev = await waitFor(() => document.querySelector('.t-pagination-mini__prev')); - fireEvent.click(jumperPrev); - const jumperNext = await waitFor(() => document.querySelector('.t-pagination-mini__next')); - fireEvent.click(jumperNext); - const jumperCurrent = await waitFor(() => document.querySelector('.t-pagination-mini__current')); - fireEvent.click(jumperCurrent); - }); - - test('onTimePickerChange & onConfirmClick', async () => { - const { container, getByText } = render(); - fireEvent.mouseDown(container.querySelector('input')); - - const timePickerItem = await waitFor(() => document.querySelector('.t-time-picker__panel-body-scroll-item')); - fireEvent.click(timePickerItem); - const confirmBtn = getByText('确定'); - fireEvent.click(confirmBtn); - }); - - test('onPresetClick', async () => { - const { container, getByText } = render( - , - ); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.mouseDown(inputEle); - - const christmasBtn = await waitFor(() => getByText('圣诞节')); - expect(christmasBtn).toBeTruthy(); - fireEvent.click(christmasBtn); - }); - - test('onYearChange', async () => { - const { container } = render(); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.mouseDown(inputEle); - - const yearSelect = await waitFor(() => document.querySelector('.t-date-picker__header-controller-year')); - fireEvent.click(yearSelect); - - const monthSelect = await waitFor(() => document.querySelector('.t-date-picker__header-controller-month')); - fireEvent.click(monthSelect); + describe('props', () => { + test('value props', async () => { + const { container } = render(); + expect(container.querySelector('.t-date-picker__cell--active')).toBeInTheDocument(); + expect(container.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('14'); + + const { container: container2 } = render(); + expect(container2.querySelector('.t-date-picker__cell--active')).toBeInTheDocument(); + expect(container2.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('12'); + }); + + test('disableDate props', async () => { + const { container: container1 } = render( + , + ); + expect(container1.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); + expect(container1.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('30'); + + const { container: container2 } = render( + , + ); + expect(container2.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); + expect(container2.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('2'); + + const { container: container3 } = render( + , + ); + expect(container3.querySelector('.t-date-picker__cell--disabled')).toBeInTheDocument(); + expect(container3.querySelector('.t-date-picker__cell--disabled').firstChild).toHaveTextContent('28'); + }); + + test('enableTimePicker props', async () => { + const { container } = render(); + expect(container.querySelector('.t-time-picker__panel')).toBeInTheDocument(); + }); + + test('firstDayOfWeek props', async () => { + const { container } = render(); + expect(container.querySelector('.t-date-picker__table table thead tr').firstChild).toHaveTextContent('二'); + }); + + test('format props', async () => { + const { container } = render( + , + ); + expect(container.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('2'); + }); + + test('mode props', async () => { + const { container: container1 } = render( + , + ); + expect(container1.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('3'); + + const { container: container2 } = render( + , + ); + expect(container2.querySelector('.t-date-picker__table-week-row--active')).toBeInTheDocument(); + expect(container2.querySelector('.t-date-picker__table-week-row--active td').firstChild).toHaveTextContent('36'); + + const { container: container3 } = render( + , + ); + expect(container3.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('11 月'); + + const { container: container4 } = render(); + expect(container4.querySelector('.t-date-picker__cell--active').firstChild).toHaveTextContent('2024'); + + // const { container: container5 } = render(); + // expect(container5.querySelector('.t-date-picker__cell--active')).toHaveTextContent('二季度'); + }); + + test('presets props', async () => { + const { container } = render(); + expect(container.querySelector('.t-date-picker__presets')).toBeInTheDocument(); + expect(container.querySelector('.t-date-picker__presets').firstChild.firstChild).toHaveTextContent('今天'); + }); + }); + + describe('events', () => { + test('cell handleClick', async () => { + const fn = vi.fn(); + const { container } = render(); + + fireEvent.click(container.querySelector('.t-date-picker__cell')); + fireEvent.click(container.querySelector('.t-date-picker__cell')); + expect(fn).toHaveBeenCalledTimes(1); + }); + + test('onJumperClick', async () => { + const fn = vi.fn(); + const { container } = render(); + + const jumperPrev = container.querySelector('.t-pagination-mini__prev'); + fireEvent.click(jumperPrev); + expect(fn).toHaveBeenCalledTimes(1); + }); + + test('onPresetClick', async () => { + const fn = vi.fn(); + const { getByText } = render( + , + ); + + const christmasBtn = getByText('圣诞节'); + fireEvent.click(christmasBtn); + expect(fn).toHaveBeenCalledTimes(1); + }); + }); + + describe('scenarios', () => { + test('presets、presetsPlacement props', async () => { + const { container } = render( + , + ); + expect(container.querySelector('.t-date-picker__presets')).toBeInTheDocument(); + expect(container.querySelector('.t-date-picker__presets').firstChild.firstChild).toHaveTextContent('今天'); + expect(container.querySelector('.t-date-picker__presets').parentNode).toHaveClass('t-date-picker__footer--left'); + }); + + test('onTimePickerChange & onConfirmClick', async () => { + const { container, getByText } = render(); + + expect(container.querySelector('.t-date-picker__panel-time')).toBeTruthy(); + expect(getByText('确定')).toBeTruthy(); + }); + + test('onYearChange & onMonthChange', async () => { + const { container } = render(); + + const yearSelect = container.querySelector('.t-date-picker__header-controller-year'); + fireEvent.click(yearSelect); + const monthSelect = container.querySelector('.t-date-picker__header-controller-month'); + fireEvent.click(monthSelect); + }); + + test('cell handleMouseEnter', async () => { + const { container } = render(); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.mouseDown(inputEle); + + fireEvent.mouseEnter(document.querySelector('.t-date-picker__cell-inner')); + fireEvent.mouseLeave(document.querySelector('.t-date-picker__cell-inner')); + }); + + test('cell handleClick', async () => { + const { container } = render(); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.mouseDown(inputEle); + + fireEvent.click(document.querySelector('.t-date-picker__cell-inner')); + fireEvent.click(document.querySelector('.t-date-picker__cell-inner')); + }); + + test('onJumperClick', async () => { + const { container } = render(); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.mouseDown(inputEle); + + const jumperPrev = await waitFor(() => document.querySelector('.t-pagination-mini__prev')); + fireEvent.click(jumperPrev); + const jumperNext = await waitFor(() => document.querySelector('.t-pagination-mini__next')); + fireEvent.click(jumperNext); + const jumperCurrent = await waitFor(() => document.querySelector('.t-pagination-mini__current')); + fireEvent.click(jumperCurrent); + }); + + test('onTimePickerChange & onConfirmClick', async () => { + const { container, getByText } = render(); + fireEvent.mouseDown(container.querySelector('input')); + + const timePickerItem = await waitFor(() => document.querySelector('.t-time-picker__panel-body-scroll-item')); + fireEvent.click(timePickerItem); + const confirmBtn = getByText('确定'); + fireEvent.click(confirmBtn); + }); + + test('onPresetClick', async () => { + const { container, getByText } = render( + , + ); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.mouseDown(inputEle); + + const christmasBtn = await waitFor(() => getByText('圣诞节')); + expect(christmasBtn).toBeTruthy(); + fireEvent.click(christmasBtn); + }); + + test('onYearChange', async () => { + const { container } = render(); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.mouseDown(inputEle); + + const yearSelect = await waitFor(() => document.querySelector('.t-date-picker__header-controller-year')); + fireEvent.click(yearSelect); + + const monthSelect = await waitFor(() => document.querySelector('.t-date-picker__header-controller-month')); + fireEvent.click(monthSelect); + }); }); }); diff --git a/packages/components/date-picker/__tests__/date-range-picker.test.tsx b/packages/components/date-picker/__tests__/date-range-picker.test.tsx index cfa6775ef1..7dc3d945eb 100644 --- a/packages/components/date-picker/__tests__/date-range-picker.test.tsx +++ b/packages/components/date-picker/__tests__/date-range-picker.test.tsx @@ -16,277 +16,282 @@ describe('DateRangePicker', () => { vi.useRealTimers(); }); - it('clearable', async () => { - const { container } = render(); - // 模拟鼠标进入 - fireEvent.mouseEnter(container.querySelector('input')); - - const clearElement = document.querySelector('.t-range-input__suffix-clear'); - expect(clearElement).not.toBeNull(); - }); - - it('disabled', () => { - const { container } = render(); - const inputElement = container.querySelector('.t-input__inner'); - expect((inputElement as HTMLInputElement).disabled).toBe(true); - }); + describe('props', () => { + it('clearable', async () => { + const { container } = render(); + // 模拟鼠标进入 + fireEvent.mouseEnter(container.querySelector('input')); + + const clearElement = document.querySelector('.t-range-input__suffix-clear'); + expect(clearElement).not.toBeNull(); + }); - it('enableTimePicker', async () => { - const { container } = render(); - act(() => { - fireEvent.mouseDown(container.querySelector('input')); - vi.runAllTimers(); + it('disabled', () => { + const { container } = render(); + const inputElement = container.querySelector('.t-input__inner'); + expect((inputElement as HTMLInputElement).disabled).toBe(true); }); - const panelElement = document.querySelector('.t-date-picker__panel-time'); - expect(panelElement).toBeInTheDocument(); - }); - it('firstDayOfWeek', async () => { - const { container } = render(); - act(() => { - fireEvent.mouseDown(container.querySelector('input')); - vi.runAllTimers(); + it('enableTimePicker', async () => { + const { container } = render(); + act(() => { + fireEvent.mouseDown(container.querySelector('input')); + vi.runAllTimers(); + }); + const panelElement = document.querySelector('.t-date-picker__panel-time'); + expect(panelElement).toBeInTheDocument(); }); - const weekElement = document.querySelector('.t-date-picker__table table thead tr th'); - expect(weekElement).toHaveTextContent('三'); - }); - it('defaultValue value format', async () => { - const { container: container1 } = render( - , - ); - const inputElement1 = container1.querySelector('.t-input__inner'); - expect((inputElement1 as HTMLInputElement).value).toEqual('2022-09-14'); + it('firstDayOfWeek', async () => { + const { container } = render(); + act(() => { + fireEvent.mouseDown(container.querySelector('input')); + vi.runAllTimers(); + }); + const weekElement = document.querySelector('.t-date-picker__table table thead tr th'); + expect(weekElement).toHaveTextContent('三'); + }); - const { container: container2 } = render(); - const inputElement2 = container2.querySelector('.t-input__inner'); - expect((inputElement2 as HTMLInputElement).value).toEqual('2022-09-14'); - }); + it('mode', async () => { + const { container } = render(); + act(() => { + fireEvent.mouseDown(container.querySelector('input')); + vi.runAllTimers(); + }); + const pickerTable = document.querySelector('.t-date-picker__table'); + expect(pickerTable).toHaveTextContent('一季度'); + }); - it('mode', async () => { - const { container } = render(); - act(() => { - fireEvent.mouseDown(container.querySelector('input')); - vi.runAllTimers(); + it('mode', async () => { + const { container } = render(); + act(() => { + fireEvent.mouseDown(container.querySelector('input')); + vi.runAllTimers(); + }); + const weekEle = document.querySelector('.t-date-picker__panel-week'); + expect(weekEle).not.toBeNull(); }); - const pickerTable = document.querySelector('.t-date-picker__table'); - expect(pickerTable).toHaveTextContent('一季度'); - }); - it('mode', async () => { - const { container } = render(); - act(() => { - fireEvent.mouseDown(container.querySelector('input')); - vi.runAllTimers(); + it('placeholder', () => { + const wrapper = render(); + expect(wrapper.container.querySelector('input')?.placeholder).toEqual('请选择日期'); }); - const weekEle = document.querySelector('.t-date-picker__panel-week'); - expect(weekEle).not.toBeNull(); - }); - it('placeholder', () => { - const wrapper = render(); - expect(wrapper.container.querySelector('input')?.placeholder).toEqual('请选择日期'); - }); + it('popupProps', async () => { + const { container } = render(); + act(() => { + fireEvent.mouseDown(container.querySelector('input')); + vi.runAllTimers(); + }); + const popupArrow = document.querySelector('.t-popup__arrow'); + expect(popupArrow).toBeInTheDocument(); + }); - it('popupProps', async () => { - const { container } = render(); - act(() => { - fireEvent.mouseDown(container.querySelector('input')); - vi.runAllTimers(); + it('presets', async () => { + const { container } = render(); + act(() => { + fireEvent.mouseDown(container.querySelector('input')); + vi.runAllTimers(); + }); + const pickerPresets = document.querySelector('.t-date-picker__presets'); + expect(pickerPresets).toHaveTextContent('特定日期范围'); }); - const popupArrow = document.querySelector('.t-popup__arrow'); - expect(popupArrow).toBeInTheDocument(); - }); - it('prefixIcon suffixIcon', async () => { - const { container } = render(} suffixIcon={} />); - const iconBrowse = container.querySelector('.t-icon-browse'); - const lockOn = container.querySelector('.t-icon-lock-on'); + it('inputProps', async () => { + const { container } = render( + , + ); + const inputWrapElement = container.querySelector('.t-input__inner'); + expect((inputWrapElement as HTMLInputElement).value).toEqual('test-inputProps'); + }); - expect(iconBrowse).toBeInTheDocument(); - expect(lockOn).toBeInTheDocument(); - }); + it('separator', async () => { + const { container } = render(); + const inputWrapElement = container.querySelector('.t-range-input__inner-separator'); + expect(inputWrapElement).toHaveTextContent('#'); + }); - it('presets', async () => { - const { container } = render(); - act(() => { - fireEvent.mouseDown(container.querySelector('input')); - vi.runAllTimers(); + it('status', async () => { + const { container } = render(); + const tInput = container.querySelector('.t-range-input'); + expect(tInput.classList.contains('t-is-warning')).toBeTruthy(); }); - const pickerPresets = document.querySelector('.t-date-picker__presets'); - expect(pickerPresets).toHaveTextContent('特定日期范围'); - }); - it('inputProps', async () => { - const { container } = render( - , - ); - const inputWrapElement = container.querySelector('.t-input__inner'); - expect((inputWrapElement as HTMLInputElement).value).toEqual('test-inputProps'); - }); + it('timePickerProps', async () => { + const { container } = render(); + act(() => { + fireEvent.mouseDown(container.querySelector('input')); + vi.runAllTimers(); + }); + const panelElement = document.querySelector('.t-date-picker__panel-time .t-is-current'); + expect(panelElement).toHaveTextContent('13'); + }); - it('separator', async () => { - const { container } = render(); - const inputWrapElement = container.querySelector('.t-range-input__inner-separator'); - expect(inputWrapElement).toHaveTextContent('#'); - }); + it('tips', async () => { + const tips = 'test-tips'; + const { queryByText } = render(); + expect(queryByText(tips)).toBeInTheDocument(); + }); + it('prefixIcon suffixIcon', async () => { + const { container } = render(} suffixIcon={} />); + const iconBrowse = container.querySelector('.t-icon-browse'); + const lockOn = container.querySelector('.t-icon-lock-on'); - it('status', async () => { - const { container } = render(); - const tInput = container.querySelector('.t-range-input'); - expect(tInput.classList.contains('t-is-warning')).toBeTruthy(); - }); + expect(iconBrowse).toBeInTheDocument(); + expect(lockOn).toBeInTheDocument(); + }); - it('timePickerProps', async () => { - const { container } = render(); - act(() => { - fireEvent.mouseDown(container.querySelector('input')); - vi.runAllTimers(); + it('label', async () => { + const label = 'test-label'; + const { container } = render(); + const prefix = container.querySelector('.t-input__prefix'); + expect(prefix).toBeTruthy(); + expect(prefix).toHaveTextContent(label); }); - const panelElement = document.querySelector('.t-date-picker__panel-time .t-is-current'); - expect(panelElement).toHaveTextContent('13'); }); - it('tips', async () => { - const tips = 'test-tips'; - const { queryByText } = render(); - expect(queryByText(tips)).toBeInTheDocument(); - }); + describe('events', () => { + it('onBlur onFocus', async () => { + const blurFn = vi.fn(); + const focusFn = vi.fn(); + + const { container } = render(); + const InputDom = container.querySelector('.t-input__inner'); + fireEvent.focus(InputDom); + expect(focusFn).toHaveBeenCalledTimes(1); + fireEvent.blur(InputDom); + expect(blurFn).toHaveBeenCalledTimes(1); + }); - it('label', async () => { - const label = 'test-label'; - const { container } = render(); - const prefix = container.querySelector('.t-input__prefix'); - expect(prefix).toBeTruthy(); - expect(prefix).toHaveTextContent(label); - }); + it('onChange onPick onInput', async () => { + const changeFn = vi.fn(); + const pickFn = vi.fn(); + const { container } = render( + , + ); + fireEvent.mouseDown(container.querySelector('input')); - it('onBlur onFocus', async () => { - const blurFn = vi.fn(); - const focusFn = vi.fn(); + const firstTBody = document.querySelector('tbody'); + fireEvent.click(firstTBody.firstChild.firstChild.firstChild); + fireEvent.click(firstTBody.firstChild.firstChild.firstChild); - const { container } = render(); - const InputDom = container.querySelector('.t-input__inner'); - fireEvent.focus(InputDom); - expect(focusFn).toHaveBeenCalledTimes(1); - fireEvent.blur(InputDom); - expect(blurFn).toHaveBeenCalledTimes(1); + expect(changeFn).toHaveBeenCalledTimes(2); + expect(pickFn).toHaveBeenCalledTimes(2); + }); }); - it('onChange onPick onInput', async () => { - const changeFn = vi.fn(); - const pickFn = vi.fn(); - const { container } = render( - , - ); - fireEvent.mouseDown(container.querySelector('input')); - - const firstTBody = document.querySelector('tbody'); - fireEvent.click(firstTBody.firstChild.firstChild.firstChild); - fireEvent.click(firstTBody.firstChild.firstChild.firstChild); - - expect(changeFn).toHaveBeenCalledTimes(2); - expect(pickFn).toHaveBeenCalledTimes(2); - }); + describe('scenarios', () => { + it('defaultValue value format', async () => { + const { container: container1 } = render( + , + ); + const inputElement1 = container1.querySelector('.t-input__inner'); + expect((inputElement1 as HTMLInputElement).value).toEqual('2022-09-14'); + + const { container: container2 } = render(); + const inputElement2 = container2.querySelector('.t-input__inner'); + expect((inputElement2 as HTMLInputElement).value).toEqual('2022-09-14'); + }); - it('panel select month and year', async () => { - const { container } = render(); - fireEvent.mouseDown(container.querySelector('input')); + it('panel select month and year', async () => { + const { container } = render(); + fireEvent.mouseDown(container.querySelector('input')); - const panelEle = document.querySelector('.t-date-picker__panel-date'); + const panelEle = document.querySelector('.t-date-picker__panel-date'); - const monthPanel = panelEle.querySelector('.t-date-picker__header-controller-month .t-input'); - fireEvent.click(monthPanel); - const monthPopup = document.querySelector('.t-select__list'); - fireEvent.click(monthPopup.firstChild); + const monthPanel = panelEle.querySelector('.t-date-picker__header-controller-month .t-input'); + fireEvent.click(monthPanel); + const monthPopup = document.querySelector('.t-select__list'); + fireEvent.click(monthPopup.firstChild); - const monthInput = document.querySelector('.t-date-picker__header-controller-month .t-input__inner'); + const monthInput = document.querySelector('.t-date-picker__header-controller-month .t-input__inner'); - expect((monthInput as HTMLInputElement).value).toEqual('1 月'); + expect((monthInput as HTMLInputElement).value).toEqual('1 月'); - const yearPanel = panelEle.querySelector('.t-date-picker__header-controller-year .t-input'); - fireEvent.click(yearPanel); - const yearPopup = document.querySelector('.t-date-picker__header-controller-year .t-select__list .t-is-selected'); - fireEvent.click(yearPopup.nextElementSibling); - const yearInput = document.querySelector('.t-date-picker__header-controller-year .t-input__inner'); - expect((yearInput as HTMLInputElement).value).toEqual('2023'); - }); + const yearPanel = panelEle.querySelector('.t-date-picker__header-controller-year .t-input'); + fireEvent.click(yearPanel); + const yearPopup = document.querySelector('.t-date-picker__header-controller-year .t-select__list .t-is-selected'); + fireEvent.click(yearPopup.nextElementSibling); + const yearInput = document.querySelector('.t-date-picker__header-controller-year .t-input__inner'); + expect((yearInput as HTMLInputElement).value).toEqual('2023'); + }); - it('pure trigger onChange onPick', async () => { - const { container } = render( - , - ); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.change(inputEle, { target: { value: '2022-09-10' } }); - expect((inputEle as HTMLInputElement).value).toEqual('2022-09-10'); - }); + it('pure trigger onChange onPick', async () => { + const { container } = render( + , + ); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.change(inputEle, { target: { value: '2022-09-10' } }); + expect((inputEle as HTMLInputElement).value).toEqual('2022-09-10'); + }); - test('cell handleMouseEnter', async () => { - const { container } = render(); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.mouseDown(inputEle); + test('cell handleMouseEnter', async () => { + const { container } = render(); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.mouseDown(inputEle); - fireEvent.mouseEnter(document.querySelector('.t-date-picker__cell')); - fireEvent.mouseLeave(document.querySelector('.t-date-picker__cell')); - }); + fireEvent.mouseEnter(document.querySelector('.t-date-picker__cell')); + fireEvent.mouseLeave(document.querySelector('.t-date-picker__cell')); + }); - test('cell handleClick', async () => { - const { container } = render(); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.mouseDown(inputEle); + test('cell handleClick', async () => { + const { container } = render(); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.mouseDown(inputEle); - fireEvent.click(document.querySelector('.t-date-picker__cell-inner')); - fireEvent.click(document.querySelector('.t-date-picker__cell-inner')); - }); + fireEvent.click(document.querySelector('.t-date-picker__cell-inner')); + fireEvent.click(document.querySelector('.t-date-picker__cell-inner')); + }); - test('onJumperClick', async () => { - const { container } = render(); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.mouseDown(inputEle); - - const jumperPrev = document.querySelector('.t-pagination-mini__prev'); - fireEvent.click(jumperPrev); - const jumperNext = document.querySelector('.t-pagination-mini__next'); - fireEvent.click(jumperNext); - const jumperCurrent = document.querySelector('.t-pagination-mini__current'); - fireEvent.click(jumperCurrent); - }); + test('onJumperClick', async () => { + const { container } = render(); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.mouseDown(inputEle); + + const jumperPrev = document.querySelector('.t-pagination-mini__prev'); + fireEvent.click(jumperPrev); + const jumperNext = document.querySelector('.t-pagination-mini__next'); + fireEvent.click(jumperNext); + const jumperCurrent = document.querySelector('.t-pagination-mini__current'); + fireEvent.click(jumperCurrent); + }); - test('onTimePickerChange & onConfirmClick', async () => { - const { container, getByText } = render(); - fireEvent.mouseDown(container.querySelector('input')); + test('onTimePickerChange & onConfirmClick', async () => { + const { container, getByText } = render(); + fireEvent.mouseDown(container.querySelector('input')); - const timePickerItem = document.querySelector('.t-time-picker__panel-body-scroll-item'); - fireEvent.click(timePickerItem); - const confirmBtn = getByText('确定'); - fireEvent.click(confirmBtn); - }); + const timePickerItem = document.querySelector('.t-time-picker__panel-body-scroll-item'); + fireEvent.click(timePickerItem); + const confirmBtn = getByText('确定'); + fireEvent.click(confirmBtn); + }); - test('onPresetClick', async () => { - const { container } = render( - , - ); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.mouseDown(inputEle); - - const christmasBtn = document.querySelector('.t-date-picker__presets .t-button'); - fireEvent.click(christmasBtn); - expect(christmasBtn).toBeTruthy(); - }); + test('onPresetClick', async () => { + const { container } = render( + , + ); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.mouseDown(inputEle); + + const christmasBtn = document.querySelector('.t-date-picker__presets .t-button'); + fireEvent.click(christmasBtn); + expect(christmasBtn).toBeTruthy(); + }); - test('onYearChange', async () => { - const { container } = render(); - const inputEle = container.querySelector('.t-input__inner'); - fireEvent.mouseDown(inputEle); + test('onYearChange', async () => { + const { container } = render(); + const inputEle = container.querySelector('.t-input__inner'); + fireEvent.mouseDown(inputEle); - const yearSelect = document.querySelector('.t-date-picker__header-controller-year'); - fireEvent.click(yearSelect); + const yearSelect = document.querySelector('.t-date-picker__header-controller-year'); + fireEvent.click(yearSelect); - const monthSelect = document.querySelector('.t-date-picker__header-controller-month'); - fireEvent.click(monthSelect); + const monthSelect = document.querySelector('.t-date-picker__header-controller-month'); + fireEvent.click(monthSelect); + }); }); }); diff --git a/packages/components/descriptions/__tests__/descriptions.test.tsx b/packages/components/descriptions/__tests__/descriptions.test.tsx index 344bfa4050..ae726660cd 100644 --- a/packages/components/descriptions/__tests__/descriptions.test.tsx +++ b/packages/components/descriptions/__tests__/descriptions.test.tsx @@ -26,258 +26,266 @@ const items = [ }, ]; -describe('Descriptions 组件测试', () => { - // base - test('base', () => { - const { container } = render(); +describe('Descriptions', () => { + describe('props', () => { + // bordered + test('bordered', () => { + const { container } = render(); - expect(container.querySelector('.t-descriptions')).toBeInTheDocument(); - }); + expect(container.querySelector('.t-descriptions__body--border')).toBeTruthy(); + }); - // bordered - test('bordered', () => { - const { container } = render(); + // colon + test('colon', () => { + const { container } = render(); - expect(container.querySelector('.t-descriptions__body--border')).toBeTruthy(); - }); + expect(container.querySelector('.t-descriptions__label')).toHaveTextContent(':'); + }); - // colon - test('colon', () => { - const { container } = render(); + test('column:4', () => { + const { container } = render(); - expect(container.querySelector('.t-descriptions__label')).toHaveTextContent(':'); - }); + const tbody = container.querySelector('tbody'); - // column - test('column=2', () => { - const { container } = render(); + // 检查 tbody 下面是否只有 1 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(1); - const tbody = container.querySelector('tbody'); + const firstTr = tbody.querySelectorAll('tr')[0]; - // 检查 tbody 下面是否只有 2 个 tr 元素 - expect(tbody.querySelectorAll('tr')).toHaveLength(2); + // 检查第 1 个 tr 元素中是否只有 8 个 td 元素 + expect(firstTr.querySelectorAll('td')).toHaveLength(8); - const secondTr = tbody.querySelectorAll('tr')[1]; + // 检查第 2 个 tr 元素中的第 4 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 + expect(firstTr.querySelectorAll('td')[7].getAttribute('colspan')).toBe('1'); + }); - // 检查第 2 个 tr 元素中是否只有 4 个 td 元素 - expect(secondTr.querySelectorAll('td')).toHaveLength(4); + // size + test(':size', () => { + const sizeList: SizeEnum[] = ['small', 'medium', 'large']; + sizeList.forEach((size) => { + const { container } = render( + , + ); + const body = container.querySelector('.t-descriptions__body'); + expect(body).toHaveClass(`t-size-${size.slice(0, 1)}`); + }); + }); + // jsx + test('jsx and DescriptionsItem span', () => { + const { container } = render( + + TDesign + 139****0609 + China Tencent Headquarters + + Shenzhen Penguin Island D1 4A Mail Center + + , + ); + + expect(container.querySelector('.t-descriptions')).toBeInTheDocument(); + }); - // 检查第 2 个 tr 元素中的第 4 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 - expect(secondTr.querySelectorAll('td')[3].getAttribute('colspan')).toBe('1'); + // props jsx + test('props jsx', () => { + const itemsProps = [ + { label: 'Name', content: 'TDesign' }, + { label: , content: '139****0609' }, + { label: 'Area', content:

China Tencent Headquarters

}, + { + label: Address, + content: Shenzhen Penguin Island D1 4A Mail Center, + }, + ]; + const { container } = render(); + + expect(container.querySelector('.t-descriptions')).toBeInTheDocument(); + }); }); - test('column:4', () => { - const { container } = render(); + describe('scenarios', () => { + // base + test('base', () => { + const { container } = render(); - const tbody = container.querySelector('tbody'); + expect(container.querySelector('.t-descriptions')).toBeInTheDocument(); + }); - // 检查 tbody 下面是否只有 1 个 tr 元素 - expect(tbody.querySelectorAll('tr')).toHaveLength(1); + // column + test('column=2', () => { + const { container } = render(); - const firstTr = tbody.querySelectorAll('tr')[0]; + const tbody = container.querySelector('tbody'); - // 检查第 1 个 tr 元素中是否只有 8 个 td 元素 - expect(firstTr.querySelectorAll('td')).toHaveLength(8); + // 检查 tbody 下面是否只有 2 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(2); - // 检查第 2 个 tr 元素中的第 4 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 - expect(firstTr.querySelectorAll('td')[7].getAttribute('colspan')).toBe('1'); - }); + const secondTr = tbody.querySelectorAll('tr')[1]; - // custom-style - test('custom-style', () => { - const customLabelClassName: React.CSSProperties = { - width: '100px', - textAlign: 'left', - }; - const customContentClassName: React.CSSProperties = { - textAlign: 'center', - }; - - const { container } = render( - , - ); - - expect(container.querySelector('.t-descriptions__label')).toHaveStyle('width: 100px; text-align: left;'); - expect(container.querySelector('.t-descriptions__content')).toHaveStyle('text-align: center;'); - }); + // 检查第 2 个 tr 元素中是否只有 4 个 td 元素 + expect(secondTr.querySelectorAll('td')).toHaveLength(4); - // direction - test('layout=horizontal columns=3', () => { - const { container } = render(); - const tbody = container.querySelector('tbody'); + // 检查第 2 个 tr 元素中的第 4 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 + expect(secondTr.querySelectorAll('td')[3].getAttribute('colspan')).toBe('1'); + }); - // 检查 tbody 下面是否只有 2 个 tr 元素 - expect(tbody.querySelectorAll('tr')).toHaveLength(2); + // custom-style + test('custom-style', () => { + const customLabelClassName: React.CSSProperties = { + width: '100px', + textAlign: 'left', + }; + const customContentClassName: React.CSSProperties = { + textAlign: 'center', + }; + + const { container } = render( + , + ); + + expect(container.querySelector('.t-descriptions__label')).toHaveStyle('width: 100px; text-align: left;'); + expect(container.querySelector('.t-descriptions__content')).toHaveStyle('text-align: center;'); + }); - // 检查第 1 个 tr 元素中是否只有 6 个 td 元素 - const firstTr = tbody.querySelectorAll('tr')[0]; - expect(firstTr.querySelectorAll('td')).toHaveLength(6); + // direction + test('layout=horizontal columns=3', () => { + const { container } = render(); + const tbody = container.querySelector('tbody'); - // 检查第 2 个 tr 元素中是否只有 2 个 td 元素 - const secondTr = tbody.querySelectorAll('tr')[1]; - expect(secondTr.querySelectorAll('td')).toHaveLength(2); + // 检查 tbody 下面是否只有 2 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(2); - // 检查第 2 个 tr 元素中的第 2 个 td 元素是否具有 colspan 属性,并检查其值是否为 5 - const secondTrTd = secondTr.querySelectorAll('td')[1]; - expect(secondTrTd.getAttribute('colspan')).toBe('5'); - }); + // 检查第 1 个 tr 元素中是否只有 6 个 td 元素 + const firstTr = tbody.querySelectorAll('tr')[0]; + expect(firstTr.querySelectorAll('td')).toHaveLength(6); - // vertical - test('layout=vertical columns=3', () => { - const { container } = render( - , - ); - const tbody = container.querySelector('tbody'); + // 检查第 2 个 tr 元素中是否只有 2 个 td 元素 + const secondTr = tbody.querySelectorAll('tr')[1]; + expect(secondTr.querySelectorAll('td')).toHaveLength(2); - // 检查 tbody 下面是否只有 4 个 tr 元素 - expect(tbody.querySelectorAll('tr')).toHaveLength(4); + // 检查第 2 个 tr 元素中的第 2 个 td 元素是否具有 colspan 属性,并检查其值是否为 5 + const secondTrTd = secondTr.querySelectorAll('td')[1]; + expect(secondTrTd.getAttribute('colspan')).toBe('5'); + }); - // 检查第 1 个 tr 元素中是否只有 2 个 td 元素 - const firstTr = tbody.querySelectorAll('tr')[0]; - expect(firstTr.querySelectorAll('td')).toHaveLength(2); + // vertical + test('layout=vertical columns=3', () => { + const { container } = render( + , + ); + const tbody = container.querySelector('tbody'); - // 检查第 3 个 tr 元素中是否只有 2 个 td 元素 - const thirdTr = tbody.querySelectorAll('tr')[2]; - expect(thirdTr.querySelectorAll('td')).toHaveLength(2); + // 检查 tbody 下面是否只有 4 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(4); - // 检查第 3 个 tr 元素中的第 1 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 - const thirdTrTd = thirdTr.querySelectorAll('td')[0]; - expect(thirdTrTd.getAttribute('colspan')).toBe('1'); - }); + // 检查第 1 个 tr 元素中是否只有 2 个 td 元素 + const firstTr = tbody.querySelectorAll('tr')[0]; + expect(firstTr.querySelectorAll('td')).toHaveLength(2); - // itemLayout=vertical - it('itemLayout=vertical', () => { - const { container } = render( - , - ); + // 检查第 3 个 tr 元素中是否只有 2 个 td 元素 + const thirdTr = tbody.querySelectorAll('tr')[2]; + expect(thirdTr.querySelectorAll('td')).toHaveLength(2); - const tbody = container.querySelector('tbody'); - // 检查 tbody 下面是否只有 4 个 tr 元素 - expect(tbody.querySelectorAll('tr')).toHaveLength(4); + // 检查第 3 个 tr 元素中的第 1 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 + const thirdTrTd = thirdTr.querySelectorAll('td')[0]; + expect(thirdTrTd.getAttribute('colspan')).toBe('1'); + }); - // 检查第 1 个 tr 元素中是否只有 3 个 td 元素 - const firstTr = tbody.querySelectorAll('tr')[0]; - expect(firstTr.querySelectorAll('td')).toHaveLength(3); + // itemLayout=vertical + it('itemLayout=vertical', () => { + const { container } = render( + , + ); - // 检查第 3 个 tr 元素中是否只有 1 个 td 元素 - const thirdTr = tbody.querySelectorAll('tr')[2]; - expect(thirdTr.querySelectorAll('td')).toHaveLength(1); + const tbody = container.querySelector('tbody'); + // 检查 tbody 下面是否只有 4 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(4); - // 检查第 3 个 tr 元素中的第 1 个 td 元素是否具有 colspan 属性,并检查其值是否为 3 - const thirdTrTd = thirdTr.querySelectorAll('td')[0]; - expect(thirdTrTd.getAttribute('colspan')).toBe('3'); - }); + // 检查第 1 个 tr 元素中是否只有 3 个 td 元素 + const firstTr = tbody.querySelectorAll('tr')[0]; + expect(firstTr.querySelectorAll('td')).toHaveLength(3); - // layout=vertical itemLayout=vertical - it('layout=vertical itemLayout=vertical', () => { - const { container } = render( - , - ); - - const tbody = container.querySelector('tbody'); - // 检查 tbody 下面是否只有 8 个 tr 元素 - expect(tbody.querySelectorAll('tr')).toHaveLength(8); - - // 检查第 1 个 tr 元素中是否只有 1 个 td 元素 - const firstTr = tbody.querySelectorAll('tr')[0]; - expect(firstTr.querySelectorAll('td')).toHaveLength(1); - - // 检查第 7 个 tr 元素中是否只有 1 个 td 元素 - const thirdTr = tbody.querySelectorAll('tr')[2]; - expect(thirdTr.querySelectorAll('td')).toHaveLength(1); - - // 检查第 7 个 tr 元素中的第 1 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 - const thirdTrTd = thirdTr.querySelectorAll('td')[0]; - expect(thirdTrTd.getAttribute('colspan')).toBe('1'); - }); + // 检查第 3 个 tr 元素中是否只有 1 个 td 元素 + const thirdTr = tbody.querySelectorAll('tr')[2]; + expect(thirdTr.querySelectorAll('td')).toHaveLength(1); - // size - test(':size', () => { - const sizeList: SizeEnum[] = ['small', 'medium', 'large']; - sizeList.forEach((size) => { - const { container } = render(); - const body = container.querySelector('.t-descriptions__body'); - expect(body).toHaveClass(`t-size-${size.slice(0, 1)}`); + // 检查第 3 个 tr 元素中的第 1 个 td 元素是否具有 colspan 属性,并检查其值是否为 3 + const thirdTrTd = thirdTr.querySelectorAll('td')[0]; + expect(thirdTrTd.getAttribute('colspan')).toBe('3'); }); - }); - - // jsx - test('jsx and DescriptionsItem span', () => { - const { container } = render( - - TDesign - 139****0609 - China Tencent Headquarters - - Shenzhen Penguin Island D1 4A Mail Center - - , - ); - - expect(container.querySelector('.t-descriptions')).toBeInTheDocument(); - }); - // props jsx - test('props jsx', () => { - const itemsProps = [ - { label: 'Name', content: 'TDesign' }, - { label: , content: '139****0609' }, - { label: 'Area', content:

China Tencent Headquarters

}, - { label: Address, content: Shenzhen Penguin Island D1 4A Mail Center }, - ]; - const { container } = render(); - - expect(container.querySelector('.t-descriptions')).toBeInTheDocument(); - }); + // layout=vertical itemLayout=vertical + it('layout=vertical itemLayout=vertical', () => { + const { container } = render( + , + ); + + const tbody = container.querySelector('tbody'); + // 检查 tbody 下面是否只有 8 个 tr 元素 + expect(tbody.querySelectorAll('tr')).toHaveLength(8); + + // 检查第 1 个 tr 元素中是否只有 1 个 td 元素 + const firstTr = tbody.querySelectorAll('tr')[0]; + expect(firstTr.querySelectorAll('td')).toHaveLength(1); + + // 检查第 7 个 tr 元素中是否只有 1 个 td 元素 + const thirdTr = tbody.querySelectorAll('tr')[2]; + expect(thirdTr.querySelectorAll('td')).toHaveLength(1); + + // 检查第 7 个 tr 元素中的第 1 个 td 元素是否具有 colspan 属性,并检查其值是否为 1 + const thirdTrTd = thirdTr.querySelectorAll('td')[0]; + expect(thirdTrTd.getAttribute('colspan')).toBe('1'); + }); - // nest - test('nest', () => { - const itemsContent = [ - { - label: 'City', - content: 'Shenzhen', - }, - { - label: 'Detail', - content: 'Penguin Island D1 4A Mail Center', - }, - ]; - - const items = [ - { - label: 'Name', - content: 'TDesign', - }, - { - label: 'Telephone Number', - content: '139****0609', - }, - { - label: 'Area', - content: 'China Tencent Headquarters', - }, - { - label: 'Address', - content: , - }, - ]; - render(); - expect(screen.getByText('Shenzhen')).not.toBeNull(); - expect(screen.getByText('Shenzhen')).toBeInTheDocument(); + // nest + test('nest', () => { + const itemsContent = [ + { + label: 'City', + content: 'Shenzhen', + }, + { + label: 'Detail', + content: 'Penguin Island D1 4A Mail Center', + }, + ]; + + const items = [ + { + label: 'Name', + content: 'TDesign', + }, + { + label: 'Telephone Number', + content: '139****0609', + }, + { + label: 'Area', + content: 'China Tencent Headquarters', + }, + { + label: 'Address', + content: , + }, + ]; + render(); + expect(screen.getByText('Shenzhen')).not.toBeNull(); + expect(screen.getByText('Shenzhen')).toBeInTheDocument(); + }); }); }); diff --git a/packages/components/dialog/__tests__/dialog.test.tsx b/packages/components/dialog/__tests__/dialog.test.tsx index d771b36de2..69081a8ba9 100644 --- a/packages/components/dialog/__tests__/dialog.test.tsx +++ b/packages/components/dialog/__tests__/dialog.test.tsx @@ -41,216 +41,222 @@ function DialogDemo(props) { ); } -describe('Dialog组件测试', () => { +describe('Dialog', () => { const user = userEvent.setup(); - test('BasicDialog', () => { - const { getByText } = render(); - expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(); - fireEvent.click(getByText('Open Dialog Modal')); - expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); - }); + describe('props', () => { + test('Dislog props.header render', async () => { + const { container } = render( + +

This is a dialog

+
, + ); - test('CloseDialog', async () => { - const { getByText } = render(); - fireEvent.click(getByText('Open Dialog Modal')); - expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); - fireEvent.click(document.querySelector('.t-dialog__close')); - await mockTimeout(() => expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(), 400); + expect(container.querySelector('.t-dialog__header')).toBeNull(); + }); }); - test('EscCloseDialog', async () => { - const onEscKeydown = vi.fn(); - const { getByText } = render(); + describe('scenarios', () => { + test('BasicDialog', () => { + const { getByText } = render(); + expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(); + fireEvent.click(getByText('Open Dialog Modal')); + expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); + }); - await fireEvent.click(getByText('Open Dialog Modal')); - expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); - await user.keyboard('{Escape}'); - expect(onEscKeydown).toHaveBeenCalled(); - }); + test('CloseDialog', async () => { + const { getByText } = render(); + fireEvent.click(getByText('Open Dialog Modal')); + expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); + fireEvent.click(document.querySelector('.t-dialog__close')); + await mockTimeout(() => expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(), 400); + }); - test('EnterConfirm', async () => { - const onConfirm = vi.fn(); - const { getByText } = render(); + test('EscCloseDialog', async () => { + const onEscKeydown = vi.fn(); + const { getByText } = render(); - expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(); + await fireEvent.click(getByText('Open Dialog Modal')); + expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); + await user.keyboard('{Escape}'); + expect(onEscKeydown).toHaveBeenCalled(); + }); - fireEvent.click(getByText('Open Dialog Modal')); - expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); - await user.keyboard('{Enter}'); - expect(onConfirm).toHaveBeenCalled(); - }); + test('EnterConfirm', async () => { + const onConfirm = vi.fn(); + const { getByText } = render(); - test('DraggableDialog', () => { - const { getByText } = render(); - fireEvent.click(getByText('Open Dialog Modal')); - expect(document.querySelector('.t-dialog__modeless')).toBeInTheDocument(); - fireEvent.mouseDown(document.querySelector('.t-dialog')); - fireEvent.mouseMove(document.querySelector('.t-dialog')); - expect(document.querySelector('.t-dialog')).toHaveStyle({ - cursor: 'move', - position: 'absolute', - }); - fireEvent.mouseUp(document.querySelector('.t-dialog')); - userEvent.keyboard('{esc}'); - expect(document.querySelector('.t-dialog')).toHaveStyle({ - left: '0px', - top: '0px', + expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(); + + fireEvent.click(getByText('Open Dialog Modal')); + expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); + await user.keyboard('{Enter}'); + expect(onConfirm).toHaveBeenCalled(); }); - }); - test('DialogPlugin', async () => { - const showDialog = () => { - const myDialog = DialogPlugin({ - header: 'Dialog-Plugin', - body: '函数调用Dialog方式一', - onConfirm: () => { - myDialog.hide(); - }, - onClose: () => { - myDialog.hide(); - }, - }); - }; - const onConfirm = () => { - const confirmDia = DialogPlugin.confirm({ - header: 'Dialog-Confirm-Plugin', - body: '函数调用Dialog方式二', - confirmBtn: 'ok', - cancelBtn: 'cancel', - onConfirm: () => { - confirmDia.hide(); - }, - onClose: () => { - confirmDia.hide(); - }, - }); - }; - const onAlert = () => { - const alertDia = DialogPlugin.alert({ - header: 'Dialog-Alert-Plugin', - body: '函数调用Dialog方式三', - confirmBtn: { - content: 'Got it!', - variant: 'base', - theme: 'danger', - }, - onConfirm: () => { - alertDia.hide(); - }, - onClose: () => { - alertDia.hide(); - }, + test('DraggableDialog', () => { + const { getByText } = render(); + fireEvent.click(getByText('Open Dialog Modal')); + expect(document.querySelector('.t-dialog__modeless')).toBeInTheDocument(); + fireEvent.mouseDown(document.querySelector('.t-dialog')); + fireEvent.mouseMove(document.querySelector('.t-dialog')); + expect(document.querySelector('.t-dialog')).toHaveStyle({ + cursor: 'move', + position: 'absolute', }); - }; - const handleDialogNode = () => { - const dialogNode = DialogPlugin({ - header: 'Dialog-Plugin', - body: 'HandleDialogNode', + fireEvent.mouseUp(document.querySelector('.t-dialog')); + userEvent.keyboard('{esc}'); + expect(document.querySelector('.t-dialog')).toHaveStyle({ + left: '0px', + top: '0px', }); - dialogNode.update({ - header: 'Updated-Dialog-Plugin', - cancelBtn: null, - confirmBtn: { - content: 'GET IT!', - variant: 'base', - theme: 'danger', - id: 'test', - }, - onConfirm: () => { - dialogNode.hide(); - dialogNode.destroy(); - }, - onClose: () => { - dialogNode.hide(); - }, - }); - }; - - const { getByText } = render( - <> -
Show Dialog
-
Confirm Dialog
-
Alert Dialog
-
Handle Dialog
- , - ); - fireEvent.click(getByText('Show Dialog')); - expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); - await user.keyboard('{Escape}'); - await mockTimeout(() => expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(), 400); - fireEvent.click(getByText('Confirm Dialog')); - expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); - await user.keyboard('{Escape}'); - await mockTimeout(() => expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(), 400); - fireEvent.click(getByText('Alert Dialog')); - expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); - await user.keyboard('{Escape}'); - await mockTimeout(() => expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(), 400); - fireEvent.click(getByText('Handle Dialog')); - expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); - await mockTimeout(() => true, 100); - fireEvent.click(document.querySelector('#test')); - await mockTimeout(() => expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(), 400); - }); + }); - test('Dislog props.header render', async () => { - const { container } = render( - -

This is a dialog

-
, - ); + test('global dialog placement and zIndex works', async () => { + render( + + + , + ); - expect(container.querySelector('.t-dialog__header')).toBeNull(); - }); + expect(document.querySelector('.t-dialog--center')).toBeInTheDocument(); + expect(document.querySelector('.t-dialog--top')).not.toBeInTheDocument(); + expect(document.querySelector('.t-dialog__ctx')).toHaveStyle({ + zIndex: '4321', + }); + }); - test('global dialog placement and zIndex works', async () => { - render( - { + render( + - - , - ); + }} + > + + , + ); - expect(document.querySelector('.t-dialog--center')).toBeInTheDocument(); - expect(document.querySelector('.t-dialog--top')).not.toBeInTheDocument(); - expect(document.querySelector('.t-dialog__ctx')).toHaveStyle({ - zIndex: '4321', + expect(document.querySelector('.t-dialog--top')).toBeInTheDocument(); + expect(document.querySelector('.t-dialog--center')).not.toBeInTheDocument(); + expect(document.querySelector('.t-dialog__ctx')).toHaveStyle({ + zIndex: '1234', + }); }); }); - test('dialog props should override global dialog config', async () => { - render( - { + test('DialogPlugin', async () => { + const showDialog = () => { + const myDialog = DialogPlugin({ + header: 'Dialog-Plugin', + body: '函数调用Dialog方式一', + onConfirm: () => { + myDialog.hide(); }, - }} - > - - , - ); + onClose: () => { + myDialog.hide(); + }, + }); + }; + const onConfirm = () => { + const confirmDia = DialogPlugin.confirm({ + header: 'Dialog-Confirm-Plugin', + body: '函数调用Dialog方式二', + confirmBtn: 'ok', + cancelBtn: 'cancel', + onConfirm: () => { + confirmDia.hide(); + }, + onClose: () => { + confirmDia.hide(); + }, + }); + }; + const onAlert = () => { + const alertDia = DialogPlugin.alert({ + header: 'Dialog-Alert-Plugin', + body: '函数调用Dialog方式三', + confirmBtn: { + content: 'Got it!', + variant: 'base', + theme: 'danger', + }, + onConfirm: () => { + alertDia.hide(); + }, + onClose: () => { + alertDia.hide(); + }, + }); + }; + const handleDialogNode = () => { + const dialogNode = DialogPlugin({ + header: 'Dialog-Plugin', + body: 'HandleDialogNode', + }); + dialogNode.update({ + header: 'Updated-Dialog-Plugin', + cancelBtn: null, + confirmBtn: { + content: 'GET IT!', + variant: 'base', + theme: 'danger', + id: 'test', + }, + onConfirm: () => { + dialogNode.hide(); + dialogNode.destroy(); + }, + onClose: () => { + dialogNode.hide(); + }, + }); + }; - expect(document.querySelector('.t-dialog--top')).toBeInTheDocument(); - expect(document.querySelector('.t-dialog--center')).not.toBeInTheDocument(); - expect(document.querySelector('.t-dialog__ctx')).toHaveStyle({ - zIndex: '1234', + const { getByText } = render( + <> +
Show Dialog
+
Confirm Dialog
+
Alert Dialog
+
Handle Dialog
+ , + ); + fireEvent.click(getByText('Show Dialog')); + expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); + await user.keyboard('{Escape}'); + await mockTimeout(() => expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(), 400); + fireEvent.click(getByText('Confirm Dialog')); + expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); + await user.keyboard('{Escape}'); + await mockTimeout(() => expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(), 400); + fireEvent.click(getByText('Alert Dialog')); + expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); + await user.keyboard('{Escape}'); + await mockTimeout(() => expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(), 400); + fireEvent.click(getByText('Handle Dialog')); + expect(document.querySelector('.t-dialog__modal')).toBeInTheDocument(); + await mockTimeout(() => true, 100); + fireEvent.click(document.querySelector('#test')); + await mockTimeout(() => expect(document.querySelector('.t-dialog__modal')).not.toBeInTheDocument(), 400); }); }); }); diff --git a/packages/components/divider/__tests__/__snapshots__/vitest-divider.test.jsx.snap b/packages/components/divider/__tests__/__snapshots__/divider.test.tsx.snap similarity index 72% rename from packages/components/divider/__tests__/__snapshots__/vitest-divider.test.jsx.snap rename to packages/components/divider/__tests__/__snapshots__/divider.test.tsx.snap index 67502c9491..de168d2ffb 100644 --- a/packages/components/divider/__tests__/__snapshots__/vitest-divider.test.jsx.snap +++ b/packages/components/divider/__tests__/__snapshots__/divider.test.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Divider Component > props.align is equal to center 1`] = ` +exports[`Divider > props > align is equal to center 1`] = `
props.align is equal to center 1`] = `
`; -exports[`Divider Component > props.align is equal to left 1`] = ` +exports[`Divider > props > align is equal to left 1`] = `
props.align is equal to left 1`] = `
`; -exports[`Divider Component > props.align is equal to right 1`] = ` +exports[`Divider > props > align is equal to right 1`] = `
props.align is equal to right 1`] = `
`; -exports[`Divider Component > props.content works fine 1`] = ` +exports[`Divider > props > content 1`] = `
props.content works fine 1`] = `
`; -exports[`Divider Component > props.layout is equal to horizontal 1`] = ` +exports[`Divider > props > layout is equal to horizontal 1`] = `
props.layout is equal to horizontal 1`] = `
`; -exports[`Divider Component > props.layout is equal to vertical 1`] = ` +exports[`Divider > props > layout is equal to vertical 1`] = `
{ +import type { TdDividerProps } from '../type'; + +describe('Divider', () => { describe('props', () => { - test('Divider 水平分割线', async () => { - const { container } = render(); + (['left', 'right', 'center'] as TdDividerProps['align'][]).forEach((item) => { + test(`align is equal to ${item}`, () => { + const { container } = render(Text); + expect(container.firstChild).toHaveClass(`t-divider--with-text-${item}`); + expect(container).toMatchSnapshot(); + }); + }); - // 校验默认 className - const defaultClass = ['t-divider', 't-divider--horizontal']; - expect(container.firstChild).toHaveClass(...defaultClass); + test('dashed', () => { + // dashed default value is false + const { container: container1 } = render(); + expect(container1.querySelector(`.${'t-divider--dashed'}`)).toBeFalsy(); + // dashed = true + const { container: container2 } = render(); + expect(container2.firstChild).toHaveClass('t-divider--dashed'); + // dashed = false + const { container: container3 } = render(); + expect(container3.querySelector(`.${'t-divider--dashed'}`)).toBeFalsy(); + }); + + (['horizontal', 'vertical'] as TdDividerProps['layout'][]).forEach((item) => { + test(`layout is equal to ${item}`, () => { + const { container } = render(); + expect(container.firstChild).toHaveClass(`t-divider--${item}`); + expect(container).toMatchSnapshot(); + }); }); + test('Divider 虚线', () => { const { container } = render(); @@ -19,16 +42,7 @@ describe('Divider 组件测试', () => { const defaultClass = ['t-divider', 't-divider--horizontal', 't-divider--dashed']; expect(container.firstChild).toHaveClass(...defaultClass); }); - test('Divider 带文字', () => { - const text = '腾讯中'; - const { container, getByText } = render({text}); - - // 校验默认 className - const defaultClass = ['t-divider', 't-divider--horizontal', 't-divider--with-text']; - expect(container.firstChild).toHaveClass(...defaultClass); - expect(getByText(text).textContent).toBe(text); - }); test('Divider content带文字 ', () => { const text = '腾讯中content'; const { container, getByText } = render(); @@ -57,6 +71,32 @@ describe('Divider 组件测试', () => { margin: '20px 0', }); }); + test('content', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('Divider 带文字', () => { + const text = '腾讯中'; + const { container, getByText } = render({text}); + + // 校验默认 className + const defaultClass = ['t-divider', 't-divider--horizontal', 't-divider--with-text']; + expect(container.firstChild).toHaveClass(...defaultClass); + + expect(getByText(text).textContent).toBe(text); + }); + }); + + describe('scenarios', () => { + test('Divider 水平分割线', async () => { + const { container } = render(); + + // 校验默认 className + const defaultClass = ['t-divider', 't-divider--horizontal']; + expect(container.firstChild).toHaveClass(...defaultClass); + }); test('Divider size vertical', () => { const text = '腾讯中content'; diff --git a/packages/components/divider/__tests__/vitest-divider.test.jsx b/packages/components/divider/__tests__/vitest-divider.test.jsx deleted file mode 100644 index 18b0080107..0000000000 --- a/packages/components/divider/__tests__/vitest-divider.test.jsx +++ /dev/null @@ -1,44 +0,0 @@ -/** - * 该文件由脚本自动生成,如需修改请联系 PMC - * This file generated by scripts of tdesign-api. `npm run api:docs Divider React(PC) vitest,finalProject` - * If you need to modify this file, contact PMC first please. - */ -import React from 'react'; -import { render } from '@test/utils'; -import { Divider } from '..'; - -describe('Divider Component', () => { - ['left', 'right', 'center'].forEach((item) => { - it(`props.align is equal to ${item}`, () => { - const { container } = render(Text); - expect(container.firstChild).toHaveClass(`t-divider--with-text-${item}`); - expect(container).toMatchSnapshot(); - }); - }); - - it('props.content works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.dashed works fine', () => { - // dashed default value is false - const { container: container1 } = render(); - expect(container1.querySelector(`.${'t-divider--dashed'}`)).toBeFalsy(); - // dashed = true - const { container: container2 } = render(); - expect(container2.firstChild).toHaveClass('t-divider--dashed'); - // dashed = false - const { container: container3 } = render(); - expect(container3.querySelector(`.${'t-divider--dashed'}`)).toBeFalsy(); - }); - - ['horizontal', 'vertical'].forEach((item) => { - it(`props.layout is equal to ${item}`, () => { - const { container } = render(); - expect(container.firstChild).toHaveClass(`t-divider--${item}`); - expect(container).toMatchSnapshot(); - }); - }); -}); diff --git a/packages/components/drawer/__tests__/drawer.test.tsx b/packages/components/drawer/__tests__/drawer.test.tsx index 495a514965..c5f5eb4263 100644 --- a/packages/components/drawer/__tests__/drawer.test.tsx +++ b/packages/components/drawer/__tests__/drawer.test.tsx @@ -30,108 +30,116 @@ function DrawerDemo(props) { ); } -describe('test Drawer', () => { - test('Drawer render', async () => { - const { getByText } = render(); - fireEvent.click(getByText('Open')); - expect(document.querySelector('.t-drawer--open')).toBeInTheDocument(); - expect(document.querySelector('.t-drawer__mask')).toBeInTheDocument(); - expect(document.querySelector('.t-drawer__content-wrapper--right')).toBeInTheDocument(); - }); - test('Drawer close', async () => { - const { getByText } = render(); - fireEvent.click(getByText('Open')); - fireEvent.click(document.querySelector('.t-drawer__close-btn')); - expect(document.querySelector('.t-drawer--open')).not.toBeInTheDocument(); - expect(document.querySelector('.t-drawer')).toBeInTheDocument(); - }); - test('Drawer mask close', async () => { - const { getByText } = render(); - fireEvent.click(getByText('Open')); - expect(document.querySelector('.t-drawer--open')).toBeInTheDocument(); - fireEvent.click(document.querySelector('.t-drawer--open')?.children[0]); - expect(document.querySelector('.t-drawer--open')).not.toBeInTheDocument(); - }); - test('Drawer placement', async () => { - const { getByText } = render(); - fireEvent.click(getByText('Open')); - expect(document.querySelector('.t-drawer__content-wrapper--right')).not.toBeInTheDocument(); - expect(document.querySelector('.t-drawer__content-wrapper--top')).toBeInTheDocument(); - }); - test('Drawer size', async () => { - const { getByText } = render(); - fireEvent.click(getByText('Open')); - expect(document.querySelector('.t-drawer__content-wrapper--right')).toHaveStyle({ width: '50%' }); - }); - test('Drawer showOverlay', () => { - const { getByText } = render(); - fireEvent.click(getByText('Open')); - expect(document.querySelector('.t-drawer__mask')).not.toBeInTheDocument(); - }); - test('Drawer header and footer custom', () => { - const { getByText } = render(自定义头部
} footer={
自定义底部
} />); - fireEvent.click(getByText('Open')); - expect(getByText('自定义头部').parentElement).toHaveClass('t-drawer__header'); - expect(getByText('自定义底部').parentElement).toHaveClass('t-drawer__footer'); - }); - test('Drawer cancelBtn and confirmBtn custom', () => { - const { getByText } = render(cancelBtn
} confirmBtn={
confirmBtn
} />); - fireEvent.click(getByText('Open')); +describe('Drawer', () => { + describe('props', () => { + test('Drawer placement', async () => { + const { getByText } = render(); + fireEvent.click(getByText('Open')); + expect(document.querySelector('.t-drawer__content-wrapper--right')).not.toBeInTheDocument(); + expect(document.querySelector('.t-drawer__content-wrapper--top')).toBeInTheDocument(); + }); + test('Drawer size', async () => { + const { getByText } = render(); + fireEvent.click(getByText('Open')); + expect(document.querySelector('.t-drawer__content-wrapper--right')).toHaveStyle({ width: '50%' }); + }); + test('Drawer showOverlay', () => { + const { getByText } = render(); + fireEvent.click(getByText('Open')); + expect(document.querySelector('.t-drawer__mask')).not.toBeInTheDocument(); + }); - const cancelBtn = getByText('cancelBtn'); - const confirmBtn = getByText('confirmBtn'); + test('Drawer cancelBtn and confirmBtn props', () => { + const { getByText } = render( + , + ); + fireEvent.click(getByText('Open')); - expect(cancelBtn.parentElement.parentElement).toHaveClass('t-drawer__footer'); - expect(confirmBtn.parentElement.parentElement).toHaveClass('t-drawer__footer'); - }); + const confirmBtn = getByText('确认按钮'); + const cancelBtn = getByText('取消按钮'); - test('Drawer cancelBtn and confirmBtn props', () => { - const { getByText } = render( - , - ); - fireEvent.click(getByText('Open')); - - const confirmBtn = getByText('确认按钮'); - const cancelBtn = getByText('取消按钮'); - - // 是否有这两个元素 - expect(cancelBtn).toBeInTheDocument(); - expect(confirmBtn).toBeInTheDocument(); - - expect(confirmBtn.parentElement).toHaveClass('t-drawer__confirm'); - expect(cancelBtn.parentElement).toHaveClass('t-drawer__cancel'); - expect(confirmBtn.parentElement.parentElement.parentElement).toHaveClass('t-drawer__footer'); - expect(cancelBtn.parentElement.parentElement.parentElement).toHaveClass('t-drawer__footer'); - - expect(confirmBtn.parentElement).toHaveClass(`t-button--theme-success`); - expect(cancelBtn.parentElement).toHaveClass(`t-button--theme-danger`); - }); + // 是否有这两个元素 + expect(cancelBtn).toBeInTheDocument(); + expect(confirmBtn).toBeInTheDocument(); + + expect(confirmBtn.parentElement).toHaveClass('t-drawer__confirm'); + expect(cancelBtn.parentElement).toHaveClass('t-drawer__cancel'); + expect(confirmBtn.parentElement.parentElement.parentElement).toHaveClass('t-drawer__footer'); + expect(cancelBtn.parentElement.parentElement.parentElement).toHaveClass('t-drawer__footer'); + + expect(confirmBtn.parentElement).toHaveClass(`t-button--theme-success`); + expect(cancelBtn.parentElement).toHaveClass(`t-button--theme-danger`); + }); + + test('Drawer mode push', () => { + const { getByText } = render(); + fireEvent.click(getByText('Open')); - test('Drawer mode push', () => { - const { getByText } = render(); - fireEvent.click(getByText('Open')); + setTimeout(() => { + expect(document.body).toHaveStyle({ + margin: '0 0 0 -300px', + }); + }, 1000); + }); + test('Drawer header and footer custom', () => { + const { getByText } = render(自定义头部
} footer={
自定义底部
} />); + fireEvent.click(getByText('Open')); + expect(getByText('自定义头部').parentElement).toHaveClass('t-drawer__header'); + expect(getByText('自定义底部').parentElement).toHaveClass('t-drawer__footer'); + }); + test('Drawer cancelBtn and confirmBtn custom', () => { + const { getByText } = render(cancelBtn
} confirmBtn={
confirmBtn
} />); + fireEvent.click(getByText('Open')); - setTimeout(() => { - expect(document.body).toHaveStyle({ - margin: '0 0 0 -300px', - }); - }, 1000); + const cancelBtn = getByText('cancelBtn'); + const confirmBtn = getByText('confirmBtn'); + + expect(cancelBtn.parentElement.parentElement).toHaveClass('t-drawer__footer'); + expect(confirmBtn.parentElement.parentElement).toHaveClass('t-drawer__footer'); + }); + }); + + describe('events', () => { + test('Drawer onCancel', () => { + const onCancelFn = vi.fn(); + const { getByText } = render(); + fireEvent.click(getByText('Open')); + expect(onCancelFn).not.toHaveBeenCalled(); + fireEvent.click(getByText('取消')); + expect(onCancelFn).toHaveBeenCalled(); + }); }); - test('Drawer onCancel', () => { - const onCancelFn = vi.fn(); - const { getByText } = render(); - fireEvent.click(getByText('Open')); - expect(onCancelFn).not.toHaveBeenCalled(); - fireEvent.click(getByText('取消')); - expect(onCancelFn).toHaveBeenCalled(); + + describe('scenarios', () => { + test('Drawer render', async () => { + const { getByText } = render(); + fireEvent.click(getByText('Open')); + expect(document.querySelector('.t-drawer--open')).toBeInTheDocument(); + expect(document.querySelector('.t-drawer__mask')).toBeInTheDocument(); + expect(document.querySelector('.t-drawer__content-wrapper--right')).toBeInTheDocument(); + }); + test('Drawer close', async () => { + const { getByText } = render(); + fireEvent.click(getByText('Open')); + fireEvent.click(document.querySelector('.t-drawer__close-btn')); + expect(document.querySelector('.t-drawer--open')).not.toBeInTheDocument(); + expect(document.querySelector('.t-drawer')).toBeInTheDocument(); + }); + test('Drawer mask close', async () => { + const { getByText } = render(); + fireEvent.click(getByText('Open')); + expect(document.querySelector('.t-drawer--open')).toBeInTheDocument(); + fireEvent.click(document.querySelector('.t-drawer--open')?.children[0]); + expect(document.querySelector('.t-drawer--open')).not.toBeInTheDocument(); + }); }); }); diff --git a/packages/components/dropdown/__tests__/dropdown.test.tsx b/packages/components/dropdown/__tests__/dropdown.test.tsx index 8a83066e75..9fee99c3a8 100644 --- a/packages/components/dropdown/__tests__/dropdown.test.tsx +++ b/packages/components/dropdown/__tests__/dropdown.test.tsx @@ -5,7 +5,7 @@ import Dropdown from '../Dropdown'; const { DropdownMenu, DropdownItem } = Dropdown; -describe('Dropdown 组件测试', () => { +describe('Dropdown', () => { const triggerElement = '触发元素'; const triggerElement2 = '触发元素2'; const clickElement = '弹出层内容'; @@ -26,83 +26,85 @@ describe('Dropdown 组件测试', () => { }, ]; - test('hover 触发测试', async () => { - const { getByText } = render( - - {triggerElement} - , - ); + describe('scenarios', () => { + test('hover 触发测试', async () => { + const { getByText } = render( + + {triggerElement} + , + ); - // 鼠标进入前,没有元素存在 - const popupContainer1 = document.querySelector('.t-dropdown'); - expect(popupContainer1).toBeNull(); + // 鼠标进入前,没有元素存在 + const popupContainer1 = document.querySelector('.t-dropdown'); + expect(popupContainer1).toBeNull(); - // 模拟鼠标进入 - act(() => { - fireEvent.mouseEnter(getByText(triggerElement)); - }); - expect(document.querySelector('.t-dropdown__item')).toHaveTextContent(dropOptions[0].content); - - // 模拟鼠标进入child - act(() => { - fireEvent.mouseEnter(getByText(triggerElement2)); - }); - // 鼠标进入后,有元素 - const popupContainer4 = document.querySelectorAll('.t-dropdown__item'); - expect(popupContainer4[2]).toHaveTextContent(dropOptions[1].children[0].content); - - // 模拟鼠标离开 - act(() => { - fireEvent.mouseLeave(getByText(triggerElement)); - }); - expect(document.querySelector('.t-dropdown__menu')).not.toBeNull(); - }); - - test('click 点击测试', async () => { - const { getByText } = render({triggerElement}); - - // 鼠标进入前,没有元素存在 - expect(document.querySelector('.t-dropdown')).toBeNull(); - - act(() => { // 模拟鼠标进入 - fireEvent.mouseEnter(getByText(triggerElement)); - }); - expect(document.querySelector('.t-dropdown')).not.toBeNull(); + act(() => { + fireEvent.mouseEnter(getByText(triggerElement)); + }); + expect(document.querySelector('.t-dropdown__item')).toHaveTextContent(dropOptions[0].content); + + // 模拟鼠标进入child + act(() => { + fireEvent.mouseEnter(getByText(triggerElement2)); + }); + // 鼠标进入后,有元素 + const popupContainer4 = document.querySelectorAll('.t-dropdown__item'); + expect(popupContainer4[2]).toHaveTextContent(dropOptions[1].children[0].content); - act(() => { - // 模拟鼠标点击 - fireEvent.click(getByText(clickElement)); + // 模拟鼠标离开 + act(() => { + fireEvent.mouseLeave(getByText(triggerElement)); + }); + expect(document.querySelector('.t-dropdown__menu')).not.toBeNull(); }); - // 鼠标点击后下拉框消失 - await mockTimeout(() => expect(document.querySelector('.t-dropdown__menu')).toBeNull()); - }); - test('child 写法模拟', async () => { - const { getByText } = render( - - {triggerElement} - - 弹出层内容 - - , - ); + test('click 点击测试', async () => { + const { getByText } = render({triggerElement}); - // 鼠标进入前,没有元素存在 - expect(document.querySelector('.t-dropdown')).toBeNull(); + // 鼠标进入前,没有元素存在 + expect(document.querySelector('.t-dropdown')).toBeNull(); - act(() => { - // 模拟鼠标进入 - fireEvent.mouseEnter(getByText(triggerElement)); - }); - expect(document.querySelector('.t-dropdown')).not.toBeNull(); + act(() => { + // 模拟鼠标进入 + fireEvent.mouseEnter(getByText(triggerElement)); + }); + expect(document.querySelector('.t-dropdown')).not.toBeNull(); - act(() => { - // 模拟鼠标离开 - fireEvent.mouseLeave(getByText(triggerElement)); + act(() => { + // 模拟鼠标点击 + fireEvent.click(getByText(clickElement)); + }); + // 鼠标点击后下拉框消失 + await mockTimeout(() => expect(document.querySelector('.t-dropdown__menu')).toBeNull()); }); - // 鼠标点击后下拉框消失 - await mockTimeout(() => expect(document.querySelector('.t-dropdown__menu')).toBeNull()); + test('child 写法模拟', async () => { + const { getByText } = render( + + {triggerElement} + + 弹出层内容 + + , + ); + + // 鼠标进入前,没有元素存在 + expect(document.querySelector('.t-dropdown')).toBeNull(); + + act(() => { + // 模拟鼠标进入 + fireEvent.mouseEnter(getByText(triggerElement)); + }); + expect(document.querySelector('.t-dropdown')).not.toBeNull(); + + act(() => { + // 模拟鼠标离开 + fireEvent.mouseLeave(getByText(triggerElement)); + }); + + // 鼠标点击后下拉框消失 + await mockTimeout(() => expect(document.querySelector('.t-dropdown__menu')).toBeNull()); + }); }); }); diff --git a/packages/components/empty/__tests__/empty.test.tsx b/packages/components/empty/__tests__/empty.test.tsx index 7be5ce5fcc..8fe3af82ad 100644 --- a/packages/components/empty/__tests__/empty.test.tsx +++ b/packages/components/empty/__tests__/empty.test.tsx @@ -1,43 +1,58 @@ /* eslint-disable */ -import { render } from '@test/utils'; -import React from 'react'; - -import Empty from '../index'; - -describe('Empty 组件测试', () => { - test('size', async () => { - const { container } = render(); - - expect(container.querySelector('.t-empty.t-size-s')).not.toBeNull(); - }); - - test('title', async () => { - const { container } = render(); - - expect(container.querySelector('.t-empty__title').innerHTML).toBe('title'); - }); - - test('description', async () => { - const { container } = render(); - - expect(container.querySelector('.t-empty__description').innerHTML).toBe('description'); - }); - - test('action', async () => { - const { container } = render(action
} />); - - expect(container.querySelector('.t-empty__action').innerHTML).toBe('
action
'); - }); - - test('type', async () => { - const { container } = render(); - const successIconPath = - 'M24 42C33.9411 42 42 33.9411 42 24C42 14.0589 33.9411 6 24 6C14.0589 6 6 14.0589 6 24C6 33.9411 14.0589 42 24 42ZM46 24C46 36.1503 36.1503 46 24 46C11.8497 46 2 36.1503 2 24C2 11.8497 11.8497 2 24 2C36.1503 2 46 11.8497 46 24ZM21 32.8284L12.1716 24L15 21.1716L21 27.1716L33 15.1716L35.8284 18L21 32.8284Z'; - expect(container.querySelector('.t-empty__image').querySelector('path').getAttribute('d')).toBe(successIconPath); - }); - - test('image', async () => { - const { container } = render(image
} />); - expect(container.querySelector('.t-empty__image').innerHTML).toBe('
image
'); +import { render } from "@test/utils"; +import React from "react"; + +import Empty from "../index"; + +describe("Empty", () => { + describe("props", () => { + test("size", async () => { + const { container } = render(); + + expect(container.querySelector(".t-empty.t-size-s")).not.toBeNull(); + }); + + test("title", async () => { + const { container } = render(); + + expect(container.querySelector(".t-empty__title").innerHTML).toBe( + "title", + ); + }); + + test("description", async () => { + const { container } = render(); + + expect(container.querySelector(".t-empty__description").innerHTML).toBe( + "description", + ); + }); + + test("type", async () => { + const { container } = render(); + const successIconPath = + "M24 42C33.9411 42 42 33.9411 42 24C42 14.0589 33.9411 6 24 6C14.0589 6 6 14.0589 6 24C6 33.9411 14.0589 42 24 42ZM46 24C46 36.1503 36.1503 46 24 46C11.8497 46 2 36.1503 2 24C2 11.8497 11.8497 2 24 2C36.1503 2 46 11.8497 46 24ZM21 32.8284L12.1716 24L15 21.1716L21 27.1716L33 15.1716L35.8284 18L21 32.8284Z"; + expect( + container + .querySelector(".t-empty__image") + .querySelector("path") + .getAttribute("d"), + ).toBe(successIconPath); + }); + + test("action", async () => { + const { container } = render(action
} />); + + expect(container.querySelector(".t-empty__action").innerHTML).toBe( + "
action
", + ); + }); + + test("image", async () => { + const { container } = render(image
} />); + expect(container.querySelector(".t-empty__image").innerHTML).toBe( + "
image
", + ); + }); }); }); diff --git a/packages/components/form/__tests__/form-list.test.tsx b/packages/components/form/__tests__/form-list.test.tsx index 5294322776..aeb181e548 100644 --- a/packages/components/form/__tests__/form-list.test.tsx +++ b/packages/components/form/__tests__/form-list.test.tsx @@ -55,936 +55,971 @@ const BasicForm = (props: FormProps & { operation }) => { ); }; -describe('FormList 组件测试', () => { - test('FormList basic API', async () => { - const onValuesChangeFn = vi.fn(); - let latestFormValues = {}; +describe('FormList', () => { + describe('instanceFunctions', () => { + test('FormList basic API', async () => { + const onValuesChangeFn = vi.fn(); + let latestFormValues = {}; + + const TestView = () => { + const [form] = Form.useForm(); + + function setFields() { + form.getFieldsValue(true); + form.setFields([{ name: 'address', value: [{ province: 'setFields' }] }]); + } + + function setFieldsValue() { + form.getFieldValue('address'); + form.setFieldsValue({ address: [{ province: 'setFieldsValue' }] }); + } + + function setValidateMessage() { + form.setValidateMessage({ + address: [{ type: 'error', message: 'message: setValidateMessage' }], + }); + } + + function validate() { + form.validate(); + } + + function validateOnly() { + form.validateOnly(); + } + + function clearValidate() { + form.clearValidate(); + } + + function getFormValues() { + const values = form.getFieldsValue(true); + document.getElementById('form-values-result')?.setAttribute('data-result', JSON.stringify(values)); + } + + return ( + { + onValuesChangeFn(changedValues, allValues); + latestFormValues = allValues; + }} + operation={() => ( + <> + + + + + + + + + +
+ + )} + /> + ); + }; - const TestView = () => { - const [form] = Form.useForm(); + const { container, queryByDisplayValue, queryByText } = render(); + const addBtn = container.querySelector('#test-add-with-data'); + const resetBtn = queryByText('reset'); - function setFields() { - form.getFieldsValue(true); - form.setFields([{ name: 'address', value: [{ province: 'setFields' }] }]); - } + // Reset onValuesChange call count + onValuesChangeFn.mockClear(); - function setFieldsValue() { - form.getFieldValue('address'); - form.setFieldsValue({ address: [{ province: 'setFieldsValue' }] }); - } + // Test 1: Add field with data and verify onValuesChange is called + fireEvent.click(addBtn); + await mockTimeout(() => true); + expect(queryByDisplayValue('guangdong')).toBeTruthy(); + expect(queryByDisplayValue('shenzhen')).toBeTruthy(); + + // Verify onValuesChange was called with correct data + expect(onValuesChangeFn).toHaveBeenCalled(); + const lastCall = onValuesChangeFn.mock.calls[onValuesChangeFn.mock.calls.length - 1]; + expect(lastCall[1]).toEqual({ + address: [{ province: 'guangdong', area: 'shenzhen' }], + }); - function setValidateMessage() { - form.setValidateMessage({ address: [{ type: 'error', message: 'message: setValidateMessage' }] }); - } + // Verify UI matches the data in onValuesChange + expect(latestFormValues).toEqual({ + address: [{ province: 'guangdong', area: 'shenzhen' }], + }); - function validate() { - form.validate(); - } + const initialCallCount = onValuesChangeFn.mock.calls.length; - function validateOnly() { - form.validateOnly(); + fireEvent.click(resetBtn); + await mockTimeout(() => true); + + // Check if UI is cleared after reset + expect(queryByDisplayValue('guangdong')).not.toBeTruthy(); + expect(queryByDisplayValue('shenzhen')).not.toBeTruthy(); + + // Verify form values are cleared (either through onValuesChange or direct form state) + const currentFormValues = container.querySelector('#form-values-result'); + if (currentFormValues) { + fireEvent.click(queryByText('getFormValues')); + await mockTimeout(() => true); + const formValuesResult = currentFormValues.getAttribute('data-result'); + const formValues = JSON.parse(formValuesResult || '{}'); + expect(formValues).toEqual({}); } - function clearValidate() { - form.clearValidate(); + // If onValuesChange was called during reset, verify the data + if (onValuesChangeFn.mock.calls.length > initialCallCount) { + const resetCall = onValuesChangeFn.mock.calls[onValuesChangeFn.mock.calls.length - 1]; + expect(resetCall[1]).toEqual({}); + expect(latestFormValues).toEqual({}); + } else { + latestFormValues = {}; } - function getFormValues() { - const values = form.getFieldsValue(true); - document.getElementById('form-values-result')?.setAttribute('data-result', JSON.stringify(values)); + const beforeAddCallCount = onValuesChangeFn.mock.calls.length; + + fireEvent.click(addBtn); + await mockTimeout(() => true); + expect(queryByDisplayValue('guangdong')).toBeTruthy(); + expect(queryByDisplayValue('shenzhen')).toBeTruthy(); + + const removeBtn = container.querySelector('.test-remove-0'); + fireEvent.click(removeBtn); + await mockTimeout(() => true); + expect(queryByDisplayValue('guangdong')).not.toBeTruthy(); + expect(queryByDisplayValue('shenzhen')).not.toBeTruthy(); + + const afterRemoveCallCount = onValuesChangeFn.mock.calls.length; + + // Verify onValuesChange was called for both add and remove operations + expect(afterRemoveCallCount).toBeGreaterThan(beforeAddCallCount); + if (afterRemoveCallCount > beforeAddCallCount) { + const removeCall = onValuesChangeFn.mock.calls[afterRemoveCallCount - 1]; + expect(removeCall[1]).toEqual({ address: [] }); } - return ( - { - onValuesChangeFn(changedValues, allValues); - latestFormValues = allValues; - }} - operation={() => ( - <> - - - - - - - - - -
- - )} - /> - ); - }; - - const { container, queryByDisplayValue, queryByText } = render(); - const addBtn = container.querySelector('#test-add-with-data'); - const resetBtn = queryByText('reset'); - - // Reset onValuesChange call count - onValuesChangeFn.mockClear(); - - // Test 1: Add field with data and verify onValuesChange is called - fireEvent.click(addBtn); - await mockTimeout(() => true); - expect(queryByDisplayValue('guangdong')).toBeTruthy(); - expect(queryByDisplayValue('shenzhen')).toBeTruthy(); - - // Verify onValuesChange was called with correct data - expect(onValuesChangeFn).toHaveBeenCalled(); - const lastCall = onValuesChangeFn.mock.calls[onValuesChangeFn.mock.calls.length - 1]; - expect(lastCall[1]).toEqual({ - address: [{ province: 'guangdong', area: 'shenzhen' }], - }); + // Test 4: setFields and verify onValuesChange + const beforeSetFieldsCallCount = onValuesChangeFn.mock.calls.length; - // Verify UI matches the data in onValuesChange - expect(latestFormValues).toEqual({ - address: [{ province: 'guangdong', area: 'shenzhen' }], - }); + fireEvent.click(queryByText('setFields')); + await mockTimeout(() => true); + expect(queryByDisplayValue('setFields')).toBeTruthy(); + + const afterSetFieldsCallCount = onValuesChangeFn.mock.calls.length; - const initialCallCount = onValuesChangeFn.mock.calls.length; + if (afterSetFieldsCallCount > beforeSetFieldsCallCount) { + const setFieldsCall = onValuesChangeFn.mock.calls[afterSetFieldsCallCount - 1]; + expect(setFieldsCall[1]).toEqual({ + address: [{ province: 'setFields' }], + }); + } - fireEvent.click(resetBtn); - await mockTimeout(() => true); + // Test 5: setFieldsValue and verify onValuesChange + const beforeSetFieldsValueCallCount = onValuesChangeFn.mock.calls.length; + fireEvent.click(queryByText('setFieldsValue')); + await mockTimeout(() => true); + expect(queryByDisplayValue('setFieldsValue')).toBeTruthy(); - // Check if UI is cleared after reset - expect(queryByDisplayValue('guangdong')).not.toBeTruthy(); - expect(queryByDisplayValue('shenzhen')).not.toBeTruthy(); + const afterSetFieldsValueCallCount = onValuesChangeFn.mock.calls.length; + if (afterSetFieldsValueCallCount > beforeSetFieldsValueCallCount) { + const setFieldsValueCall = onValuesChangeFn.mock.calls[afterSetFieldsValueCallCount - 1]; + expect(setFieldsValueCall[1]).toEqual({ + address: [{ province: 'setFieldsValue' }], + }); + } - // Verify form values are cleared (either through onValuesChange or direct form state) - const currentFormValues = container.querySelector('#form-values-result'); - if (currentFormValues) { fireEvent.click(queryByText('getFormValues')); await mockTimeout(() => true); - const formValuesResult = currentFormValues.getAttribute('data-result'); + const formValuesResult = container.querySelector('#form-values-result')?.getAttribute('data-result'); const formValues = JSON.parse(formValuesResult || '{}'); - expect(formValues).toEqual({}); - } - - // If onValuesChange was called during reset, verify the data - if (onValuesChangeFn.mock.calls.length > initialCallCount) { - const resetCall = onValuesChangeFn.mock.calls[onValuesChangeFn.mock.calls.length - 1]; - expect(resetCall[1]).toEqual({}); - expect(latestFormValues).toEqual({}); - } else { - latestFormValues = {}; - } - - const beforeAddCallCount = onValuesChangeFn.mock.calls.length; - - fireEvent.click(addBtn); - await mockTimeout(() => true); - expect(queryByDisplayValue('guangdong')).toBeTruthy(); - expect(queryByDisplayValue('shenzhen')).toBeTruthy(); - - const removeBtn = container.querySelector('.test-remove-0'); - fireEvent.click(removeBtn); - await mockTimeout(() => true); - expect(queryByDisplayValue('guangdong')).not.toBeTruthy(); - expect(queryByDisplayValue('shenzhen')).not.toBeTruthy(); - - const afterRemoveCallCount = onValuesChangeFn.mock.calls.length; - - // Verify onValuesChange was called for both add and remove operations - expect(afterRemoveCallCount).toBeGreaterThan(beforeAddCallCount); - if (afterRemoveCallCount > beforeAddCallCount) { - const removeCall = onValuesChangeFn.mock.calls[afterRemoveCallCount - 1]; - expect(removeCall[1]).toEqual({ address: [] }); - } - - // Test 4: setFields and verify onValuesChange - const beforeSetFieldsCallCount = onValuesChangeFn.mock.calls.length; - - fireEvent.click(queryByText('setFields')); - await mockTimeout(() => true); - expect(queryByDisplayValue('setFields')).toBeTruthy(); - - const afterSetFieldsCallCount = onValuesChangeFn.mock.calls.length; - - if (afterSetFieldsCallCount > beforeSetFieldsCallCount) { - const setFieldsCall = onValuesChangeFn.mock.calls[afterSetFieldsCallCount - 1]; - expect(setFieldsCall[1]).toEqual({ - address: [{ province: 'setFields' }], - }); - } - - // Test 5: setFieldsValue and verify onValuesChange - const beforeSetFieldsValueCallCount = onValuesChangeFn.mock.calls.length; - fireEvent.click(queryByText('setFieldsValue')); - await mockTimeout(() => true); - expect(queryByDisplayValue('setFieldsValue')).toBeTruthy(); - - const afterSetFieldsValueCallCount = onValuesChangeFn.mock.calls.length; - if (afterSetFieldsValueCallCount > beforeSetFieldsValueCallCount) { - const setFieldsValueCall = onValuesChangeFn.mock.calls[afterSetFieldsValueCallCount - 1]; - expect(setFieldsValueCall[1]).toEqual({ + expect(formValues).toEqual(latestFormValues); + expect(formValues).toEqual({ address: [{ province: 'setFieldsValue' }], }); - } - - fireEvent.click(queryByText('getFormValues')); - await mockTimeout(() => true); - const formValuesResult = container.querySelector('#form-values-result')?.getAttribute('data-result'); - const formValues = JSON.parse(formValuesResult || '{}'); - expect(formValues).toEqual(latestFormValues); - expect(formValues).toEqual({ - address: [{ province: 'setFieldsValue' }], - }); - const beforeManualInputCallCount = onValuesChangeFn.mock.calls.length; - const provinceInput = container.querySelector('input[value="setFieldsValue"]') as HTMLInputElement; - fireEvent.change(provinceInput, { target: { value: 'manual-input' } }); - await mockTimeout(() => true); + const beforeManualInputCallCount = onValuesChangeFn.mock.calls.length; + const provinceInput = container.querySelector('input[value="setFieldsValue"]') as HTMLInputElement; + fireEvent.change(provinceInput, { target: { value: 'manual-input' } }); + await mockTimeout(() => true); - const afterManualInputCallCount = onValuesChangeFn.mock.calls.length; - if (afterManualInputCallCount > beforeManualInputCallCount) { - const manualInputCall = onValuesChangeFn.mock.calls[afterManualInputCallCount - 1]; - expect(manualInputCall[1]).toEqual({ - address: [{ province: 'manual-input' }], - }); - expect(latestFormValues).toEqual({ - address: [{ province: 'manual-input' }], - }); - } - - // validate validateOnly test - fireEvent.click(queryByText('validateOnly')); - await mockTimeout(() => true); - expect(queryByText('省份必填')).not.toBeTruthy(); - fireEvent.click(queryByText('validate')); - await mockTimeout(() => true); - expect(queryByText('地区必填')).toBeTruthy(); - fireEvent.click(queryByText('clearValidate')); - expect(queryByText('地区必填')).not.toBeTruthy(); + const afterManualInputCallCount = onValuesChangeFn.mock.calls.length; + if (afterManualInputCallCount > beforeManualInputCallCount) { + const manualInputCall = onValuesChangeFn.mock.calls[afterManualInputCallCount - 1]; + expect(manualInputCall[1]).toEqual({ + address: [{ province: 'manual-input' }], + }); + expect(latestFormValues).toEqual({ + address: [{ province: 'manual-input' }], + }); + } + + // validate validateOnly test + fireEvent.click(queryByText('validateOnly')); + await mockTimeout(() => true); + expect(queryByText('省份必填')).not.toBeTruthy(); + fireEvent.click(queryByText('validate')); + await mockTimeout(() => true); + expect(queryByText('地区必填')).toBeTruthy(); + fireEvent.click(queryByText('clearValidate')); + expect(queryByText('地区必填')).not.toBeTruthy(); + }); }); - test('FormList reset to initial data', async () => { - const TestView = () => { - const [form] = Form.useForm(); + describe('scenarios', () => { + test('FormList reset to initial data', async () => { + const TestView = () => { + const [form] = Form.useForm(); - const initialFormData = { - address: [ - { province: 'guangdong', area: 'shenzhen' }, - { province: 'beijing', area: 'beijing' }, - ], + const initialFormData = { + address: [ + { province: 'guangdong', area: 'shenzhen' }, + { province: 'beijing', area: 'beijing' }, + ], + }; + + return ( + } + /> + ); }; - return ( - } - /> - ); - }; - - const { container, queryByText, getByPlaceholderText } = render(); - - // 验证初始数据渲染正确 - expect((getByPlaceholderText('area-input-0') as HTMLInputElement).value).toBe('shenzhen'); - expect((getByPlaceholderText('area-input-1') as HTMLInputElement).value).toBe('beijing'); - - // 删除 beijing - const removeBtn1 = container.querySelector('.test-remove-1'); - fireEvent.click(removeBtn1); - await mockTimeout(() => true); - // 只剩 shenzhen - expect((getByPlaceholderText('area-input-0') as HTMLInputElement).value).toBe('shenzhen'); - expect(container.querySelector('[placeholder="area-input-1"]')).toBeFalsy(); - - // 添加空数据 - const addBtn = container.querySelector('#test-add'); - fireEvent.click(addBtn); - await mockTimeout(() => true); - expect((getByPlaceholderText('area-input-0') as HTMLInputElement).value).toBe('shenzhen'); - expect(container.querySelector('[placeholder="area-input-1"]')).toBeTruthy(); - expect((getByPlaceholderText('area-input-1') as HTMLInputElement).value).toBe(''); - - // 再删除 shenzhen - const removeBtn0 = container.querySelector('.test-remove-0'); - fireEvent.click(removeBtn0); - await mockTimeout(() => true); - expect((getByPlaceholderText('area-input-0') as HTMLInputElement).value).toBe(''); - expect(container.querySelector('[placeholder="area-input-1"]')).toBeFalsy(); - - // 点击 reset 重置 - const resetBtn = queryByText('reset'); - fireEvent.click(resetBtn); - await mockTimeout(() => true); - - // 恢复到初始化数据 - expect((getByPlaceholderText('area-input-0') as HTMLInputElement).value).toBe('shenzhen'); - expect((getByPlaceholderText('area-input-1') as HTMLInputElement).value).toBe('beijing'); - }); + const { container, queryByText, getByPlaceholderText } = render(); - test('FormList setFields not trigger onValueChange', async () => { - const fn = vi.fn(); + // 验证初始数据渲染正确 + expect((getByPlaceholderText('area-input-0') as HTMLInputElement).value).toBe('shenzhen'); + expect((getByPlaceholderText('area-input-1') as HTMLInputElement).value).toBe('beijing'); - const TestView = () => { - const [form] = Form.useForm(); + // 删除 beijing + const removeBtn1 = container.querySelector('.test-remove-1'); + fireEvent.click(removeBtn1); + await mockTimeout(() => true); + // 只剩 shenzhen + expect((getByPlaceholderText('area-input-0') as HTMLInputElement).value).toBe('shenzhen'); + expect(container.querySelector('[placeholder="area-input-1"]')).toBeFalsy(); - function setFields() { - form.setFields([{ name: 'address', value: [{ province: 'setFields' }] }]); - } + // 添加空数据 + const addBtn = container.querySelector('#test-add'); + fireEvent.click(addBtn); + await mockTimeout(() => true); + expect((getByPlaceholderText('area-input-0') as HTMLInputElement).value).toBe('shenzhen'); + expect(container.querySelector('[placeholder="area-input-1"]')).toBeTruthy(); + expect((getByPlaceholderText('area-input-1') as HTMLInputElement).value).toBe(''); - return ( -
- - {(fields) => ( - <> - {fields.map(({ key, name, ...restField }) => ( - - - + // 再删除 shenzhen + const removeBtn0 = container.querySelector('.test-remove-0'); + fireEvent.click(removeBtn0); + await mockTimeout(() => true); + expect((getByPlaceholderText('area-input-0') as HTMLInputElement).value).toBe(''); + expect(container.querySelector('[placeholder="area-input-1"]')).toBeFalsy(); + + // 点击 reset 重置 + const resetBtn = queryByText('reset'); + fireEvent.click(resetBtn); + await mockTimeout(() => true); + + // 恢复到初始化数据 + expect((getByPlaceholderText('area-input-0') as HTMLInputElement).value).toBe('shenzhen'); + expect((getByPlaceholderText('area-input-1') as HTMLInputElement).value).toBe('beijing'); + }); + + test('FormList setFields not trigger onValueChange', async () => { + const fn = vi.fn(); + + const TestView = () => { + const [form] = Form.useForm(); + + function setFields() { + form.setFields([{ name: 'address', value: [{ province: 'setFields' }] }]); + } + + return ( + + + {(fields) => ( + <> + {fields.map(({ key, name, ...restField }) => ( + + + + - - ))} - - )} - - - - -
- ); - }; - - const { queryByText } = render(); - - // 第一次更新会触发,后续相同值更新不再触发 - fireEvent.click(queryByText('setFields')); - await mockTimeout(); - expect(fn).toHaveBeenCalledTimes(1); - fireEvent.click(queryByText('setFields')); - await mockTimeout(); - expect(fn).toHaveBeenCalledTimes(1); - }); + ))} + + )} + + + + + + ); + }; - test('FormList with nested structures', async () => { - const TestView = () => { - const [form] = Form.useForm(); + const { queryByText } = render(); + + // 第一次更新会触发,后续相同值更新不再触发 + fireEvent.click(queryByText('setFields')); + await mockTimeout(); + expect(fn).toHaveBeenCalledTimes(1); + fireEvent.click(queryByText('setFields')); + await mockTimeout(); + expect(fn).toHaveBeenCalledTimes(1); + }); - function setFields() { - form.setFields([ - { - name: 'users', - value: [ + test('FormList with nested structures', async () => { + const TestView = () => { + const [form] = Form.useForm(); + + function setFields() { + form.setFields([ + { + name: 'users', + value: [ + { + name: 'Alice', + projects: [ + { + projectName: 'Website Redesign', + tasks: [ + { taskName: 'Design mockups', status: 'completed' }, + { + taskName: 'Frontend development', + status: 'in-progress', + }, + ], + }, + ], + }, + ], + }, + ]); + } + + function setFieldsValue() { + form.setFieldsValue({ + users: [ { - name: 'Alice', + name: 'Bob', projects: [ { - projectName: 'Website Redesign', + projectName: 'Mobile App', tasks: [ - { taskName: 'Design mockups', status: 'completed' }, - { taskName: 'Frontend development', status: 'in-progress' }, + { taskName: 'API integration', status: 'pending' }, + { taskName: 'UI implementation', status: 'in-progress' }, ], }, ], }, ], - }, - ]); - } + }); + } - function setFieldsValue() { - form.setFieldsValue({ - users: [ - { - name: 'Bob', - projects: [ - { - projectName: 'Mobile App', - tasks: [ - { taskName: 'API integration', status: 'pending' }, - { taskName: 'UI implementation', status: 'in-progress' }, - ], - }, - ], - }, - ], - }); - } + function addNestedData() { + form.setFieldsValue({ + users: [ + { + name: 'Charlie', + projects: [ + { + projectName: 'Backend Service', + tasks: [ + { taskName: 'Database design', status: 'completed' }, + { taskName: 'API development', status: 'in-progress' }, + { taskName: 'Testing', status: 'pending' }, + ], + }, + { + projectName: 'DevOps', + tasks: [{ taskName: 'CI/CD setup', status: 'completed' }], + }, + ], + }, + { + name: 'David', + projects: [ + { + projectName: 'Documentation', + tasks: [{ taskName: 'Write API docs', status: 'in-progress' }], + }, + ], + }, + ], + }); + } + + function validate() { + form.validate(); + } + + function clearValidate() { + form.clearValidate(); + } + + function getFieldsValueAll() { + const allValues = form.getFieldsValue(true); + document.getElementById('all-values-result')?.setAttribute('data-result', JSON.stringify(allValues)); + } + + function getFieldsValueByPath() { + // Test 1: Get specific user's data + const usersPath = form.getFieldsValue(['users']); + + // Test 2: Get specific nested path - first user's projects + const firstUserProjects = form.getFieldsValue([['users', 0, 'projects']]); + + // Test 3: Get specific nested path - first user's first project's tasks + const firstProjectTasks = form.getFieldsValue([['users', 0, 'projects', 0, 'tasks']]); + + // Test 4: Get multiple paths at once + const multiplePaths = form.getFieldsValue([ + ['users', 0, 'name'], + ['users', 1, 'name'], + ['users', 0, 'projects', 0, 'projectName'], + ]); + + const result = { + usersPath, + firstUserProjects, + firstProjectTasks, + multiplePaths, + }; + document.getElementById('path-values-result')?.setAttribute('data-result', JSON.stringify(result)); + } + + return ( +
+ + {(userFields, { add: addUser, remove: removeUser }) => ( + <> + {userFields.map(({ key: userKey, name: userName }, userIndex) => ( + + + + + + + {(projectFields, { add: addProject, remove: removeProject }) => ( + <> + {projectFields.map(({ key: projectKey, name: projectName }, projectIndex) => ( + + + + + + + {(taskFields, { add: addTask, remove: removeTask }) => ( + <> + {taskFields.map(({ key: taskKey, name: taskName }, taskIndex) => ( + + + + + + + + + removeTask(taskName)} + /> + + + ))} + + + + + )} + + + + removeProject(projectName)} + /> + + + ))} + + + + + )} + + + removeUser(userName)} + /> + + + ))} + + + + + )} + + + + + + + + + + +
+
+ + ); + }; - function addNestedData() { - form.setFieldsValue({ - users: [ - { - name: 'Charlie', - projects: [ - { - projectName: 'Backend Service', - tasks: [ - { taskName: 'Database design', status: 'completed' }, - { taskName: 'API development', status: 'in-progress' }, - { taskName: 'Testing', status: 'pending' }, - ], - }, - { - projectName: 'DevOps', - tasks: [{ taskName: 'CI/CD setup', status: 'completed' }], - }, - ], - }, + const { container, queryByText, getByPlaceholderText } = render(); + + // Test setFields + fireEvent.click(queryByText('setFields')); + await mockTimeout(); + expect((getByPlaceholderText('user-name-0') as HTMLInputElement).value).toBe('Alice'); + expect((getByPlaceholderText('project-name-0-0') as HTMLInputElement).value).toBe('Website Redesign'); + expect((getByPlaceholderText('task-name-0-0-0') as HTMLInputElement).value).toBe('Design mockups'); + expect((getByPlaceholderText('task-status-0-0-0') as HTMLInputElement).value).toBe('completed'); + expect((getByPlaceholderText('task-name-0-0-1') as HTMLInputElement).value).toBe('Frontend development'); + expect((getByPlaceholderText('task-status-0-0-1') as HTMLInputElement).value).toBe('in-progress'); + + // Test setFieldsValue + fireEvent.click(queryByText('setFieldsValue')); + await mockTimeout(); + expect((getByPlaceholderText('user-name-0') as HTMLInputElement).value).toBe('Bob'); + expect((getByPlaceholderText('project-name-0-0') as HTMLInputElement).value).toBe('Mobile App'); + expect((getByPlaceholderText('task-name-0-0-0') as HTMLInputElement).value).toBe('API integration'); + expect((getByPlaceholderText('task-status-0-0-0') as HTMLInputElement).value).toBe('pending'); + expect((getByPlaceholderText('task-name-0-0-1') as HTMLInputElement).value).toBe('UI implementation'); + expect((getByPlaceholderText('task-status-0-0-1') as HTMLInputElement).value).toBe('in-progress'); + + // Test addNestedData - multiple users with nested projects and tasks + fireEvent.click(queryByText('addNestedData')); + await mockTimeout(); + + expect((getByPlaceholderText('user-name-0') as HTMLInputElement).value).toBe('Charlie'); + expect((getByPlaceholderText('project-name-0-0') as HTMLInputElement).value).toBe('Backend Service'); + expect((getByPlaceholderText('task-name-0-0-0') as HTMLInputElement).value).toBe('Database design'); + expect((getByPlaceholderText('task-status-0-0-0') as HTMLInputElement).value).toBe('completed'); + expect((getByPlaceholderText('task-name-0-0-1') as HTMLInputElement).value).toBe('API development'); + expect((getByPlaceholderText('task-status-0-0-1') as HTMLInputElement).value).toBe('in-progress'); + expect((getByPlaceholderText('task-name-0-0-2') as HTMLInputElement).value).toBe('Testing'); + expect((getByPlaceholderText('task-status-0-0-2') as HTMLInputElement).value).toBe('pending'); + expect((getByPlaceholderText('project-name-0-1') as HTMLInputElement).value).toBe('DevOps'); + expect((getByPlaceholderText('task-name-0-1-0') as HTMLInputElement).value).toBe('CI/CD setup'); + expect((getByPlaceholderText('task-status-0-1-0') as HTMLInputElement).value).toBe('completed'); + expect((getByPlaceholderText('user-name-1') as HTMLInputElement).value).toBe('David'); + expect((getByPlaceholderText('project-name-1-0') as HTMLInputElement).value).toBe('Documentation'); + expect((getByPlaceholderText('task-name-1-0-0') as HTMLInputElement).value).toBe('Write API docs'); + expect((getByPlaceholderText('task-status-1-0-0') as HTMLInputElement).value).toBe('in-progress'); + + // Test getFieldsValue(true) - should get all form values + fireEvent.click(queryByText('getFieldsValue(true)')); + await mockTimeout(); + const allValuesResult = container.querySelector('#all-values-result')?.getAttribute('data-result'); + const allValues = JSON.parse(allValuesResult || '{}'); + expect(allValues).toHaveProperty('users'); + expect(Array.isArray(allValues.users)).toBe(true); + expect(allValues.users.length).toBe(2); + expect(allValues.users[0].name).toBe('Charlie'); + expect(allValues.users[0].projects.length).toBe(2); + expect(allValues.users[0].projects[0].projectName).toBe('Backend Service'); + expect(allValues.users[0].projects[0].tasks.length).toBe(3); + expect(allValues.users[1].name).toBe('David'); + + // Test getFieldsValue with complex namePath + fireEvent.click(queryByText('getFieldsValue(namePath)')); + await mockTimeout(); + const pathValuesResult = container.querySelector('#path-values-result')?.getAttribute('data-result'); + const pathValues = JSON.parse(pathValuesResult || '{}'); + + // Test 1: Verify users path returns all users data + expect(pathValues.usersPath).toHaveProperty('users'); + expect(Array.isArray(pathValues.usersPath.users)).toBe(true); + expect(pathValues.usersPath.users.length).toBe(2); + expect(pathValues.usersPath.users[0].name).toBe('Charlie'); + expect(pathValues.usersPath.users[1].name).toBe('David'); + + // Test 2: Verify nested path returns first user's projects + expect(pathValues.firstUserProjects).toHaveProperty('users'); + expect(pathValues.firstUserProjects.users[0]).toHaveProperty('projects'); + expect(Array.isArray(pathValues.firstUserProjects.users[0].projects)).toBe(true); + expect(pathValues.firstUserProjects.users[0].projects.length).toBe(2); + expect(pathValues.firstUserProjects.users[0].projects[0].projectName).toBe('Backend Service'); + expect(pathValues.firstUserProjects.users[0].projects[1].projectName).toBe('DevOps'); + + // Test 3: Verify deep nested path returns first project's tasks + expect(pathValues.firstProjectTasks).toHaveProperty('users'); + expect(pathValues.firstProjectTasks.users[0].projects[0]).toHaveProperty('tasks'); + expect(Array.isArray(pathValues.firstProjectTasks.users[0].projects[0].tasks)).toBe(true); + expect(pathValues.firstProjectTasks.users[0].projects[0].tasks.length).toBe(3); + expect(pathValues.firstProjectTasks.users[0].projects[0].tasks[0].taskName).toBe('Database design'); + expect(pathValues.firstProjectTasks.users[0].projects[0].tasks[1].taskName).toBe('API development'); + expect(pathValues.firstProjectTasks.users[0].projects[0].tasks[2].taskName).toBe('Testing'); + + // Test 4: Verify multiple paths returns specific fields + expect(pathValues.multiplePaths).toHaveProperty('users'); + expect(pathValues.multiplePaths.users[0]).toHaveProperty('name'); + expect(pathValues.multiplePaths.users[0].name).toBe('Charlie'); + expect(pathValues.multiplePaths.users[1]).toHaveProperty('name'); + expect(pathValues.multiplePaths.users[1].name).toBe('David'); + expect(pathValues.multiplePaths.users[0].projects[0]).toHaveProperty('projectName'); + expect(pathValues.multiplePaths.users[0].projects[0].projectName).toBe('Backend Service'); + + // Test remove nested task - remove first task of Charlie's Backend Service project + const removeTaskBtn = container.querySelector('.test-remove-task-0-0-0'); + fireEvent.click(removeTaskBtn); + await mockTimeout(); + expect((getByPlaceholderText('user-name-0') as HTMLInputElement).value).toBe('Charlie'); + // After removing first task, the second task (API development) becomes the first one + expect((getByPlaceholderText('task-name-0-0-0') as HTMLInputElement).value).toBe('API development'); + expect((getByPlaceholderText('task-status-0-0-0') as HTMLInputElement).value).toBe('in-progress'); + expect((getByPlaceholderText('task-name-0-0-1') as HTMLInputElement).value).toBe('Testing'); + expect((getByPlaceholderText('task-status-0-0-1') as HTMLInputElement).value).toBe('pending'); + // The third task placeholder should not exist anymore + expect(container.querySelector('[placeholder="task-name-0-0-2"]')).toBeFalsy(); + + // Test remove project - remove Charlie's second project (DevOps) + const removeProjectBtn = container.querySelector('.test-remove-project-0-1'); + fireEvent.click(removeProjectBtn); + await mockTimeout(); + // After removing DevOps project, only Backend Service should remain for Charlie + expect(container.querySelector('[placeholder="project-name-0-1"]')).toBeFalsy(); + expect(container.querySelector('[placeholder="task-name-0-1-0"]')).toBeFalsy(); + + // Test remove user - remove David + const removeUserBtn = container.querySelector('.test-remove-user-1'); + fireEvent.click(removeUserBtn); + await mockTimeout(); + // After removing David, only Charlie should remain + expect(container.querySelector('[placeholder="user-name-1"]')).toBeFalsy(); + expect(container.querySelector('[placeholder="project-name-1-0"]')).toBeFalsy(); + + // Add an empty user to test validation + const addUserBtn = container.querySelector('#test-add-user'); + fireEvent.click(addUserBtn); + await mockTimeout(); + + // Test validate on nested FormList + fireEvent.click(queryByText('validate')); + await mockTimeout(); + expect(queryByText('用户名必填')).toBeTruthy(); + + // Test clearValidate + fireEvent.click(queryByText('clearValidate')); + await mockTimeout(); + expect(queryByText('用户名必填')).not.toBeTruthy(); + }); + + test('FormList with shouldUpdate', async () => { + const TestView = () => { + const [form] = Form.useForm(); + + const INIT_DATA = { + services: [ { - name: 'David', - projects: [ - { - projectName: 'Documentation', - tasks: [{ taskName: 'Write API docs', status: 'in-progress' }], - }, + modelName: 'modelA', + routes: [ + { type: 'weight', weight: 50, abtest: 'cid' }, + { type: 'abtest', weight: 30, abtest: 'uid' }, ], }, ], - }); - } - - function validate() { - form.validate(); - } - - function clearValidate() { - form.clearValidate(); - } - - function getFieldsValueAll() { - const allValues = form.getFieldsValue(true); - document.getElementById('all-values-result')?.setAttribute('data-result', JSON.stringify(allValues)); - } - - function getFieldsValueByPath() { - // Test 1: Get specific user's data - const usersPath = form.getFieldsValue(['users']); - - // Test 2: Get specific nested path - first user's projects - const firstUserProjects = form.getFieldsValue([['users', 0, 'projects']]); - - // Test 3: Get specific nested path - first user's first project's tasks - const firstProjectTasks = form.getFieldsValue([['users', 0, 'projects', 0, 'tasks']]); - - // Test 4: Get multiple paths at once - const multiplePaths = form.getFieldsValue([ - ['users', 0, 'name'], - ['users', 1, 'name'], - ['users', 0, 'projects', 0, 'projectName'], - ]); - - const result = { - usersPath, - firstUserProjects, - firstProjectTasks, - multiplePaths, }; - document.getElementById('path-values-result')?.setAttribute('data-result', JSON.stringify(result)); - } - - return ( -
- - {(userFields, { add: addUser, remove: removeUser }) => ( - <> - {userFields.map(({ key: userKey, name: userName }, userIndex) => ( - - - - - - {(projectFields, { add: addProject, remove: removeProject }) => ( - <> - {projectFields.map(({ key: projectKey, name: projectName }, projectIndex) => ( - - - - - - - {(taskFields, { add: addTask, remove: removeTask }) => ( - <> - {taskFields.map(({ key: taskKey, name: taskName }, taskIndex) => ( - - - - - - + return ( + + + {(fields) => ( + <> + {fields.map(({ key, name: serviceName }) => ( +
+ + {(routeFields, { add: addRoute }) => ( +
+ {routeFields.map((f) => ( +
+ + + + + + p.services?.[serviceName]?.routes?.[f.name]?.type !== + n.services?.[serviceName]?.routes?.[f.name]?.type + } + > + {({ getFieldValue }) => { + const type = getFieldValue(['services', serviceName, 'routes', f.name, 'type']); + if (type === 'weight') { + return ( + + - - removeTask(taskName)} - /> + ); + } + if (type === 'abtest') { + return ( + + - - ))} - - - - - )} - - - - removeProject(projectName)} - /> - - - ))} - + ); + } + return null; + }} + +
+ ))} + - - - )} - - - removeUser(userName)} - /> - - - ))} - - - - - )} - - - - - - - - - - -
-
- - ); - }; - - const { container, queryByText, getByPlaceholderText } = render(); - - // Test setFields - fireEvent.click(queryByText('setFields')); - await mockTimeout(); - expect((getByPlaceholderText('user-name-0') as HTMLInputElement).value).toBe('Alice'); - expect((getByPlaceholderText('project-name-0-0') as HTMLInputElement).value).toBe('Website Redesign'); - expect((getByPlaceholderText('task-name-0-0-0') as HTMLInputElement).value).toBe('Design mockups'); - expect((getByPlaceholderText('task-status-0-0-0') as HTMLInputElement).value).toBe('completed'); - expect((getByPlaceholderText('task-name-0-0-1') as HTMLInputElement).value).toBe('Frontend development'); - expect((getByPlaceholderText('task-status-0-0-1') as HTMLInputElement).value).toBe('in-progress'); - - // Test setFieldsValue - fireEvent.click(queryByText('setFieldsValue')); - await mockTimeout(); - expect((getByPlaceholderText('user-name-0') as HTMLInputElement).value).toBe('Bob'); - expect((getByPlaceholderText('project-name-0-0') as HTMLInputElement).value).toBe('Mobile App'); - expect((getByPlaceholderText('task-name-0-0-0') as HTMLInputElement).value).toBe('API integration'); - expect((getByPlaceholderText('task-status-0-0-0') as HTMLInputElement).value).toBe('pending'); - expect((getByPlaceholderText('task-name-0-0-1') as HTMLInputElement).value).toBe('UI implementation'); - expect((getByPlaceholderText('task-status-0-0-1') as HTMLInputElement).value).toBe('in-progress'); - - // Test addNestedData - multiple users with nested projects and tasks - fireEvent.click(queryByText('addNestedData')); - await mockTimeout(); - - expect((getByPlaceholderText('user-name-0') as HTMLInputElement).value).toBe('Charlie'); - expect((getByPlaceholderText('project-name-0-0') as HTMLInputElement).value).toBe('Backend Service'); - expect((getByPlaceholderText('task-name-0-0-0') as HTMLInputElement).value).toBe('Database design'); - expect((getByPlaceholderText('task-status-0-0-0') as HTMLInputElement).value).toBe('completed'); - expect((getByPlaceholderText('task-name-0-0-1') as HTMLInputElement).value).toBe('API development'); - expect((getByPlaceholderText('task-status-0-0-1') as HTMLInputElement).value).toBe('in-progress'); - expect((getByPlaceholderText('task-name-0-0-2') as HTMLInputElement).value).toBe('Testing'); - expect((getByPlaceholderText('task-status-0-0-2') as HTMLInputElement).value).toBe('pending'); - expect((getByPlaceholderText('project-name-0-1') as HTMLInputElement).value).toBe('DevOps'); - expect((getByPlaceholderText('task-name-0-1-0') as HTMLInputElement).value).toBe('CI/CD setup'); - expect((getByPlaceholderText('task-status-0-1-0') as HTMLInputElement).value).toBe('completed'); - expect((getByPlaceholderText('user-name-1') as HTMLInputElement).value).toBe('David'); - expect((getByPlaceholderText('project-name-1-0') as HTMLInputElement).value).toBe('Documentation'); - expect((getByPlaceholderText('task-name-1-0-0') as HTMLInputElement).value).toBe('Write API docs'); - expect((getByPlaceholderText('task-status-1-0-0') as HTMLInputElement).value).toBe('in-progress'); - - // Test getFieldsValue(true) - should get all form values - fireEvent.click(queryByText('getFieldsValue(true)')); - await mockTimeout(); - const allValuesResult = container.querySelector('#all-values-result')?.getAttribute('data-result'); - const allValues = JSON.parse(allValuesResult || '{}'); - expect(allValues).toHaveProperty('users'); - expect(Array.isArray(allValues.users)).toBe(true); - expect(allValues.users.length).toBe(2); - expect(allValues.users[0].name).toBe('Charlie'); - expect(allValues.users[0].projects.length).toBe(2); - expect(allValues.users[0].projects[0].projectName).toBe('Backend Service'); - expect(allValues.users[0].projects[0].tasks.length).toBe(3); - expect(allValues.users[1].name).toBe('David'); - - // Test getFieldsValue with complex namePath - fireEvent.click(queryByText('getFieldsValue(namePath)')); - await mockTimeout(); - const pathValuesResult = container.querySelector('#path-values-result')?.getAttribute('data-result'); - const pathValues = JSON.parse(pathValuesResult || '{}'); - - // Test 1: Verify users path returns all users data - expect(pathValues.usersPath).toHaveProperty('users'); - expect(Array.isArray(pathValues.usersPath.users)).toBe(true); - expect(pathValues.usersPath.users.length).toBe(2); - expect(pathValues.usersPath.users[0].name).toBe('Charlie'); - expect(pathValues.usersPath.users[1].name).toBe('David'); - - // Test 2: Verify nested path returns first user's projects - expect(pathValues.firstUserProjects).toHaveProperty('users'); - expect(pathValues.firstUserProjects.users[0]).toHaveProperty('projects'); - expect(Array.isArray(pathValues.firstUserProjects.users[0].projects)).toBe(true); - expect(pathValues.firstUserProjects.users[0].projects.length).toBe(2); - expect(pathValues.firstUserProjects.users[0].projects[0].projectName).toBe('Backend Service'); - expect(pathValues.firstUserProjects.users[0].projects[1].projectName).toBe('DevOps'); - - // Test 3: Verify deep nested path returns first project's tasks - expect(pathValues.firstProjectTasks).toHaveProperty('users'); - expect(pathValues.firstProjectTasks.users[0].projects[0]).toHaveProperty('tasks'); - expect(Array.isArray(pathValues.firstProjectTasks.users[0].projects[0].tasks)).toBe(true); - expect(pathValues.firstProjectTasks.users[0].projects[0].tasks.length).toBe(3); - expect(pathValues.firstProjectTasks.users[0].projects[0].tasks[0].taskName).toBe('Database design'); - expect(pathValues.firstProjectTasks.users[0].projects[0].tasks[1].taskName).toBe('API development'); - expect(pathValues.firstProjectTasks.users[0].projects[0].tasks[2].taskName).toBe('Testing'); - - // Test 4: Verify multiple paths returns specific fields - expect(pathValues.multiplePaths).toHaveProperty('users'); - expect(pathValues.multiplePaths.users[0]).toHaveProperty('name'); - expect(pathValues.multiplePaths.users[0].name).toBe('Charlie'); - expect(pathValues.multiplePaths.users[1]).toHaveProperty('name'); - expect(pathValues.multiplePaths.users[1].name).toBe('David'); - expect(pathValues.multiplePaths.users[0].projects[0]).toHaveProperty('projectName'); - expect(pathValues.multiplePaths.users[0].projects[0].projectName).toBe('Backend Service'); - - // Test remove nested task - remove first task of Charlie's Backend Service project - const removeTaskBtn = container.querySelector('.test-remove-task-0-0-0'); - fireEvent.click(removeTaskBtn); - await mockTimeout(); - expect((getByPlaceholderText('user-name-0') as HTMLInputElement).value).toBe('Charlie'); - // After removing first task, the second task (API development) becomes the first one - expect((getByPlaceholderText('task-name-0-0-0') as HTMLInputElement).value).toBe('API development'); - expect((getByPlaceholderText('task-status-0-0-0') as HTMLInputElement).value).toBe('in-progress'); - expect((getByPlaceholderText('task-name-0-0-1') as HTMLInputElement).value).toBe('Testing'); - expect((getByPlaceholderText('task-status-0-0-1') as HTMLInputElement).value).toBe('pending'); - // The third task placeholder should not exist anymore - expect(container.querySelector('[placeholder="task-name-0-0-2"]')).toBeFalsy(); - - // Test remove project - remove Charlie's second project (DevOps) - const removeProjectBtn = container.querySelector('.test-remove-project-0-1'); - fireEvent.click(removeProjectBtn); - await mockTimeout(); - // After removing DevOps project, only Backend Service should remain for Charlie - expect(container.querySelector('[placeholder="project-name-0-1"]')).toBeFalsy(); - expect(container.querySelector('[placeholder="task-name-0-1-0"]')).toBeFalsy(); - - // Test remove user - remove David - const removeUserBtn = container.querySelector('.test-remove-user-1'); - fireEvent.click(removeUserBtn); - await mockTimeout(); - // After removing David, only Charlie should remain - expect(container.querySelector('[placeholder="user-name-1"]')).toBeFalsy(); - expect(container.querySelector('[placeholder="project-name-1-0"]')).toBeFalsy(); - - // Add an empty user to test validation - const addUserBtn = container.querySelector('#test-add-user'); - fireEvent.click(addUserBtn); - await mockTimeout(); - - // Test validate on nested FormList - fireEvent.click(queryByText('validate')); - await mockTimeout(); - expect(queryByText('用户名必填')).toBeTruthy(); - - // Test clearValidate - fireEvent.click(queryByText('clearValidate')); - await mockTimeout(); - expect(queryByText('用户名必填')).not.toBeTruthy(); - }); - - test('FormList with shouldUpdate', async () => { - const TestView = () => { - const [form] = Form.useForm(); - - const INIT_DATA = { - services: [ - { - modelName: 'modelA', - routes: [ - { type: 'weight', weight: 50, abtest: 'cid' }, - { type: 'abtest', weight: 30, abtest: 'uid' }, - ], - }, - ], +
+ )} + +
+ ))} + + )} + + + ); }; - return ( -
- - {(fields) => ( - <> - {fields.map(({ key, name: serviceName }) => ( -
- - {(routeFields, { add: addRoute }) => ( -
- {routeFields.map((f) => ( -
- - - - - - p.services?.[serviceName]?.routes?.[f.name]?.type !== - n.services?.[serviceName]?.routes?.[f.name]?.type - } - > - {({ getFieldValue }) => { - const type = getFieldValue(['services', serviceName, 'routes', f.name, 'type']); - if (type === 'weight') { - return ( - - - - ); - } - if (type === 'abtest') { - return ( - - - - ); - } - return null; - }} - -
- ))} - - -
- )} -
-
- ))} - - )} -
-
- ); - }; - - const { container, getByPlaceholderText } = render(); - - // Test initial data - first route (type: weight) - const weightRadio0 = container.querySelector('[data-route-index="0"] input[value="weight"]') as HTMLInputElement; - expect(weightRadio0.checked).toBe(true); - expect((getByPlaceholderText('route-weight-0-0') as HTMLInputElement).value).toBe('50'); - expect(container.querySelector('[placeholder="route-abtest-0-0"]')).toBeFalsy(); - - // Test initial data - second route (type: abtest) - const abtestRadio1 = container.querySelector('[data-route-index="1"] input[value="abtest"]') as HTMLInputElement; - expect(abtestRadio1.checked).toBe(true); - expect((getByPlaceholderText('route-abtest-0-1') as HTMLInputElement).value).toBe('uid'); - expect(container.querySelector('[placeholder="route-weight-0-1"]')).toBeFalsy(); - - // Test switching first route from weight to abtest - const abtestRadio0 = container.querySelector('[data-route-index="0"] input[value="abtest"]') as HTMLInputElement; - fireEvent.click(abtestRadio0); - await mockTimeout(); - expect((getByPlaceholderText('route-abtest-0-0') as HTMLInputElement).value).toBe('cid'); - expect(container.querySelector('[placeholder="route-weight-0-0"]')).toBeFalsy(); - - // Test switching first route back to weight - fireEvent.click(weightRadio0); - await mockTimeout(); - expect((getByPlaceholderText('route-weight-0-0') as HTMLInputElement).value).toBe('50'); - expect(container.querySelector('[placeholder="route-abtest-0-0"]')).toBeFalsy(); - - // Test manual modification persistence - modify weight value manually - const weightInput0 = getByPlaceholderText('route-weight-0-0') as HTMLInputElement; - fireEvent.change(weightInput0, { target: { value: '200' } }); - await mockTimeout(); - expect(weightInput0.value).toBe('200'); - - // Switch to abtest - fireEvent.click(abtestRadio0); - await mockTimeout(); - expect(container.querySelector('[placeholder="route-weight-0-0"]')).toBeFalsy(); - expect((getByPlaceholderText('route-abtest-0-0') as HTMLInputElement).value).toBe('cid'); - - // Switch back to weight - should show manually modified value (200) - fireEvent.click(weightRadio0); - await mockTimeout(); - expect((getByPlaceholderText('route-weight-0-0') as HTMLInputElement).value).toBe('200'); - expect(container.querySelector('[placeholder="route-abtest-0-0"]')).toBeFalsy(); - - // Test switching second route from abtest to weight - const weightRadio1 = container.querySelector('[data-route-index="1"] input[value="weight"]') as HTMLInputElement; - fireEvent.click(weightRadio1); - await mockTimeout(); - expect((getByPlaceholderText('route-weight-0-1') as HTMLInputElement).value).toBe('30'); - expect(container.querySelector('[placeholder="route-abtest-0-1"]')).toBeFalsy(); - - // Test manual modification persistence - modify weight value manually after switching - const weightInput1 = getByPlaceholderText('route-weight-0-1') as HTMLInputElement; - fireEvent.change(weightInput1, { target: { value: '100' } }); - await mockTimeout(); - expect(weightInput1.value).toBe('100'); - - // Switch back to abtest - should show initial abtest value (uid) - fireEvent.click(abtestRadio1); - await mockTimeout(); - expect((getByPlaceholderText('route-abtest-0-1') as HTMLInputElement).value).toBe('uid'); - expect(container.querySelector('[placeholder="route-weight-0-1"]')).toBeFalsy(); - - // Switch to weight again - should show manually modified value (100) - fireEvent.click(weightRadio1); - await mockTimeout(); - expect((getByPlaceholderText('route-weight-0-1') as HTMLInputElement).value).toBe('100'); - expect(container.querySelector('[placeholder="route-abtest-0-1"]')).toBeFalsy(); - - // Modify abtest value manually - fireEvent.click(abtestRadio1); - await mockTimeout(); - const abtestInput1 = getByPlaceholderText('route-abtest-0-1') as HTMLInputElement; - expect(abtestInput1.value).toBe('uid'); - fireEvent.change(abtestInput1, { target: { value: 'custom-key' } }); - await mockTimeout(); - expect(abtestInput1.value).toBe('custom-key'); - - // Switch to weight - fireEvent.click(weightRadio1); - await mockTimeout(); - expect(container.querySelector('[placeholder="route-abtest-0-1"]')).toBeFalsy(); - expect((getByPlaceholderText('route-weight-0-1') as HTMLInputElement).value).toBe('100'); - - // Switch back to abtest - should show manually modified value - fireEvent.click(abtestRadio1); - await mockTimeout(); - expect((getByPlaceholderText('route-abtest-0-1') as HTMLInputElement).value).toBe('custom-key'); - expect(container.querySelector('[placeholder="route-weight-0-1"]')).toBeFalsy(); - - // Test adding default route (empty data) - const addDefaultBtn = container.querySelector('#test-add-route-0-default'); - fireEvent.click(addDefaultBtn); - await mockTimeout(); - const newRouteRadios = container.querySelectorAll('[data-route-index="2"] input[type="radio"]'); - expect(newRouteRadios.length).toBe(2); - // No radio should be checked initially - const checkedRadio = container.querySelector('[data-route-index="2"] input[type="radio"]:checked'); - expect(checkedRadio).toBeFalsy(); - // No conditional field should be rendered when type is empty - expect(container.querySelector('[placeholder="route-weight-0-2"]')).toBeFalsy(); - expect(container.querySelector('[placeholder="route-abtest-0-2"]')).toBeFalsy(); - - // Test setting type to weight for new route - const newWeightRadio = container.querySelector('[data-route-index="2"] input[value="weight"]') as HTMLInputElement; - fireEvent.click(newWeightRadio); - await mockTimeout(); - const newWeightInput = getByPlaceholderText('route-weight-0-2') as HTMLInputElement; - expect(newWeightInput).toBeTruthy(); - expect(newWeightInput.value).toBe(''); - - // Test setting weight value - fireEvent.change(newWeightInput, { target: { value: '100' } }); - await mockTimeout(); - expect(newWeightInput.value).toBe('100'); - - // Test switching new route to abtest - const newAbtestRadio = container.querySelector('[data-route-index="2"] input[value="abtest"]') as HTMLInputElement; - fireEvent.click(newAbtestRadio); - await mockTimeout(); - expect(container.querySelector('[placeholder="route-weight-0-2"]')).toBeFalsy(); - const newAbtestInput = getByPlaceholderText('route-abtest-0-2') as HTMLInputElement; - expect(newAbtestInput).toBeTruthy(); - expect(newAbtestInput.value).toBe(''); - - // Test setting abtest value - fireEvent.change(newAbtestInput, { target: { value: 'new-key' } }); - await mockTimeout(); - expect(newAbtestInput.value).toBe('new-key'); - - // Test switching back to weight - should show manually modified value (100) - fireEvent.click(newWeightRadio); - await mockTimeout(); - expect(container.querySelector('[placeholder="route-abtest-0-2"]')).toBeFalsy(); - const weightInputAgain = getByPlaceholderText('route-weight-0-2') as HTMLInputElement; - expect(weightInputAgain.value).toBe('100'); - - // Test adding specified route (with initial data) - const addSpecifiedBtn = container.querySelector('#test-add-route-0-specified'); - fireEvent.click(addSpecifiedBtn); - await mockTimeout(); - const specifiedWeightRadio = container.querySelector( - '[data-route-index="3"] input[value="weight"]', - ) as HTMLInputElement; - expect(specifiedWeightRadio.checked).toBe(true); - const specifiedWeightInput = getByPlaceholderText('route-weight-0-3') as HTMLInputElement; - expect(specifiedWeightInput.value).toBe('50'); - expect(container.querySelector('[placeholder="route-abtest-0-3"]')).toBeFalsy(); - - // Test switching specified route to abtest - const specifiedAbtestRadio = container.querySelector( - '[data-route-index="3"] input[value="abtest"]', - ) as HTMLInputElement; - fireEvent.click(specifiedAbtestRadio); - await mockTimeout(); - const specifiedAbtestInput = getByPlaceholderText('route-abtest-0-3') as HTMLInputElement; - expect(specifiedAbtestInput.value).toBe('cid'); - expect(container.querySelector('[placeholder="route-weight-0-3"]')).toBeFalsy(); - - // Test switching specified route back to weight - should show initial weight value - fireEvent.click(specifiedWeightRadio); - await mockTimeout(); - const specifiedWeightInputAgain = getByPlaceholderText('route-weight-0-3') as HTMLInputElement; - expect(specifiedWeightInputAgain.value).toBe('50'); - expect(container.querySelector('[placeholder="route-abtest-0-3"]')).toBeFalsy(); + const { container, getByPlaceholderText } = render(); + + // Test initial data - first route (type: weight) + const weightRadio0 = container.querySelector('[data-route-index="0"] input[value="weight"]') as HTMLInputElement; + expect(weightRadio0.checked).toBe(true); + expect((getByPlaceholderText('route-weight-0-0') as HTMLInputElement).value).toBe('50'); + expect(container.querySelector('[placeholder="route-abtest-0-0"]')).toBeFalsy(); + + // Test initial data - second route (type: abtest) + const abtestRadio1 = container.querySelector('[data-route-index="1"] input[value="abtest"]') as HTMLInputElement; + expect(abtestRadio1.checked).toBe(true); + expect((getByPlaceholderText('route-abtest-0-1') as HTMLInputElement).value).toBe('uid'); + expect(container.querySelector('[placeholder="route-weight-0-1"]')).toBeFalsy(); + + // Test switching first route from weight to abtest + const abtestRadio0 = container.querySelector('[data-route-index="0"] input[value="abtest"]') as HTMLInputElement; + fireEvent.click(abtestRadio0); + await mockTimeout(); + expect((getByPlaceholderText('route-abtest-0-0') as HTMLInputElement).value).toBe('cid'); + expect(container.querySelector('[placeholder="route-weight-0-0"]')).toBeFalsy(); + + // Test switching first route back to weight + fireEvent.click(weightRadio0); + await mockTimeout(); + expect((getByPlaceholderText('route-weight-0-0') as HTMLInputElement).value).toBe('50'); + expect(container.querySelector('[placeholder="route-abtest-0-0"]')).toBeFalsy(); + + // Test manual modification persistence - modify weight value manually + const weightInput0 = getByPlaceholderText('route-weight-0-0') as HTMLInputElement; + fireEvent.change(weightInput0, { target: { value: '200' } }); + await mockTimeout(); + expect(weightInput0.value).toBe('200'); + + // Switch to abtest + fireEvent.click(abtestRadio0); + await mockTimeout(); + expect(container.querySelector('[placeholder="route-weight-0-0"]')).toBeFalsy(); + expect((getByPlaceholderText('route-abtest-0-0') as HTMLInputElement).value).toBe('cid'); + + // Switch back to weight - should show manually modified value (200) + fireEvent.click(weightRadio0); + await mockTimeout(); + expect((getByPlaceholderText('route-weight-0-0') as HTMLInputElement).value).toBe('200'); + expect(container.querySelector('[placeholder="route-abtest-0-0"]')).toBeFalsy(); + + // Test switching second route from abtest to weight + const weightRadio1 = container.querySelector('[data-route-index="1"] input[value="weight"]') as HTMLInputElement; + fireEvent.click(weightRadio1); + await mockTimeout(); + expect((getByPlaceholderText('route-weight-0-1') as HTMLInputElement).value).toBe('30'); + expect(container.querySelector('[placeholder="route-abtest-0-1"]')).toBeFalsy(); + + // Test manual modification persistence - modify weight value manually after switching + const weightInput1 = getByPlaceholderText('route-weight-0-1') as HTMLInputElement; + fireEvent.change(weightInput1, { target: { value: '100' } }); + await mockTimeout(); + expect(weightInput1.value).toBe('100'); + + // Switch back to abtest - should show initial abtest value (uid) + fireEvent.click(abtestRadio1); + await mockTimeout(); + expect((getByPlaceholderText('route-abtest-0-1') as HTMLInputElement).value).toBe('uid'); + expect(container.querySelector('[placeholder="route-weight-0-1"]')).toBeFalsy(); + + // Switch to weight again - should show manually modified value (100) + fireEvent.click(weightRadio1); + await mockTimeout(); + expect((getByPlaceholderText('route-weight-0-1') as HTMLInputElement).value).toBe('100'); + expect(container.querySelector('[placeholder="route-abtest-0-1"]')).toBeFalsy(); + + // Modify abtest value manually + fireEvent.click(abtestRadio1); + await mockTimeout(); + const abtestInput1 = getByPlaceholderText('route-abtest-0-1') as HTMLInputElement; + expect(abtestInput1.value).toBe('uid'); + fireEvent.change(abtestInput1, { target: { value: 'custom-key' } }); + await mockTimeout(); + expect(abtestInput1.value).toBe('custom-key'); + + // Switch to weight + fireEvent.click(weightRadio1); + await mockTimeout(); + expect(container.querySelector('[placeholder="route-abtest-0-1"]')).toBeFalsy(); + expect((getByPlaceholderText('route-weight-0-1') as HTMLInputElement).value).toBe('100'); + + // Switch back to abtest - should show manually modified value + fireEvent.click(abtestRadio1); + await mockTimeout(); + expect((getByPlaceholderText('route-abtest-0-1') as HTMLInputElement).value).toBe('custom-key'); + expect(container.querySelector('[placeholder="route-weight-0-1"]')).toBeFalsy(); + + // Test adding default route (empty data) + const addDefaultBtn = container.querySelector('#test-add-route-0-default'); + fireEvent.click(addDefaultBtn); + await mockTimeout(); + const newRouteRadios = container.querySelectorAll('[data-route-index="2"] input[type="radio"]'); + expect(newRouteRadios.length).toBe(2); + // No radio should be checked initially + const checkedRadio = container.querySelector('[data-route-index="2"] input[type="radio"]:checked'); + expect(checkedRadio).toBeFalsy(); + // No conditional field should be rendered when type is empty + expect(container.querySelector('[placeholder="route-weight-0-2"]')).toBeFalsy(); + expect(container.querySelector('[placeholder="route-abtest-0-2"]')).toBeFalsy(); + + // Test setting type to weight for new route + const newWeightRadio = container.querySelector( + '[data-route-index="2"] input[value="weight"]', + ) as HTMLInputElement; + fireEvent.click(newWeightRadio); + await mockTimeout(); + const newWeightInput = getByPlaceholderText('route-weight-0-2') as HTMLInputElement; + expect(newWeightInput).toBeTruthy(); + expect(newWeightInput.value).toBe(''); + + // Test setting weight value + fireEvent.change(newWeightInput, { target: { value: '100' } }); + await mockTimeout(); + expect(newWeightInput.value).toBe('100'); + + // Test switching new route to abtest + const newAbtestRadio = container.querySelector( + '[data-route-index="2"] input[value="abtest"]', + ) as HTMLInputElement; + fireEvent.click(newAbtestRadio); + await mockTimeout(); + expect(container.querySelector('[placeholder="route-weight-0-2"]')).toBeFalsy(); + const newAbtestInput = getByPlaceholderText('route-abtest-0-2') as HTMLInputElement; + expect(newAbtestInput).toBeTruthy(); + expect(newAbtestInput.value).toBe(''); + + // Test setting abtest value + fireEvent.change(newAbtestInput, { target: { value: 'new-key' } }); + await mockTimeout(); + expect(newAbtestInput.value).toBe('new-key'); + + // Test switching back to weight - should show manually modified value (100) + fireEvent.click(newWeightRadio); + await mockTimeout(); + expect(container.querySelector('[placeholder="route-abtest-0-2"]')).toBeFalsy(); + const weightInputAgain = getByPlaceholderText('route-weight-0-2') as HTMLInputElement; + expect(weightInputAgain.value).toBe('100'); + + // Test adding specified route (with initial data) + const addSpecifiedBtn = container.querySelector('#test-add-route-0-specified'); + fireEvent.click(addSpecifiedBtn); + await mockTimeout(); + const specifiedWeightRadio = container.querySelector( + '[data-route-index="3"] input[value="weight"]', + ) as HTMLInputElement; + expect(specifiedWeightRadio.checked).toBe(true); + const specifiedWeightInput = getByPlaceholderText('route-weight-0-3') as HTMLInputElement; + expect(specifiedWeightInput.value).toBe('50'); + expect(container.querySelector('[placeholder="route-abtest-0-3"]')).toBeFalsy(); + + // Test switching specified route to abtest + const specifiedAbtestRadio = container.querySelector( + '[data-route-index="3"] input[value="abtest"]', + ) as HTMLInputElement; + fireEvent.click(specifiedAbtestRadio); + await mockTimeout(); + const specifiedAbtestInput = getByPlaceholderText('route-abtest-0-3') as HTMLInputElement; + expect(specifiedAbtestInput.value).toBe('cid'); + expect(container.querySelector('[placeholder="route-weight-0-3"]')).toBeFalsy(); + + // Test switching specified route back to weight - should show initial weight value + fireEvent.click(specifiedWeightRadio); + await mockTimeout(); + const specifiedWeightInputAgain = getByPlaceholderText('route-weight-0-3') as HTMLInputElement; + expect(specifiedWeightInputAgain.value).toBe('50'); + expect(container.querySelector('[placeholder="route-abtest-0-3"]')).toBeFalsy(); + }); }); }); diff --git a/packages/components/form/__tests__/form.test.tsx b/packages/components/form/__tests__/form.test.tsx index 35e193ba2a..ea602ab9ff 100644 --- a/packages/components/form/__tests__/form.test.tsx +++ b/packages/components/form/__tests__/form.test.tsx @@ -13,760 +13,798 @@ import type { TdFormProps } from '../index'; const { FormItem, FormList } = Form; -describe('Form 组件测试', () => { +describe('Form', () => { const submitFn = vi.fn(); const resetFn = vi.fn(); - test('reset & submit', async () => { - const { container } = render( -
- - - - - - - - - - -
, - ); - - fireEvent.click(container.querySelector('#test-reset')); - expect(resetFn).toHaveBeenCalledTimes(1); - fireEvent.click(container.querySelector('#test-submit')); - await mockTimeout(() => true); - expect(submitFn).toHaveBeenCalledTimes(1); - }); - test('form instance', async () => { - const fn = vi.fn(); + describe('events', () => { + test('reset & submit', async () => { + const { container } = render( +
+ + + + + + + + + + +
, + ); - const TestForm = () => { - const [form] = Form.useForm(); + fireEvent.click(container.querySelector('#test-reset')); + expect(resetFn).toHaveBeenCalledTimes(1); + fireEvent.click(container.querySelector('#test-submit')); + await mockTimeout(() => true); + expect(submitFn).toHaveBeenCalledTimes(1); + }); + }); - function setFields() { - form.getFieldsValue(true); - form.setFields([{ name: 'input1', value: 'setFields' }]); - } + describe('instanceFunctions', () => { + test('form instance', async () => { + const fn = vi.fn(); - function setFieldsValue() { - form.getFieldValue('input1'); - form.setFieldsValue({ input1: 'setFieldsValue' }); - } + const TestForm = () => { + const [form] = Form.useForm(); - function setValidateMessage() { - form.setValidateMessage({ - input1: [{ type: 'error', message: 'message: setValidateMessage' }], - }); - } + function setFields() { + form.getFieldsValue(true); + form.setFields([{ name: 'input1', value: 'setFields' }]); + } - function validate() { - form.validate(); - } + function setFieldsValue() { + form.getFieldValue('input1'); + form.setFieldsValue({ input1: 'setFieldsValue' }); + } - function validateOnly() { - form.validateOnly(); - } + function setValidateMessage() { + form.setValidateMessage({ + input1: [{ type: 'error', message: 'message: setValidateMessage' }], + }); + } - function clearValidate() { - form.clearValidate(); - } + function validate() { + form.validate(); + } - return ( -
- - - - - - - - - - - - -
- ); - }; - const { getByPlaceholderText, getByText, queryByText } = render(); - - // setFields setFieldsValue setValidateMessage test - expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual(''); - fireEvent.click(getByText('setFields')); - expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual('setFields'); - await mockTimeout(); - expect(fn).toHaveBeenCalled(); - - fireEvent.click(getByText('setFieldsValue')); - await mockTimeout(); - expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual('setFieldsValue'); - expect(fn).toHaveBeenCalled(); - - fireEvent.click(getByText('setValidateMessage')); - expect(queryByText('message: setValidateMessage')).toBeTruthy(); - - // validate validateOnly test - fireEvent.click(getByText('validateOnly')); - await mockTimeout(() => true); - expect(queryByText('input1 未填写')).not.toBeTruthy(); - fireEvent.click(getByText('reset')); - fireEvent.click(getByText('validate')); - await mockTimeout(() => true); - expect(queryByText('input1 未填写')).toBeTruthy(); - fireEvent.click(getByText('clearValidate')); - expect(queryByText('input1 未填写')).not.toBeTruthy(); - }); + function validateOnly() { + form.validateOnly(); + } - test('form setFieldsValue', () => { - const mockName = 'name'; - const mockName1 = 'name1'; - const mockBirthday = '1996-01-24'; - const mockBirthday1 = '1996-01-25'; - const mockArea = '北京'; - const mockArea1 = '北京'; - const TestForm = () => { - const [form] = Form.useForm(); - const handleSetFormData = () => { - form.setFieldsValue({ - user: { - name: mockName1, - }, - birthday: mockBirthday1, - address: [ - { - area: mockArea1, - }, - ], - }); + function clearValidate() { + form.clearValidate(); + } + + return ( +
+ + + + + + + + + + + + +
+ ); }; - return ( -
); + + // setFields setFieldsValue setValidateMessage test + expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual(''); + fireEvent.click(getByText('setFields')); + expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual('setFields'); + await mockTimeout(); + expect(fn).toHaveBeenCalled(); + + fireEvent.click(getByText('setFieldsValue')); + await mockTimeout(); + expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual('setFieldsValue'); + expect(fn).toHaveBeenCalled(); + + fireEvent.click(getByText('setValidateMessage')); + expect(queryByText('message: setValidateMessage')).toBeTruthy(); + + // validate validateOnly test + fireEvent.click(getByText('validateOnly')); + await mockTimeout(() => true); + expect(queryByText('input1 未填写')).not.toBeTruthy(); + fireEvent.click(getByText('reset')); + fireEvent.click(getByText('validate')); + await mockTimeout(() => true); + expect(queryByText('input1 未填写')).toBeTruthy(); + fireEvent.click(getByText('clearValidate')); + expect(queryByText('input1 未填写')).not.toBeTruthy(); + }); + + test('form setFieldsValue', () => { + const mockName = 'name'; + const mockName1 = 'name1'; + const mockBirthday = '1996-01-24'; + const mockBirthday1 = '1996-01-25'; + const mockArea = '北京'; + const mockArea1 = '北京'; + const TestForm = () => { + const [form] = Form.useForm(); + const handleSetFormData = () => { + form.setFieldsValue({ user: { - name: mockName, + name: mockName1, }, - birthday: mockBirthday, + birthday: mockBirthday1, address: [ { - area: mockArea, + area: mockArea1, }, ], - }} - > - - - - - - - - {(fields) => ( - <> - {fields.map(({ key, name, ...restField }) => ( - - - + }); + }; + return ( + + + + + + + + + {(fields) => ( + <> + {fields.map(({ key, name, ...restField }) => ( + + + + - - ))} - - )} - - - - -
- ); - }; + ))} + + )} + + + + + + ); + }; - const { getByPlaceholderText, getByText } = render(); - expect((getByPlaceholderText('name') as HTMLInputElement).value).toBe(mockName); - expect((getByPlaceholderText('birthday') as HTMLInputElement).value).toBe(mockBirthday); - expect((getByPlaceholderText('area') as HTMLInputElement).value).toBe(mockArea); + const { getByPlaceholderText, getByText } = render(); + expect((getByPlaceholderText('name') as HTMLInputElement).value).toBe(mockName); + expect((getByPlaceholderText('birthday') as HTMLInputElement).value).toBe(mockBirthday); + expect((getByPlaceholderText('area') as HTMLInputElement).value).toBe(mockArea); - fireEvent.click(getByText('SetFormData')); - expect((getByPlaceholderText('name') as HTMLInputElement).value).toBe(mockName1); - expect((getByPlaceholderText('birthday') as HTMLInputElement).value).toBe(mockBirthday1); - expect((getByPlaceholderText('area') as HTMLInputElement).value).toBe(mockArea1); + fireEvent.click(getByText('SetFormData')); + expect((getByPlaceholderText('name') as HTMLInputElement).value).toBe(mockName1); + expect((getByPlaceholderText('birthday') as HTMLInputElement).value).toBe(mockBirthday1); + expect((getByPlaceholderText('area') as HTMLInputElement).value).toBe(mockArea1); + }); }); - test('Form.reset works fine', async () => { - const initialVal = 'test input'; + describe('props', () => { + test('Form.reset works fine', async () => { + const initialVal = 'test input'; - const TestForm = () => { - const [resetType, setResetType] = useState('initial'); - return ( -
+ const TestForm = () => { + const [resetType, setResetType] = useState('initial'); + return (
- - +
+ + +
+
+ + + + + + +
-
- - - - - - -
-
- ); - }; + ); + }; - const { getByPlaceholderText, getByText } = render(); - expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual(initialVal); + const { getByPlaceholderText, getByText } = render(); + expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual(initialVal); - const inputThenReset = () => { - fireEvent.change(getByPlaceholderText('input1'), { - target: { value: 'value1' }, - }); - expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual('value1'); - fireEvent.click(getByText('reset')); - }; + const inputThenReset = () => { + fireEvent.change(getByPlaceholderText('input1'), { + target: { value: 'value1' }, + }); + expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual('value1'); + fireEvent.click(getByText('reset')); + }; - // test initial value - inputThenReset(); - expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual(initialVal); + // test initial value + inputThenReset(); + expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual(initialVal); - // test empty value - fireEvent.click(getByText('reset-empty')); - inputThenReset(); - expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual(''); + // test empty value + fireEvent.click(getByText('reset-empty')); + inputThenReset(); + expect((getByPlaceholderText('input1') as HTMLInputElement).value).toEqual(''); + }); }); - test('Form.setValidateMessage works fine', () => { - const TestForm = () => { - const [form] = Form.useForm(); - - useEffect(() => { - form.setValidateMessage({ - notArray: { - // @ts-ignore - type: 'error', - message: 'not array message', - }, - empty: [], - username: [ - { + describe('instanceFunctions', () => { + test('Form.setValidateMessage works fine', () => { + const TestForm = () => { + const [form] = Form.useForm(); + + useEffect(() => { + form.setValidateMessage({ + notArray: { + // @ts-ignore type: 'error', - message: 'custom error message', + message: 'not array message', }, - ], - password: [ - { - type: 'warning', - message: 'custom warning message', - }, - ], - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + empty: [], + username: [ + { + type: 'error', + message: 'custom error message', + }, + ], + password: [ + { + type: 'warning', + message: 'custom warning message', + }, + ], + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - return ( -
- - - - - - - - - - - - -
- ); - }; + return ( +
+ + + + + + + + + + + + +
+ ); + }; - const { container } = render(); - expect(container.querySelector('.not-array .t-input__extra')).toBeNull(); - expect(container.querySelector('.empty .t-input__extra')).toBeNull(); - expect(container.querySelector('.username .t-input__extra').innerHTML).toBe('custom error message'); - expect(container.querySelector('.password .t-input__extra').innerHTML).toBe('custom warning message'); - }); + const { container } = render(); + expect(container.querySelector('.not-array .t-input__extra')).toBeNull(); + expect(container.querySelector('.empty .t-input__extra')).toBeNull(); + expect(container.querySelector('.username .t-input__extra').innerHTML).toBe('custom error message'); + expect(container.querySelector('.password .t-input__extra').innerHTML).toBe('custom warning message'); + }); - test('Form.getValidateMessage works fine', async () => { - const TestForm = () => { - const [form] = Form.useForm(); - const [message, setMessage] = useState(null); + test('Form.getValidateMessage works fine', async () => { + const TestForm = () => { + const [form] = Form.useForm(); + const [message, setMessage] = useState(null); - const handleGetAll = () => { - const msg = form.getValidateMessage(); - setMessage(msg); + const handleGetAll = () => { + const msg = form.getValidateMessage(); + setMessage(msg); + }; + + const handleGetSpecific = () => { + const msg = form.getValidateMessage(['username', 'email']); + setMessage(msg); + }; + + const handleValidate = async () => { + await form.validate(); + const msg = form.getValidateMessage(); + setMessage(msg); + }; + + return ( +
+
+ + + + + + + + + + + + + + + + + +
+ {message && ( +
+
{JSON.stringify(message, null, 2)}
+
+ )} +
+ ); }; - const handleGetSpecific = () => { - const msg = form.getValidateMessage(['username', 'email']); - setMessage(msg); + const { getByText, queryByTestId, getByTestId } = render(); + + // getValidateMessage returns undefined when no validation errors + fireEvent.click(getByText('getAllMessages')); + await mockDelay(); + expect(queryByTestId('message-display')).toBeNull(); + + // getValidateMessage returns all validation messages after validate + fireEvent.click(getByText('validate')); + await mockDelay(); + fireEvent.click(getByText('getAllMessages')); + await mockDelay(); + const allMessagesDisplay = getByTestId('message-display'); + expect(allMessagesDisplay.textContent).toContain('username is required'); + expect(allMessagesDisplay.textContent).toContain('email is required'); + expect(allMessagesDisplay.textContent).toContain('phone is required'); + expect(allMessagesDisplay.textContent).toContain('address is required'); + // array field name is converted to string key + expect(allMessagesDisplay.textContent).toContain('user,address'); + + // getValidateMessage returns specific field messages + fireEvent.click(getByText('getSpecificMessages')); + await mockDelay(); + const specificMessagesDisplay = getByTestId('message-display'); + const messageContent = specificMessagesDisplay.textContent; + expect(messageContent).toContain('username is required'); + expect(messageContent).toContain('email is required'); + expect(messageContent).not.toContain('phone is required'); + expect(messageContent).not.toContain('address is required'); + }); + }); + + describe('scenarios', () => { + test('Form disabled `input` keydown enter submit form', async () => { + const TestForm = () => { + return ( +
+ + + + + + +
+ ); }; - const handleValidate = async () => { - await form.validate(); - const msg = form.getValidateMessage(); - setMessage(msg); + const { container, getByPlaceholderText, getByText } = render(); + fireEvent.keyDown(getByPlaceholderText('username'), { key: 'Enter' }); + await mockDelay(); + expect(container.querySelector('.t-input__extra')).toBeNull(); + + fireEvent.keyDown(getByText('submit'), { key: 'Enter' }); + await mockDelay(); + expect(container.querySelector('.t-input__extra')).toBeNull(); + }); + }); + + describe('props', () => { + test('FormItem without icon works fine', async () => { + const TestForm = () => { + return ( +
+ + + + + + +
+ ); }; - return ( -
-
+ const { container, getByText } = render(); + fireEvent.click(getByText('submit')); + await mockDelay(); + expect(container.querySelector('.t-form__status')).toBeNull(); + }); + test('FormItem preset icon works fine', async () => { + const TestForm = () => { + return ( + } + rules={[{ required: true, message: '请输入帮助' }]} > - + - - - + - {message && ( -
-
{JSON.stringify(message, null, 2)}
-
- )} -
- ); - }; - - const { getByText, queryByTestId, getByTestId } = render(); - - // getValidateMessage returns undefined when no validation errors - fireEvent.click(getByText('getAllMessages')); - await mockDelay(); - expect(queryByTestId('message-display')).toBeNull(); - - // getValidateMessage returns all validation messages after validate - fireEvent.click(getByText('validate')); - await mockDelay(); - fireEvent.click(getByText('getAllMessages')); - await mockDelay(); - const allMessagesDisplay = getByTestId('message-display'); - expect(allMessagesDisplay.textContent).toContain('username is required'); - expect(allMessagesDisplay.textContent).toContain('email is required'); - expect(allMessagesDisplay.textContent).toContain('phone is required'); - expect(allMessagesDisplay.textContent).toContain('address is required'); - // array field name is converted to string key - expect(allMessagesDisplay.textContent).toContain('user,address'); - - // getValidateMessage returns specific field messages - fireEvent.click(getByText('getSpecificMessages')); - await mockDelay(); - const specificMessagesDisplay = getByTestId('message-display'); - const messageContent = specificMessagesDisplay.textContent; - expect(messageContent).toContain('username is required'); - expect(messageContent).toContain('email is required'); - expect(messageContent).not.toContain('phone is required'); - expect(messageContent).not.toContain('address is required'); - }); - - test('Form disabled `input` keydown enter submit form', async () => { - const TestForm = () => { - return ( -
- - - - - - -
- ); - }; - - const { container, getByPlaceholderText, getByText } = render(); - fireEvent.keyDown(getByPlaceholderText('username'), { key: 'Enter' }); - await mockDelay(); - expect(container.querySelector('.t-input__extra')).toBeNull(); - - fireEvent.keyDown(getByText('submit'), { key: 'Enter' }); - await mockDelay(); - expect(container.querySelector('.t-input__extra')).toBeNull(); - }); - - test('FormItem without icon works fine', async () => { - const TestForm = () => { - return ( -
- - - - - - -
- ); - }; + ); + }; - const { container, getByText } = render(); - fireEvent.click(getByText('submit')); - await mockDelay(); - expect(container.querySelector('.t-form__status')).toBeNull(); + const { container, getByText } = render(); + expect(container.querySelector('.help .t-icon-help-circle')).toBeTruthy(); + fireEvent.click(getByText('submit')); + await mockDelay(); + expect(container.querySelector('.username .t-icon-check-circle-filled')).toBeTruthy(); + expect(container.querySelector('.email .t-icon-error-circle-filled')).toBeTruthy(); + expect(container.querySelector('.phone .t-icon-close-circle-filled')).toBeTruthy(); + expect(container.querySelector('.help .t-icon-help-circle')).toBeTruthy(); + }); }); - test('FormItem preset icon works fine', async () => { - const TestForm = () => { - return ( -
- - - - - - - - - - } - rules={[{ required: true, message: '请输入帮助' }]} + describe('props', () => { + test('FormItem.props.name can be array', () => { + const mockName = 'name'; + const mockBirtyday = '1996-01-24'; + const TestForm = () => { + return ( + - - - - - -
- ); - }; - - const { container, getByText } = render(); - expect(container.querySelector('.help .t-icon-help-circle')).toBeTruthy(); - fireEvent.click(getByText('submit')); - await mockDelay(); - expect(container.querySelector('.username .t-icon-check-circle-filled')).toBeTruthy(); - expect(container.querySelector('.email .t-icon-error-circle-filled')).toBeTruthy(); - expect(container.querySelector('.phone .t-icon-close-circle-filled')).toBeTruthy(); - expect(container.querySelector('.help .t-icon-help-circle')).toBeTruthy(); - }); + + + + + + + + ); + }; - test('FormItem.props.name can be array', () => { - const mockName = 'name'; - const mockBirtyday = '1996-01-24'; - const TestForm = () => { - return ( -
- - - - - - -
- ); - }; + const { getByPlaceholderText } = render(); + expect((getByPlaceholderText('name') as HTMLInputElement).value).toBe(mockName); + expect((getByPlaceholderText('birthday') as HTMLInputElement).value).toBe(mockBirtyday); + }); + }); - const { getByPlaceholderText } = render(); - expect((getByPlaceholderText('name') as HTMLInputElement).value).toBe(mockName); - expect((getByPlaceholderText('birthday') as HTMLInputElement).value).toBe(mockBirtyday); + describe('scenarios', () => { + test('FormItem blur validate works fine', async () => { + const TestForm = () => { + return ( +
+ + + +
+ ); + }; + const { container, getByPlaceholderText } = render(); + fireEvent.blur(getByPlaceholderText('username')); + await mockDelay(); + expect(container.querySelector('.t-input__extra').innerHTML).toBe('please input username'); + }); }); - test('FormItem blur validate works fine', async () => { - const TestForm = () => { - return ( -
- { + test('FormItem rules nested name', async () => { + const TestForm = () => { + return ( + - - -
- ); - }; - const { container, getByPlaceholderText } = render(); - fireEvent.blur(getByPlaceholderText('username')); - await mockDelay(); - expect(container.querySelector('.t-input__extra').innerHTML).toBe('please input username'); - }); - test('FormItem rules nested name', async () => { - const TestForm = () => { - return ( -
- - - - - - + + + + + + - - - -
- ); - }; - const { container, getByText } = render(); - fireEvent.click(getByText('提交')); - await mockDelay(); - expect(container.querySelectorAll('.t-form--has-error').length).toBe(2); - }); - test('FormItem rules min max', async () => { - const TestForm = () => { - const initialValues = { - year1: -2, - year2: 1, - year3: 4, - year4: -4, - year5: -1, - year6: 2, + + + + + ); }; - return ( -
- - - - - - - - - - - - - - - - - - - - - -
- ); - }; - const { container, getByText, getByPlaceholderText } = render(); - fireEvent.click(getByText('提交')); - await mockDelay(); - expect(container.querySelector('.t-input__extra')).toBeNull(); - - // 错误验证 - fireEvent.change(getByPlaceholderText('year1'), { target: { value: -4 } }); - fireEvent.change(getByPlaceholderText('year2'), { target: { value: -1 } }); - fireEvent.change(getByPlaceholderText('year3'), { target: { value: 2 } }); - fireEvent.change(getByPlaceholderText('year4'), { target: { value: -2 } }); - fireEvent.change(getByPlaceholderText('year5'), { target: { value: 1 } }); - fireEvent.change(getByPlaceholderText('year6'), { target: { value: 4 } }); - fireEvent.click(getByText('提交')); - await mockDelay(); - const inputExtra = container.querySelectorAll('.t-input__extra'); - inputExtra.forEach((item: { innerHTML: string }, index: number) => { - expect(item.innerHTML).toBe(`year${index + 1} error`); + const { container, getByText } = render(); + fireEvent.click(getByText('提交')); + await mockDelay(); + expect(container.querySelectorAll('.t-form--has-error').length).toBe(2); }); }); - test('动态渲染并初始赋值', () => { - const TestForm = () => { - const [form] = Form.useForm(); - const setMessage = () => { - form.setFieldsValue({ - gender: 'female', - radio2: '3', - }); + describe('scenarios', () => { + test('FormItem rules min max', async () => { + const TestForm = () => { + const initialValues = { + year1: -2, + year2: 1, + year3: 4, + year4: -4, + year5: -1, + year6: 2, + }; + return ( +
+ + + + + + + + + + + + + + + + + + + + + +
+ ); }; + const { container, getByText, getByPlaceholderText } = render(); + fireEvent.click(getByText('提交')); + await mockDelay(); + expect(container.querySelector('.t-input__extra')).toBeNull(); + + // 错误验证 + fireEvent.change(getByPlaceholderText('year1'), { + target: { value: -4 }, + }); + fireEvent.change(getByPlaceholderText('year2'), { + target: { value: -1 }, + }); + fireEvent.change(getByPlaceholderText('year3'), { target: { value: 2 } }); + fireEvent.change(getByPlaceholderText('year4'), { + target: { value: -2 }, + }); + fireEvent.change(getByPlaceholderText('year5'), { target: { value: 1 } }); + fireEvent.change(getByPlaceholderText('year6'), { target: { value: 4 } }); + fireEvent.click(getByText('提交')); + await mockDelay(); + const inputExtra = container.querySelectorAll('.t-input__extra'); + inputExtra.forEach((item: { innerHTML: string }, index: number) => { + expect(item.innerHTML).toBe(`year${index + 1} error`); + }); + }); - return ( -
- - - 男性 - 女性 - - - prev.gender !== next.gender}> - {({ getFieldValue }) => { - if (getFieldValue('gender') === 'female') { + test('动态渲染并初始赋值', () => { + const TestForm = () => { + const [form] = Form.useForm(); + const setMessage = () => { + form.setFieldsValue({ + gender: 'female', + radio2: '3', + }); + }; + + return ( + + + + 男性 + 女性 + + + prev.gender !== next.gender}> + {({ getFieldValue }) => { + if (getFieldValue('gender') === 'female') { + return ( + + + 选项三 + + 选项四 + + + + ); + } return ( - - - 选项三 - - 选项四 - + + + 选项一 + 选项二 ); - } - return ( - - - 选项一 - 选项二 - - - ); - }} - + }} + - - - - - ); - }; + + + + + ); + }; - const { container, getByText } = render(); - fireEvent.click(getByText('设置信息')); + const { container, getByText } = render(); + fireEvent.click(getByText('设置信息')); - expect(container.querySelector('.radio-value-3')).toHaveClass('t-is-checked'); - }); + expect(container.querySelector('.radio-value-3')).toHaveClass('t-is-checked'); + }); - test('FormItem without initialData resets to empty on remount with resetType=initial', async () => { - const TestForm = () => { - const [form] = Form.useForm(); - const [visible, setVisible] = useState(false); + test('FormItem without initialData resets to empty on remount with resetType=initial', async () => { + const TestForm = () => { + const [form] = Form.useForm(); + const [visible, setVisible] = useState(false); - return ( -
- - {visible && ( -
-
- - - - -
- -
- )} -
- ); - }; + return ( +
+ + {visible && ( +
+
+ + + + +
+ +
+ )} +
+ ); + }; - const { getByText, getByPlaceholderText, queryByPlaceholderText } = render(); + const { getByText, getByPlaceholderText, queryByPlaceholderText } = render(); - // First open: type a value then close - fireEvent.click(getByText('Open')); - fireEvent.change(getByPlaceholderText('tag'), { - target: { value: 'dirty' }, - }); - expect((getByPlaceholderText('tag') as HTMLInputElement).value).toBe('dirty'); - fireEvent.click(getByText('close')); - expect(queryByPlaceholderText('tag')).toBeNull(); + // First open: type a value then close + fireEvent.click(getByText('Open')); + fireEvent.change(getByPlaceholderText('tag'), { + target: { value: 'dirty' }, + }); + expect((getByPlaceholderText('tag') as HTMLInputElement).value).toBe('dirty'); + fireEvent.click(getByText('close')); + expect(queryByPlaceholderText('tag')).toBeNull(); - // Second open: field should start empty - fireEvent.click(getByText('Open')); - expect((getByPlaceholderText('tag') as HTMLInputElement).value).toBe(''); + // Second open: field should start empty + fireEvent.click(getByText('Open')); + expect((getByPlaceholderText('tag') as HTMLInputElement).value).toBe(''); - // reset should also produce empty - fireEvent.change(getByPlaceholderText('tag'), { - target: { value: 'dirty-again' }, + // reset should also produce empty + fireEvent.change(getByPlaceholderText('tag'), { + target: { value: 'dirty-again' }, + }); + fireEvent.click(getByText('Reset')); + expect((getByPlaceholderText('tag') as HTMLInputElement).value).toBe(''); }); - fireEvent.click(getByText('Reset')); - expect((getByPlaceholderText('tag') as HTMLInputElement).value).toBe(''); - }); - test('FormItem setFields not trigger onValueChange', async () => { - const fn = vi.fn(); + test('FormItem setFields not trigger onValueChange', async () => { + const fn = vi.fn(); - const TestForm = () => { - const [form] = Form.useForm(); + const TestForm = () => { + const [form] = Form.useForm(); - function setFields() { - form.setFields?.([{ name: ['user', 'course'], value: ['la'] }]); - } + function setFields() { + form.setFields?.([{ name: ['user', 'course'], value: ['la'] }]); + } - return ( -
- - - 加辣 - 加麻 - 不要葱花 - - - - - -
- ); - }; - const { getByText, container } = render(); - - expect(container.querySelector('.t-is-checked')).toBe(null); - fireEvent.click(getByText('setFields')); - expect((container.querySelector('.t-is-checked input') as HTMLInputElement).value).toEqual('la'); - await mockTimeout(); - expect(fn).toHaveBeenCalledTimes(1); - fireEvent.click(getByText('setFields')); - expect(fn).toHaveBeenCalledTimes(1); + return ( +
+ + + 加辣 + 加麻 + 不要葱花 + + + + + +
+ ); + }; + const { getByText, container } = render(); + + expect(container.querySelector('.t-is-checked')).toBe(null); + fireEvent.click(getByText('setFields')); + expect((container.querySelector('.t-is-checked input') as HTMLInputElement).value).toEqual('la'); + await mockTimeout(); + expect(fn).toHaveBeenCalledTimes(1); + fireEvent.click(getByText('setFields')); + expect(fn).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/packages/components/grid/__tests__/grid.test.tsx b/packages/components/grid/__tests__/grid.test.tsx index b8136a6c8a..b68c45497a 100644 --- a/packages/components/grid/__tests__/grid.test.tsx +++ b/packages/components/grid/__tests__/grid.test.tsx @@ -4,113 +4,125 @@ import { render } from '@test/utils'; import Col from '../Col'; import Row from '../Row'; -describe('Grid 组件测试', () => { - test('Grid ', async () => { - window.innerWidth = 1500; - const { container } = render( - - - , - ); - // 校验默认className - const defaultClass = ['t-row']; - expect(container.firstChild).toHaveClass(...defaultClass); - }); +describe('Row', () => { + describe('props', () => { + test('Grid ', async () => { + window.innerWidth = 1500; + const { container } = render( + + + , + ); + // 校验默认className + const defaultClass = ['t-row']; + expect(container.firstChild).toHaveClass(...defaultClass); + }); - test('Row gutter', async () => { - // 校验默认className - const defaultClass = ['t-row']; - window.innerWidth = 1300; + test('Row gutter', async () => { + // 校验默认className + const defaultClass = ['t-row']; + window.innerWidth = 1300; - // number - const { container: container1 } = render( - - - , - ); + // number + const { container: container1 } = render( + + + , + ); - // Array - const { container: container2 } = render( - - - , - ); + // Array + const { container: container2 } = render( + + + , + ); - // Array - const { container: container3 } = render( - - - , - ); + // Array + const { container: container3 } = render( + + + , + ); - // Object - const { container: container4 } = render( - - - , - ); + // Object + const { container: container4 } = render( + + + , + ); - const col1 = container1.querySelector('.t-col-1'); - expect(col1).not.toBeNull(); - const col2 = container2.querySelector('.t-col-2'); - expect(col2).not.toBeNull(); - const col3 = container3.querySelector('.t-col-3'); - expect(col3).not.toBeNull(); - const col4 = container4.querySelector('.t-col-4'); - expect(col4).not.toBeNull(); + const col1 = container1.querySelector('.t-col-1'); + expect(col1).not.toBeNull(); + const col2 = container2.querySelector('.t-col-2'); + expect(col2).not.toBeNull(); + const col3 = container3.querySelector('.t-col-3'); + expect(col3).not.toBeNull(); + const col4 = container4.querySelector('.t-col-4'); + expect(col4).not.toBeNull(); - expect(container1.firstChild).toHaveClass(...defaultClass); - expect(container2.firstChild).toHaveClass(...defaultClass); - expect(container3.firstChild).toHaveClass(...defaultClass); - expect(container4.firstChild).toHaveClass(...defaultClass); + expect(container1.firstChild).toHaveClass(...defaultClass); + expect(container2.firstChild).toHaveClass(...defaultClass); + expect(container3.firstChild).toHaveClass(...defaultClass); + expect(container4.firstChild).toHaveClass(...defaultClass); + }); }); +}); - test('Col flex', async () => { - window.innerWidth = 800; - // number - const { container: container1 } = render( - - -
2 / 5
- - -
3 / 5
- -
, - ); +describe('Col', () => { + describe('props', () => { + test('Col flex', async () => { + window.innerWidth = 800; + // number + const { container: container1 } = render( + + +
2 / 5
+ + +
3 / 5
+ +
, + ); - // string - const { container: container2 } = render( - - -
2 / 5
- - -
3 / 5
- -
, - ); + // string + const { container: container2 } = render( + + +
2 / 5
+ + +
3 / 5
+ +
, + ); - // auto - const { container: container3 } = render( - - -
2 / 5
- - -
3 / 5
- -
, - ); - expect(container1.firstChild.firstChild).toHaveStyle({ flex: '2 2 auto' }); - expect(container2.firstChild.firstChild).toHaveStyle({ flex: '0 0 100px' }); - expect(container3.firstChild.firstChild).toHaveStyle({ flexBasis: 'auto' }); + // auto + const { container: container3 } = render( + + +
2 / 5
+ + +
3 / 5
+ +
, + ); + expect(container1.firstChild.firstChild).toHaveStyle({ + flex: '2 2 auto', + }); + expect(container2.firstChild.firstChild).toHaveStyle({ + flex: '0 0 100px', + }); + expect(container3.firstChild.firstChild).toHaveStyle({ + flexBasis: 'auto', + }); + }); }); }); diff --git a/packages/components/guide/__tests__/__snapshots__/vitest-guide.test.jsx.snap b/packages/components/guide/__tests__/__snapshots__/guide.test.tsx.snap similarity index 98% rename from packages/components/guide/__tests__/__snapshots__/vitest-guide.test.jsx.snap rename to packages/components/guide/__tests__/__snapshots__/guide.test.tsx.snap index 7446299aa3..a960703728 100644 --- a/packages/components/guide/__tests__/__snapshots__/vitest-guide.test.jsx.snap +++ b/packages/components/guide/__tests__/__snapshots__/guide.test.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Guide Component > GuideStep.body works fine 1`] = ` +exports[`GuideStep > props > body 1`] = ` @@ -178,7 +178,7 @@ exports[`Guide Component > GuideStep.body works fine 1`] = ` `; -exports[`Guide Component > GuideStep.children works fine 1`] = ` +exports[`GuideStep > props > children 1`] = ` @@ -316,7 +316,7 @@ exports[`Guide Component > GuideStep.children works fine 1`] = ` `; -exports[`Guide Component > GuideStep.content works fine 1`] = ` +exports[`GuideStep > props > content 1`] = ` @@ -454,7 +454,7 @@ exports[`Guide Component > GuideStep.content works fine 1`] = ` `; -exports[`Guide Component > GuideStep.highlightContent works fine 1`] = ` +exports[`GuideStep > props > highlightContent 1`] = ` @@ -635,7 +635,7 @@ exports[`Guide Component > GuideStep.highlightContent works fine 1`] = ` `; -exports[`Guide Component > GuideStep.mode is equal to dialog 1`] = ` +exports[`GuideStep > props > mode is equal to dialog 1`] = `
GuideStep.mode is equal to dialog 1`] = `
`; -exports[`Guide Component > GuideStep.mode is equal to popup 1`] = ` +exports[`GuideStep > props > mode is equal to popup 1`] = `
GuideStep.mode is equal to popup 1`] = `
`; -exports[`Guide Component > GuideStep.placement is equal to bottom-left 1`] = ` +exports[`GuideStep > props > placement is equal to bottom-left 1`] = ` @@ -1005,7 +1005,7 @@ exports[`Guide Component > GuideStep.placement is equal to bottom-left 1`] = ` `; -exports[`Guide Component > GuideStep.stepOverlayClass is equal to t-test-guide-step-overlay 1`] = ` +exports[`GuideStep > props > stepOverlayClass is equal to t-test-guide-step-overlay 1`] = ` @@ -1179,7 +1179,7 @@ exports[`Guide Component > GuideStep.stepOverlayClass is equal to t-test-guide-s `; -exports[`Guide Component > GuideStep.title works fine 1`] = ` +exports[`GuideStep > props > title 1`] = ` diff --git a/packages/components/guide/__tests__/guide.test.tsx b/packages/components/guide/__tests__/guide.test.tsx new file mode 100644 index 0000000000..3d8d14475a --- /dev/null +++ b/packages/components/guide/__tests__/guide.test.tsx @@ -0,0 +1,516 @@ +import React from 'react'; +import { fireEvent, mockDelay, render, vi } from '@test/utils'; + +import Button from '../../button'; +import { Row } from '../../grid'; +import Input from '../../input'; +import { Guide } from '..'; + +function GuideContent() { + return ( +
+
+
Guide 用户引导
+
按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。
+
+
+
Label
+ +
+
+
Label
+ +
+ + + + +
+ ); +} + +const STEPS = [ + { + element: '.main-title-base', + title: '新手引导标题', + body: '新手引导的说明文案', + placement: 'bottom-right', + }, + { + element: () => document.body.querySelector('.label-field-base'), + title: '新手引导标题', + body: '新手引导的说明文案', + placement: 'bottom', + }, + { + element: '.action-base', + title: '新手引导标题', + body: '新手引导的说明文案', + placement: 'right', + }, +]; + +function getGuideDefaultMount(props: Record = {}, events: Record = {}) { + return render( +
+ + +
, + ); +} + +function getGuideMultipleStepsMount(props: Record = {}, events: Record = {}) { + return render( +
+ + +
, + ); +} + +function getCustomGuideStepMount(props: Record = {}) { + const steps = [{ ...STEPS[0], ...props }]; + return render( +
+ + +
, + ); +} + +function getCustomMultipleGuideStepMount(props: Record = {}) { + const { current = 0, ...guideStepProps } = props; + const steps = [...STEPS]; + steps[current] = { ...STEPS[current], ...guideStepProps }; + return render( +
+ + +
, + ); +} + +describe('Guide', () => { + describe('props', () => { + test('current 0', async () => { + getGuideMultipleStepsMount({ current: 0 }); + await mockDelay(60); + const tGuideCounterDom = document.querySelector('.t-guide__counter'); + expect(tGuideCounterDom.textContent).toBe('1/3'); + const tGuideTitleDom = document.querySelectorAll('.t-guide__title'); + expect(tGuideTitleDom.length).toBe(1); + const tGuideDescDom = document.querySelectorAll('.t-guide__desc'); + expect(tGuideDescDom.length).toBe(1); + const tGuideActionTGuideSkipDom = document.querySelectorAll('.t-guide__action .t-guide__skip'); + expect(tGuideActionTGuideSkipDom.length).toBe(1); + const tGuideActionTGuidePrevDom = document.querySelector('.t-guide__action .t-guide__prev'); + expect(tGuideActionTGuidePrevDom).toBeFalsy(); + const tGuideActionTGuideNextDom = document.querySelectorAll('.t-guide__action .t-guide__next'); + expect(tGuideActionTGuideNextDom.length).toBe(1); + const tGuideActionTGuideFinishDom = document.querySelector('.t-guide__action .t-guide__finish'); + expect(tGuideActionTGuideFinishDom).toBeFalsy(); + }); + + test('current 1', async () => { + getGuideMultipleStepsMount({ current: 1 }); + await mockDelay(60); + const tGuideCounterDom = document.querySelector('.t-guide__counter'); + expect(tGuideCounterDom.textContent).toBe('2/3'); + const tGuideTitleDom = document.querySelectorAll('.t-guide__title'); + expect(tGuideTitleDom.length).toBe(1); + const tGuideDescDom = document.querySelectorAll('.t-guide__desc'); + expect(tGuideDescDom.length).toBe(1); + const tGuideActionTGuideSkipDom = document.querySelectorAll('.t-guide__action .t-guide__skip'); + expect(tGuideActionTGuideSkipDom.length).toBe(1); + const tGuideActionTGuidePrevDom = document.querySelectorAll('.t-guide__action .t-guide__prev'); + expect(tGuideActionTGuidePrevDom.length).toBe(1); + const tGuideActionTGuideNextDom = document.querySelectorAll('.t-guide__action .t-guide__next'); + expect(tGuideActionTGuideNextDom.length).toBe(1); + const tGuideActionTGuideFinishDom = document.querySelector('.t-guide__action .t-guide__finish'); + expect(tGuideActionTGuideFinishDom).toBeFalsy(); + }); + + test('current 2', async () => { + getGuideMultipleStepsMount({ current: 2 }); + await mockDelay(60); + const tGuideCounterDom = document.querySelector('.t-guide__counter'); + expect(tGuideCounterDom.textContent).toBe('3/3'); + const tGuideTitleDom = document.querySelectorAll('.t-guide__title'); + expect(tGuideTitleDom.length).toBe(1); + const tGuideDescDom = document.querySelectorAll('.t-guide__desc'); + expect(tGuideDescDom.length).toBe(1); + const tGuideActionTGuideSkipDom = document.querySelector('.t-guide__action .t-guide__skip'); + expect(tGuideActionTGuideSkipDom).toBeFalsy(); + const tGuideActionTGuidePrevDom = document.querySelectorAll('.t-guide__action .t-guide__prev'); + expect(tGuideActionTGuidePrevDom.length).toBe(1); + const tGuideActionTGuideNextDom = document.querySelector('.t-guide__action .t-guide__next'); + expect(tGuideActionTGuideNextDom).toBeFalsy(); + const tGuideActionTGuideFinishDom = document.querySelectorAll('.t-guide__action .t-guide__finish'); + expect(tGuideActionTGuideFinishDom.length).toBe(1); + }); + + test('current works fine. `{"document.t-guide__counter":false}` should exist', async () => { + getGuideMultipleStepsMount({ current: -1 }); + await mockDelay(60); + const tGuideCounterDom = document.querySelector('.t-guide__counter'); + expect(tGuideCounterDom).toBeFalsy(); + }); + + test(`finishButtonProps is equal to {theme: 'warning'}`, async () => { + getGuideMultipleStepsMount({ + current: 2, + finishButtonProps: { theme: 'warning' }, + }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-guide__finish'); + expect(domWrapper).toHaveClass('t-button--theme-warning'); + }); + + test('hideCounter works fine. `{"document.t-guide__counter":false}` should exist', async () => { + getGuideDefaultMount({ hideCounter: true }); + await mockDelay(60); + const tGuideCounterDom = document.querySelector('.t-guide__counter'); + expect(tGuideCounterDom).toBeFalsy(); + }); + + test('hidePrev works fine. `{"document.t-guide__action .t-guide__prev":false}` should exist', async () => { + getGuideMultipleStepsMount({ current: 1, hidePrev: true }); + await mockDelay(60); + const tGuideActionTGuidePrevDom = document.querySelector('.t-guide__action .t-guide__prev'); + expect(tGuideActionTGuidePrevDom).toBeFalsy(); + }); + + test('hideSkip works fine. `{"document.t-guide__action .t-guide__skip":false}` should exist', async () => { + getGuideMultipleStepsMount({ current: 1, hideSkip: true }); + await mockDelay(60); + const tGuideActionTGuideSkipDom = document.querySelector('.t-guide__action .t-guide__skip'); + expect(tGuideActionTGuideSkipDom).toBeFalsy(); + }); + + test(`highlightPadding is equal to 32`, async () => { + getGuideDefaultMount({ highlightPadding: 32 }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-guide__highlight--mask'); + expect(domWrapper.style.width).toBe('64px'); + expect(domWrapper.style.height).toBe('64px'); + expect(domWrapper.style.top).toBe('-32px'); + expect(domWrapper.style.left).toBe('-32px'); + const domWrapper1 = document.querySelector('.t-guide__reference'); + expect(domWrapper1.style.width).toBe('64px'); + expect(domWrapper1.style.height).toBe('64px'); + expect(domWrapper1.style.top).toBe('-32px'); + expect(domWrapper1.style.left).toBe('-32px'); + }); + + test(`nextButtonProps is equal to {theme: 'warning'}`, async () => { + getGuideMultipleStepsMount({ + current: 1, + nextButtonProps: { theme: 'warning' }, + }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-guide__next'); + expect(domWrapper).toHaveClass('t-button--theme-warning'); + }); + + test(`prevButtonProps is equal to {theme: 'warning'}`, async () => { + getGuideMultipleStepsMount({ + current: 2, + prevButtonProps: { theme: 'warning' }, + }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-guide__prev'); + expect(domWrapper).toHaveClass('t-button--theme-warning'); + }); + + test('showOverlay works fine. `{"document.t-guide__highlight--mask":1}` should exist', async () => { + getGuideDefaultMount({ showOverlay: true }); + await mockDelay(60); + const tGuideHighlightMaskDom = document.querySelectorAll('.t-guide__highlight--mask'); + expect(tGuideHighlightMaskDom.length).toBe(1); + }); + + test('showOverlay works fine. `{"document.t-guide__highlight--mask":false}` should exist', async () => { + getGuideDefaultMount({ showOverlay: false }); + await mockDelay(60); + const tGuideHighlightMaskDom = document.querySelector('.t-guide__highlight--mask'); + expect(tGuideHighlightMaskDom).toBeFalsy(); + }); + + test(`skipButtonProps is equal to {theme: 'warning'}`, async () => { + getGuideMultipleStepsMount({ + current: 0, + skipButtonProps: { theme: 'warning' }, + }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-guide__skip'); + expect(domWrapper).toHaveClass('t-button--theme-warning'); + }); + + test('steps', async () => { + getGuideDefaultMount(); + await mockDelay(60); + const tGuideCounterDom = document.querySelector('.t-guide__counter'); + expect(tGuideCounterDom.textContent).toBe('1/1'); + const tGuideTitleDom = document.querySelectorAll('.t-guide__title'); + expect(tGuideTitleDom.length).toBe(1); + const tGuideDescDom = document.querySelectorAll('.t-guide__desc'); + expect(tGuideDescDom.length).toBe(1); + const tGuideActionTGuideSkipDom = document.querySelector('.t-guide__action .t-guide__skip'); + expect(tGuideActionTGuideSkipDom).toBeFalsy(); + const tGuideActionTGuidePrevDom = document.querySelector('.t-guide__action .t-guide__prev'); + expect(tGuideActionTGuidePrevDom).toBeFalsy(); + const tGuideActionTGuideNextDom = document.querySelector('.t-guide__action .t-guide__next'); + expect(tGuideActionTGuideNextDom).toBeFalsy(); + const tGuideActionTGuideFinishDom = document.querySelectorAll('.t-guide__action .t-guide__finish'); + expect(tGuideActionTGuideFinishDom.length).toBe(1); + }); + + test(`zIndex is equal to 5000`, async () => { + getGuideDefaultMount({ zIndex: '5000' }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-guide__overlay'); + expect(domWrapper.style.zIndex).toBe('4998'); + const domWrapper1 = document.querySelector('.t-guide__highlight--mask'); + expect(domWrapper1.style.zIndex).toBe('4999'); + }); + test('counter', async () => { + getGuideDefaultMount({ + counter: TNode, + }); + await mockDelay(60); + const customNodeDom = document.querySelector('.custom-node'); + expect(customNodeDom).toBeTruthy(); + const tGuideCounterDom = document.querySelector('.t-guide__counter'); + expect(tGuideCounterDom).toBeTruthy(); + }); + + test('counter is a function with params', async () => { + const fn = vi.fn(); + getGuideDefaultMount({ counter: fn }); + await mockDelay(60); + expect(fn).toHaveBeenCalled(); + expect(fn.mock.calls[0][0].total).toBe(1); + expect(fn.mock.calls[0][0].current).toBe(0); + }); + }); + + describe('events', () => { + test('change next', async () => { + const onChangeFn = vi.fn(); + getGuideMultipleStepsMount({ current: 0 }, { onChange: onChangeFn }); + await mockDelay(60); + fireEvent.click(document.querySelector('.t-guide__next')); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(1); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + expect(onChangeFn.mock.calls[0][1].total).toBe(3); + }); + + test('change prev', async () => { + const onChangeFn = vi.fn(); + getGuideMultipleStepsMount({ current: 1 }, { onChange: onChangeFn }); + await mockDelay(60); + fireEvent.click(document.querySelector('.t-guide__prev')); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(0); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + expect(onChangeFn.mock.calls[0][1].total).toBe(3); + }); + + test('finish', async () => { + const onFinishFn = vi.fn(); + getGuideMultipleStepsMount({ current: 2 }, { onFinish: onFinishFn }); + await mockDelay(60); + fireEvent.click(document.querySelector('.t-guide__finish')); + expect(onFinishFn).toHaveBeenCalled(); + expect(onFinishFn.mock.calls[0][0].current).toBe(2); + expect(onFinishFn.mock.calls[0][0].e.type).toBe('click'); + expect(onFinishFn.mock.calls[0][0].total).toBe(3); + }); + + test('nextStepClick', async () => { + const onNextStepClickFn = vi.fn(); + getGuideMultipleStepsMount({ current: 1 }, { onNextStepClick: onNextStepClickFn }); + await mockDelay(60); + fireEvent.click(document.querySelector('.t-guide__next')); + expect(onNextStepClickFn).toHaveBeenCalled(); + expect(onNextStepClickFn.mock.calls[0][0].current).toBe(1); + expect(onNextStepClickFn.mock.calls[0][0].next).toBe(2); + expect(onNextStepClickFn.mock.calls[0][0].e.type).toBe('click'); + expect(onNextStepClickFn.mock.calls[0][0].total).toBe(3); + }); + + test('prevStepClick', async () => { + const onPrevStepClickFn = vi.fn(); + getGuideMultipleStepsMount({ current: 1 }, { onPrevStepClick: onPrevStepClickFn }); + await mockDelay(60); + fireEvent.click(document.querySelector('.t-guide__prev')); + expect(onPrevStepClickFn).toHaveBeenCalled(); + expect(onPrevStepClickFn.mock.calls[0][0].current).toBe(1); + expect(onPrevStepClickFn.mock.calls[0][0].prev).toBe(0); + expect(onPrevStepClickFn.mock.calls[0][0].e.type).toBe('click'); + expect(onPrevStepClickFn.mock.calls[0][0].total).toBe(3); + }); + + test('skip', async () => { + const onSkipFn = vi.fn(); + getGuideMultipleStepsMount({ current: 0 }, { onSkip: onSkipFn }); + await mockDelay(60); + fireEvent.click(document.querySelector('.t-guide__skip')); + expect(onSkipFn).toHaveBeenCalled(); + expect(onSkipFn.mock.calls[0][0].current).toBe(0); + expect(onSkipFn.mock.calls[0][0].e.type).toBe('click'); + expect(onSkipFn.mock.calls[0][0].total).toBe(3); + }); + }); +}); + +describe('GuideStep', () => { + describe('props', () => { + test(`highlightPadding is equal to 32`, async () => { + getCustomGuideStepMount({ highlightPadding: 32 }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-guide__highlight--mask'); + expect(domWrapper.style.width).toBe('64px'); + expect(domWrapper.style.height).toBe('64px'); + expect(domWrapper.style.top).toBe('-32px'); + expect(domWrapper.style.left).toBe('-32px'); + const domWrapper1 = document.querySelector('.t-guide__reference'); + expect(domWrapper1.style.width).toBe('64px'); + expect(domWrapper1.style.height).toBe('64px'); + expect(domWrapper1.style.top).toBe('-32px'); + expect(domWrapper1.style.left).toBe('-32px'); + }); + + const modeExpectedDom = ['.t-popup', '.t-guide__dialog']; + (['popup', 'dialog'] as const).forEach((item, index) => { + test(`mode is equal to ${item}`, async () => { + const { container } = getCustomGuideStepMount({ mode: item }); + await mockDelay(60); + const modeExpectedDomIndexDom = document.querySelector(modeExpectedDom[index]); + expect(modeExpectedDomIndexDom).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + }); + + test(`nextButtonProps is equal to {theme: 'warning'}`, async () => { + getCustomMultipleGuideStepMount({ + current: 1, + nextButtonProps: { theme: 'warning' }, + }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-guide__next'); + expect(domWrapper).toHaveClass('t-button--theme-warning'); + }); + + test(`placement is equal to bottom-left`, async () => { + getCustomGuideStepMount({ placement: 'bottom-left' }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-popup'); + expect(domWrapper.getAttribute('data-popper-placement')).toBe('bottom-start'); + expect(document.body).toMatchSnapshot(); + }); + + test(`popupProps is equal to {placement: 'top-left'}`, async () => { + getCustomGuideStepMount({ popupProps: { placement: 'top-left' } }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-popup'); + expect(domWrapper.getAttribute('data-popper-placement')).toBe('top-start'); + }); + + test(`prevButtonProps is equal to {theme: 'warning'}`, async () => { + getCustomMultipleGuideStepMount({ + current: 2, + prevButtonProps: { theme: 'warning' }, + }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-guide__prev'); + expect(domWrapper).toHaveClass('t-button--theme-warning'); + }); + + test('showOverlay: .t-guide__highlight--mask should exit if showOverlay=true', async () => { + getCustomMultipleGuideStepMount({ showOverlay: true }); + await mockDelay(60); + const tGuideHighlightMaskDom = document.querySelectorAll('.t-guide__highlight--mask'); + expect(tGuideHighlightMaskDom.length).toBe(1); + }); + + test('showOverlay: .t-guide__highlight--mask should not exit if showOverlay=false', async () => { + getCustomMultipleGuideStepMount({ showOverlay: false }); + await mockDelay(60); + const tGuideHighlightMaskDom = document.querySelector('.t-guide__highlight--mask'); + expect(tGuideHighlightMaskDom).toBeFalsy(); + }); + + test(`skipButtonProps is equal to {theme: 'warning'}`, async () => { + getCustomMultipleGuideStepMount({ + current: 1, + skipButtonProps: { theme: 'warning' }, + }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-guide__skip'); + expect(domWrapper).toHaveClass('t-button--theme-warning'); + }); + + test(`stepOverlayClass is equal to t-test-guide-step-overlay`, async () => { + getCustomGuideStepMount({ + stepOverlayClass: 't-test-guide-step-overlay', + }); + await mockDelay(60); + const domWrapper = document.querySelector('.t-popup'); + expect(domWrapper).toHaveClass('t-test-guide-step-overlay'); + expect(document.body).toMatchSnapshot(); + }); + test('body', async () => { + getCustomGuideStepMount({ + body: TNode, + }); + await mockDelay(60); + const customNodeDom = document.querySelector('.custom-node'); + expect(customNodeDom).toBeTruthy(); + expect(document.body).toMatchSnapshot(); + }); + + test('children', async () => { + getCustomGuideStepMount({ + children: TNode, + }); + await mockDelay(60); + const customNodeDom = document.querySelector('.custom-node'); + expect(customNodeDom).toBeTruthy(); + expect(document.body).toMatchSnapshot(); + }); + + test('content', async () => { + getCustomGuideStepMount({ + content: TNode, + }); + await mockDelay(60); + const customNodeDom = document.querySelector('.custom-node'); + expect(customNodeDom).toBeTruthy(); + expect(document.body).toMatchSnapshot(); + }); + + test('highlightContent', async () => { + getCustomGuideStepMount({ + highlightContent: TNode, + }); + await mockDelay(60); + const customNodeDom = document.querySelector('.custom-node'); + expect(customNodeDom).toBeTruthy(); + expect(document.body).toMatchSnapshot(); + }); + + test('title', async () => { + getCustomGuideStepMount({ + title: TNode, + }); + await mockDelay(60); + const customNodeDom = document.querySelector('.custom-node'); + expect(customNodeDom).toBeTruthy(); + expect(document.body).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/components/guide/__tests__/mount.jsx b/packages/components/guide/__tests__/mount.jsx deleted file mode 100644 index 40d6fc0bb9..0000000000 --- a/packages/components/guide/__tests__/mount.jsx +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { render } from '@test/utils'; -import Input from '../../input'; -import Button from '../../button'; -import { Row } from '../../grid'; - -function GuideContent() { - return ( -
-
-
Guide 用户引导
-
按钮用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。
-
-
-
Label
- -
-
-
Label
- -
- - - - -
- ); -} - -const STEPS = [ - { - element: '.main-title-base', - title: '新手引导标题', - body: '新手引导的说明文案', - placement: 'bottom-right', - }, - { - element: () => document.body.querySelector('.label-field-base'), - title: '新手引导标题', - body: '新手引导的说明文案', - placement: 'bottom', - }, - { - element: '.action-base', - title: '新手引导标题', - body: '新手引导的说明文案', - placement: 'right', - }, -]; - -// only one step -export function getGuideDefaultMount(Guide, props, events) { - return render( -
- - -
- ); -} - -// three steps -export function getGuideMultipleStepsMount(Guide, props, events) { - return render( -
- - -
- ); -} - -// custom step props -export function getCustomGuideStepMount(Guide, props) { - const steps = [{ ...STEPS[0], ...props}] - return render( -
- - -
- ); -} - -// custom multiple step props -export function getCustomMultipleGuideStepMount(Guide, props) { - const { current = 0, ...guideStepProps } = props; - let steps = [...STEPS]; - steps[current] = { ...STEPS[current], ...guideStepProps }; - return render( -
- - -
- ); -} - -export default {} diff --git a/packages/components/guide/__tests__/vitest-guide.test.jsx b/packages/components/guide/__tests__/vitest-guide.test.jsx deleted file mode 100644 index 7db3285870..0000000000 --- a/packages/components/guide/__tests__/vitest-guide.test.jsx +++ /dev/null @@ -1,399 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * 该文件由脚本自动生成,如需修改请联系 PMC - * This file generated by scripts of tdesign-api. `npm run api:docs Guide React(PC) vitest,finalProject` - * If you need to modify this file, contact PMC first please. - */ -import React from 'react'; -import { fireEvent, vi, render, mockDelay } from '@test/utils'; -import { Guide } from '..'; -import { - getGuideDefaultMount, - getGuideMultipleStepsMount, - getCustomGuideStepMount, - getCustomMultipleGuideStepMount, -} from './mount'; - -describe('Guide Component', () => { - it('props.counter works fine', async () => { - getGuideDefaultMount(Guide, { counter: TNode }); - await mockDelay(60); - const customNodeDom = document.querySelector('.custom-node'); - expect(customNodeDom).toBeTruthy(); - const tGuideCounterDom = document.querySelector('.t-guide__counter'); - expect(tGuideCounterDom).toBeTruthy(); - }); - - it('props.counter is a function with params', async () => { - const fn = vi.fn(); - getGuideDefaultMount(Guide, { counter: fn }); - await mockDelay(60); - expect(fn).toHaveBeenCalled(); - expect(fn.mock.calls[0][0].total).toBe(1); - expect(fn.mock.calls[0][0].current).toBe(0); - }); - - it('props.current works fine.', async () => { - getGuideMultipleStepsMount(Guide, { current: 0 }); - await mockDelay(60); - const tGuideCounterDom = document.querySelector('.t-guide__counter'); - expect(tGuideCounterDom.textContent).toBe('1/3'); - const tGuideTitleDom = document.querySelectorAll('.t-guide__title'); - expect(tGuideTitleDom.length).toBe(1); - const tGuideDescDom = document.querySelectorAll('.t-guide__desc'); - expect(tGuideDescDom.length).toBe(1); - const tGuideActionTGuideSkipDom = document.querySelectorAll('.t-guide__action .t-guide__skip'); - expect(tGuideActionTGuideSkipDom.length).toBe(1); - const tGuideActionTGuidePrevDom = document.querySelector('.t-guide__action .t-guide__prev'); - expect(tGuideActionTGuidePrevDom).toBeFalsy(); - const tGuideActionTGuideNextDom = document.querySelectorAll('.t-guide__action .t-guide__next'); - expect(tGuideActionTGuideNextDom.length).toBe(1); - const tGuideActionTGuideFinishDom = document.querySelector('.t-guide__action .t-guide__finish'); - expect(tGuideActionTGuideFinishDom).toBeFalsy(); - }); - - it('props.current works fine.', async () => { - getGuideMultipleStepsMount(Guide, { current: 1 }); - await mockDelay(60); - const tGuideCounterDom = document.querySelector('.t-guide__counter'); - expect(tGuideCounterDom.textContent).toBe('2/3'); - const tGuideTitleDom = document.querySelectorAll('.t-guide__title'); - expect(tGuideTitleDom.length).toBe(1); - const tGuideDescDom = document.querySelectorAll('.t-guide__desc'); - expect(tGuideDescDom.length).toBe(1); - const tGuideActionTGuideSkipDom = document.querySelectorAll('.t-guide__action .t-guide__skip'); - expect(tGuideActionTGuideSkipDom.length).toBe(1); - const tGuideActionTGuidePrevDom = document.querySelectorAll('.t-guide__action .t-guide__prev'); - expect(tGuideActionTGuidePrevDom.length).toBe(1); - const tGuideActionTGuideNextDom = document.querySelectorAll('.t-guide__action .t-guide__next'); - expect(tGuideActionTGuideNextDom.length).toBe(1); - const tGuideActionTGuideFinishDom = document.querySelector('.t-guide__action .t-guide__finish'); - expect(tGuideActionTGuideFinishDom).toBeFalsy(); - }); - - it('props.current works fine.', async () => { - getGuideMultipleStepsMount(Guide, { current: 2 }); - await mockDelay(60); - const tGuideCounterDom = document.querySelector('.t-guide__counter'); - expect(tGuideCounterDom.textContent).toBe('3/3'); - const tGuideTitleDom = document.querySelectorAll('.t-guide__title'); - expect(tGuideTitleDom.length).toBe(1); - const tGuideDescDom = document.querySelectorAll('.t-guide__desc'); - expect(tGuideDescDom.length).toBe(1); - const tGuideActionTGuideSkipDom = document.querySelector('.t-guide__action .t-guide__skip'); - expect(tGuideActionTGuideSkipDom).toBeFalsy(); - const tGuideActionTGuidePrevDom = document.querySelectorAll('.t-guide__action .t-guide__prev'); - expect(tGuideActionTGuidePrevDom.length).toBe(1); - const tGuideActionTGuideNextDom = document.querySelector('.t-guide__action .t-guide__next'); - expect(tGuideActionTGuideNextDom).toBeFalsy(); - const tGuideActionTGuideFinishDom = document.querySelectorAll('.t-guide__action .t-guide__finish'); - expect(tGuideActionTGuideFinishDom.length).toBe(1); - }); - - it('props.current works fine. `{"document.t-guide__counter":false}` should exist', async () => { - getGuideMultipleStepsMount(Guide, { current: -1 }); - await mockDelay(60); - const tGuideCounterDom = document.querySelector('.t-guide__counter'); - expect(tGuideCounterDom).toBeFalsy(); - }); - - it(`props.finishButtonProps is equal to {theme: 'warning'}`, async () => { - getGuideMultipleStepsMount(Guide, { current: 2, finishButtonProps: { theme: 'warning' } }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-guide__finish'); - expect(domWrapper).toHaveClass('t-button--theme-warning'); - }); - - it('props.hideCounter works fine. `{"document.t-guide__counter":false}` should exist', async () => { - getGuideDefaultMount(Guide, { hideCounter: true }); - await mockDelay(60); - const tGuideCounterDom = document.querySelector('.t-guide__counter'); - expect(tGuideCounterDom).toBeFalsy(); - }); - - it('props.hidePrev works fine. `{"document.t-guide__action .t-guide__prev":false}` should exist', async () => { - getGuideMultipleStepsMount(Guide, { current: 1, hidePrev: true }); - await mockDelay(60); - const tGuideActionTGuidePrevDom = document.querySelector('.t-guide__action .t-guide__prev'); - expect(tGuideActionTGuidePrevDom).toBeFalsy(); - }); - - it('props.hideSkip works fine. `{"document.t-guide__action .t-guide__skip":false}` should exist', async () => { - getGuideMultipleStepsMount(Guide, { current: 1, hideSkip: true }); - await mockDelay(60); - const tGuideActionTGuideSkipDom = document.querySelector('.t-guide__action .t-guide__skip'); - expect(tGuideActionTGuideSkipDom).toBeFalsy(); - }); - - it(`props.highlightPadding is equal to 32`, async () => { - getGuideDefaultMount(Guide, { highlightPadding: 32 }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-guide__highlight--mask'); - expect(domWrapper.style.width).toBe('64px'); - expect(domWrapper.style.height).toBe('64px'); - expect(domWrapper.style.top).toBe('-32px'); - expect(domWrapper.style.left).toBe('-32px'); - const domWrapper1 = document.querySelector('.t-guide__reference'); - expect(domWrapper1.style.width).toBe('64px'); - expect(domWrapper1.style.height).toBe('64px'); - expect(domWrapper1.style.top).toBe('-32px'); - expect(domWrapper1.style.left).toBe('-32px'); - }); - - it(`props.nextButtonProps is equal to {theme: 'warning'}`, async () => { - getGuideMultipleStepsMount(Guide, { current: 1, nextButtonProps: { theme: 'warning' } }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-guide__next'); - expect(domWrapper).toHaveClass('t-button--theme-warning'); - }); - - it(`props.prevButtonProps is equal to {theme: 'warning'}`, async () => { - getGuideMultipleStepsMount(Guide, { current: 2, prevButtonProps: { theme: 'warning' } }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-guide__prev'); - expect(domWrapper).toHaveClass('t-button--theme-warning'); - }); - - it('props.showOverlay works fine. `{"document.t-guide__highlight--mask":1}` should exist', async () => { - getGuideDefaultMount(Guide, { showOverlay: true }); - await mockDelay(60); - const tGuideHighlightMaskDom = document.querySelectorAll('.t-guide__highlight--mask'); - expect(tGuideHighlightMaskDom.length).toBe(1); - }); - - it('props.showOverlay works fine. `{"document.t-guide__highlight--mask":false}` should exist', async () => { - getGuideDefaultMount(Guide, { showOverlay: false }); - await mockDelay(60); - const tGuideHighlightMaskDom = document.querySelector('.t-guide__highlight--mask'); - expect(tGuideHighlightMaskDom).toBeFalsy(); - }); - - it(`props.skipButtonProps is equal to {theme: 'warning'}`, async () => { - getGuideMultipleStepsMount(Guide, { current: 0, skipButtonProps: { theme: 'warning' } }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-guide__skip'); - expect(domWrapper).toHaveClass('t-button--theme-warning'); - }); - - it('props.steps works fine.', async () => { - getGuideDefaultMount(Guide); - await mockDelay(60); - const tGuideCounterDom = document.querySelector('.t-guide__counter'); - expect(tGuideCounterDom.textContent).toBe('1/1'); - const tGuideTitleDom = document.querySelectorAll('.t-guide__title'); - expect(tGuideTitleDom.length).toBe(1); - const tGuideDescDom = document.querySelectorAll('.t-guide__desc'); - expect(tGuideDescDom.length).toBe(1); - const tGuideActionTGuideSkipDom = document.querySelector('.t-guide__action .t-guide__skip'); - expect(tGuideActionTGuideSkipDom).toBeFalsy(); - const tGuideActionTGuidePrevDom = document.querySelector('.t-guide__action .t-guide__prev'); - expect(tGuideActionTGuidePrevDom).toBeFalsy(); - const tGuideActionTGuideNextDom = document.querySelector('.t-guide__action .t-guide__next'); - expect(tGuideActionTGuideNextDom).toBeFalsy(); - const tGuideActionTGuideFinishDom = document.querySelectorAll('.t-guide__action .t-guide__finish'); - expect(tGuideActionTGuideFinishDom.length).toBe(1); - }); - - it(`props.zIndex is equal to 5000`, async () => { - getGuideDefaultMount(Guide, { zIndex: '5000' }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-guide__overlay'); - expect(domWrapper.style.zIndex).toBe('4998'); - const domWrapper1 = document.querySelector('.t-guide__highlight--mask'); - expect(domWrapper1.style.zIndex).toBe('4999'); - }); - - it('events.change works fine', async () => { - const onChangeFn = vi.fn(); - getGuideMultipleStepsMount(Guide, { current: 0 }, { onChange: onChangeFn }); - await mockDelay(60); - fireEvent.click(document.querySelector('.t-guide__next')); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(1); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - expect(onChangeFn.mock.calls[0][1].total).toBe(3); - }); - - it('events.change works fine', async () => { - const onChangeFn = vi.fn(); - getGuideMultipleStepsMount(Guide, { current: 1 }, { onChange: onChangeFn }); - await mockDelay(60); - fireEvent.click(document.querySelector('.t-guide__prev')); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(0); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - expect(onChangeFn.mock.calls[0][1].total).toBe(3); - }); - - it('events.finish works fine', async () => { - const onFinishFn = vi.fn(); - getGuideMultipleStepsMount(Guide, { current: 2 }, { onFinish: onFinishFn }); - await mockDelay(60); - fireEvent.click(document.querySelector('.t-guide__finish')); - expect(onFinishFn).toHaveBeenCalled(); - expect(onFinishFn.mock.calls[0][0].current).toBe(2); - expect(onFinishFn.mock.calls[0][0].e.type).toBe('click'); - expect(onFinishFn.mock.calls[0][0].total).toBe(3); - }); - - it('events.nextStepClick works fine', async () => { - const onNextStepClickFn = vi.fn(); - getGuideMultipleStepsMount(Guide, { current: 1 }, { onNextStepClick: onNextStepClickFn }); - await mockDelay(60); - fireEvent.click(document.querySelector('.t-guide__next')); - expect(onNextStepClickFn).toHaveBeenCalled(); - expect(onNextStepClickFn.mock.calls[0][0].current).toBe(1); - expect(onNextStepClickFn.mock.calls[0][0].next).toBe(2); - expect(onNextStepClickFn.mock.calls[0][0].e.type).toBe('click'); - expect(onNextStepClickFn.mock.calls[0][0].total).toBe(3); - }); - - it('events.prevStepClick works fine', async () => { - const onPrevStepClickFn = vi.fn(); - getGuideMultipleStepsMount(Guide, { current: 1 }, { onPrevStepClick: onPrevStepClickFn }); - await mockDelay(60); - fireEvent.click(document.querySelector('.t-guide__prev')); - expect(onPrevStepClickFn).toHaveBeenCalled(); - expect(onPrevStepClickFn.mock.calls[0][0].current).toBe(1); - expect(onPrevStepClickFn.mock.calls[0][0].prev).toBe(0); - expect(onPrevStepClickFn.mock.calls[0][0].e.type).toBe('click'); - expect(onPrevStepClickFn.mock.calls[0][0].total).toBe(3); - }); - - it('events.skip works fine', async () => { - const onSkipFn = vi.fn(); - getGuideMultipleStepsMount(Guide, { current: 0 }, { onSkip: onSkipFn }); - await mockDelay(60); - fireEvent.click(document.querySelector('.t-guide__skip')); - expect(onSkipFn).toHaveBeenCalled(); - expect(onSkipFn.mock.calls[0][0].current).toBe(0); - expect(onSkipFn.mock.calls[0][0].e.type).toBe('click'); - expect(onSkipFn.mock.calls[0][0].total).toBe(3); - }); -}); - -describe('Guide Component', () => { - it('GuideStep.body works fine', async () => { - getCustomGuideStepMount(Guide, { body: TNode }); - await mockDelay(60); - const customNodeDom = document.querySelector('.custom-node'); - expect(customNodeDom).toBeTruthy(); - expect(document.body).toMatchSnapshot(); - }); - - it('GuideStep.children works fine', async () => { - getCustomGuideStepMount(Guide, { children: TNode }); - await mockDelay(60); - const customNodeDom = document.querySelector('.custom-node'); - expect(customNodeDom).toBeTruthy(); - expect(document.body).toMatchSnapshot(); - }); - - it('GuideStep.content works fine', async () => { - getCustomGuideStepMount(Guide, { content: TNode }); - await mockDelay(60); - const customNodeDom = document.querySelector('.custom-node'); - expect(customNodeDom).toBeTruthy(); - expect(document.body).toMatchSnapshot(); - }); - - it('GuideStep.highlightContent works fine', async () => { - getCustomGuideStepMount(Guide, { highlightContent: TNode }); - await mockDelay(60); - const customNodeDom = document.querySelector('.custom-node'); - expect(customNodeDom).toBeTruthy(); - expect(document.body).toMatchSnapshot(); - }); - - it(`GuideStep.highlightPadding is equal to 32`, async () => { - getCustomGuideStepMount(Guide, { highlightPadding: 32 }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-guide__highlight--mask'); - expect(domWrapper.style.width).toBe('64px'); - expect(domWrapper.style.height).toBe('64px'); - expect(domWrapper.style.top).toBe('-32px'); - expect(domWrapper.style.left).toBe('-32px'); - const domWrapper1 = document.querySelector('.t-guide__reference'); - expect(domWrapper1.style.width).toBe('64px'); - expect(domWrapper1.style.height).toBe('64px'); - expect(domWrapper1.style.top).toBe('-32px'); - expect(domWrapper1.style.left).toBe('-32px'); - }); - - const modeExpectedDom = ['.t-popup', '.t-guide__dialog']; - ['popup', 'dialog'].forEach((item, index) => { - it(`GuideStep.mode is equal to ${item}`, async () => { - const { container } = getCustomGuideStepMount(Guide, { mode: item }); - await mockDelay(60); - const modeExpectedDomIndexDom = document.querySelector(modeExpectedDom[index]); - expect(modeExpectedDomIndexDom).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - }); - - it(`GuideStep.nextButtonProps is equal to {theme: 'warning'}`, async () => { - getCustomMultipleGuideStepMount(Guide, { current: 1, nextButtonProps: { theme: 'warning' } }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-guide__next'); - expect(domWrapper).toHaveClass('t-button--theme-warning'); - }); - - it(`GuideStep.placement is equal to bottom-left`, async () => { - getCustomGuideStepMount(Guide, { placement: 'bottom-left' }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-popup'); - expect(domWrapper.getAttribute('data-popper-placement')).toBe('bottom-start'); - expect(document.body).toMatchSnapshot(); - }); - - it(`GuideStep.popupProps is equal to {placement: 'top-left'}`, async () => { - getCustomGuideStepMount(Guide, { popupProps: { placement: 'top-left' } }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-popup'); - expect(domWrapper.getAttribute('data-popper-placement')).toBe('top-start'); - }); - - it(`GuideStep.prevButtonProps is equal to {theme: 'warning'}`, async () => { - getCustomMultipleGuideStepMount(Guide, { current: 2, prevButtonProps: { theme: 'warning' } }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-guide__prev'); - expect(domWrapper).toHaveClass('t-button--theme-warning'); - }); - - it('props.showOverlay: .t-guide__highlight--mask should exit if showOverlay=true', async () => { - getCustomMultipleGuideStepMount(Guide, { showOverlay: true }); - await mockDelay(60); - const tGuideHighlightMaskDom = document.querySelectorAll('.t-guide__highlight--mask'); - expect(tGuideHighlightMaskDom.length).toBe(1); - }); - - it('props.showOverlay: .t-guide__highlight--mask should not exit if showOverlay=false', async () => { - getCustomMultipleGuideStepMount(Guide, { showOverlay: false }); - await mockDelay(60); - const tGuideHighlightMaskDom = document.querySelector('.t-guide__highlight--mask'); - expect(tGuideHighlightMaskDom).toBeFalsy(); - }); - - it(`GuideStep.skipButtonProps is equal to {theme: 'warning'}`, async () => { - getCustomMultipleGuideStepMount(Guide, { current: 1, skipButtonProps: { theme: 'warning' } }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-guide__skip'); - expect(domWrapper).toHaveClass('t-button--theme-warning'); - }); - - it(`GuideStep.stepOverlayClass is equal to t-test-guide-step-overlay`, async () => { - getCustomGuideStepMount(Guide, { stepOverlayClass: 't-test-guide-step-overlay' }); - await mockDelay(60); - const domWrapper = document.querySelector('.t-popup'); - expect(domWrapper).toHaveClass('t-test-guide-step-overlay'); - expect(document.body).toMatchSnapshot(); - }); - - it('GuideStep.title works fine', async () => { - getCustomGuideStepMount(Guide, { title: TNode }); - await mockDelay(60); - const customNodeDom = document.querySelector('.custom-node'); - expect(customNodeDom).toBeTruthy(); - expect(document.body).toMatchSnapshot(); - }); -}); diff --git a/packages/components/image-viewer/__tests__/__snapshots__/image-viewer.test.tsx.snap b/packages/components/image-viewer/__tests__/__snapshots__/image-viewer.test.tsx.snap index 7b300dbd00..c9e85d1c73 100644 --- a/packages/components/image-viewer/__tests__/__snapshots__/image-viewer.test.tsx.snap +++ b/packages/components/image-viewer/__tests__/__snapshots__/image-viewer.test.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`ImageViewer 工具栏操作快照 > 多图导航:上一张/下一张按钮快照 > toolbar-navigate-next 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 工具栏操作快照 > 多图导航:上一张/下一张按钮快照 > toolbar-navigate-next 1`] = `
@@ -79,7 +79,7 @@ exports[`ImageViewer 工具栏操作快照 > 多图导航:上一张/下一张 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-ra4-overlap-fill1)" + mask="url(#t-icon-image-instance-rcq-overlap-fill1)" /> 多图导航:上一张/下一张 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-ra5-overlap-fill1)" + mask="url(#t-icon-image-instance-rcr-overlap-fill1)" /> 多图导航:上一张/下一张 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-ra6-overlap-fill1)" + mask="url(#t-icon-image-instance-rcs-overlap-fill1)" /> 多图导航:上一张/下一张 多图导航:上一张/下一张 > 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-rab-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-rd1-overlap-fill1)" /> 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-rac-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-rd2-overlap-fill1)" /> 多图导航:上一张/下一张 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-rad-overlap-fill1)" + mask="url(#t-icon-image-instance-rd3-overlap-fill1)" /> 多图导航:上一张/下一张 `; -exports[`ImageViewer 工具栏操作快照 > 多图导航:上一张/下一张按钮快照 > toolbar-navigate-prev 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 工具栏操作快照 > 多图导航:上一张/下一张按钮快照 > toolbar-navigate-prev 1`] = `
@@ -1000,7 +1000,7 @@ exports[`ImageViewer 工具栏操作快照 > 多图导航:上一张/下一张 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-ra4-overlap-fill1)" + mask="url(#t-icon-image-instance-rcq-overlap-fill1)" /> 多图导航:上一张/下一张 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-ra5-overlap-fill1)" + mask="url(#t-icon-image-instance-rcr-overlap-fill1)" /> 多图导航:上一张/下一张 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-ra6-overlap-fill1)" + mask="url(#t-icon-image-instance-rcs-overlap-fill1)" /> 多图导航:上一张/下一张 多图导航:上一张/下一张 > 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-rab-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-rd1-overlap-fill1)" /> 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-rac-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-rd2-overlap-fill1)" /> 多图导航:上一张/下一张 多图导航:上一张/下一张 多图导航:上一张/下一张 d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-rad-overlap-fill1)" + mask="url(#t-icon-image-instance-rd3-overlap-fill1)" /> 多图导航:上一张/下一张 `; -exports[`ImageViewer 工具栏操作快照 > 点击原始大小按钮后快照 > toolbar-after-reset 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 工具栏操作快照 > 点击原始大小按钮后快照 > toolbar-after-reset 1`] = `
@@ -1876,7 +1876,7 @@ exports[`ImageViewer 工具栏操作快照 > 点击原始大小按钮后快照 > 点击原始大小按钮后快照 > > 点击原始大小按钮后快照 > 点击原始大小按钮后快照 > d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r9u-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-rck-overlap-fill1)" /> 点击原始大小按钮后快照 > 点击原始大小按钮后快照 > d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r9v-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-rcl-overlap-fill1)" /> 点击原始大小按钮后快照 > 点击原始大小按钮后快照 > 点击原始大小按钮后快照 > d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-ra0-overlap-fill1)" + mask="url(#t-icon-image-instance-rcm-overlap-fill1)" /> 点击原始大小按钮后快照 > `; -exports[`ImageViewer 工具栏操作快照 > 点击放大按钮后快照 > toolbar-after-zoom-in 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 工具栏操作快照 > 点击放大按钮后快照 > toolbar-after-zoom-in 1`] = `
@@ -2334,7 +2334,7 @@ exports[`ImageViewer 工具栏操作快照 > 点击放大按钮后快照 > toolb 点击放大按钮后快照 > toolb > 点击放大按钮后快照 > toolb 点击放大按钮后快照 > toolb d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r99-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-rbv-overlap-fill1)" /> 点击放大按钮后快照 > toolb 点击放大按钮后快照 > toolb d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r9a-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-rc0-overlap-fill1)" /> 点击放大按钮后快照 > toolb 点击放大按钮后快照 > toolb 点击放大按钮后快照 > toolb d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r9b-overlap-fill1)" + mask="url(#t-icon-image-instance-rc1-overlap-fill1)" /> 点击放大按钮后快照 > toolb `; -exports[`ImageViewer 工具栏操作快照 > 点击旋转按钮后快照 > toolbar-after-rotate 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 工具栏操作快照 > 点击旋转按钮后快照 > toolbar-after-rotate 1`] = `
@@ -2792,7 +2792,7 @@ exports[`ImageViewer 工具栏操作快照 > 点击旋转按钮后快照 > toolb 点击旋转按钮后快照 > toolb > 点击旋转按钮后快照 > toolb 点击旋转按钮后快照 > toolb d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r92-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-rbo-overlap-fill1)" /> 点击旋转按钮后快照 > toolb 点击旋转按钮后快照 > toolb d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r93-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-rbp-overlap-fill1)" /> 点击旋转按钮后快照 > toolb 点击旋转按钮后快照 > toolb 点击旋转按钮后快照 > toolb d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r94-overlap-fill1)" + mask="url(#t-icon-image-instance-rbq-overlap-fill1)" /> 点击旋转按钮后快照 > toolb `; -exports[`ImageViewer 工具栏操作快照 > 点击缩小按钮后快照 > toolbar-after-zoom-out 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 工具栏操作快照 > 点击缩小按钮后快照 > toolbar-after-zoom-out 1`] = `
@@ -3250,7 +3250,7 @@ exports[`ImageViewer 工具栏操作快照 > 点击缩小按钮后快照 > toolb 点击缩小按钮后快照 > toolb > 点击缩小按钮后快照 > toolb 点击缩小按钮后快照 > toolb d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r9g-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-rc6-overlap-fill1)" /> 点击缩小按钮后快照 > toolb 点击缩小按钮后快照 > toolb d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r9h-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-rc7-overlap-fill1)" /> 点击缩小按钮后快照 > toolb 点击缩小按钮后快照 > toolb 点击缩小按钮后快照 > toolb d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r9i-overlap-fill1)" + mask="url(#t-icon-image-instance-rc8-overlap-fill1)" /> 点击缩小按钮后快照 > toolb `; -exports[`ImageViewer 工具栏操作快照 > 点击镜像按钮后快照 > toolbar-after-mirror 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 工具栏操作快照 > 点击镜像按钮后快照 > toolbar-after-mirror 1`] = `
@@ -3708,7 +3708,7 @@ exports[`ImageViewer 工具栏操作快照 > 点击镜像按钮后快照 > toolb 点击镜像按钮后快照 > toolb > 点击镜像按钮后快照 > toolb 点击镜像按钮后快照 > toolb d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r9n-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-rcd-overlap-fill1)" /> 点击镜像按钮后快照 > toolb 点击镜像按钮后快照 > toolb d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r9o-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-rce-overlap-fill1)" /> 点击镜像按钮后快照 > toolb 点击镜像按钮后快照 > toolb 点击镜像按钮后快照 > toolb d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r9p-overlap-fill1)" + mask="url(#t-icon-image-instance-rcf-overlap-fill1)" /> 点击镜像按钮后快照 > toolb `; -exports[`ImageViewer 快照测试 > DefaultTrigger 多张图片默认触发器 > image-viewer-default-trigger-multi 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > DefaultTrigger 多张图片默认触发器 > image-viewer-default-trigger-multi 1`] = `
DefaultTrigger 多张图片默认触发器 > DefaultTrigger 多张图片默认触发器 > DefaultTrigger 多张图片默认触发器 > d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r76-overlap-fill1)" + mask="url(#t-icon-image-instance-r9s-overlap-fill1)" /> DefaultTrigger 多张图片默认触发器 > DefaultTrigger 多张图片默认触发器 > DefaultTrigger 多张图片默认触发器 > d="M11.9997 4C6.86881 4 2.52275 7.36017 1.04199 12C2.52275 16.6398 6.86881 20 11.9997 20C17.1306 20 21.4766 16.6398 22.9574 12C21.4766 7.36017 17.1306 4 11.9997 4Z" fill="transparent" id="fill1" - mask="url(#t-icon-browse-instance-r77-overlap-fill1)" + mask="url(#t-icon-browse-instance-r9t-overlap-fill1)" /> DefaultTrigger 多张图片默认触发器 >
`; -exports[`ImageViewer 快照测试 > DefaultTrigger 默认触发器 > image-viewer-default-trigger 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > DefaultTrigger 默认触发器 > image-viewer-default-trigger 1`] = `
DefaultTrigger 默认触发器 > image-viewe DefaultTrigger 默认触发器 > image-viewe DefaultTrigger 默认触发器 > image-viewe d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r74-overlap-fill1)" + mask="url(#t-icon-image-instance-r9q-overlap-fill1)" /> DefaultTrigger 默认触发器 > image-viewe DefaultTrigger 默认触发器 > image-viewe DefaultTrigger 默认触发器 > image-viewe d="M11.9997 4C6.86881 4 2.52275 7.36017 1.04199 12C2.52275 16.6398 6.86881 20 11.9997 20C17.1306 20 21.4766 16.6398 22.9574 12C21.4766 7.36017 17.1306 4 11.9997 4Z" fill="transparent" id="fill1" - mask="url(#t-icon-browse-instance-r75-overlap-fill1)" + mask="url(#t-icon-browse-instance-r9r-overlap-fill1)" /> DefaultTrigger 默认触发器 > image-viewe
`; -exports[`ImageViewer 快照测试 > attach 到指定容器 > image-viewer-attach-custom-container 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > attach 到指定容器 > image-viewer-attach-custom-container 1`] = `
@@ -4643,7 +4643,7 @@ exports[`ImageViewer 快照测试 > attach 到指定容器 > image-viewer-attach attach 到指定容器 > image-viewer-attach > attach 到指定容器 > image-viewer-attach attach 到指定容器 > image-viewer-attach d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r6v-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-r9l-overlap-fill1)" /> attach 到指定容器 > image-viewer-attach attach 到指定容器 > image-viewer-attach d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r70-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-r9m-overlap-fill1)" /> attach 到指定容器 > image-viewer-attach attach 到指定容器 > image-viewer-attach attach 到指定容器 > image-viewer-attach d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r71-overlap-fill1)" + mask="url(#t-icon-image-instance-r9n-overlap-fill1)" /> attach 到指定容器 > image-viewer-attach
`; -exports[`ImageViewer 快照测试 > modeless 模式 > image-viewer-modeless 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > modeless 模式 > image-viewer-modeless 1`] = `
@@ -5185,7 +5185,7 @@ exports[`ImageViewer 快照测试 > modeless 模式 > image-viewer-modeless 1`] modeless 模式 > image-viewer-modeless 1`] > modeless 模式 > image-viewer-modeless 1`] modeless 模式 > image-viewer-modeless 1`] d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r66-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-r8s-overlap-fill1)" /> modeless 模式 > image-viewer-modeless 1`] modeless 模式 > image-viewer-modeless 1`] d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r67-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-r8t-overlap-fill1)" /> modeless 模式 > image-viewer-modeless 1`] modeless 模式 > image-viewer-modeless 1`] modeless 模式 > image-viewer-modeless 1`] d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r68-overlap-fill1)" + mask="url(#t-icon-image-instance-r8u-overlap-fill1)" /> modeless 模式 > image-viewer-modeless 1`] `; -exports[`ImageViewer 快照测试 > modeless 模式切换图片 > image-viewer-modeless-second-image 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > modeless 模式切换图片 > image-viewer-modeless-second-image 1`] = `
@@ -5686,7 +5686,7 @@ exports[`ImageViewer 快照测试 > modeless 模式切换图片 > image-viewer-m modeless 模式切换图片 > image-viewer-m > modeless 模式切换图片 > image-viewer-m modeless 模式切换图片 > image-viewer-m d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r6d-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-r93-overlap-fill1)" /> modeless 模式切换图片 > image-viewer-m modeless 模式切换图片 > image-viewer-m d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r6e-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-r94-overlap-fill1)" /> modeless 模式切换图片 > image-viewer-m modeless 模式切换图片 > image-viewer-m modeless 模式切换图片 > image-viewer-m d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r6f-overlap-fill1)" + mask="url(#t-icon-image-instance-r95-overlap-fill1)" /> modeless 模式切换图片 > image-viewer-m `; -exports[`ImageViewer 快照测试 > 不可拖拽 > image-viewer-not-draggable 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > 不可拖拽 > image-viewer-not-draggable 1`] = `
@@ -6103,7 +6103,7 @@ exports[`ImageViewer 快照测试 > 不可拖拽 > image-viewer-not-draggable 1` 不可拖拽 > image-viewer-not-draggable 1` > 不可拖拽 > image-viewer-not-draggable 1` 不可拖拽 > image-viewer-not-draggable 1` d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r5i-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-r88-overlap-fill1)" /> 不可拖拽 > image-viewer-not-draggable 1` 不可拖拽 > image-viewer-not-draggable 1` d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r5j-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-r89-overlap-fill1)" /> 不可拖拽 > image-viewer-not-draggable 1` 不可拖拽 > image-viewer-not-draggable 1` 不可拖拽 > image-viewer-not-draggable 1` d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r5k-overlap-fill1)" + mask="url(#t-icon-image-instance-r8a-overlap-fill1)" /> 不可拖拽 > image-viewer-not-draggable 1` `; -exports[`ImageViewer 快照测试 > 关闭按钮为 false > image-viewer-close-btn-disabled 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > 关闭按钮为 false > image-viewer-close-btn-disabled 1`] = `
@@ -6561,7 +6561,7 @@ exports[`ImageViewer 快照测试 > 关闭按钮为 false > image-viewer-close-b 关闭按钮为 false > image-viewer-close-b > 关闭按钮为 false > image-viewer-close-b 关闭按钮为 false > image-viewer-close-b d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r55-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-r7r-overlap-fill1)" /> 关闭按钮为 false > image-viewer-close-b 关闭按钮为 false > image-viewer-close-b d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r56-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-r7s-overlap-fill1)" /> 关闭按钮为 false > image-viewer-close-b 关闭按钮为 false > image-viewer-close-b 关闭按钮为 false > image-viewer-close-b d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r57-overlap-fill1)" + mask="url(#t-icon-image-instance-r7t-overlap-fill1)" /> 关闭按钮为 false > image-viewer-close-b `; -exports[`ImageViewer 快照测试 > 单张图片默认状态 > image-viewer-single-image-default 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > 单张图片默认状态 > image-viewer-single-image-default 1`] = `
@@ -6995,7 +6995,7 @@ exports[`ImageViewer 快照测试 > 单张图片默认状态 > image-viewer-sing 单张图片默认状态 > image-viewer-sing > 单张图片默认状态 > image-viewer-sing 单张图片默认状态 > image-viewer-sing d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r4b-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-r71-overlap-fill1)" /> 单张图片默认状态 > image-viewer-sing 单张图片默认状态 > image-viewer-sing d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r4c-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-r72-overlap-fill1)" /> 单张图片默认状态 > image-viewer-sing 单张图片默认状态 > image-viewer-sing 单张图片默认状态 > image-viewer-sing d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r4d-overlap-fill1)" + mask="url(#t-icon-image-instance-r73-overlap-fill1)" /> 单张图片默认状态 > image-viewer-sing `; -exports[`ImageViewer 快照测试 > 多张图片默认状态 > image-viewer-multi-image-default 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > 多张图片默认状态 > image-viewer-multi-image-default 1`] = `
@@ -7498,7 +7498,7 @@ exports[`ImageViewer 快照测试 > 多张图片默认状态 > image-viewer-mult 多张图片默认状态 > image-viewer-mult 多张图片默认状态 > image-viewer-mult d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r4h-overlap-fill1)" + mask="url(#t-icon-image-instance-r77-overlap-fill1)" /> 多张图片默认状态 > image-viewer-mult 多张图片默认状态 > image-viewer-mult 多张图片默认状态 > image-viewer-mult d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r4i-overlap-fill1)" + mask="url(#t-icon-image-instance-r78-overlap-fill1)" /> 多张图片默认状态 > image-viewer-mult 多张图片默认状态 > image-viewer-mult 多张图片默认状态 > image-viewer-mult d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r4j-overlap-fill1)" + mask="url(#t-icon-image-instance-r79-overlap-fill1)" /> 多张图片默认状态 > image-viewer-mult 多张图片默认状态 > image-viewer-mult > 多张图片默认状态 > image-viewer-mult 多张图片默认状态 > image-viewer-mult d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r4o-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-r7e-overlap-fill1)" /> 多张图片默认状态 > image-viewer-mult 多张图片默认状态 > image-viewer-mult d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r4p-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-r7f-overlap-fill1)" /> 多张图片默认状态 > image-viewer-mult 多张图片默认状态 > image-viewer-mult 多张图片默认状态 > image-viewer-mult d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r4q-overlap-fill1)" + mask="url(#t-icon-image-instance-r7g-overlap-fill1)" /> 多张图片默认状态 > image-viewer-mult `; -exports[`ImageViewer 快照测试 > 无遮罩层 > image-viewer-no-overlay 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > 无遮罩层 > image-viewer-no-overlay 1`] = `
@@ -8371,7 +8371,7 @@ exports[`ImageViewer 快照测试 > 无遮罩层 > image-viewer-no-overlay 1`] = 无遮罩层 > image-viewer-no-overlay 1`] = > 无遮罩层 > image-viewer-no-overlay 1`] = 无遮罩层 > image-viewer-no-overlay 1`] = d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r5b-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-r81-overlap-fill1)" /> 无遮罩层 > image-viewer-no-overlay 1`] = 无遮罩层 > image-viewer-no-overlay 1`] = d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r5c-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-r82-overlap-fill1)" /> 无遮罩层 > image-viewer-no-overlay 1`] = 无遮罩层 > image-viewer-no-overlay 1`] = 无遮罩层 > image-viewer-no-overlay 1`] = d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r5d-overlap-fill1)" + mask="url(#t-icon-image-instance-r83-overlap-fill1)" /> 无遮罩层 > image-viewer-no-overlay 1`] = `; -exports[`ImageViewer 快照测试 > 自定义 imageScale 配置 > image-viewer-custom-scale-config 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > 自定义 imageScale 配置 > image-viewer-custom-scale-config 1`] = `
@@ -8874,7 +8874,7 @@ exports[`ImageViewer 快照测试 > 自定义 imageScale 配置 > image-viewer-c 自定义 imageScale 配置 > image-viewer-c 自定义 imageScale 配置 > image-viewer-c d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r5o-overlap-fill1)" + mask="url(#t-icon-image-instance-r8e-overlap-fill1)" /> 自定义 imageScale 配置 > image-viewer-c 自定义 imageScale 配置 > image-viewer-c 自定义 imageScale 配置 > image-viewer-c d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r5p-overlap-fill1)" + mask="url(#t-icon-image-instance-r8f-overlap-fill1)" /> 自定义 imageScale 配置 > image-viewer-c 自定义 imageScale 配置 > image-viewer-c > 自定义 imageScale 配置 > image-viewer-c 自定义 imageScale 配置 > image-viewer-c d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r5u-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-r8k-overlap-fill1)" /> 自定义 imageScale 配置 > image-viewer-c 自定义 imageScale 配置 > image-viewer-c d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r5v-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-r8l-overlap-fill1)" /> 自定义 imageScale 配置 > image-viewer-c 自定义 imageScale 配置 > image-viewer-c 自定义 imageScale 配置 > image-viewer-c d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r60-overlap-fill1)" + mask="url(#t-icon-image-instance-r8m-overlap-fill1)" /> 自定义 imageScale 配置 > image-viewer-c `; -exports[`ImageViewer 快照测试 > 自定义 title > image-viewer-custom-title 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > 自定义 title > image-viewer-custom-title 1`] = `
@@ -9671,7 +9671,7 @@ exports[`ImageViewer 快照测试 > 自定义 title > image-viewer-custom-title 自定义 title > image-viewer-custom-title 自定义 title > image-viewer-custom-title d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r6i-overlap-fill1)" + mask="url(#t-icon-image-instance-r98-overlap-fill1)" /> 自定义 title > image-viewer-custom-title 自定义 title > image-viewer-custom-title 自定义 title > image-viewer-custom-title d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r6j-overlap-fill1)" + mask="url(#t-icon-image-instance-r99-overlap-fill1)" /> 自定义 title > image-viewer-custom-title 自定义 title > image-viewer-custom-title > 自定义 title > image-viewer-custom-title 自定义 title > image-viewer-custom-title d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r6o-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-r9e-overlap-fill1)" /> 自定义 title > image-viewer-custom-title 自定义 title > image-viewer-custom-title d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r6p-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-r9f-overlap-fill1)" /> 自定义 title > image-viewer-custom-title 自定义 title > image-viewer-custom-title 自定义 title > image-viewer-custom-title d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r6q-overlap-fill1)" + mask="url(#t-icon-image-instance-r9g-overlap-fill1)" /> 自定义 title > image-viewer-custom-title `; -exports[`ImageViewer 快照测试 > 自定义关闭按钮 > image-viewer-custom-close-btn 1`] = ` +exports[`ImageViewer > scenarios > ImageViewer 快照测试 > 自定义关闭按钮 > image-viewer-custom-close-btn 1`] = `
@@ -10425,7 +10425,7 @@ exports[`ImageViewer 快照测试 > 自定义关闭按钮 > image-viewer-custom- 自定义关闭按钮 > image-viewer-custom- > 自定义关闭按钮 > image-viewer-custom- 自定义关闭按钮 > image-viewer-custom- d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-out-instance-r4v-overlap-fill1)" + mask="url(#t-icon-zoom-out-instance-r7l-overlap-fill1)" /> 自定义关闭按钮 > image-viewer-custom- 自定义关闭按钮 > image-viewer-custom- d="M15.8033 15.8033C12.8744 18.7322 8.12563 18.7322 5.1967 15.8033C2.26777 12.8744 2.26777 8.12563 5.1967 5.1967C8.12563 2.26777 12.8744 2.26777 15.8033 5.1967C18.7322 8.12563 18.7322 12.8744 15.8033 15.8033Z" fill="transparent" id="fill1" - mask="url(#t-icon-zoom-in-instance-r50-overlap-fill1)" + mask="url(#t-icon-zoom-in-instance-r7m-overlap-fill1)" /> 自定义关闭按钮 > image-viewer-custom- 自定义关闭按钮 > image-viewer-custom- 自定义关闭按钮 > image-viewer-custom- d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-r51-overlap-fill1)" + mask="url(#t-icon-image-instance-r7n-overlap-fill1)" /> { - const triggerText = '预览单张图片'; - - test('基本渲染和关闭', async () => { - const onClose = vi.fn(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - const imgContainer = document.querySelector('.t-image-viewer-preview-image'); - expect(imgContainer).toBeNull(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); +describe('ImageViewer', () => { + describe('props', () => { + // ─── innerClassName 属性 ────────────────────────────────────────────── + describe('innerClassName 属性', () => { + test('innerClassName 应用到图片容器', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>innerClassName测试; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const picEl = document.querySelector('.t-image-viewer__modal-pic'); + expect(picEl?.classList.contains('custom-inner')).toBe(true); + }); + }); + + // ─── showOverlay 为 false ───────────────────────────────────────────── + describe('showOverlay 属性', () => { + test('showOverlay 为 false 时不渲染遮罩', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>遮罩测试; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const mask = document.querySelector('.t-image-viewer__modal-mask'); + expect(mask).toBeNull(); + }); + }); + + // ─── closeOnOverlay 为 false ────────────────────────────────────────── + describe('closeOnOverlay 属性', () => { + test('closeOnOverlay 为 false 时点击遮罩不关闭', async () => { + const onClose = vi.fn(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>遮罩不关闭测试; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const mask = document.querySelector('.t-image-viewer__modal-mask'); + expect(mask).toBeTruthy(); + act(() => { + fireEvent.click(mask); + }); + expect(onClose).not.toHaveBeenCalled(); + }); + }); + // ─── closeBtn 为函数 ────────────────────────────────────────────────── + describe('closeBtn 为函数', () => { + test('closeBtn 为函数时渲染自定义关闭按钮', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>函数关闭按钮; + const CloseBtnNode = ({ onClose }: any) => ( + + ); + return } />; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const customBtn = document.querySelector('[data-testid="custom-close"]'); + expect(customBtn).toBeTruthy(); + expect(customBtn?.textContent).toBe('X'); + }); + }); + + // ─── title 为函数 ───────────────────────────────────────────────────── + describe('title 为 TNode', () => { + test('title 为 ReactNode 时渲染自定义标题', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>标题测试; + return ( + 自定义标题} + /> + ); + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const titleEl = document.querySelector('.custom-title-node'); + expect(titleEl).toBeTruthy(); + expect(titleEl?.textContent).toBe('自定义标题'); + }); + }); + }); + + describe('events', () => { + // ─── onClose trigger 来源验证 ─────────────────────────────────────────── + describe('onClose trigger 来源', () => { + const triggerText = '关闭来源测试'; + + test('点击关闭按钮时 trigger 为 close-btn', async () => { + const onClose = vi.fn(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const closeBtn = document.querySelector('.t-image-viewer__modal-close-bt'); + expect(closeBtn).toBeTruthy(); + act(() => { + fireEvent.click(closeBtn); + }); + expect(onClose).toHaveBeenCalledWith({ + trigger: 'close-btn', + e: expect.any(Object), + }); + }); + + test('点击遮罩时 trigger 为 overlay', async () => { + const onClose = vi.fn(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const mask = document.querySelector('.t-image-viewer__modal-mask'); + expect(mask).toBeTruthy(); + act(() => { + fireEvent.click(mask); + }); + expect(onClose).toHaveBeenCalledWith({ + trigger: 'overlay', + e: expect.any(Object), + }); + }); + + test('ESC 关闭时 trigger 为 esc', async () => { + const user = userEvent.setup(); + const onClose = vi.fn(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + await user.type(document.body, '{Escape}'); + expect(onClose).toHaveBeenCalledWith({ + trigger: 'esc', + e: expect.any(Object), + }); + }); + }); + + // ─── onDownload 回调 ──────────────────────────────────────────────────── + describe('onDownload 下载回调', () => { + const triggerText = '下载测试'; + + test('自定义 onDownload 时被调用', async () => { + const onDownload = vi.fn(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const downloadBtn = + document + .querySelector('.t-image-viewer__modal-icon [data-td-icon="download"]') + ?.closest('.t-image-viewer__modal-icon') ?? + document.querySelector('.t-icon-download')?.closest('.t-image-viewer__modal-icon'); + expect(downloadBtn).toBeTruthy(); + act(() => { + fireEvent.click(downloadBtn); + }); + expect(onDownload).toHaveBeenCalledWith(imgUrl); + }); + + test('currentImage.download 为 false 时不显示下载按钮', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const downloadIcon = document.querySelector('.t-icon-download'); + expect(downloadIcon).toBeNull(); + }); + }); + }); + + describe('instanceFunctions', () => { + // ─── open(index) 指定索引打开 ────────────────────────────────────────── + describe('open(index) 指定索引打开', () => { + test('open 传入 index 后显示对应图片', async () => { + const onIndexChange = vi.fn(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open(2)}>指定索引打开; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const indexText = document.querySelector('.t-image-viewer__modal-index'); + expect(indexText?.textContent).toContain('3/3'); + }); + }); + }); + + describe('scenarios', () => { + describe('ImageViewer 组件测试', () => { + const triggerText = '预览单张图片'; + + test('基本渲染和关闭', async () => { + const onClose = vi.fn(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + const imgContainer = document.querySelector('.t-image-viewer-preview-image'); + expect(imgContainer).toBeNull(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + expect(onClose).toHaveBeenCalledTimes(0); + const imgModal = document.querySelector('.t-image-viewer__modal-pic'); + expect(imgModal).toBeTruthy(); + + const closeBtn = document.querySelector('.t-image-viewer__modal-close-bt'); + act(() => { + fireEvent.click(closeBtn); + }); + expect(onClose).toHaveBeenCalledTimes(1); + await mockTimeout(() => expect(document.querySelector('.t-image-viewer-preview-image')).toBeNull()); + }); + + test('trigger 为非函数', async () => { + const BasicImageViewer = () => { + const trigger = {triggerText}; + return ; + }; + const { getByText } = render(); + expect(getByText(triggerText)).toBeTruthy(); + }); + + test('attach 默认为 body', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + const imgContainer = document.body.querySelector('.t-image-viewer-preview-image'); + expect(imgContainer).toBeNull(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + const imgModal = document.body.querySelector('.t-image-viewer__modal-pic'); + expect(imgModal).toBeTruthy(); + }); + + test('attach 为函数', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return document.body} />; + }; + const { getByText } = render(); + + const imgContainer = document.body.querySelector('.t-image-viewer-preview-image'); + expect(imgContainer).toBeNull(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + act(() => { + const imgModal = document.body.querySelector('.t-image-viewer__modal-pic'); + expect(imgModal).toBeTruthy(); + }); + }); + }); + + describe('ImageViewerMini 组件测试', () => { + const triggerText = '预览单张图片'; + + test('modeless 模式', async () => { + const onClose = vi.fn(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + const miniFooter = await waitFor(() => document.querySelector('.t-image-viewer-mini__footer')); + expect(miniFooter).toBeTruthy(); + + const closeBtn = await waitFor(() => document.querySelector('.t-icon-close')); + act(() => { + fireEvent.click(closeBtn); + }); + expect(onClose).toHaveBeenCalledTimes(1); + }); + }); + + describe('ImageViewerModal 组件测试', () => { + const triggerText = '预览单张图片'; + + test('键盘操作和遮罩关闭', async () => { + const user = userEvent.setup(); + const onClose = vi.fn(); + const onIndexChange = vi.fn(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ( + + ); + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await user.type(document.body, '{ArrowRight}'); + expect(onIndexChange).toHaveBeenCalledTimes(1); + + await user.type(document.body, '{ArrowLeft}'); + expect(onIndexChange).toHaveBeenCalledTimes(2); + + await user.type(document.body, '{Escape}'); + expect(onClose).toHaveBeenCalledTimes(1); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + const mask = await waitFor(() => document.querySelector('.t-image-viewer__modal-mask')); + act(() => { + fireEvent.click(mask); + }); + expect(onClose).toHaveBeenCalledTimes(2); + }); + + test('弹窗关闭后键盘事件失效', async () => { + const user = userEvent.setup(); + const onIndexChange = vi.fn(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + await user.type(document.body, '{ArrowRight}'); + expect(onIndexChange).toHaveBeenCalledTimes(1); + + await user.type(document.body, '{Escape}'); + + await user.type(document.body, '{ArrowRight}'); + await user.type(document.body, '{ArrowLeft}'); + expect(onIndexChange).toHaveBeenCalledTimes(1); + }); + + test('单图操作(缩放/旋转/重置)', async () => { + const user = userEvent.setup(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + const img = document.querySelector('.t-image-viewer__modal-image'); + expect(getComputedStyle(img).transform).toBe('rotateZ(0deg) scale(1)'); + + await user.type(document.body, '{ArrowUp}'); + expect(getComputedStyle(img).transform).toBe('rotateZ(0deg) scale(1.2)'); + + await user.type(document.body, '{ArrowDown}'); + expect(getComputedStyle(img).transform).toBe('rotateZ(0deg) scale(1)'); + + const rotateIcon = await waitFor(() => document.querySelector('.t-icon-rotation')); + act(() => { + fireEvent.click(rotateIcon); + }); + expect(getComputedStyle(img).transform).toBe('rotateZ(-90deg) scale(1)'); + + const resetIcon = await waitFor(() => document.querySelector('.t-icon-image')); + act(() => { + fireEvent.click(resetIcon); + }); + expect(getComputedStyle(img).transform).toBe('rotateZ(0deg) scale(1)'); + }); + + test('自定义关闭按钮', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return closeBtn} />; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + expect(getByText('closeBtn')).toBeTruthy(); + }); + + test('closeOnEscKeydown 为 false', async () => { + const user = userEvent.setup(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + expect(document.querySelector('.t-image-viewer-preview-image')).toBeInTheDocument(); + + await user.type(document.body, '{Escape}'); + await mockDelay(300); + expect(document.querySelector('.t-image-viewer-preview-image')).toBeInTheDocument(); + }); + + test('imageScale defaultScale 正常范围', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ( + + ); + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + + expect(document.querySelector('.t-image-viewer__modal-image')).toBeInTheDocument(); + expect(document.querySelector('.t-image-viewer__modal-image')).toHaveStyle({ + transform: 'rotateZ(0deg) scale(2)', + }); + }); + + test('imageScale defaultScale 超过 max 被截断', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ( + + ); + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + + expect(document.querySelector('.t-image-viewer__modal-image')).toBeInTheDocument(); + expect(document.querySelector('.t-image-viewer__modal-image')).toHaveStyle({ + transform: 'rotateZ(0deg) scale(3)', + }); + }); + + test('imageScale defaultScale 低于 min 被截断', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ( + + ); + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + + expect(document.querySelector('.t-image-viewer__modal-image')).toBeInTheDocument(); + expect(document.querySelector('.t-image-viewer__modal-image')).toHaveStyle({ + transform: 'rotateZ(0deg) scale(2.5)', + }); + }); + + test('imageScale max 意外小于 min 时使用 min', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ( + + ); + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + + expect(document.querySelector('.t-image-viewer__modal-image')).toBeInTheDocument(); + expect(document.querySelector('.t-image-viewer__modal-image')).toHaveStyle({ + transform: 'rotateZ(0deg) scale(2.5)', + }); + }); + + test('imageReferrerpolicy 属性', async () => { + const referrerPolicy = 'strict-origin-when-cross-origin'; + + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + + expect(document.querySelector('.t-image-viewer__modal-image')?.getAttribute('referrerpolicy')).toBe( + referrerPolicy, + ); + }); + }); + + // ─── 快照测试 ───────────────────────────────────────────────────────────────── + describe('ImageViewer 快照测试', () => { + const triggerText = '预览单张图片'; + + test('单张图片默认状态', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + expect(document.body).toMatchSnapshot('image-viewer-single-image-default'); + }); + + test('多张图片默认状态', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + expect(document.body).toMatchSnapshot('image-viewer-multi-image-default'); + }); + + test('自定义关闭按钮', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ✕} />; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + expect(document.body).toMatchSnapshot('image-viewer-custom-close-btn'); + }); + + test('关闭按钮为 false', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + expect(document.body).toMatchSnapshot('image-viewer-close-btn-disabled'); + }); + + test('无遮罩层', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + expect(document.body).toMatchSnapshot('image-viewer-no-overlay'); + }); + + test('不可拖拽', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + expect(document.body).toMatchSnapshot('image-viewer-not-draggable'); + }); + + test('自定义 imageScale 配置', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ( + + ); + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + expect(document.body).toMatchSnapshot('image-viewer-custom-scale-config'); + }); + + test('modeless 模式', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + expect(document.body).toMatchSnapshot('image-viewer-modeless'); + }); + + test('modeless 模式切换图片', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + const user = userEvent.setup(); + await user.type(document.body, '{ArrowRight}'); + await mockDelay(); + + expect(document.body).toMatchSnapshot('image-viewer-modeless-second-image'); + }); + + test('自定义 title', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + expect(document.body).toMatchSnapshot('image-viewer-custom-title'); + }); + + test('attach 到指定容器', async () => { + const customContainer = document.createElement('div'); + customContainer.id = 'custom-attach-container'; + document.body.appendChild(customContainer); + + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return customContainer} />; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + + await mockDelay(); + expect(customContainer).toMatchSnapshot('image-viewer-attach-custom-container'); + + document.body.removeChild(customContainer); + }); + + test('DefaultTrigger 默认触发器', async () => { + const { container } = render(); + await mockDelay(); + expect(container).toMatchSnapshot('image-viewer-default-trigger'); + }); + + test('DefaultTrigger 多张图片默认触发器', async () => { + const { container } = render(); + await mockDelay(); + expect(container).toMatchSnapshot('image-viewer-default-trigger-multi'); + }); + }); + + describe('ImageViewerModal 滚轮向中心缩放', () => { + const fireWheelEvent = (deltaY: number) => { + act(() => { + document.dispatchEvent( + new WheelEvent('wheel', { + deltaY, + bubbles: true, + cancelable: true, + }), + ); + }); + }; + + const parseTranslate = (style: string) => { + const match = style.match(/translate\(([-\d.]+)px,\s*([-\d.]+)px\)/); + return match ? { x: parseFloat(match[1]), y: parseFloat(match[2]) } : { x: 0, y: 0 }; + }; + + test('图片超出视口时缩小触发向中心路径', async () => { + const transformModule = await import('@tdesign/common-js/image-viewer/transform'); + const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); + + const triggerText = '预览图片'; + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + await mockDelay(); + + const modalBox = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; + expect(modalBox).toBeTruthy(); + + fireWheelEvent(-120); + fireWheelEvent(-120); + await mockDelay(); + + const scaleText = document.querySelector('.t-image-viewer__utils-scale')?.textContent; + expect(scaleText).toBe('200%'); + + exceedsSpy.mockReturnValue(true); + exceedsSpy.mockClear(); + + fireWheelEvent(120); + await mockDelay(); + + expect(exceedsSpy).toHaveBeenCalled(); + + const scaleTextAfter = document.querySelector('.t-image-viewer__utils-scale')?.textContent; + expect(scaleTextAfter).toBe('150%'); + + const translateAfter = parseTranslate(modalBox.style.transform); + expect(translateAfter).toEqual({ x: 0, y: 0 }); + + exceedsSpy.mockRestore(); + }); + + test('图片在视口内时缩小走普通路径', async () => { + const transformModule = await import('@tdesign/common-js/image-viewer/transform'); + const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); + + const triggerText = '预览图片2'; + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + await mockDelay(); + + const modalBox = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; + const translateBefore = parseTranslate(modalBox.style.transform); + + exceedsSpy.mockClear(); + + fireWheelEvent(120); + await mockDelay(); + + expect(exceedsSpy).toHaveBeenCalled(); + + const translateAfter = parseTranslate(modalBox.style.transform); + expect(translateAfter).toEqual(translateBefore); + + exceedsSpy.mockRestore(); + }); + + test('滚轮放大时走 onZoomIn 普通路径', async () => { + const triggerText = '放大路径测试'; + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + await mockDelay(); + + const scaleText = document.querySelector('.t-image-viewer__utils-scale')?.textContent; + expect(scaleText).toBe('100%'); + + fireWheelEvent(-120); + await mockDelay(); + + const scaleTextAfter = document.querySelector('.t-image-viewer__utils-scale')?.textContent; + expect(scaleTextAfter).toBe('150%'); + }); + + test('滚轮事件调用 preventDefault', async () => { + const triggerText = 'preventDefault测试'; + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + + act(() => { + fireEvent.click(getByText(triggerText)); + }); + await mockDelay(); - expect(onClose).toHaveBeenCalledTimes(0); - const imgModal = document.querySelector('.t-image-viewer__modal-pic'); - expect(imgModal).toBeTruthy(); + const wheelEvent = new WheelEvent('wheel', { + deltaY: -120, + bubbles: true, + cancelable: true, + }); + const preventDefaultSpy = vi.spyOn(wheelEvent, 'preventDefault'); - const closeBtn = document.querySelector('.t-image-viewer__modal-close-bt'); - act(() => { - fireEvent.click(closeBtn); - }); - expect(onClose).toHaveBeenCalledTimes(1); - await mockTimeout(() => expect(document.querySelector('.t-image-viewer-preview-image')).toBeNull()); - }); + act(() => { + document.dispatchEvent(wheelEvent); + }); - test('trigger 为非函数', async () => { - const BasicImageViewer = () => { - const trigger = {triggerText}; - return ; - }; - const { getByText } = render(); - expect(getByText(triggerText)).toBeTruthy(); - }); + expect(preventDefaultSpy).toHaveBeenCalled(); - test('attach 默认为 body', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - const imgContainer = document.body.querySelector('.t-image-viewer-preview-image'); - expect(imgContainer).toBeNull(); - - act(() => { - fireEvent.click(getByText(triggerText)); + preventDefaultSpy.mockRestore(); + }); }); - const imgModal = document.body.querySelector('.t-image-viewer__modal-pic'); - expect(imgModal).toBeTruthy(); - }); - - test('attach 为函数', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return document.body} />; - }; - const { getByText } = render(); - - const imgContainer = document.body.querySelector('.t-image-viewer-preview-image'); - expect(imgContainer).toBeNull(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - act(() => { - const imgModal = document.body.querySelector('.t-image-viewer__modal-pic'); - expect(imgModal).toBeTruthy(); - }); - }); -}); - -describe('ImageViewerMini 组件测试', () => { - const triggerText = '预览单张图片'; - - test('modeless 模式', async () => { - const onClose = vi.fn(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - const miniFooter = await waitFor(() => document.querySelector('.t-image-viewer-mini__footer')); - expect(miniFooter).toBeTruthy(); - - const closeBtn = await waitFor(() => document.querySelector('.t-icon-close')); - act(() => { - fireEvent.click(closeBtn); - }); - expect(onClose).toHaveBeenCalledTimes(1); - }); -}); - -describe('ImageViewerModal 组件测试', () => { - const triggerText = '预览单张图片'; - - test('键盘操作和遮罩关闭', async () => { - const user = userEvent.setup(); - const onClose = vi.fn(); - const onIndexChange = vi.fn(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ( - - ); - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await user.type(document.body, '{ArrowRight}'); - expect(onIndexChange).toHaveBeenCalledTimes(1); - - await user.type(document.body, '{ArrowLeft}'); - expect(onIndexChange).toHaveBeenCalledTimes(2); + describe('ImageViewerModal 向中心缩放动画', () => { + const fireWheelEvent = (deltaY: number) => { + act(() => { + document.dispatchEvent( + new WheelEvent('wheel', { + deltaY, + bubbles: true, + cancelable: true, + }), + ); + }); + }; + + const TRANSITIONING_CLASS = 't-image-viewer__modal-box--transitioning'; + + test('向中心缩放时添加 transitioning class', async () => { + const transformModule = await import('@tdesign/common-js/image-viewer/transform'); + const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); + + const triggerText = '动画测试1'; + render( + open()}>{triggerText}} + images={[imgUrl]} + imageScale={{ max: 2, min: 0.5, step: 0.5 }} + />, + ); - await user.type(document.body, '{Escape}'); - expect(onClose).toHaveBeenCalledTimes(1); + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); - act(() => { - fireEvent.click(getByText(triggerText)); - }); - const mask = await waitFor(() => document.querySelector('.t-image-viewer__modal-mask')); - act(() => { - fireEvent.click(mask); - }); - expect(onClose).toHaveBeenCalledTimes(2); - }); + fireWheelEvent(-120); + fireWheelEvent(-120); + await mockDelay(); - test('弹窗关闭后键盘事件失效', async () => { - const user = userEvent.setup(); - const onIndexChange = vi.fn(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - await user.type(document.body, '{ArrowRight}'); - expect(onIndexChange).toHaveBeenCalledTimes(1); + exceedsSpy.mockReturnValue(true); - await user.type(document.body, '{Escape}'); + fireWheelEvent(120); - await user.type(document.body, '{ArrowRight}'); - await user.type(document.body, '{ArrowLeft}'); - expect(onIndexChange).toHaveBeenCalledTimes(1); - }); + const box = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; + expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); - test('单图操作(缩放/旋转/重置)', async () => { - const user = userEvent.setup(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); + exceedsSpy.mockRestore(); + }); - act(() => { - fireEvent.click(getByText(triggerText)); - }); + test('transitionend 后移除 transitioning class', async () => { + const transformModule = await import('@tdesign/common-js/image-viewer/transform'); + const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); - const img = document.querySelector('.t-image-viewer__modal-image'); - expect(getComputedStyle(img).transform).toBe('rotateZ(0deg) scale(1)'); + render( + open()}>动画测试2} + images={[imgUrl]} + imageScale={{ max: 2, min: 0.5, step: 0.5 }} + />, + ); - await user.type(document.body, '{ArrowUp}'); - expect(getComputedStyle(img).transform).toBe('rotateZ(0deg) scale(1.2)'); + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); - await user.type(document.body, '{ArrowDown}'); - expect(getComputedStyle(img).transform).toBe('rotateZ(0deg) scale(1)'); + fireWheelEvent(-120); + fireWheelEvent(-120); + await mockDelay(); - const rotateIcon = await waitFor(() => document.querySelector('.t-icon-rotation')); - act(() => { - fireEvent.click(rotateIcon); - }); - expect(getComputedStyle(img).transform).toBe('rotateZ(-90deg) scale(1)'); + exceedsSpy.mockReturnValue(true); + fireWheelEvent(120); - const resetIcon = await waitFor(() => document.querySelector('.t-icon-image')); - act(() => { - fireEvent.click(resetIcon); - }); - expect(getComputedStyle(img).transform).toBe('rotateZ(0deg) scale(1)'); - }); + const box = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; + expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); - test('自定义关闭按钮', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return closeBtn} />; - }; - const { getByText } = render(); + act(() => { + const e = new Event('transitionend', { bubbles: true }); + Object.defineProperty(e, 'propertyName', { value: 'transform' }); + box.dispatchEvent(e); + }); - act(() => { - fireEvent.click(getByText(triggerText)); - }); - expect(getByText('closeBtn')).toBeTruthy(); - }); + expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(false); - test('closeOnEscKeydown 为 false', async () => { - const user = userEvent.setup(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); + exceedsSpy.mockRestore(); + }); - act(() => { - fireEvent.click(getByText(triggerText)); - }); - expect(document.querySelector('.t-image-viewer-preview-image')).toBeInTheDocument(); + test('fallback timer(350ms)超时后自动移除 transitioning class', async () => { + vi.useFakeTimers(); - await user.type(document.body, '{Escape}'); - await mockDelay(300); - expect(document.querySelector('.t-image-viewer-preview-image')).toBeInTheDocument(); - }); + const transformModule = await import('@tdesign/common-js/image-viewer/transform'); + const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); - test('imageScale defaultScale 正常范围', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ( - - ); - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); + render( + open()}>动画测试3} + images={[imgUrl]} + imageScale={{ max: 2, min: 0.5, step: 0.5 }} + />, + ); - await mockDelay(); + act(() => { + fireEvent.click(document.querySelector('span')); + }); - expect(document.querySelector('.t-image-viewer__modal-image')).toBeInTheDocument(); - expect(document.querySelector('.t-image-viewer__modal-image')).toHaveStyle({ - transform: 'rotateZ(0deg) scale(2)', - }); - }); + act(() => { + fireWheelEvent(-120); + fireWheelEvent(-120); + }); - test('imageScale defaultScale 超过 max 被截断', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ( - - ); - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); + exceedsSpy.mockReturnValue(true); + act(() => { + fireWheelEvent(120); + }); - await mockDelay(); + const box = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; + expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); - expect(document.querySelector('.t-image-viewer__modal-image')).toBeInTheDocument(); - expect(document.querySelector('.t-image-viewer__modal-image')).toHaveStyle({ - transform: 'rotateZ(0deg) scale(3)', - }); - }); + act(() => { + vi.advanceTimersByTime(400); + }); + expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(false); - test('imageScale defaultScale 低于 min 被截断', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ( - - ); - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); + vi.useRealTimers(); + exceedsSpy.mockRestore(); + }); - await mockDelay(); + test('连续快速缩放:fallback timer 重置', async () => { + vi.useFakeTimers(); - expect(document.querySelector('.t-image-viewer__modal-image')).toBeInTheDocument(); - expect(document.querySelector('.t-image-viewer__modal-image')).toHaveStyle({ - transform: 'rotateZ(0deg) scale(2.5)', - }); - }); + const transformModule = await import('@tdesign/common-js/image-viewer/transform'); + const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); - test('imageScale max 意外小于 min 时使用 min', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ( - open()}>动画测试4} + images={[imgUrl]} + imageScale={{ max: 2, min: 0.5, step: 0.5 }} + />, + ); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + + act(() => { + fireWheelEvent(-120); + fireWheelEvent(-120); + }); + + exceedsSpy.mockReturnValue(true); + + act(() => { + fireWheelEvent(120); + }); + const box = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; + expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); + + act(() => { + vi.advanceTimersByTime(200); + }); + act(() => { + fireWheelEvent(120); + }); + expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); + + act(() => { + vi.advanceTimersByTime(200); + }); + expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); + + act(() => { + vi.advanceTimersByTime(200); + }); + expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(false); + + vi.useRealTimers(); + exceedsSpy.mockRestore(); + }); + }); + + describe('ImageViewer 工具栏操作快照', () => { + const triggerText = '工具栏测试'; + + const openViewer = async (images = [imgUrl]) => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + const { getByText } = render(); + act(() => { + fireEvent.click(getByText(triggerText)); + }); + await mockDelay(); + return { getByText }; + }; + + test('点击旋转按钮后快照', async () => { + await openViewer(); + const rotateBtn = document.querySelector('.t-icon-rotation')?.closest('.t-image-viewer__modal-icon'); + expect(rotateBtn).toBeTruthy(); + act(() => { + fireEvent.click(rotateBtn); + }); + await mockDelay(); + expect(document.body).toMatchSnapshot('toolbar-after-rotate'); + }); + + test('点击放大按钮后快照', async () => { + await openViewer(); + const zoomInBtn = document.querySelector('.t-icon-zoom-in')?.closest('.t-image-viewer__modal-icon'); + expect(zoomInBtn).toBeTruthy(); + act(() => { + fireEvent.click(zoomInBtn); + }); + await mockDelay(); + expect(document.body).toMatchSnapshot('toolbar-after-zoom-in'); + }); + + test('点击缩小按钮后快照', async () => { + await openViewer(); + const zoomOutBtn = document.querySelector('.t-icon-zoom-out')?.closest('.t-image-viewer__modal-icon'); + expect(zoomOutBtn).toBeTruthy(); + act(() => { + fireEvent.click(zoomOutBtn); + }); + await mockDelay(); + expect(document.body).toMatchSnapshot('toolbar-after-zoom-out'); + }); + + test('点击镜像按钮后快照', async () => { + await openViewer(); + const mirrorBtn = document.querySelector('.t-icon-mirror')?.closest('.t-image-viewer__modal-icon'); + expect(mirrorBtn).toBeTruthy(); + act(() => { + fireEvent.click(mirrorBtn); + }); + await mockDelay(); + expect(document.body).toMatchSnapshot('toolbar-after-mirror'); + }); + + test('点击原始大小按钮后快照', async () => { + await openViewer(); + const resetBtn = document.querySelector('.t-icon-image')?.closest('.t-image-viewer__modal-icon'); + expect(resetBtn).toBeTruthy(); + act(() => { + fireEvent.click(resetBtn); + }); + await mockDelay(); + expect(document.body).toMatchSnapshot('toolbar-after-reset'); + }); + + test('多图导航:上一张/下一张按钮快照', async () => { + await openViewer([imgUrl, imgUrl2, imgUrl3]); + + const nextBtn = document.querySelector('.t-icon-chevron-right')?.closest('.t-image-viewer__modal-icon'); + expect(nextBtn).toBeTruthy(); + act(() => { + fireEvent.click(nextBtn); + }); + await mockDelay(); + expect(document.body).toMatchSnapshot('toolbar-navigate-next'); + + const nextBtn2 = document.querySelector('.t-icon-chevron-right')?.closest('.t-image-viewer__modal-icon'); + expect(nextBtn2).toBeTruthy(); + act(() => { + fireEvent.click(nextBtn2); + }); + await mockDelay(); + + const prevBtn = document.querySelector('.t-icon-chevron-left')?.closest('.t-image-viewer__modal-icon'); + expect(prevBtn).toBeTruthy(); + act(() => { + fireEvent.click(prevBtn); + }); + await mockDelay(); + expect(document.body).toMatchSnapshot('toolbar-navigate-prev'); + }); + }); + + // ─── 受控模式 ────────────────────────────────────────────────────────── + describe('受控 visible/index', () => { + test('受控 visible 打开/关闭', async () => { + const ControlledViewer = () => { + const [visible, setVisible] = React.useState(false); + return ( + <> + + + setVisible(false)} /> + + ); + }; + render(); + + expect(document.querySelector('.t-image-viewer-preview-image')).toBeNull(); + + act(() => { + fireEvent.click(document.querySelector('[data-testid="open"]')); + }); + await mockDelay(); + expect(document.querySelector('.t-image-viewer-preview-image')).toBeTruthy(); + + act(() => { + fireEvent.click(document.querySelector('[data-testid="close"]')); + }); + await mockTimeout(() => expect(document.querySelector('.t-image-viewer-preview-image')).toBeNull()); + }); + + test('受控 index 切换图片', async () => { + const ControlledViewer = () => { + const [index, setIndex] = React.useState(0); + return ( + <> + + setIndex(i as number)} + /> + + ); + }; + render(); + await mockDelay(); + + // 初始 index=0 + const indexText = document.querySelector('.t-image-viewer__modal-index'); + expect(indexText).toBeTruthy(); + + // 外部修改 index 为 2 + act(() => { + fireEvent.click(document.querySelector('[data-testid="set-index"]')); + }); + await mockDelay(); + + // 验证组件已更新到第 3 张 + const updatedIndexText = document.querySelector('.t-image-viewer__modal-index'); + expect(updatedIndexText?.textContent).toContain('3/3'); + }); + }); + + // ─── 图片加载错误状态 ────────────────────────────────────────────────── + describe('图片加载错误状态', () => { + const triggerText = '错误状态测试'; + + test('图片加载失败显示错误 UI', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>{triggerText}; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const img = document.querySelector('.t-image-viewer__modal-image') as HTMLImageElement; + expect(img).toBeTruthy(); + + act(() => { + fireEvent.error(img); + }); + await mockDelay(); + + const errorEl = document.querySelector('.t-image-viewer__img-error'); + // In JSDOM, error UI may render; verify either error element appears or image is hidden + const hasError = + errorEl !== null || document.querySelector('.t-image-viewer__modal-image[style*="display: none"]') !== null; + expect(hasError).toBe(true); + }); + }); + + // ─── 缩略图导航 ────────────────────────────────────────────────────── + describe('ImageViewerHeader 缩略图导航', () => { + test('点击缩略图切换到对应图片', async () => { + const onIndexChange = vi.fn(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>缩略图测试; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const headerBoxes = document.querySelectorAll('.t-image-viewer__header-box'); + expect(headerBoxes.length).toBe(3); + + act(() => { + fireEvent.click(headerBoxes[2]); + }); + expect(onIndexChange).toHaveBeenCalledWith(2, { trigger: 'current' }); + }); + + test('当前图片缩略图有 active 状态', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>缩略图测试; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const headerBoxes = document.querySelectorAll('.t-image-viewer__header-box'); + expect(headerBoxes[0].classList.contains('t-is-active')).toBe(true); + expect(headerBoxes[1].classList.contains('t-is-active')).toBe(false); + }); + }); + + // ─── DefaultTrigger 点击交互 ────────────────────────────────────────── + describe('DefaultTrigger 点击交互', () => { + test('默认触发器点击后打开预览', async () => { + render(); + + const triggerEl = document.querySelector('.t-image-viewer__trigger'); + expect(triggerEl).toBeTruthy(); + + act(() => { + fireEvent.click(triggerEl); + }); + await mockDelay(); + + expect(document.querySelector('.t-image-viewer-preview-image')).toBeTruthy(); + }); + }); + + // ─── 切换图片重置状态 ──────────────────────────────────────────────── + describe('切换图片后重置旋转/缩放/位移', () => { + test('切换图片后缩放重置为 1', async () => { + const user = userEvent.setup(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>重置测试; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + // 放大 + await user.type(document.body, '{ArrowUp}'); + await mockDelay(); + const scaleText = document.querySelector('.t-image-viewer__utils-scale')?.textContent; + expect(scaleText).not.toBe('100%'); + + // 切换到下一张 + await user.type(document.body, '{ArrowRight}'); + await mockDelay(); + + const scaleTextAfter = document.querySelector('.t-image-viewer__utils-scale')?.textContent; + expect(scaleTextAfter).toBe('100%'); + }); + }); + + // ─── 第一张/最后一张箭头 disabled ────────────────────────────────────── + describe('第一张/最后一张箭头 disabled 状态', () => { + test('第一张图片时 prev 按钮有 disabled 样式', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>箭头测试; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const prevBtn = document.querySelector('.t-image-viewer__modal-prev-bt'); + expect(prevBtn?.classList.contains('t-is-disabled')).toBe(true); + + const nextBtn = document.querySelector('.t-image-viewer__modal-next-bt'); + expect(nextBtn?.classList.contains('t-is-disabled')).toBe(false); + }); + + test('最后一张图片时 next 按钮有 disabled 样式', async () => { + const user = userEvent.setup(); + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>箭头测试2; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + await user.type(document.body, '{ArrowRight}'); + await mockDelay(); + + const nextBtn = document.querySelector('.t-image-viewer__modal-next-bt'); + expect(nextBtn?.classList.contains('t-is-disabled')).toBe(true); + }); + }); + + // ─── 空图片数组 ─────────────────────────────────────────────────────── + describe('空图片数组', () => { + test('images 为空数组时不渲染弹窗', async () => { + const BasicImageViewer = () => { + const trigger = ({ open }) => open()}>空数组测试; + return ; + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + expect(document.querySelector('.t-image-viewer-preview-image')).toBeNull(); + }); + }); + + // ─── imageScale 动态变化重置 ────────────────────────────────────────── + describe('imageScale 动态变化重置缩放', () => { + test('imageScale 变化后缩放重置', async () => { + const DynamicScaleViewer = () => { + const [scale, setScale] = React.useState({ max: 2, - min: 2.5, - step: 0.5, - defaultScale: 2, - }} - /> - ); - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - - expect(document.querySelector('.t-image-viewer__modal-image')).toBeInTheDocument(); - expect(document.querySelector('.t-image-viewer__modal-image')).toHaveStyle({ - transform: 'rotateZ(0deg) scale(2.5)', - }); - }); - - test('imageReferrerpolicy 属性', async () => { - const referrerPolicy = 'strict-origin-when-cross-origin'; - - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - - expect(document.querySelector('.t-image-viewer__modal-image')?.getAttribute('referrerpolicy')).toBe(referrerPolicy); - }); -}); - -// ─── 快照测试 ───────────────────────────────────────────────────────────────── -describe('ImageViewer 快照测试', () => { - const triggerText = '预览单张图片'; - - test('单张图片默认状态', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - expect(document.body).toMatchSnapshot('image-viewer-single-image-default'); - }); - - test('多张图片默认状态', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - expect(document.body).toMatchSnapshot('image-viewer-multi-image-default'); - }); - - test('自定义关闭按钮', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ✕} />; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - expect(document.body).toMatchSnapshot('image-viewer-custom-close-btn'); - }); - - test('关闭按钮为 false', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - expect(document.body).toMatchSnapshot('image-viewer-close-btn-disabled'); - }); - - test('无遮罩层', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - expect(document.body).toMatchSnapshot('image-viewer-no-overlay'); - }); - - test('不可拖拽', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - expect(document.body).toMatchSnapshot('image-viewer-not-draggable'); - }); - - test('自定义 imageScale 配置', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ( - - ); - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - expect(document.body).toMatchSnapshot('image-viewer-custom-scale-config'); - }); - - test('modeless 模式', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - expect(document.body).toMatchSnapshot('image-viewer-modeless'); - }); - - test('modeless 模式切换图片', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - const user = userEvent.setup(); - await user.type(document.body, '{ArrowRight}'); - await mockDelay(); - - expect(document.body).toMatchSnapshot('image-viewer-modeless-second-image'); - }); - - test('自定义 title', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - expect(document.body).toMatchSnapshot('image-viewer-custom-title'); - }); - - test('attach 到指定容器', async () => { - const customContainer = document.createElement('div'); - customContainer.id = 'custom-attach-container'; - document.body.appendChild(customContainer); - - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return customContainer} />; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - - await mockDelay(); - expect(customContainer).toMatchSnapshot('image-viewer-attach-custom-container'); - - document.body.removeChild(customContainer); - }); - - test('DefaultTrigger 默认触发器', async () => { - const { container } = render(); - await mockDelay(); - expect(container).toMatchSnapshot('image-viewer-default-trigger'); - }); - - test('DefaultTrigger 多张图片默认触发器', async () => { - const { container } = render(); - await mockDelay(); - expect(container).toMatchSnapshot('image-viewer-default-trigger-multi'); - }); -}); - -describe('ImageViewerModal 滚轮向中心缩放', () => { - const fireWheelEvent = (deltaY: number) => { - act(() => { - document.dispatchEvent(new WheelEvent('wheel', { deltaY, bubbles: true, cancelable: true })); - }); - }; - - const parseTranslate = (style: string) => { - const match = style.match(/translate\(([-\d.]+)px,\s*([-\d.]+)px\)/); - return match ? { x: parseFloat(match[1]), y: parseFloat(match[2]) } : { x: 0, y: 0 }; - }; - - test('图片超出视口时缩小触发向中心路径', async () => { - const transformModule = await import('@tdesign/common-js/image-viewer/transform'); - const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); - - const triggerText = '预览图片'; - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - await mockDelay(); - - const modalBox = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; - expect(modalBox).toBeTruthy(); - - fireWheelEvent(-120); - fireWheelEvent(-120); - await mockDelay(); - - const scaleText = document.querySelector('.t-image-viewer__utils-scale')?.textContent; - expect(scaleText).toBe('200%'); - - exceedsSpy.mockReturnValue(true); - exceedsSpy.mockClear(); - - fireWheelEvent(120); - await mockDelay(); - - expect(exceedsSpy).toHaveBeenCalled(); - - const scaleTextAfter = document.querySelector('.t-image-viewer__utils-scale')?.textContent; - expect(scaleTextAfter).toBe('150%'); - - const translateAfter = parseTranslate(modalBox.style.transform); - expect(translateAfter).toEqual({ x: 0, y: 0 }); - - exceedsSpy.mockRestore(); - }); - - test('图片在视口内时缩小走普通路径', async () => { - const transformModule = await import('@tdesign/common-js/image-viewer/transform'); - const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); - - const triggerText = '预览图片2'; - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); + step: 0.2, + }); + const trigger = ({ open }) => open()}>动态缩放; + return ( + <> + + + + ); + }; + render(); + + act(() => { + fireEvent.click(document.querySelector('span')); + }); + await mockDelay(); + + const scaleText = document.querySelector('.t-image-viewer__utils-scale')?.textContent; + expect(scaleText).toBe('100%'); + + act(() => { + fireEvent.click(document.querySelector('[data-testid="change-scale"]')); + }); + await mockDelay(); + + const scaleTextAfter = document.querySelector('.t-image-viewer__utils-scale')?.textContent; + expect(scaleTextAfter).toBe('100%'); + }); }); - await mockDelay(); - - const modalBox = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; - const translateBefore = parseTranslate(modalBox.style.transform); - - exceedsSpy.mockClear(); - - fireWheelEvent(120); - await mockDelay(); - - expect(exceedsSpy).toHaveBeenCalled(); - - const translateAfter = parseTranslate(modalBox.style.transform); - expect(translateAfter).toEqual(translateBefore); - - exceedsSpy.mockRestore(); - }); - - test('滚轮放大时走 onZoomIn 普通路径', async () => { - const triggerText = '放大路径测试'; - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - await mockDelay(); - - const scaleText = document.querySelector('.t-image-viewer__utils-scale')?.textContent; - expect(scaleText).toBe('100%'); - - fireWheelEvent(-120); - await mockDelay(); - - const scaleTextAfter = document.querySelector('.t-image-viewer__utils-scale')?.textContent; - expect(scaleTextAfter).toBe('150%'); - }); - - test('滚轮事件调用 preventDefault', async () => { - const triggerText = 'preventDefault测试'; - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - - act(() => { - fireEvent.click(getByText(triggerText)); - }); - await mockDelay(); - - const wheelEvent = new WheelEvent('wheel', { - deltaY: -120, - bubbles: true, - cancelable: true, - }); - const preventDefaultSpy = vi.spyOn(wheelEvent, 'preventDefault'); - - act(() => { - document.dispatchEvent(wheelEvent); - }); - - expect(preventDefaultSpy).toHaveBeenCalled(); - - preventDefaultSpy.mockRestore(); - }); -}); - -describe('ImageViewerModal 向中心缩放动画', () => { - const fireWheelEvent = (deltaY: number) => { - act(() => { - document.dispatchEvent(new WheelEvent('wheel', { deltaY, bubbles: true, cancelable: true })); - }); - }; - - const TRANSITIONING_CLASS = 't-image-viewer__modal-box--transitioning'; - - test('向中心缩放时添加 transitioning class', async () => { - const transformModule = await import('@tdesign/common-js/image-viewer/transform'); - const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); - - const triggerText = '动画测试1'; - render( - open()}>{triggerText}} - images={[imgUrl]} - imageScale={{ max: 2, min: 0.5, step: 0.5 }} - />, - ); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - fireWheelEvent(-120); - fireWheelEvent(-120); - await mockDelay(); - - exceedsSpy.mockReturnValue(true); - - fireWheelEvent(120); - - const box = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; - expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); - - exceedsSpy.mockRestore(); - }); - - test('transitionend 后移除 transitioning class', async () => { - const transformModule = await import('@tdesign/common-js/image-viewer/transform'); - const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); - - render( - open()}>动画测试2} - images={[imgUrl]} - imageScale={{ max: 2, min: 0.5, step: 0.5 }} - />, - ); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - fireWheelEvent(-120); - fireWheelEvent(-120); - await mockDelay(); - - exceedsSpy.mockReturnValue(true); - fireWheelEvent(120); - - const box = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; - expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); - - act(() => { - const e = new Event('transitionend', { bubbles: true }); - Object.defineProperty(e, 'propertyName', { value: 'transform' }); - box.dispatchEvent(e); - }); - - expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(false); - - exceedsSpy.mockRestore(); - }); - - test('fallback timer(350ms)超时后自动移除 transitioning class', async () => { - vi.useFakeTimers(); - - const transformModule = await import('@tdesign/common-js/image-viewer/transform'); - const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); - - render( - open()}>动画测试3} - images={[imgUrl]} - imageScale={{ max: 2, min: 0.5, step: 0.5 }} - />, - ); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - - act(() => { - fireWheelEvent(-120); - fireWheelEvent(-120); - }); - - exceedsSpy.mockReturnValue(true); - act(() => { - fireWheelEvent(120); - }); - - const box = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; - expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); - - act(() => { - vi.advanceTimersByTime(400); - }); - expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(false); - - vi.useRealTimers(); - exceedsSpy.mockRestore(); - }); - - test('连续快速缩放:fallback timer 重置', async () => { - vi.useFakeTimers(); - - const transformModule = await import('@tdesign/common-js/image-viewer/transform'); - const exceedsSpy = vi.spyOn(transformModule, 'isImageExceedsViewport').mockReturnValue(false); - - render( - open()}>动画测试4} - images={[imgUrl]} - imageScale={{ max: 2, min: 0.5, step: 0.5 }} - />, - ); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - - act(() => { - fireWheelEvent(-120); - fireWheelEvent(-120); - }); - - exceedsSpy.mockReturnValue(true); - - act(() => { - fireWheelEvent(120); - }); - const box = document.querySelector('.t-image-viewer__modal-box') as HTMLElement; - expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); - - act(() => { - vi.advanceTimersByTime(200); - }); - act(() => { - fireWheelEvent(120); - }); - expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); - - act(() => { - vi.advanceTimersByTime(200); - }); - expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(true); - - act(() => { - vi.advanceTimersByTime(200); - }); - expect(box.classList.contains(TRANSITIONING_CLASS)).toBe(false); - - vi.useRealTimers(); - exceedsSpy.mockRestore(); - }); -}); - -describe('ImageViewer 工具栏操作快照', () => { - const triggerText = '工具栏测试'; - - const openViewer = async (images = [imgUrl]) => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - const { getByText } = render(); - act(() => { - fireEvent.click(getByText(triggerText)); - }); - await mockDelay(); - return { getByText }; - }; - - test('点击旋转按钮后快照', async () => { - await openViewer(); - const rotateBtn = document.querySelector('.t-icon-rotation')?.closest('.t-image-viewer__modal-icon'); - expect(rotateBtn).toBeTruthy(); - act(() => { - fireEvent.click(rotateBtn); - }); - await mockDelay(); - expect(document.body).toMatchSnapshot('toolbar-after-rotate'); - }); - - test('点击放大按钮后快照', async () => { - await openViewer(); - const zoomInBtn = document.querySelector('.t-icon-zoom-in')?.closest('.t-image-viewer__modal-icon'); - expect(zoomInBtn).toBeTruthy(); - act(() => { - fireEvent.click(zoomInBtn); - }); - await mockDelay(); - expect(document.body).toMatchSnapshot('toolbar-after-zoom-in'); - }); - - test('点击缩小按钮后快照', async () => { - await openViewer(); - const zoomOutBtn = document.querySelector('.t-icon-zoom-out')?.closest('.t-image-viewer__modal-icon'); - expect(zoomOutBtn).toBeTruthy(); - act(() => { - fireEvent.click(zoomOutBtn); - }); - await mockDelay(); - expect(document.body).toMatchSnapshot('toolbar-after-zoom-out'); - }); - - test('点击镜像按钮后快照', async () => { - await openViewer(); - const mirrorBtn = document.querySelector('.t-icon-mirror')?.closest('.t-image-viewer__modal-icon'); - expect(mirrorBtn).toBeTruthy(); - act(() => { - fireEvent.click(mirrorBtn); - }); - await mockDelay(); - expect(document.body).toMatchSnapshot('toolbar-after-mirror'); - }); - - test('点击原始大小按钮后快照', async () => { - await openViewer(); - const resetBtn = document.querySelector('.t-icon-image')?.closest('.t-image-viewer__modal-icon'); - expect(resetBtn).toBeTruthy(); - act(() => { - fireEvent.click(resetBtn); - }); - await mockDelay(); - expect(document.body).toMatchSnapshot('toolbar-after-reset'); - }); - - test('多图导航:上一张/下一张按钮快照', async () => { - await openViewer([imgUrl, imgUrl2, imgUrl3]); - - const nextBtn = document.querySelector('.t-icon-chevron-right')?.closest('.t-image-viewer__modal-icon'); - expect(nextBtn).toBeTruthy(); - act(() => { - fireEvent.click(nextBtn); - }); - await mockDelay(); - expect(document.body).toMatchSnapshot('toolbar-navigate-next'); - - const nextBtn2 = document.querySelector('.t-icon-chevron-right')?.closest('.t-image-viewer__modal-icon'); - expect(nextBtn2).toBeTruthy(); - act(() => { - fireEvent.click(nextBtn2); - }); - await mockDelay(); - - const prevBtn = document.querySelector('.t-icon-chevron-left')?.closest('.t-image-viewer__modal-icon'); - expect(prevBtn).toBeTruthy(); - act(() => { - fireEvent.click(prevBtn); - }); - await mockDelay(); - expect(document.body).toMatchSnapshot('toolbar-navigate-prev'); - }); -}); - -// ─── onClose trigger 来源验证 ─────────────────────────────────────────── -describe('onClose trigger 来源', () => { - const triggerText = '关闭来源测试'; - - test('点击关闭按钮时 trigger 为 close-btn', async () => { - const onClose = vi.fn(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const closeBtn = document.querySelector('.t-image-viewer__modal-close-bt'); - expect(closeBtn).toBeTruthy(); - act(() => { - fireEvent.click(closeBtn); - }); - expect(onClose).toHaveBeenCalledWith({ - trigger: 'close-btn', - e: expect.any(Object), - }); - }); - - test('点击遮罩时 trigger 为 overlay', async () => { - const onClose = vi.fn(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const mask = document.querySelector('.t-image-viewer__modal-mask'); - expect(mask).toBeTruthy(); - act(() => { - fireEvent.click(mask); - }); - expect(onClose).toHaveBeenCalledWith({ - trigger: 'overlay', - e: expect.any(Object), - }); - }); - - test('ESC 关闭时 trigger 为 esc', async () => { - const user = userEvent.setup(); - const onClose = vi.fn(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - await user.type(document.body, '{Escape}'); - expect(onClose).toHaveBeenCalledWith({ - trigger: 'esc', - e: expect.any(Object), - }); - }); -}); - -// ─── onDownload 回调 ──────────────────────────────────────────────────── -describe('onDownload 下载回调', () => { - const triggerText = '下载测试'; - - test('自定义 onDownload 时被调用', async () => { - const onDownload = vi.fn(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const downloadBtn = - document - .querySelector('.t-image-viewer__modal-icon [data-td-icon="download"]') - ?.closest('.t-image-viewer__modal-icon') ?? - document.querySelector('.t-icon-download')?.closest('.t-image-viewer__modal-icon'); - expect(downloadBtn).toBeTruthy(); - act(() => { - fireEvent.click(downloadBtn); - }); - expect(onDownload).toHaveBeenCalledWith(imgUrl); - }); - - test('currentImage.download 为 false 时不显示下载按钮', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const downloadIcon = document.querySelector('.t-icon-download'); - expect(downloadIcon).toBeNull(); - }); -}); - -// ─── 受控模式 ────────────────────────────────────────────────────────── -describe('受控 visible/index', () => { - test('受控 visible 打开/关闭', async () => { - const ControlledViewer = () => { - const [visible, setVisible] = React.useState(false); - return ( - <> - - - setVisible(false)} /> - - ); - }; - render(); - - expect(document.querySelector('.t-image-viewer-preview-image')).toBeNull(); - - act(() => { - fireEvent.click(document.querySelector('[data-testid="open"]')); - }); - await mockDelay(); - expect(document.querySelector('.t-image-viewer-preview-image')).toBeTruthy(); - - act(() => { - fireEvent.click(document.querySelector('[data-testid="close"]')); - }); - await mockTimeout(() => expect(document.querySelector('.t-image-viewer-preview-image')).toBeNull()); - }); - - test('受控 index 切换图片', async () => { - const ControlledViewer = () => { - const [index, setIndex] = React.useState(0); - return ( - <> - - setIndex(i as number)} - /> - - ); - }; - render(); - await mockDelay(); - - // 初始 index=0 - const indexText = document.querySelector('.t-image-viewer__modal-index'); - expect(indexText).toBeTruthy(); - - // 外部修改 index 为 2 - act(() => { - fireEvent.click(document.querySelector('[data-testid="set-index"]')); - }); - await mockDelay(); - - // 验证组件已更新到第 3 张 - const updatedIndexText = document.querySelector('.t-image-viewer__modal-index'); - expect(updatedIndexText?.textContent).toContain('3/3'); - }); -}); - -// ─── 图片加载错误状态 ────────────────────────────────────────────────── -describe('图片加载错误状态', () => { - const triggerText = '错误状态测试'; - - test('图片加载失败显示错误 UI', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>{triggerText}; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const img = document.querySelector('.t-image-viewer__modal-image') as HTMLImageElement; - expect(img).toBeTruthy(); - - act(() => { - fireEvent.error(img); - }); - await mockDelay(); - - const errorEl = document.querySelector('.t-image-viewer__img-error'); - // In JSDOM, error UI may render; verify either error element appears or image is hidden - const hasError = - errorEl !== null || document.querySelector('.t-image-viewer__modal-image[style*="display: none"]') !== null; - expect(hasError).toBe(true); - }); -}); - -// ─── open(index) 指定索引打开 ────────────────────────────────────────── -describe('open(index) 指定索引打开', () => { - test('open 传入 index 后显示对应图片', async () => { - const onIndexChange = vi.fn(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open(2)}>指定索引打开; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const indexText = document.querySelector('.t-image-viewer__modal-index'); - expect(indexText?.textContent).toContain('3/3'); - }); -}); - -// ─── 缩略图导航 ────────────────────────────────────────────────────── -describe('ImageViewerHeader 缩略图导航', () => { - test('点击缩略图切换到对应图片', async () => { - const onIndexChange = vi.fn(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>缩略图测试; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const headerBoxes = document.querySelectorAll('.t-image-viewer__header-box'); - expect(headerBoxes.length).toBe(3); - - act(() => { - fireEvent.click(headerBoxes[2]); - }); - expect(onIndexChange).toHaveBeenCalledWith(2, { trigger: 'current' }); - }); - - test('当前图片缩略图有 active 状态', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>缩略图测试; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const headerBoxes = document.querySelectorAll('.t-image-viewer__header-box'); - expect(headerBoxes[0].classList.contains('t-is-active')).toBe(true); - expect(headerBoxes[1].classList.contains('t-is-active')).toBe(false); - }); -}); - -// ─── closeBtn 为函数 ────────────────────────────────────────────────── -describe('closeBtn 为函数', () => { - test('closeBtn 为函数时渲染自定义关闭按钮', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>函数关闭按钮; - const CloseBtnNode = ({ onClose }: any) => ( - - ); - return } />; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const customBtn = document.querySelector('[data-testid="custom-close"]'); - expect(customBtn).toBeTruthy(); - expect(customBtn?.textContent).toBe('X'); - }); -}); - -// ─── DefaultTrigger 点击交互 ────────────────────────────────────────── -describe('DefaultTrigger 点击交互', () => { - test('默认触发器点击后打开预览', async () => { - render(); - - const triggerEl = document.querySelector('.t-image-viewer__trigger'); - expect(triggerEl).toBeTruthy(); - - act(() => { - fireEvent.click(triggerEl); - }); - await mockDelay(); - - expect(document.querySelector('.t-image-viewer-preview-image')).toBeTruthy(); - }); -}); - -// ─── 切换图片重置状态 ──────────────────────────────────────────────── -describe('切换图片后重置旋转/缩放/位移', () => { - test('切换图片后缩放重置为 1', async () => { - const user = userEvent.setup(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>重置测试; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - // 放大 - await user.type(document.body, '{ArrowUp}'); - await mockDelay(); - const scaleText = document.querySelector('.t-image-viewer__utils-scale')?.textContent; - expect(scaleText).not.toBe('100%'); - - // 切换到下一张 - await user.type(document.body, '{ArrowRight}'); - await mockDelay(); - - const scaleTextAfter = document.querySelector('.t-image-viewer__utils-scale')?.textContent; - expect(scaleTextAfter).toBe('100%'); - }); -}); - -// ─── innerClassName 属性 ────────────────────────────────────────────── -describe('innerClassName 属性', () => { - test('innerClassName 应用到图片容器', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>innerClassName测试; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const picEl = document.querySelector('.t-image-viewer__modal-pic'); - expect(picEl?.classList.contains('custom-inner')).toBe(true); - }); -}); - -// ─── showOverlay 为 false ───────────────────────────────────────────── -describe('showOverlay 属性', () => { - test('showOverlay 为 false 时不渲染遮罩', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>遮罩测试; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const mask = document.querySelector('.t-image-viewer__modal-mask'); - expect(mask).toBeNull(); - }); -}); - -// ─── closeOnOverlay 为 false ────────────────────────────────────────── -describe('closeOnOverlay 属性', () => { - test('closeOnOverlay 为 false 时点击遮罩不关闭', async () => { - const onClose = vi.fn(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>遮罩不关闭测试; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const mask = document.querySelector('.t-image-viewer__modal-mask'); - expect(mask).toBeTruthy(); - act(() => { - fireEvent.click(mask); - }); - expect(onClose).not.toHaveBeenCalled(); - }); -}); - -// ─── title 为函数 ───────────────────────────────────────────────────── -describe('title 为 TNode', () => { - test('title 为 ReactNode 时渲染自定义标题', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>标题测试; - return ( - 自定义标题} - /> - ); - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const titleEl = document.querySelector('.custom-title-node'); - expect(titleEl).toBeTruthy(); - expect(titleEl?.textContent).toBe('自定义标题'); - }); -}); - -// ─── 第一张/最后一张箭头 disabled ────────────────────────────────────── -describe('第一张/最后一张箭头 disabled 状态', () => { - test('第一张图片时 prev 按钮有 disabled 样式', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>箭头测试; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const prevBtn = document.querySelector('.t-image-viewer__modal-prev-bt'); - expect(prevBtn?.classList.contains('t-is-disabled')).toBe(true); - - const nextBtn = document.querySelector('.t-image-viewer__modal-next-bt'); - expect(nextBtn?.classList.contains('t-is-disabled')).toBe(false); - }); - - test('最后一张图片时 next 按钮有 disabled 样式', async () => { - const user = userEvent.setup(); - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>箭头测试2; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - await user.type(document.body, '{ArrowRight}'); - await mockDelay(); - - const nextBtn = document.querySelector('.t-image-viewer__modal-next-bt'); - expect(nextBtn?.classList.contains('t-is-disabled')).toBe(true); - }); -}); - -// ─── 空图片数组 ─────────────────────────────────────────────────────── -describe('空图片数组', () => { - test('images 为空数组时不渲染弹窗', async () => { - const BasicImageViewer = () => { - const trigger = ({ open }) => open()}>空数组测试; - return ; - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - expect(document.querySelector('.t-image-viewer-preview-image')).toBeNull(); - }); -}); - -// ─── imageScale 动态变化重置 ────────────────────────────────────────── -describe('imageScale 动态变化重置缩放', () => { - test('imageScale 变化后缩放重置', async () => { - const DynamicScaleViewer = () => { - const [scale, setScale] = React.useState({ max: 2, min: 0.5, step: 0.2 }); - const trigger = ({ open }) => open()}>动态缩放; - return ( - <> - - - - ); - }; - render(); - - act(() => { - fireEvent.click(document.querySelector('span')); - }); - await mockDelay(); - - const scaleText = document.querySelector('.t-image-viewer__utils-scale')?.textContent; - expect(scaleText).toBe('100%'); - - act(() => { - fireEvent.click(document.querySelector('[data-testid="change-scale"]')); - }); - await mockDelay(); - - const scaleTextAfter = document.querySelector('.t-image-viewer__utils-scale')?.textContent; - expect(scaleTextAfter).toBe('100%'); }); }); diff --git a/packages/components/image/__tests__/__snapshots__/vitest-image.test.jsx.snap b/packages/components/image/__tests__/__snapshots__/image.test.tsx.snap similarity index 87% rename from packages/components/image/__tests__/__snapshots__/vitest-image.test.jsx.snap rename to packages/components/image/__tests__/__snapshots__/image.test.tsx.snap index a431c35fc2..0187b5accb 100644 --- a/packages/components/image/__tests__/__snapshots__/vitest-image.test.jsx.snap +++ b/packages/components/image/__tests__/__snapshots__/image.test.tsx.snap @@ -1,41 +1,41 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Image Component > props.fit is equal to contain 1`] = ` +exports[`Image > props > fit is equal to contain 1`] = ` `; -exports[`Image Component > props.fit is equal to cover 1`] = ` +exports[`Image > props > fit is equal to cover 1`] = ` `; -exports[`Image Component > props.fit is equal to fill 1`] = ` +exports[`Image > props > fit is equal to fill 1`] = ` `; -exports[`Image Component > props.fit is equal to none 1`] = ` +exports[`Image > props > fit is equal to none 1`] = ` `; -exports[`Image Component > props.fit is equal to scale-down 1`] = ` +exports[`Image > props > fit is equal to scale-down 1`] = ` `; -exports[`Image Component > props.loading works fine 1`] = ` +exports[`Image > props > loading 1`] = `
props.loading works fine 1`] = `
`; -exports[`Image Component > props.overlayContent works fine 1`] = ` +exports[`Image > props > overlayContent 1`] = `
props.overlayContent works fine 1`] = ` props.overlayContent works fine 1`] = ` props.overlayContent works fine 1`] = ` d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-rb-overlap-fill1)" + mask="url(#t-icon-image-instance-rk-overlap-fill1)" /> props.overlayContent works fine 1`] = `
`; -exports[`Image Component > props.placeholder works fine 1`] = ` +exports[`Image > props > placeholder 1`] = `
props.placeholder works fine 1`] = ` props.placeholder works fine 1`] = ` props.placeholder works fine 1`] = ` d="M3 21H20L9 10L3 16V21Z" fill="transparent" id="fill1" - mask="url(#t-icon-image-instance-rd-overlap-fill1)" + mask="url(#t-icon-image-instance-rl-overlap-fill1)" /> ) { + return render( + 预览
} + {...props} + />, + ); +} + +describe('Image', () => { + describe('props', () => { + test('alt', () => { + const wrapper = render(text image load failed); + const container = wrapper.container.querySelector('img'); + expect(container.getAttribute('alt')).toBe('text image load failed'); + }); + + ['contain', 'cover', 'fill', 'none', 'scale-down'].forEach((item) => { + test(`fit is equal to ${item}`, () => { + const wrapper = render(); + const container = wrapper.container.querySelector('.t-image'); + expect(container).toHaveClass(`t-image--fit-${item}`); + expect(container).toMatchSnapshot(); + }); + }); + + test('gallery', () => { + const { container: container1 } = render(); + expect(container1.querySelector('.t-image__wrapper--gallery')).toBeFalsy(); + const { container: container2 } = render(); + expect(container2.firstChild).toHaveClass('t-image__wrapper--gallery'); + const { container: container3 } = render(); + expect(container3.querySelector('.t-image__wrapper--gallery')).toBeFalsy(); + }); + + test('gallery shadow', () => { + const { container } = render(); + expect(container.querySelector('.t-image__gallery-shadow')).toBeTruthy(); + }); + + const positionClassNameMap = { + top: 't-image--position-top', + bottom: 't-image--position-bottom', + left: 't-image--position-left', + right: 't-image--position-right', + center: 't-image--position-center', + }; + Object.entries(positionClassNameMap).forEach(([enumValue, expectedClassName]) => { + test(`position is equal to ${enumValue}`, () => { + const wrapper = render(); + const container = wrapper.container.querySelector('.t-image'); + expect(container).toHaveClass(expectedClassName); + }); + }); + + ['circle', 'round', 'square'].forEach((item) => { + test(`shape is equal to ${item}`, () => { + const wrapper = render(); + const container = wrapper.container.querySelector('.t-image__wrapper'); + expect(container).toHaveClass(`t-image__wrapper--shape-${item}`); + }); + }); + + test('srcset', () => { + const { container } = render( + , + ); + const domWrapper = container.querySelector('picture > source'); + expect(domWrapper.getAttribute('srcset')).toBe('https://tdesign.gtimg.com/img/tdesign-image.avif'); + const domWrapper1 = container.querySelector('picture > source:nth-child(2)'); + expect(domWrapper1.getAttribute('srcset')).toBe('https://tdesign.gtimg.com/img/tdesign-image.webp'); + }); + test('error', () => { + const { container } = render( + TNode} src="https://this.is.an.error.img.com" />, + ); + const imgDom = container.querySelector('img'); + simulateImageEvent(imgDom, 'error'); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + + test('loading', () => { + const { container } = render(TNode} />); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('overlayContent', () => { + const { container } = render(TNode} />); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container.querySelector('.t-image__overlay-content')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('placeholder', () => { + const { container } = render(TNode} />); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + }); + + describe('events', () => { + test('error', () => { + const onErrorFn = vi.fn(); + const { container } = render(); + const imgDom = container.querySelector('img'); + simulateImageEvent(imgDom, 'error'); + expect(container.querySelector('.t-image__error')).toBeTruthy(); + expect(container.querySelector('.t-icon-image-error')).toBeTruthy(); + expect(onErrorFn).toHaveBeenCalled(); + expect(onErrorFn.mock.calls[0][0].e.type).toBe('error'); + }); + + test('load', () => { + const onLoadFn1 = vi.fn(); + const { container } = render(); + + const imgDom1 = container.querySelector('img'); + simulateImageEvent(imgDom1, 'load'); + expect(onLoadFn1).toHaveBeenCalled(); + expect(onLoadFn1.mock.calls[0][0].e.type).toBe('load'); + }); + }); + + describe('scenarios', () => { + test('overlayTrigger hover', async () => { + const { container } = getOverlayImageMount({ + overlayTrigger: 'hover', + src: 'https://tdesign.gtimg.com/demo/demo-image-1.png', + }); + fireEvent.mouseEnter(container.querySelector('.t-image__wrapper')); + await mockDelay(); + expect(container.querySelector('.t-image__overlay-content')).toBeTruthy(); + expect(container.querySelector('.t-image__overlay-content--hidden')).toBeFalsy(); + fireEvent.mouseLeave(container.querySelector('.t-image__wrapper')); + await mockDelay(); + expect(container.querySelector('.t-image__overlay-content--hidden')).toBeTruthy(); + }); + + test('lazy', () => { + (document.body as HTMLElement & { height?: number }).height = 250; + const win = window as Window & { observeCallbackhasCalled?: boolean }; + win.observeCallbackhasCalled = false; + const observe = (element: Element, callback: (entries: Array<{ isIntersecting: boolean }>) => void) => { + element.parentNode.parentNode.addEventListener('scroll', () => { + if (win.observeCallbackhasCalled) { + callback([{ isIntersecting: false }]); + } else { + win.observeCallbackhasCalled = true; + callback([{ isIntersecting: true }]); + } + }); + }; + mockIntersectionObserver({}, { observe }); + + const { container } = render( + + {Array.from({ length: 16 }).map((_, index) => ( + + ))} + , + ); + + const spaceElement = container.querySelector('.t-space'); + fireEvent.scroll(spaceElement, { target: { scrollY: 400 } }); + expect(spaceElement.firstChild.querySelector('img')).not.toBeNull(); + expect(spaceElement.lastChild.querySelector('img')).toBeNull(); + }); + }); +}); diff --git a/packages/components/image/__tests__/lazy.test.jsx b/packages/components/image/__tests__/lazy.test.jsx deleted file mode 100644 index f095c77f0f..0000000000 --- a/packages/components/image/__tests__/lazy.test.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { fireEvent, render, mockIntersectionObserver } from '@test/utils'; -import { Image } from '..'; -import Space from '../../space'; - -describe('Image Component', () => { - beforeAll(() => { - // 用于判断是否已经触发回调 - window.observeCallbackhasCalled = false; - const observe = (element, callback) => { - // 监控图片容器的滚动事件,后面会触发容器的滚动。 - element.parentNode.parentNode.addEventListener('scroll', () => { - if (window.observeCallbackhasCalled) { - callback([{ isIntersecting: false }]); - } else { - window.observeCallbackhasCalled = true; - callback([{ isIntersecting: true }]); - } - }); - } - mockIntersectionObserver({}, { observe }); - }); - - it('props.lazy works fine', () => { - document.body.height = 250; - - const { container } = render( - - { - Array.from({ length: 16 }).map((_, index) => ( - - )) - } - - ); - - const spaceElement = container.querySelector('.t-space'); - fireEvent.scroll(spaceElement, { target: { scrollY: 400 } }); - // 滚动后,第一张图片会加载,但后面的图片不会加载 - expect(spaceElement.firstChild.querySelector('img')).not.toBeNull() - expect(spaceElement.lastChild.querySelector('img')).toBeNull() - - }); -}); diff --git a/packages/components/image/__tests__/mount.jsx b/packages/components/image/__tests__/mount.jsx deleted file mode 100644 index 5aa239efb4..0000000000 --- a/packages/components/image/__tests__/mount.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { render } from '@test/utils'; - -function ImageOverlay() { - return
预览
-} - -export function getOverlayImageMount(Image, props, events) { - return render( - } - {...props} - {...events} - > - ); -} diff --git a/packages/components/image/__tests__/vitest-image.test.jsx b/packages/components/image/__tests__/vitest-image.test.jsx deleted file mode 100644 index 8607aca52e..0000000000 --- a/packages/components/image/__tests__/vitest-image.test.jsx +++ /dev/null @@ -1,149 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * 该文件由脚本自动生成,如需修改请联系 PMC - * This file generated by scripts of tdesign-api. `npm run api:docs Image React(PC) vitest,finalProject` - * If you need to modify this file, contact PMC first please. - */ -import React from 'react'; -import { fireEvent, vi, render, mockDelay, simulateImageEvent } from '@test/utils'; -import { Image } from '..'; -import { getOverlayImageMount } from './mount'; - -describe('Image Component', () => { - it('props.alt works fine', () => { - const wrapper = render({'text); - const container = wrapper.container.querySelector('img'); - expect(container.getAttribute('alt')).toBe('text image load failed'); - }); - - it('props.error works fine', () => { - const { container } = render( - TNode} src={'https://this.is.an.error.img.com'}>, - ); - const imgDom = container.querySelector('img'); - simulateImageEvent(imgDom, 'error'); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - ['contain', 'cover', 'fill', 'none', 'scale-down'].forEach((item) => { - it(`props.fit is equal to ${item}`, () => { - const wrapper = render(); - const container = wrapper.container.querySelector('.t-image'); - expect(container).toHaveClass(`t-image--fit-${item}`); - expect(container).toMatchSnapshot(); - }); - }); - - it('props.gallery works fine', () => { - // gallery default value is false - const { container: container1 } = render(); - expect(container1.querySelector(`.${'t-image__wrapper--gallery'}`)).toBeFalsy(); - // gallery = true - const { container: container2 } = render(); - expect(container2.firstChild).toHaveClass('t-image__wrapper--gallery'); - // gallery = false - const { container: container3 } = render(); - expect(container3.querySelector(`.${'t-image__wrapper--gallery'}`)).toBeFalsy(); - }); - - it('props.gallery works fine. `".t-image__gallery-shadow"` should exist', () => { - const { container } = render(); - expect(container.querySelector('.t-image__gallery-shadow')).toBeTruthy(); - }); - - it('props.loading works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.overlayContent works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container.querySelector('.t-image__overlay-content')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.overlayTrigger: show overlay content on hover', async () => { - const { container } = getOverlayImageMount(Image, { - overlayTrigger: 'hover', - src: 'https://tdesign.gtimg.com/demo/demo-image-1.png', - }); - fireEvent.mouseEnter(container.querySelector('.t-image__wrapper')); - await mockDelay(); - expect(container.querySelector('.t-image__overlay-content')).toBeTruthy(); - expect(container.querySelector('.t-image__overlay-content--hidden')).toBeFalsy(); - fireEvent.mouseLeave(container.querySelector('.t-image__wrapper')); - await mockDelay(); - expect(container.querySelector('.t-image__overlay-content--hidden')).toBeTruthy(); - }); - - it('props.placeholder works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - const positionClassNameMap = { - top: 't-image--position-top', - bottom: 't-image--position-bottom', - left: 't-image--position-left', - right: 't-image--position-right', - center: 't-image--position-center', - }; - Object.entries(positionClassNameMap).forEach(([enumValue, expectedClassName]) => { - it(`props.position is equal to ${enumValue}`, () => { - let propValue = { true: true, false: false }[enumValue]; - propValue = propValue === undefined ? enumValue : propValue; - const wrapper = render(); - const container = wrapper.container.querySelector('.t-image'); - expect(container).toHaveClass(expectedClassName); - }); - }); - - ['circle', 'round', 'square'].forEach((item) => { - it(`props.shape is equal to ${item}`, () => { - const wrapper = render(); - const container = wrapper.container.querySelector('.t-image__wrapper'); - expect(container).toHaveClass(`t-image__wrapper--shape-${item}`); - }); - }); - - it(`props.srcset is equal to {'image/avif': 'https://tdesign.gtimg.com/img/tdesign-image.avif','image/webp': 'https://tdesign.gtimg.com/img/tdesign-image.webp'}`, () => { - const { container } = render( - , - ); - const domWrapper = container.querySelector('picture > source'); - expect(domWrapper.getAttribute('srcset')).toBe('https://tdesign.gtimg.com/img/tdesign-image.avif'); - const domWrapper1 = container.querySelector('picture > source:nth-child(2)'); - expect(domWrapper1.getAttribute('srcset')).toBe('https://tdesign.gtimg.com/img/tdesign-image.webp'); - }); - - it('events.error works fine', () => { - const onErrorFn = vi.fn(); - const { container } = render(); - const imgDom = container.querySelector('img'); - simulateImageEvent(imgDom, 'error'); - expect(container.querySelector('.t-image__error')).toBeTruthy(); - expect(container.querySelector('.t-icon-image-error')).toBeTruthy(); - expect(onErrorFn).toHaveBeenCalled(); - expect(onErrorFn.mock.calls[0][0].e.type).toBe('error'); - }); - - it('events.load works fine', () => { - const onLoadFn1 = vi.fn(); - const { container } = render( - , - ); - - const imgDom1 = container.querySelector('img'); - simulateImageEvent(imgDom1, 'load'); - expect(onLoadFn1).toHaveBeenCalled(); - expect(onLoadFn1.mock.calls[0][0].e.type).toBe('load'); - }); -}); diff --git a/packages/components/input-adornment/__tests__/input-adornment.test.tsx b/packages/components/input-adornment/__tests__/input-adornment.test.tsx index ae6dd70249..20d8c7bde5 100644 --- a/packages/components/input-adornment/__tests__/input-adornment.test.tsx +++ b/packages/components/input-adornment/__tests__/input-adornment.test.tsx @@ -3,8 +3,10 @@ // import InputAdornment from '../index'; // TODO -describe('InputAdornment 组件测试', () => { - test('dom', () => { - expect(true).toBe(true); +describe('InputAdornment', () => { + describe('scenarios', () => { + test('dom', () => { + expect(true).toBe(true); + }); }); }); diff --git a/packages/components/input-number/__tests__/input-number.test.tsx b/packages/components/input-number/__tests__/input-number.test.tsx index 7231b5fd91..81d98be214 100644 --- a/packages/components/input-number/__tests__/input-number.test.tsx +++ b/packages/components/input-number/__tests__/input-number.test.tsx @@ -4,73 +4,79 @@ import userEvent from '@testing-library/user-event'; import InputNumber from '../index'; -describe('InputNumber 组件测试', () => { - const InputNumberPlaceholder = '请输入内容'; - const InputNumberValue = 9; +const InputNumberPlaceholder = '请输入内容'; +const InputNumberValue = 9; - test('input number change', async () => { - const changeFn = vi.fn(); - const { container, queryByPlaceholderText } = render( - , - ); - expect(container.children[0].classList.contains('t-input-number')).toBeTruthy(); - expect(queryByPlaceholderText(InputNumberPlaceholder)).toBeInTheDocument(); - fireEvent.change(queryByPlaceholderText(InputNumberPlaceholder), { - target: { value: InputNumberValue }, +describe('InputNumber', () => { + describe('props', () => { + test('autofocus', () => { + const { container } = render(); + const wrapper = container.querySelector('input'); + expect(wrapper.getAttribute('autofocus')).toBeDefined(); }); - expect(changeFn).toHaveBeenCalledTimes(1); - expect(changeFn.mock.calls[0][0]).toBe(InputNumberValue); - }); - - test('input number theme column', async () => { - const { queryByPlaceholderText, container } = render( - , - ); - fireEvent.mouseEnter(container.firstChild); - fireEvent.click(container.querySelector('.t-input-number__increase')); - expect((queryByPlaceholderText(InputNumberPlaceholder) as HTMLInputElement).value).toEqual('6'); - }); + test('disabled', async () => { + const changeFn = vi.fn(); + const { queryByPlaceholderText } = render( + , + ); + expect((queryByPlaceholderText(InputNumberPlaceholder) as HTMLInputElement).disabled).toBeTruthy(); + }); - test('autofocus', () => { - const { container } = render(); - const wrapper = container.querySelector('input'); - expect(wrapper.getAttribute('autofocus')).toBeDefined(); + test('size', async () => { + const { container } = render(); + expect(container.children[0].classList.contains('t-size-l')).toBeTruthy(); + }); }); - test('blur', async () => { - const blurFn = vi.fn(); - const { queryByPlaceholderText } = render(); - const InputDom = queryByPlaceholderText(InputNumberPlaceholder); - fireEvent.change(InputDom, { target: { value: 1 } }); - fireEvent.blur(InputDom); - expect(blurFn).toHaveBeenCalledTimes(1); - }); + describe('events', () => { + test('blur', async () => { + const blurFn = vi.fn(); + const { queryByPlaceholderText } = render(); + const InputDom = queryByPlaceholderText(InputNumberPlaceholder); + fireEvent.change(InputDom, { target: { value: 1 } }); + fireEvent.blur(InputDom); + expect(blurFn).toHaveBeenCalledTimes(1); + }); - test('keyDown', async () => { - const user = userEvent.setup(); - const onEnterFn = vi.fn(); - const onKeydownFn = vi.fn(); - const { queryByPlaceholderText } = render( - , - ); - const InputNumberDom = queryByPlaceholderText(InputNumberPlaceholder); - await user.type(InputNumberDom, '123{enter}'); + test('keyDown', async () => { + const user = userEvent.setup(); + const onEnterFn = vi.fn(); + const onKeydownFn = vi.fn(); + const { queryByPlaceholderText } = render( + , + ); + const InputNumberDom = queryByPlaceholderText(InputNumberPlaceholder); + await user.type(InputNumberDom, '123{enter}'); - expect(onEnterFn).toHaveBeenCalled(); - expect(onKeydownFn).toHaveBeenCalled(); + expect(onEnterFn).toHaveBeenCalled(); + expect(onKeydownFn).toHaveBeenCalled(); + }); }); - test('disabled', async () => { - const changeFn = vi.fn(); - const { queryByPlaceholderText } = render( - , - ); - expect((queryByPlaceholderText(InputNumberPlaceholder) as HTMLInputElement).disabled).toBeTruthy(); - }); + describe('scenarios', () => { + test('input number change', async () => { + const changeFn = vi.fn(); + const { container, queryByPlaceholderText } = render( + , + ); + expect(container.children[0].classList.contains('t-input-number')).toBeTruthy(); + expect(queryByPlaceholderText(InputNumberPlaceholder)).toBeInTheDocument(); + fireEvent.change(queryByPlaceholderText(InputNumberPlaceholder), { + target: { value: InputNumberValue }, + }); + expect(changeFn).toHaveBeenCalledTimes(1); + expect(changeFn.mock.calls[0][0]).toBe(InputNumberValue); + }); + + test('input number theme column', async () => { + const { queryByPlaceholderText, container } = render( + , + ); - test('size', async () => { - const { container } = render(); - expect(container.children[0].classList.contains('t-size-l')).toBeTruthy(); + fireEvent.mouseEnter(container.firstChild); + fireEvent.click(container.querySelector('.t-input-number__increase')); + expect((queryByPlaceholderText(InputNumberPlaceholder) as HTMLInputElement).value).toEqual('6'); + }); }); }); diff --git a/packages/components/input/__tests__/__snapshots__/vitest-input.test.jsx.snap b/packages/components/input/__tests__/__snapshots__/input.test.tsx.snap similarity index 79% rename from packages/components/input/__tests__/__snapshots__/vitest-input.test.jsx.snap rename to packages/components/input/__tests__/__snapshots__/input.test.tsx.snap index 5501fe33ca..2bcfc70980 100644 --- a/packages/components/input/__tests__/__snapshots__/vitest-input.test.jsx.snap +++ b/packages/components/input/__tests__/__snapshots__/input.test.tsx.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Input Component > props.align is equal to center 1`] = ` +exports[`Input > props > align is equal to center 1`] = `
@@ -13,7 +13,7 @@ exports[`Input Component > props.align is equal to center 1`] = `
`; -exports[`Input Component > props.align is equal to left 1`] = ` +exports[`Input > props > align is equal to left 1`] = `
@@ -26,7 +26,7 @@ exports[`Input Component > props.align is equal to left 1`] = `
`; -exports[`Input Component > props.align is equal to right 1`] = ` +exports[`Input > props > align is equal to right 1`] = `
@@ -39,7 +39,7 @@ exports[`Input Component > props.align is equal to right 1`] = `
`; -exports[`Input Component > props.label works fine 1`] = ` +exports[`Input > props > label 1`] = `
props.label works fine 1`] = `
`; -exports[`Input Component > props.status is equal to default 1`] = ` +exports[`Input > props > status is equal to default 1`] = `
@@ -81,7 +81,7 @@ exports[`Input Component > props.status is equal to default 1`] = `
`; -exports[`Input Component > props.status is equal to error 1`] = ` +exports[`Input > props > status is equal to error 1`] = `
@@ -94,7 +94,7 @@ exports[`Input Component > props.status is equal to error 1`] = `
`; -exports[`Input Component > props.status is equal to success 1`] = ` +exports[`Input > props > status is equal to success 1`] = `
@@ -107,7 +107,7 @@ exports[`Input Component > props.status is equal to success 1`] = `
`; -exports[`Input Component > props.status is equal to warning 1`] = ` +exports[`Input > props > status is equal to warning 1`] = `
@@ -120,7 +120,7 @@ exports[`Input Component > props.status is equal to warning 1`] = `
`; -exports[`Input Component > props.suffix works fine 1`] = ` +exports[`Input > props > suffix 1`] = `
props.suffix works fine 1`] = `
`; -exports[`Input Component > props.suffixIcon works fine 1`] = ` +exports[`Input > props > suffixIcon 1`] = `
+ + + , + ); +} + +describe('InputGroup', () => { + describe('props', () => { + test('separate', () => { + // separate default value is + const { container: container1 } = getInputGroupDefaultMount(); + expect(container1.querySelector(`.${'t-input-group--separate'}`)).toBeFalsy(); + // separate = true + const { container: container2 } = getInputGroupDefaultMount({ + separate: true, + }); + expect(container2.firstChild).toHaveClass('t-input-group--separate'); + // separate = false + const { container: container3 } = getInputGroupDefaultMount({ + separate: false, + }); + expect(container3.querySelector(`.${'t-input-group--separate'}`)).toBeFalsy(); + }); + }); +}); diff --git a/packages/components/input/__tests__/input.test.tsx b/packages/components/input/__tests__/input.test.tsx index 2339b4ee77..66608216f5 100644 --- a/packages/components/input/__tests__/input.test.tsx +++ b/packages/components/input/__tests__/input.test.tsx @@ -1,182 +1,629 @@ import React from 'react'; -import { act, fireEvent, render, vi } from '@test/utils'; +import { act, fireEvent, mockDelay, render, simulateInputChange, vi } from '@test/utils'; import userEvent from '@testing-library/user-event'; -import Input from '../Input'; - -describe('Input 组件测试', () => { - const InputPlaceholder = '请输入内容'; - const InputValue = '24/05/2020'; - test('create', async () => { - const changeFn = vi.fn(); - const { container, queryByPlaceholderText } = render(); - expect(container.children[0].children[0].classList.contains('t-input')).toBeTruthy(); - expect(queryByPlaceholderText(InputPlaceholder)).toBeInTheDocument(); - fireEvent.change(queryByPlaceholderText(InputPlaceholder), { - target: { value: InputValue }, - }); - expect(changeFn).toHaveBeenCalledTimes(1); - expect(changeFn.mock.calls[0][0]).toBe(InputValue); - }); - test('clearable', async () => { - const clearFn = vi.fn(); - const { queryByPlaceholderText, container } = render( - , - ); - fireEvent.change(queryByPlaceholderText(InputPlaceholder), { - target: { value: InputValue }, +import { Input } from '..'; + +import type { TdInputProps } from '../type'; + +const InputPlaceholder = '请输入内容'; +const InputValue = '24/05/2020'; + +describe('Input', () => { + describe('props', () => { + const alignClassNameList = [{ 't-align-left': false }, 't-align-center', 't-align-right']; + (['left', 'center', 'right'] as TdInputProps['align'][]).forEach((item, index) => { + test(`align is equal to ${item}`, () => { + const wrapper = render(); + const container = wrapper.container.querySelector('.t-input'); + if (typeof alignClassNameList[index] === 'string') { + expect(container).toHaveClass(alignClassNameList[index]); + } else if (typeof alignClassNameList[index] === 'object') { + const classNameKey = Object.keys(alignClassNameList[index])[0]; + expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); + } + expect(container).toMatchSnapshot(); + }); }); - expect((queryByPlaceholderText(InputPlaceholder) as HTMLInputElement).value).toEqual(InputValue); - fireEvent.mouseEnter(container.firstChild.firstChild); - fireEvent.click(container.querySelector('.t-input__suffix-clear')); - expect((queryByPlaceholderText(InputPlaceholder) as HTMLInputElement).value).toEqual(''); - }); - test('clearable can not work when mouseLeave', async () => { - const { queryByPlaceholderText, container } = render(); - fireEvent.change(queryByPlaceholderText(InputPlaceholder), { - target: { value: InputValue }, - }); - fireEvent.mouseEnter(container.firstChild.firstChild); - expect(container.querySelector('.t-input__suffix-clear')).toBeInTheDocument(); - fireEvent.mouseLeave(container.firstChild.firstChild); - expect(container.querySelector('.t-input__suffix-clear')).not.toBeInTheDocument(); - }); - test('should not lost focus when clear input', async () => { - const blurFn = vi.fn(); - const { queryByPlaceholderText, container } = render( - , - ); - const InputDom = queryByPlaceholderText(InputPlaceholder); - fireEvent.change(InputDom, { target: { value: InputValue } }); - fireEvent.mouseEnter(container.firstChild.firstChild); - const clearIcon = container.querySelector('.t-input__suffix-clear'); - fireEvent.mouseDown(clearIcon); - fireEvent.mouseUp(clearIcon); - fireEvent.click(clearIcon); - expect(blurFn).toHaveBeenCalledTimes(0); - fireEvent.blur(InputDom); - expect(blurFn).toHaveBeenCalledTimes(1); - }); - test('onComposition can be call', async () => { - const user = userEvent.setup(); - const onCompositionStartFn = vi.fn(); - const onCompositionEndFn = vi.fn(); - const { queryByPlaceholderText } = render( - , - ); - const InputDom = queryByPlaceholderText(InputPlaceholder) as HTMLInputElement; - await user.type(InputDom, InputValue); - fireEvent.compositionStart(InputDom); - await user.type(InputDom, InputValue); - fireEvent.compositionEnd(InputDom); - fireEvent.compositionEnd(InputDom); - await user.type(InputDom, InputValue); - expect(onCompositionStartFn).toHaveBeenCalled(); - expect(onCompositionEndFn).toHaveBeenCalled(); - expect(InputDom.value).toBe([InputValue, InputValue, InputValue].join('')); - }); - test('composing value should not be kept when value is reset by outside', async () => { - // 模拟中文输入法的一次合成输入 - const imeInput = (input: HTMLInputElement, value: string) => { - fireEvent.compositionStart(input, { target: { value: input.value } }); - fireEvent.change(input, { target: { value } }); - fireEvent.compositionEnd(input, { target: { value } }); - }; - - const ControlledInput = () => { - const [value, setValue] = React.useState(''); - const [, setCompositionCount] = React.useState(0); - return ( - <> - setValue(v as string)} - // 开始合成时触发重渲染,复现 SelectInput 的 setIsTyping 更新。 - onCompositionstart={() => setCompositionCount((count) => count + 1)} - /> - - + + test('allowInputOverMax', () => { + const { container } = render(); + const inputDom = container.querySelector('input'); + simulateInputChange(inputDom, 'Hello TDesign'); + const attrDom = container.querySelector('input'); + expect(attrDom.value).toBe('Hello'); + }); + + test('autocomplete', () => { + const wrapper = render(); + const container = wrapper.container.querySelector('input'); + expect(container.getAttribute('autocomplete')).toBe('https://tdesign.tencent.com/'); + }); + + test(`autofocus is equal to false`, () => { + const { container } = render(); + const domWrapper = container.querySelector('input'); + expect(domWrapper.getAttribute('autofocus')).toBeNull(); + }); + + test(`autofocus is equal to true`, () => { + const { container } = render(); + const domWrapper = container.querySelector('input'); + expect(domWrapper.getAttribute('autofocus')).toBeDefined(); + }); + + test('clearable: clear icon should exist on input mouseenter', async () => { + const { container } = render(); + fireEvent.mouseEnter(container.querySelector('.t-input')); + await mockDelay(); + expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); + }); + + test('clearable: click clear icon could clear input value to be empty', async () => { + const onClearFn1 = vi.fn(); + const onChangeFn1 = vi.fn(); + const { container } = render( + , ); - }; - const { queryByPlaceholderText, getByText } = render(); - const InputDom = queryByPlaceholderText(InputPlaceholder) as HTMLInputElement; + fireEvent.mouseEnter(container.querySelector('.t-input')); + await mockDelay(300); + expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); + fireEvent.click(container.querySelector('.t-input__suffix-clear')); + expect(onClearFn1).toHaveBeenCalled(); + expect(onClearFn1.mock.calls[0][0].e.stopPropagation).toBeTruthy(); + expect(onClearFn1.mock.calls[0][0].e.type).toBe('click'); + expect(onChangeFn1).toHaveBeenCalled(); + expect(onChangeFn1.mock.calls[0][0]).toBe(''); + expect(onChangeFn1.mock.calls[0][1].e.stopPropagation).toBeTruthy(); + expect(onChangeFn1.mock.calls[0][1].e.type).toBe('click'); + }); + + test('clearable: type=password, browseIcon and clearableIcon', async () => { + const { container } = render(); + expect(container.querySelector('.t-icon-browse-off')).toBeTruthy(); + fireEvent.mouseEnter(container.querySelector('.t-input')); + await mockDelay(300); + expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); + }); - imeInput(InputDom, '苹'); - expect(InputDom.value).toBe('苹'); + test('disabled', () => { + // disabled default value is + const wrapper1 = render(); + const container1 = wrapper1.container.querySelector('.t-input'); + expect(container1.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); + // disabled = true + const wrapper2 = render(); + const container2 = wrapper2.container.querySelector('.t-input'); + expect(container2).toHaveClass('t-is-disabled'); + // disabled = false + const wrapper3 = render(); + const container3 = wrapper3.container.querySelector('.t-input'); + expect(container3.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); + }); - // 外部清空输入框内容后,再次进入合成态不应残留上一次的输入 - fireEvent.click(getByText('reset')); - expect(InputDom.value).toBe(''); + test('format: focus and blur states have different value', () => { + const { container } = render( `${val} 元`} value="100">); + fireEvent.focus(container.querySelector('input')); + const attrDom = container.querySelector('input'); + expect(attrDom.value).toBe('100'); + fireEvent.blur(container.querySelector('input')); + const attrDom1 = container.querySelector('input'); + expect(attrDom1.value).toBe('100 元'); + }); - fireEvent.compositionStart(InputDom, { target: { value: InputDom.value } }); - expect(InputDom.value).toBe(''); + test(`inputClass is equal to name1 name2`, () => { + const { container } = render(); + const domWrapper = container.querySelector('.t-input'); + expect(domWrapper).toHaveClass('name1'); + expect(domWrapper).toHaveClass('name2'); + }); - fireEvent.change(InputDom, { target: { value: 'xiang' } }); - fireEvent.compositionEnd(InputDom, { target: { value: '香' } }); - expect(InputDom.value).toBe('香'); - }); - test('keyDown', async () => { - const user = userEvent.setup(); - const onEnterFn = vi.fn(); - const onKeydownFn = vi.fn(); - const { queryByPlaceholderText } = render( - , + test(`inputClass is equal to ['name1', 'name2']`, () => { + const { container } = render(); + const domWrapper = container.querySelector('.t-input'); + expect(domWrapper).toHaveClass('name1'); + expect(domWrapper).toHaveClass('name2'); + }); + + test(`inputClass is equal to { name1: true, name2: false }`, () => { + const { container } = render(); + const domWrapper = container.querySelector('.t-input'); + expect(domWrapper).toHaveClass('name1'); + expect(domWrapper.classList.contains('name2')).toBeFalsy(); + }); + + test('maxcharacter: length of value is over than maxcharacter', () => { + const onChangeFn = vi.fn(); + render(); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe('你好'); + expect(onChangeFn.mock.calls[0][1].trigger).toBe('initial'); + }); + + test('maxlength: length of value is over than maxlength', () => { + const onChangeFn = vi.fn(); + render(); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe('Hello'); + expect(onChangeFn.mock.calls[0][1].trigger).toBe('initial'); + }); + + test('name', () => { + const wrapper = render(); + const container = wrapper.container.querySelector('input'); + expect(container.getAttribute('name')).toBe('input-name'); + }); + + test('placeholder', () => { + const wrapper = render(); + const container = wrapper.container.querySelector('input'); + expect(container.getAttribute('placeholder')).toBe('this is input placeholder'); + }); + + test('readonly', () => { + // readonly default value is false + const wrapper1 = render(); + const container1 = wrapper1.container.querySelector('.t-input'); + expect(container1.querySelector(`.${'t-is-readonly'}`)).toBeFalsy(); + // readonly = true + const wrapper2 = render(); + const container2 = wrapper2.container.querySelector('.t-input'); + expect(container2).toHaveClass('t-is-readonly'); + // readonly = false + const wrapper3 = render(); + const container3 = wrapper3.container.querySelector('.t-input'); + expect(container3.querySelector(`.${'t-is-readonly'}`)).toBeFalsy(); + }); + + test('showClearIconOnEmpty', async () => { + const { container } = render(); + fireEvent.mouseEnter(container.querySelector('.t-input')); + await mockDelay(); + expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); + }); + + test('showLimitNumber: `{".t-input__limit-number":{"text":"2/5"}}` should exist', () => { + const { container } = render(); + expect(container.querySelector('.t-input__limit-number').textContent).toBe('2/5'); + }); + + const sizeClassNameList = ['t-size-s', { 't-size-m': false }, 't-size-l']; + (['small', 'medium', 'large'] as TdInputProps['size'][]).forEach((item, index) => { + test(`size is equal to ${item}`, () => { + const wrapper = render(); + const container = wrapper.container.querySelector('.t-input'); + if (typeof sizeClassNameList[index] === 'string') { + expect(container).toHaveClass(sizeClassNameList[index]); + } else if (typeof sizeClassNameList[index] === 'object') { + const classNameKey = Object.keys(sizeClassNameList[index])[0]; + expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); + } + }); + }); + + const statusClassNameList = [{ 't-is-default': false }, 't-is-success', 't-is-warning', 't-is-error']; + (['default', 'success', 'warning', 'error'] as TdInputProps['status'][]).forEach((item, index) => { + test(`status is equal to ${item}`, () => { + const wrapper = render(); + const container = wrapper.container.querySelector('.t-input'); + if (typeof statusClassNameList[index] === 'string') { + expect(container).toHaveClass(statusClassNameList[index]); + } else if (typeof statusClassNameList[index] === 'object') { + const classNameKey = Object.keys(statusClassNameList[index])[0]; + expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); + } + expect(container).toMatchSnapshot(); + }); + }); + + test('tips is equal this is a tip', () => { + const { container } = render(); + expect(container.querySelectorAll('.t-input__tips').length).toBe(1); + }); + + const attributeValues = ['text', 'number', 'url', 'tel', 'password', 'search', 'submit', 'hidden']; + (['text', 'number', 'url', 'tel', 'password', 'search', 'submit', 'hidden'] as TdInputProps['type'][]).forEach( + (item, index) => { + test(`type is equal to ${item}`, () => { + const wrapper = render(); + const container = wrapper.container.querySelector('input'); + expect(container.getAttribute('type')).toBe(attributeValues[index]); + }); + }, ); - const InputDom = queryByPlaceholderText(InputPlaceholder); - await user.type(InputDom, 'abc{enter}'); - expect(onEnterFn).toHaveBeenCalled(); - expect(onKeydownFn).toHaveBeenCalled(); - }); - test('disabled', async () => { - const changeFn = vi.fn(); - const { queryByPlaceholderText } = render(); - expect((queryByPlaceholderText(InputPlaceholder) as HTMLInputElement).disabled).toBeTruthy(); - }); - test('password', async () => { - const { queryByPlaceholderText, container } = render(); - expect((queryByPlaceholderText(InputPlaceholder) as HTMLInputElement).type).toEqual('password'); - expect(container.querySelector('.t-icon-browse-off')).toBeTruthy(); - fireEvent.click(container.querySelector('.t-input__suffix-clear')); - expect(container.querySelector('.t-icon-browse')).toBeTruthy(); - }); - test('password can be toggle when disabled', async () => { - const { container } = render(); + test('type is equal password', () => { + const { container } = render(); + expect(container.querySelectorAll('.t-icon-browse-off').length).toBe(1); + }); - expect(container.querySelector('.t-icon-browse-off')).toBeTruthy(); - fireEvent.click(container.querySelector('.t-input__suffix-clear')); - expect(container.querySelector('.t-icon-browse-off')).toBeTruthy(); - }); - test('status', async () => { - const { container } = render(); - expect(container.children[0].children[0].classList.contains('t-is-error')).toBeTruthy(); - }); - test('size', async () => { - const { container } = render(); - expect(container.children[0].children[0].classList.contains('t-size-l')).toBeTruthy(); + test('type: password could be visible by click browse icon', () => { + const { container } = render(); + fireEvent.click(container.querySelector('.t-icon-browse-off')); + expect(container.querySelector('.t-icon-browse')).toBeTruthy(); + const attrDom = container.querySelector('input'); + expect(attrDom.getAttribute('type')).toBe('text'); + fireEvent.click(container.querySelector('.t-icon-browse')); + expect(container.querySelector('.t-icon-browse-off')).toBeTruthy(); + const attrDom1 = container.querySelector('input'); + expect(attrDom1.getAttribute('type')).toBe('password'); + }); + + test('disabled', async () => { + const changeFn = vi.fn(); + const { queryByPlaceholderText } = render(); + expect((queryByPlaceholderText(InputPlaceholder) as HTMLInputElement).disabled).toBeTruthy(); + }); + test('status', async () => { + const { container } = render(); + expect(container.children[0].children[0].classList.contains('t-is-error')).toBeTruthy(); + }); + test('size', async () => { + const { container } = render(); + expect(container.children[0].children[0].classList.contains('t-size-l')).toBeTruthy(); + }); + test('label', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container.querySelector('.t-input__prefix')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('prefixIcon', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container.querySelector('.t-input__prefix-icon')).toBeTruthy(); + }); + + test('suffix', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container.querySelector('.t-input__suffix')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('suffixIcon', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container.querySelector('.t-input__suffix-icon')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('label display', async () => { + const text = 'test-label'; + const { getByText } = await render(); + + act(() => { + expect(getByText(text)).toBeTruthy(); + }); + }); + + test('prefixIcon display', async () => { + const text = 'test-prefixIcon'; + const { getByText } = await render({text}} />); + + act(() => { + expect(getByText(text)).toBeTruthy(); + }); + }); }); - test('label display', async () => { - const text = 'test-label'; - const { getByText } = await render(); - act(() => { - expect(getByText(text)).toBeTruthy(); + describe('events', () => { + test('blur', async () => { + const onFocusFn = vi.fn(); + const onBlurFn1 = vi.fn(); + const { container } = render(); + fireEvent.focus(container.querySelector('input')); + expect(onFocusFn).toHaveBeenCalled(); + expect(onFocusFn.mock.calls[0][0]).toBe('initial-input-value'); + expect(onFocusFn.mock.calls[0][1].e.type).toBe('focus'); + fireEvent.blur(container.querySelector('input')); + await mockDelay(300); + expect(onBlurFn1).toHaveBeenCalled(); + expect(onBlurFn1.mock.calls[0][0]).toBe('initial-input-value'); + expect(onBlurFn1.mock.calls[0][1].e.type).toBe('blur'); + }); + + test('change: empty value could trigger change event', () => { + const onChangeFn = vi.fn(); + const { container } = render(); + const inputDom = container.querySelector('input'); + simulateInputChange(inputDom, 'initial value'); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe('initial value'); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('change'); + }); + + test('change: controlled value test', () => { + const onChangeFn = vi.fn(); + const { container } = render(); + const inputDom = container.querySelector('input'); + simulateInputChange(inputDom, 'Hello TDesign'); + const attrDom = container.querySelector('input'); + expect(attrDom.value).toBe('TDesign'); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe('Hello TDesign'); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('change'); + }); + + test('change: uncontrolled value test', () => { + const onChangeFn = vi.fn(); + const { container } = render(); + const inputDom = container.querySelector('input'); + simulateInputChange(inputDom, 'Hello TDesign'); + const attrDom = container.querySelector('input'); + expect(attrDom.value).toBe('Hello TDesign'); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe('Hello TDesign'); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('change'); + }); + + test('click', () => { + const fn = vi.fn(); + const { container } = render(); + fireEvent.click(container.querySelector('.t-input')); + expect(fn).toHaveBeenCalled(); + expect(fn.mock.calls[0][0].e.type).toBe('click'); + }); + + test('compositionend', () => { + const onCompositionendFn = vi.fn(); + const { container } = render(); + fireEvent.compositionEnd(container.querySelector('input')); + expect(onCompositionendFn).toHaveBeenCalled(); + expect(onCompositionendFn.mock.calls[0][0]).toBe('输入结束'); + expect(onCompositionendFn.mock.calls[0][1].e.type).toBe('compositionend'); + }); + + test('compositionstart', () => { + const onCompositionstartFn = vi.fn(); + const { container } = render(); + fireEvent.compositionStart(container.querySelector('input')); + expect(onCompositionstartFn).toHaveBeenCalled(); + expect(onCompositionstartFn.mock.calls[0][0]).toBe('输入开始'); + expect(onCompositionstartFn.mock.calls[0][1].e.type).toBe('compositionstart'); + }); + + test('enter', () => { + const onEnterFn1 = vi.fn(); + const { container } = render(); + fireEvent.focus(container.querySelector('input')); + fireEvent.keyDown(container.querySelector('input'), { + key: 'Enter', + code: 'Enter', + charCode: 13, + }); + expect(onEnterFn1).toHaveBeenCalled(); + expect(onEnterFn1.mock.calls[0][0]).toBe('text'); + expect(onEnterFn1.mock.calls[0][1].e.type).toBe('keydown'); + }); + + test('focus', () => { + const onFocusFn = vi.fn(); + const { container } = render(); + fireEvent.focus(container.querySelector('input')); + expect(onFocusFn).toHaveBeenCalled(); + expect(onFocusFn.mock.calls[0][0]).toBe(''); + expect(onFocusFn.mock.calls[0][1].e.type).toBe('focus'); + }); + + test('keydown', () => { + const onKeydownFn = vi.fn(); + const { container } = render(); + fireEvent.keyDown(container.querySelector('input')); + expect(onKeydownFn).toHaveBeenCalled(); + expect(onKeydownFn.mock.calls[0][0]).toBe('text'); + expect(onKeydownFn.mock.calls[0][1].e.type).toBe('keydown'); + }); + + test('keypress', () => { + const onKeydownFn = vi.fn(); + const { container } = render(); + fireEvent.keyDown(container.querySelector('input')); + expect(onKeydownFn).toHaveBeenCalled(); + expect(onKeydownFn.mock.calls[0][0]).toBe('text'); + expect(onKeydownFn.mock.calls[0][1].e.type).toBe('keydown'); + }); + + test('keyup', () => { + const onKeyupFn = vi.fn(); + const { container } = render(); + fireEvent.keyUp(container.querySelector('input')); + expect(onKeyupFn).toHaveBeenCalled(); + expect(onKeyupFn.mock.calls[0][0]).toBe('text'); + expect(onKeyupFn.mock.calls[0][1].e.type).toBe('keyup'); + }); + + test('mouseenter', async () => { + const onMouseenterFn = vi.fn(); + const { container } = render(); + fireEvent.mouseEnter(container.querySelector('.t-input')); + await mockDelay(); + expect(onMouseenterFn).toHaveBeenCalled(); + expect(onMouseenterFn.mock.calls[0][0].e.type).toBe('mouseenter'); + }); + + test('mouseleave', async () => { + const onMouseleaveFn = vi.fn(); + const { container } = render(); + fireEvent.mouseLeave(container.querySelector('.t-input')); + await mockDelay(); + expect(onMouseleaveFn).toHaveBeenCalled(); + expect(onMouseleaveFn.mock.calls[0][0].e.type).toBe('mouseleave'); + }); + + test('paste', () => { + const onPasteFn = vi.fn(); + const { container } = render(); + fireEvent.paste(container.querySelector('input')); + expect(onPasteFn).toHaveBeenCalled(); + expect(onPasteFn.mock.calls[0][0].e.type).toBe('paste'); + }); + + test('validate', () => { + const onValidateFn = vi.fn(); + render(); + expect(onValidateFn).toHaveBeenCalled(); + expect(onValidateFn.mock.calls[0][0].error).toBe('exceed-maximum'); + }); + + test('wheel', () => { + const onWheelFn = vi.fn(); + const { container } = render(); + fireEvent.wheel(container.querySelector('input')); + expect(onWheelFn).toHaveBeenCalled(); + expect(onWheelFn.mock.calls[0][0].e.type).toBe('wheel'); + }); + + test('onComposition can be call', async () => { + const user = userEvent.setup(); + const onCompositionStartFn = vi.fn(); + const onCompositionEndFn = vi.fn(); + const { queryByPlaceholderText } = render( + , + ); + const InputDom = queryByPlaceholderText(InputPlaceholder) as HTMLInputElement; + await user.type(InputDom, InputValue); + fireEvent.compositionStart(InputDom); + await user.type(InputDom, InputValue); + fireEvent.compositionEnd(InputDom); + fireEvent.compositionEnd(InputDom); + await user.type(InputDom, InputValue); + expect(onCompositionStartFn).toHaveBeenCalled(); + expect(onCompositionEndFn).toHaveBeenCalled(); + expect(InputDom.value).toBe([InputValue, InputValue, InputValue].join('')); + }); + + test('keyDown', async () => { + const user = userEvent.setup(); + const onEnterFn = vi.fn(); + const onKeydownFn = vi.fn(); + const { queryByPlaceholderText } = render( + , + ); + const InputDom = queryByPlaceholderText(InputPlaceholder); + await user.type(InputDom, 'abc{enter}'); + expect(onEnterFn).toHaveBeenCalled(); + expect(onKeydownFn).toHaveBeenCalled(); }); }); - test('prefixIcon display', async () => { - const text = 'test-prefixIcon'; - const { getByText } = await render({text}} />); + describe('scenarios', () => { + test('create', async () => { + const changeFn = vi.fn(); + const { container, queryByPlaceholderText } = render( + , + ); + expect(container.children[0].children[0].classList.contains('t-input')).toBeTruthy(); + expect(queryByPlaceholderText(InputPlaceholder)).toBeInTheDocument(); + fireEvent.change(queryByPlaceholderText(InputPlaceholder), { + target: { value: InputValue }, + }); + expect(changeFn).toHaveBeenCalledTimes(1); + expect(changeFn.mock.calls[0][0]).toBe(InputValue); + }); + test('clearable', async () => { + const clearFn = vi.fn(); + const { queryByPlaceholderText, container } = render( + , + ); + fireEvent.change(queryByPlaceholderText(InputPlaceholder), { + target: { value: InputValue }, + }); + expect((queryByPlaceholderText(InputPlaceholder) as HTMLInputElement).value).toEqual(InputValue); + fireEvent.mouseEnter(container.firstChild.firstChild); + fireEvent.click(container.querySelector('.t-input__suffix-clear')); + expect((queryByPlaceholderText(InputPlaceholder) as HTMLInputElement).value).toEqual(''); + }); + test('clearable can not work when mouseLeave', async () => { + const { queryByPlaceholderText, container } = render(); + fireEvent.change(queryByPlaceholderText(InputPlaceholder), { + target: { value: InputValue }, + }); + fireEvent.mouseEnter(container.firstChild.firstChild); + expect(container.querySelector('.t-input__suffix-clear')).toBeInTheDocument(); + fireEvent.mouseLeave(container.firstChild.firstChild); + expect(container.querySelector('.t-input__suffix-clear')).not.toBeInTheDocument(); + }); + test('should not lost focus when clear input', async () => { + const blurFn = vi.fn(); + const { queryByPlaceholderText, container } = render( + , + ); + const InputDom = queryByPlaceholderText(InputPlaceholder); + fireEvent.change(InputDom, { target: { value: InputValue } }); + fireEvent.mouseEnter(container.firstChild.firstChild); + const clearIcon = container.querySelector('.t-input__suffix-clear'); + fireEvent.mouseDown(clearIcon); + fireEvent.mouseUp(clearIcon); + fireEvent.click(clearIcon); + expect(blurFn).toHaveBeenCalledTimes(0); + fireEvent.blur(InputDom); + expect(blurFn).toHaveBeenCalledTimes(1); + }); + test('composing value should not be kept when value is reset by outside', async () => { + // 模拟中文输入法的一次合成输入 + const imeInput = (input: HTMLInputElement, value: string) => { + fireEvent.compositionStart(input, { target: { value: input.value } }); + fireEvent.change(input, { target: { value } }); + fireEvent.compositionEnd(input, { target: { value } }); + }; + + const ControlledInput = () => { + const [value, setValue] = React.useState(''); + const [, setCompositionCount] = React.useState(0); + return ( + <> + setValue(v as string)} + // 开始合成时触发重渲染,复现 SelectInput 的 setIsTyping 更新。 + onCompositionstart={() => setCompositionCount((count) => count + 1)} + /> + + + ); + }; + const { queryByPlaceholderText, getByText } = render(); + const InputDom = queryByPlaceholderText(InputPlaceholder) as HTMLInputElement; + + imeInput(InputDom, '苹'); + expect(InputDom.value).toBe('苹'); + + // 外部清空输入框内容后,再次进入合成态不应残留上一次的输入 + fireEvent.click(getByText('reset')); + expect(InputDom.value).toBe(''); + + fireEvent.compositionStart(InputDom, { + target: { value: InputDom.value }, + }); + expect(InputDom.value).toBe(''); + + fireEvent.change(InputDom, { target: { value: 'xiang' } }); + fireEvent.compositionEnd(InputDom, { target: { value: '香' } }); + expect(InputDom.value).toBe('香'); + }); + test('password', async () => { + const { queryByPlaceholderText, container } = render(); + expect((queryByPlaceholderText(InputPlaceholder) as HTMLInputElement).type).toEqual('password'); + + expect(container.querySelector('.t-icon-browse-off')).toBeTruthy(); + fireEvent.click(container.querySelector('.t-input__suffix-clear')); + expect(container.querySelector('.t-icon-browse')).toBeTruthy(); + }); + test('password can be toggle when disabled', async () => { + const { container } = render(); - act(() => { - expect(getByText(text)).toBeTruthy(); + expect(container.querySelector('.t-icon-browse-off')).toBeTruthy(); + fireEvent.click(container.querySelector('.t-input__suffix-clear')); + expect(container.querySelector('.t-icon-browse-off')).toBeTruthy(); }); }); }); diff --git a/packages/components/input/__tests__/mount.jsx b/packages/components/input/__tests__/mount.jsx deleted file mode 100644 index 99d2567571..0000000000 --- a/packages/components/input/__tests__/mount.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { render } from '@test/utils'; -import { Input } from '..'; - -export function getInputGroupDefaultMount(InputGroup, props) { - return render( - - - - , - ); -} - -export default getInputGroupDefaultMount; diff --git a/packages/components/input/__tests__/vitest-input.test.jsx b/packages/components/input/__tests__/vitest-input.test.jsx deleted file mode 100644 index d8962729a7..0000000000 --- a/packages/components/input/__tests__/vitest-input.test.jsx +++ /dev/null @@ -1,448 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * 该文件由脚本自动生成,如需修改请联系 PMC - * This file generated by scripts of tdesign-api. `npm run api:docs Input React(PC) vitest,finalProject` - * If you need to modify this file, contact PMC first please. - */ -import React from 'react'; -import { fireEvent, vi, render, mockDelay, simulateInputChange } from '@test/utils'; -import { Input, InputGroup } from '..'; -import { getInputGroupDefaultMount } from './mount'; - -describe('Input Component', () => { - const alignClassNameList = [{ 't-align-left': false }, 't-align-center', 't-align-right']; - ['left', 'center', 'right'].forEach((item, index) => { - it(`props.align is equal to ${item}`, () => { - const wrapper = render(); - const container = wrapper.container.querySelector('.t-input'); - if (typeof alignClassNameList[index] === 'string') { - expect(container).toHaveClass(alignClassNameList[index]); - } else if (typeof alignClassNameList[index] === 'object') { - const classNameKey = Object.keys(alignClassNameList[index])[0]; - expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); - } - expect(container).toMatchSnapshot(); - }); - }); - - it('props.allowInputOverMax works fine', () => { - const { container } = render(); - const inputDom = container.querySelector('input'); - simulateInputChange(inputDom, 'Hello TDesign'); - const attrDom = container.querySelector('input'); - expect(attrDom.value).toBe('Hello'); - }); - - it('props.autocomplete works fine', () => { - const wrapper = render(); - const container = wrapper.container.querySelector('input'); - expect(container.getAttribute('autocomplete')).toBe('https://tdesign.tencent.com/'); - }); - - it(`props.autofocus is equal to false`, () => { - const { container } = render(); - const domWrapper = container.querySelector('input'); - expect(domWrapper.getAttribute('autofocus')).toBeNull(); - }); - it(`props.autofocus is equal to true`, () => { - const { container } = render(); - const domWrapper = container.querySelector('input'); - expect(domWrapper.getAttribute('autofocus')).toBeDefined(); - }); - - it('props.clearable: clear icon should exist on input mouseenter', async () => { - const { container } = render(); - fireEvent.mouseEnter(container.querySelector('.t-input')); - await mockDelay(); - expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); - }); - - it('props.clearable: click clear icon could clear input value to be empty', async () => { - const onClearFn1 = vi.fn(); - const onChangeFn1 = vi.fn(); - const { container } = render( - , - ); - fireEvent.mouseEnter(container.querySelector('.t-input')); - await mockDelay(300); - expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); - fireEvent.click(container.querySelector('.t-input__suffix-clear')); - expect(onClearFn1).toHaveBeenCalled(); - expect(onClearFn1.mock.calls[0][0].e.stopPropagation).toBeTruthy(); - expect(onClearFn1.mock.calls[0][0].e.type).toBe('click'); - expect(onChangeFn1).toHaveBeenCalled(); - expect(onChangeFn1.mock.calls[0][0]).toBe(''); - expect(onChangeFn1.mock.calls[0][1].e.stopPropagation).toBeTruthy(); - expect(onChangeFn1.mock.calls[0][1].e.type).toBe('click'); - }); - - it('props.clearable: type=password, browseIcon and clearableIcon works fine', async () => { - const { container } = render(); - expect(container.querySelector('.t-icon-browse-off')).toBeTruthy(); - fireEvent.mouseEnter(container.querySelector('.t-input')); - await mockDelay(300); - expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); - }); - - it('props.disabled works fine', () => { - // disabled default value is - const wrapper1 = render(); - const container1 = wrapper1.container.querySelector('.t-input'); - expect(container1.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - // disabled = true - const wrapper2 = render(); - const container2 = wrapper2.container.querySelector('.t-input'); - expect(container2).toHaveClass('t-is-disabled'); - // disabled = false - const wrapper3 = render(); - const container3 = wrapper3.container.querySelector('.t-input'); - expect(container3.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - }); - - it('props.format: focus and blur states have different value', () => { - const { container } = render( `${val} 元`} value="100">); - fireEvent.focus(container.querySelector('input')); - const attrDom = container.querySelector('input'); - expect(attrDom.value).toBe('100'); - fireEvent.blur(container.querySelector('input')); - const attrDom1 = container.querySelector('input'); - expect(attrDom1.value).toBe('100 元'); - }); - - it(`props.inputClass is equal to name1 name2`, () => { - const { container } = render(); - const domWrapper = container.querySelector('.t-input'); - expect(domWrapper).toHaveClass('name1'); - expect(domWrapper).toHaveClass('name2'); - }); - it(`props.inputClass is equal to ['name1', 'name2']`, () => { - const { container } = render(); - const domWrapper = container.querySelector('.t-input'); - expect(domWrapper).toHaveClass('name1'); - expect(domWrapper).toHaveClass('name2'); - }); - it(`props.inputClass is equal to { name1: true, name2: false }`, () => { - const { container } = render(); - const domWrapper = container.querySelector('.t-input'); - expect(domWrapper).toHaveClass('name1'); - expect(domWrapper.classList.contains('name2')).toBeFalsy(); - }); - - it('props.label works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container.querySelector('.t-input__prefix')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.maxcharacter: length of value is over than maxcharacter', () => { - const onChangeFn = vi.fn(); - const { container } = render(); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe('你好'); - expect(onChangeFn.mock.calls[0][1].trigger).toBe('initial'); - }); - - it('props.maxlength: length of value is over than maxlength', () => { - const onChangeFn = vi.fn(); - const { container } = render(); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe('Hello'); - expect(onChangeFn.mock.calls[0][1].trigger).toBe('initial'); - }); - - it('props.name works fine', () => { - const wrapper = render(); - const container = wrapper.container.querySelector('input'); - expect(container.getAttribute('name')).toBe('input-name'); - }); - - it('props.placeholder works fine', () => { - const wrapper = render(); - const container = wrapper.container.querySelector('input'); - expect(container.getAttribute('placeholder')).toBe('this is input placeholder'); - }); - - it('props.prefixIcon works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container.querySelector('.t-input__prefix-icon')).toBeTruthy(); - }); - - it('props.readonly works fine', () => { - // readonly default value is false - const wrapper1 = render(); - const container1 = wrapper1.container.querySelector('.t-input'); - expect(container1.querySelector(`.${'t-is-readonly'}`)).toBeFalsy(); - // readonly = true - const wrapper2 = render(); - const container2 = wrapper2.container.querySelector('.t-input'); - expect(container2).toHaveClass('t-is-readonly'); - // readonly = false - const wrapper3 = render(); - const container3 = wrapper3.container.querySelector('.t-input'); - expect(container3.querySelector(`.${'t-is-readonly'}`)).toBeFalsy(); - }); - - it('props.showClearIconOnEmpty works fine', async () => { - const { container } = render(); - fireEvent.mouseEnter(container.querySelector('.t-input')); - await mockDelay(); - expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); - }); - - it('props.showLimitNumber works fine. `{".t-input__limit-number":{"text":"2/5"}}` should exist', () => { - const { container } = render(); - expect(container.querySelector('.t-input__limit-number').textContent).toBe('2/5'); - }); - - const sizeClassNameList = ['t-size-s', { 't-size-m': false }, 't-size-l']; - ['small', 'medium', 'large'].forEach((item, index) => { - it(`props.size is equal to ${item}`, () => { - const wrapper = render(); - const container = wrapper.container.querySelector('.t-input'); - if (typeof sizeClassNameList[index] === 'string') { - expect(container).toHaveClass(sizeClassNameList[index]); - } else if (typeof sizeClassNameList[index] === 'object') { - const classNameKey = Object.keys(sizeClassNameList[index])[0]; - expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); - } - }); - }); - - const statusClassNameList = [{ 't-is-default': false }, 't-is-success', 't-is-warning', 't-is-error']; - ['default', 'success', 'warning', 'error'].forEach((item, index) => { - it(`props.status is equal to ${item}`, () => { - const wrapper = render(); - const container = wrapper.container.querySelector('.t-input'); - if (typeof statusClassNameList[index] === 'string') { - expect(container).toHaveClass(statusClassNameList[index]); - } else if (typeof statusClassNameList[index] === 'object') { - const classNameKey = Object.keys(statusClassNameList[index])[0]; - expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); - } - expect(container).toMatchSnapshot(); - }); - }); - - it('props.suffix works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container.querySelector('.t-input__suffix')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.suffixIcon works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container.querySelector('.t-input__suffix-icon')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.tips is equal this is a tip', () => { - const { container } = render(); - expect(container.querySelectorAll('.t-input__tips').length).toBe(1); - }); - - const attributeValues = ['text', 'number', 'url', 'tel', 'password', 'search', 'submit', 'hidden']; - ['text', 'number', 'url', 'tel', 'password', 'search', 'submit', 'hidden'].forEach((item, index) => { - it(`props.type is equal to ${item}`, () => { - const wrapper = render(); - const container = wrapper.container.querySelector('input'); - expect(container.getAttribute('type')).toBe(attributeValues[index]); - }); - }); - - it('props.type is equal password', () => { - const { container } = render(); - expect(container.querySelectorAll('.t-icon-browse-off').length).toBe(1); - }); - - it('props.type: password could be visible by click browse icon', () => { - const { container } = render(); - fireEvent.click(container.querySelector('.t-icon-browse-off')); - expect(container.querySelector('.t-icon-browse')).toBeTruthy(); - const attrDom = container.querySelector('input'); - expect(attrDom.getAttribute('type')).toBe('text'); - fireEvent.click(container.querySelector('.t-icon-browse')); - expect(container.querySelector('.t-icon-browse-off')).toBeTruthy(); - const attrDom1 = container.querySelector('input'); - expect(attrDom1.getAttribute('type')).toBe('password'); - }); - - it('events.blur works fine', async () => { - const onFocusFn = vi.fn(); - const onBlurFn1 = vi.fn(); - const { container } = render(); - fireEvent.focus(container.querySelector('input')); - expect(onFocusFn).toHaveBeenCalled(); - expect(onFocusFn.mock.calls[0][0]).toBe('initial-input-value'); - expect(onFocusFn.mock.calls[0][1].e.type).toBe('focus'); - fireEvent.blur(container.querySelector('input')); - await mockDelay(300); - expect(onBlurFn1).toHaveBeenCalled(); - expect(onBlurFn1.mock.calls[0][0]).toBe('initial-input-value'); - expect(onBlurFn1.mock.calls[0][1].e.type).toBe('blur'); - }); - - it('events.change: empty value could trigger change event', () => { - const onChangeFn = vi.fn(); - const { container } = render(); - const inputDom = container.querySelector('input'); - simulateInputChange(inputDom, 'initial value'); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe('initial value'); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('change'); - }); - - it('events.change: controlled value test', () => { - const onChangeFn = vi.fn(); - const { container } = render(); - const inputDom = container.querySelector('input'); - simulateInputChange(inputDom, 'Hello TDesign'); - const attrDom = container.querySelector('input'); - expect(attrDom.value).toBe('TDesign'); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe('Hello TDesign'); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('change'); - }); - - it('events.change: uncontrolled value test', () => { - const onChangeFn = vi.fn(); - const { container } = render(); - const inputDom = container.querySelector('input'); - simulateInputChange(inputDom, 'Hello TDesign'); - const attrDom = container.querySelector('input'); - expect(attrDom.value).toBe('Hello TDesign'); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe('Hello TDesign'); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('change'); - }); - - it('events.click works fine', () => { - const fn = vi.fn(); - const { container } = render(); - fireEvent.click(container.querySelector('.t-input')); - expect(fn).toHaveBeenCalled(); - expect(fn.mock.calls[0][0].e.type).toBe('click'); - }); - - it('events.compositionend works fine', () => { - const onCompositionendFn = vi.fn(); - const { container } = render(); - fireEvent.compositionEnd(container.querySelector('input')); - expect(onCompositionendFn).toHaveBeenCalled(); - expect(onCompositionendFn.mock.calls[0][0]).toBe('输入结束'); - expect(onCompositionendFn.mock.calls[0][1].e.type).toBe('compositionend'); - }); - - it('events.compositionstart works fine', () => { - const onCompositionstartFn = vi.fn(); - const { container } = render(); - fireEvent.compositionStart(container.querySelector('input')); - expect(onCompositionstartFn).toHaveBeenCalled(); - expect(onCompositionstartFn.mock.calls[0][0]).toBe('输入开始'); - expect(onCompositionstartFn.mock.calls[0][1].e.type).toBe('compositionstart'); - }); - - it('events.enter works fine', () => { - const onEnterFn1 = vi.fn(); - const { container } = render(); - fireEvent.focus(container.querySelector('input')); - fireEvent.keyDown(container.querySelector('input'), { key: 'Enter', code: 'Enter', charCode: 13 }); - expect(onEnterFn1).toHaveBeenCalled(); - expect(onEnterFn1.mock.calls[0][0]).toBe('text'); - expect(onEnterFn1.mock.calls[0][1].e.type).toBe('keydown'); - }); - - it('events.focus works fine', () => { - const onFocusFn = vi.fn(); - const { container } = render(); - fireEvent.focus(container.querySelector('input')); - expect(onFocusFn).toHaveBeenCalled(); - expect(onFocusFn.mock.calls[0][0]).toBe(''); - expect(onFocusFn.mock.calls[0][1].e.type).toBe('focus'); - }); - - it('events.keydown works fine', () => { - const onKeydownFn = vi.fn(); - const { container } = render(); - fireEvent.keyDown(container.querySelector('input')); - expect(onKeydownFn).toHaveBeenCalled(); - expect(onKeydownFn.mock.calls[0][0]).toBe('text'); - expect(onKeydownFn.mock.calls[0][1].e.type).toBe('keydown'); - }); - - it('events.keypress works fine', () => { - const onKeydownFn = vi.fn(); - const { container } = render(); - fireEvent.keyDown(container.querySelector('input')); - expect(onKeydownFn).toHaveBeenCalled(); - expect(onKeydownFn.mock.calls[0][0]).toBe('text'); - expect(onKeydownFn.mock.calls[0][1].e.type).toBe('keydown'); - }); - - it('events.keyup works fine', () => { - const onKeyupFn = vi.fn(); - const { container } = render(); - fireEvent.keyUp(container.querySelector('input')); - expect(onKeyupFn).toHaveBeenCalled(); - expect(onKeyupFn.mock.calls[0][0]).toBe('text'); - expect(onKeyupFn.mock.calls[0][1].e.type).toBe('keyup'); - }); - - it('events.mouseenter works fine', async () => { - const onMouseenterFn = vi.fn(); - const { container } = render(); - fireEvent.mouseEnter(container.querySelector('.t-input')); - await mockDelay(); - expect(onMouseenterFn).toHaveBeenCalled(); - expect(onMouseenterFn.mock.calls[0][0].e.type).toBe('mouseenter'); - }); - - it('events.mouseleave works fine', async () => { - const onMouseleaveFn = vi.fn(); - const { container } = render(); - fireEvent.mouseLeave(container.querySelector('.t-input')); - await mockDelay(); - expect(onMouseleaveFn).toHaveBeenCalled(); - expect(onMouseleaveFn.mock.calls[0][0].e.type).toBe('mouseleave'); - }); - - it('events.paste works fine', () => { - const onPasteFn = vi.fn(); - const { container } = render(); - fireEvent.paste(container.querySelector('input')); - expect(onPasteFn).toHaveBeenCalled(); - expect(onPasteFn.mock.calls[0][0].e.type).toBe('paste'); - }); - - it('events.validate works fine', () => { - const onValidateFn = vi.fn(); - const { container } = render(); - expect(onValidateFn).toHaveBeenCalled(); - expect(onValidateFn.mock.calls[0][0].error).toBe('exceed-maximum'); - }); - - it('events.wheel works fine', () => { - const onWheelFn = vi.fn(); - const { container } = render(); - fireEvent.wheel(container.querySelector('input')); - expect(onWheelFn).toHaveBeenCalled(); - expect(onWheelFn.mock.calls[0][0].e.type).toBe('wheel'); - }); -}); - -describe('InputGroup Component', () => { - it('props.separate works fine', () => { - // separate default value is - const { container: container1 } = getInputGroupDefaultMount(InputGroup); - expect(container1.querySelector(`.${'t-input-group--separate'}`)).toBeFalsy(); - // separate = true - const { container: container2 } = getInputGroupDefaultMount(InputGroup, { separate: true }); - expect(container2.firstChild).toHaveClass('t-input-group--separate'); - // separate = false - const { container: container3 } = getInputGroupDefaultMount(InputGroup, { separate: false }); - expect(container3.querySelector(`.${'t-input-group--separate'}`)).toBeFalsy(); - }); -}); diff --git a/packages/components/layout/__tests__/layout.test.tsx b/packages/components/layout/__tests__/layout.test.tsx index b377a278a4..89bbe331f9 100644 --- a/packages/components/layout/__tests__/layout.test.tsx +++ b/packages/components/layout/__tests__/layout.test.tsx @@ -5,33 +5,50 @@ import Layout from '../index'; const { Header, Content, Footer, Aside } = Layout; -// TODO -describe('Layout 组件测试', () => { - test('Layout direction ', () => { - const { container } = render(); - const layoutElement = container.querySelector('.t-layout'); - expect(layoutElement).not.toBeNull(); - const horizontal = container.querySelector('.t-layout__direction-horizontal'); - expect(horizontal).not.toBeNull(); +describe('Layout', () => { + describe('props', () => { + test('Layout direction ', () => { + const { container } = render(); + const layoutElement = container.querySelector('.t-layout'); + expect(layoutElement).not.toBeNull(); + const horizontal = container.querySelector('.t-layout__direction-horizontal'); + expect(horizontal).not.toBeNull(); + }); }); +}); - test('Header ', () => { - const { container } = render(
); - expect(container).not.toBeNull(); +describe('Header', () => { + describe('scenarios', () => { + test('Header ', () => { + const { container } = render(
); + expect(container).not.toBeNull(); + }); }); +}); - test('Content ', () => { - const { container } = render(); - expect(container).not.toBeNull(); +describe('Content', () => { + describe('scenarios', () => { + test('Content ', () => { + const { container } = render(); + expect(container).not.toBeNull(); + }); }); +}); - test('Footer ', () => { - const { container } = render(
} />); - expect(document.querySelector('.t-link__suffix-icon')).toBeInTheDocument(); + test('theme', () => { + expect(render().container.firstChild).toHaveClass('t-link--theme-danger', ''); + expect(render().container.firstChild).toHaveClass('t-link--theme-default', ''); + expect(render().container.firstChild).toHaveClass('t-link--theme-primary', ''); + expect(render().container.firstChild).toHaveClass('t-link--theme-success', ''); + expect(render().container.firstChild).toHaveClass('t-link--theme-warning', ''); + expect(render().container.firstChild).toHaveClass('t-link--theme-default', ''); + }); + test('children', () => { + const { container } = render( + + TNode + , + ); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('content', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); + + test('prefixIcon', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + + test('suffixIcon', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + + test('icon', () => { + render(
} />); + expect(document.querySelector('.t-link__prefix-icon')).toBeInTheDocument(); + + render(
} />); + expect(document.querySelector('.t-link__suffix-icon')).toBeInTheDocument(); + }); }); - test('size', () => { - expect(render().container.firstChild).toHaveClass('t-size-l'); - expect(render().container.firstChild).toHaveClass('t-size-s'); + describe('events', () => { + test('click', () => { + const fn = vi.fn(); + const { container } = render(); + fireEvent.click(container.firstChild); + expect(fn).toHaveBeenCalled(); + expect(fn.mock.calls[0][0].stopPropagation).toBeTruthy(); + expect(fn.mock.calls[0][0].type).toBe('click'); + }); }); - test('theme', () => { - expect(render().container.firstChild).toHaveClass('t-link--theme-danger', ''); - expect(render().container.firstChild).toHaveClass('t-link--theme-default', ''); - expect(render().container.firstChild).toHaveClass('t-link--theme-primary', ''); - expect(render().container.firstChild).toHaveClass('t-link--theme-success', ''); - expect(render().container.firstChild).toHaveClass('t-link--theme-warning', ''); - expect(render().container.firstChild).toHaveClass('t-link--theme-default', ''); + describe('scenarios', () => { + test('base', () => { + const { container } = render(查看链接); + expect(container.querySelectorAll('.t-link')).toHaveLength(1); + expect(container.querySelectorAll('.t-link')[0].textContent).toEqual('查看链接'); + }); + + test('disabled', () => { + const fn = vi.fn(); + const { container } = render(); + expect(container.firstChild).toHaveClass('t-is-disabled', ''); + + fireEvent.mouseEnter(screen.getByTestId('disabled')); + expect(screen.getByTestId('disabled')).not.toHaveClass('t-link--hover-underline'); + + fireEvent.click(container.firstChild); + expect(fn).toHaveBeenCalledTimes(0); + }); }); }); diff --git a/packages/components/link/__tests__/vitest-link.test.jsx b/packages/components/link/__tests__/vitest-link.test.jsx deleted file mode 100644 index 49bc1d3fde..0000000000 --- a/packages/components/link/__tests__/vitest-link.test.jsx +++ /dev/null @@ -1,114 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * 该文件由脚本自动生成,如需修改请联系 PMC - * This file generated by scripts of tdesign-api. `npm run api:docs Link React(PC) vitest,finalProject` - * If you need to modify this file, contact PMC first please. - */ -import React from 'react'; -import { fireEvent, vi, render } from '@test/utils'; -import { Link } from '..'; - -describe('Link Component', () => { - it('props.children works fine', () => { - const { container } = render( - - TNode - , - ); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.content works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.disabled works fine', () => { - // disabled default value is - const { container: container1 } = render(Text); - expect(container1.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - // disabled = true - const { container: container2 } = render(Text); - expect(container2.firstChild).toHaveClass('t-is-disabled'); - expect(container2).toMatchSnapshot(); - // disabled = false - const { container: container3 } = render(Text); - expect(container3.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - expect(container3).toMatchSnapshot(); - }); - - ['color', 'underline'].forEach((item) => { - it(`props.hover is equal to ${item}`, () => { - const { container } = render(Text); - expect(container.firstChild).toHaveClass(`t-link--hover-${item}`); - expect(container).toMatchSnapshot(); - }); - }); - - it('props.href works fine', () => { - const { container } = render(Text); - expect(container.firstChild.getAttribute('href')).toBe('https://tdesign.tencent.com/'); - expect(container).toMatchSnapshot(); - }); - - it('props.prefixIcon works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - const sizeClassNameList = ['t-size-s', { 't-size-m': false }, 't-size-l']; - ['small', 'medium', 'large'].forEach((item, index) => { - it(`props.size is equal to ${item}`, () => { - const { container } = render(Text); - if (typeof sizeClassNameList[index] === 'string') { - expect(container.firstChild).toHaveClass(sizeClassNameList[index]); - } else if (typeof sizeClassNameList[index] === 'object') { - const classNameKey = Object.keys(sizeClassNameList[index])[0]; - expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); - } - expect(container).toMatchSnapshot(); - }); - }); - - it('props.suffixIcon works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - it('props.target works fine', () => { - const { container } = render(Text); - expect(container.firstChild.getAttribute('target')).toBe('_blank'); - expect(container).toMatchSnapshot(); - }); - - ['default', 'primary', 'danger', 'warning', 'success'].forEach((item) => { - it(`props.theme is equal to ${item}`, () => { - const { container } = render(Text); - expect(container.firstChild).toHaveClass(`t-link--theme-${item}`); - expect(container).toMatchSnapshot(); - }); - }); - - it('props.underline works fine', () => { - // underline default value is - const { container: container1 } = render(Text); - expect(container1.querySelector(`.${'t-is-underline'}`)).toBeFalsy(); - // underline = true - const { container: container2 } = render(Text); - expect(container2.firstChild).toHaveClass('t-is-underline'); - // underline = false - const { container: container3 } = render(Text); - expect(container3.querySelector(`.${'t-is-underline'}`)).toBeFalsy(); - }); - - it('events.click works fine', () => { - const fn = vi.fn(); - const { container } = render(); - fireEvent.click(container.firstChild); - expect(fn).toHaveBeenCalled(); - expect(fn.mock.calls[0][0].stopPropagation).toBeTruthy(); - expect(fn.mock.calls[0][0].type).toBe('click'); - }); -}); diff --git a/packages/components/list/__tests__/list.test.tsx b/packages/components/list/__tests__/list.test.tsx index 4024eab66e..cc248b8237 100644 --- a/packages/components/list/__tests__/list.test.tsx +++ b/packages/components/list/__tests__/list.test.tsx @@ -5,7 +5,7 @@ import List from '../List'; const { ListItem, ListItemMeta } = List; -describe('List 组件测试', () => { +describe('List', () => { const data = [ { id: 1, content: '列表内容列表内容列表内容' }, { id: 2, content: '列表内容列表内容列表内容' }, @@ -13,27 +13,16 @@ describe('List 组件测试', () => { { id: 4, content: '列表内容列表内容列表内容' }, ]; - describe('List Props', () => { - test('asyncLoading is string & onLoadMore', () => { - const clickFn = vi.fn(); - const { container, queryByText } = render( - { - clickFn(); - }} - > - {data.map((val) => ( - {val.content} - ))} - , - ); - expect(container.querySelector('.t-list__load--load-more')).toBeInTheDocument(); - expect(queryByText('点击加载更多')).toBeInTheDocument(); - fireEvent.click(queryByText('点击加载更多')); - expect(clickFn).toHaveBeenCalledTimes(1); + describe('props', () => { + test('asyncLoading props', () => { + const { container } = render(); + + expect(container.querySelector('.t-loading')).not.toBeNull(); + expect(container.querySelector('.t-loading')).toBeInTheDocument(); }); + }); + describe('events', () => { test('onScroll', () => { const fn = vi.fn(); const { container } = render( @@ -52,6 +41,28 @@ describe('List 组件测试', () => { fireEvent.scroll(container.firstChild); expect(fn).toHaveBeenCalled(); }); + }); + + describe('scenarios', () => { + test('asyncLoading is string & onLoadMore', () => { + const clickFn = vi.fn(); + const { container, queryByText } = render( + { + clickFn(); + }} + > + {data.map((val) => ( + {val.content} + ))} + , + ); + expect(container.querySelector('.t-list__load--load-more')).toBeInTheDocument(); + expect(queryByText('点击加载更多')).toBeInTheDocument(); + fireEvent.click(queryByText('点击加载更多')); + expect(clickFn).toHaveBeenCalledTimes(1); + }); test('header and footer props', () => { const header = 'header content'; @@ -63,16 +74,11 @@ describe('List 组件测试', () => { expect(queryByText(footer)).not.toBeNull(); expect(queryByText(footer)).toBeInTheDocument(); }); - - test('asyncLoading props', () => { - const { container } = render(); - - expect(container.querySelector('.t-loading')).not.toBeNull(); - expect(container.querySelector('.t-loading')).toBeInTheDocument(); - }); }); +}); - describe('ListItem Component Test', () => { +describe('ListItem', () => { + describe('props', () => { test('content and children render', () => { const contextText = 'content render'; const { container } = render({contextText}
} />); @@ -96,8 +102,10 @@ describe('List 组件测试', () => { expect(queryByText('操作1')).toBeInTheDocument(); }); }); +}); - describe('ListItemMeta Component Test', () => { +describe('ListItemMeta', () => { + describe('props', () => { const imgSrc = 'https://tdesign.gtimg.com/site/images/list-icon.png'; const description = 'Test Description'; test('image string', () => { @@ -106,6 +114,12 @@ describe('List 组件测试', () => { expect(container.querySelector('img')).not.toBeNull(); expect(container.querySelector('img')).toBeInTheDocument(); }); + test('description string', () => { + const { queryByText } = render(); + + expect(queryByText(description)).not.toBeNull(); + expect(queryByText(description)).toBeInTheDocument(); + }); test('image TNode', () => { const Img = () => test img; const { container } = render(} />); @@ -113,12 +127,6 @@ describe('List 组件测试', () => { expect(container.querySelector('#img_test')).not.toBeNull(); expect(container.querySelector('#img_test')).toBeInTheDocument(); }); - test('description string', () => { - const { queryByText } = render(); - - expect(queryByText(description)).not.toBeNull(); - expect(queryByText(description)).toBeInTheDocument(); - }); test('description TNode', () => { const Description = () =>
{description}
; const { container } = render(} />); diff --git a/packages/components/loading/__tests__/loading.test.tsx b/packages/components/loading/__tests__/loading.test.tsx index 22b3070b40..c648f3e4ad 100644 --- a/packages/components/loading/__tests__/loading.test.tsx +++ b/packages/components/loading/__tests__/loading.test.tsx @@ -6,102 +6,107 @@ import { defaultGlobalConfig } from '../../config-provider/ConfigContext'; import Loading from '../Loading'; import { LoadingPlugin as loading } from '../plugin'; -describe('Loading 组件测试', () => { - // loading为true显示加载组件 - test('render loading when loading props is true', async () => { - const { container } = render(); - expect(container.querySelector('.t-loading')).toBeTruthy(); - }); - // loading为false不显示加载组件 - test('render null when loading is false', async () => { - const { container } = render(); - expect(container.querySelector('.t-loading')).toBeNull(); - }); - test('loading className works', async () => { - const { container } = render(); - expect(container.querySelector('.t-custom-loading')).toBeTruthy(); - }); - - test('loading style works', async () => { - const { container } = render(); - expect(container.querySelector('.t-loading').getAttribute('style')).toBe('color: red;'); - }); +describe('Loading', () => { + describe('props', () => { + // loading为true显示加载组件 + test('render loading when loading props is true', async () => { + const { container } = render(); + expect(container.querySelector('.t-loading')).toBeTruthy(); + }); + // loading为false不显示加载组件 + test('render null when loading is false', async () => { + const { container } = render(); + expect(container.querySelector('.t-loading')).toBeNull(); + }); + test('loading className works', async () => { + const { container } = render(); + expect(container.querySelector('.t-custom-loading')).toBeTruthy(); + }); - test('loading indicator works', async () => { - const { container } = render(indicator
}>); - expect(container.querySelector('.t-loading').querySelector('div')).toBeTruthy(); - expect(container.querySelector('.t-loading').querySelector('.t-loading__gradient-conic')).toBeNull(); - }); + test('loading style works', async () => { + const { container } = render(); + expect(container.querySelector('.t-loading').getAttribute('style')).toBe('color: red;'); + }); - test('loading text works', async () => { - const { container } = render(); - expect(container.querySelector('.t-loading').querySelector('.t-loading__text').innerHTML).toBe('加载中'); - }); + test('loading text works', async () => { + const { container } = render(); + expect(container.querySelector('.t-loading').querySelector('.t-loading__text').innerHTML).toBe('加载中'); + }); - test('loading size works', async () => { - const { container } = render(); - // 设置size 非枚举 则渲染font size 的style 且不渲染size的class - expect(container.querySelector('.t-loading').getAttribute('style')).toBe('font-size: 36px;'); - expect(container.querySelector('.t-size-m')).toBeNull(); + test('loading size works', async () => { + const { container } = render(); + // 设置size 非枚举 则渲染font size 的style 且不渲染size的class + expect(container.querySelector('.t-loading').getAttribute('style')).toBe('font-size: 36px;'); + expect(container.querySelector('.t-size-m')).toBeNull(); + }); + test('loading indicator works', async () => { + const { container } = render(indicator
}>); + expect(container.querySelector('.t-loading').querySelector('div')).toBeTruthy(); + expect(container.querySelector('.t-loading').querySelector('.t-loading__gradient-conic')).toBeNull(); + }); }); - test('global loading config works', async () => { - const { container } = render( - - - , - ); + describe('scenarios', () => { + test('global loading config works', async () => { + const { container } = render( + + + , + ); - expect(container.querySelector('.t-loading__text')).toHaveTextContent('全局加载中'); - expect(container.querySelector('.t-loading')).toHaveStyle({ - fontSize: '36px', - zIndex: '1234', + expect(container.querySelector('.t-loading__text')).toHaveTextContent('全局加载中'); + expect(container.querySelector('.t-loading')).toHaveStyle({ + fontSize: '36px', + zIndex: '1234', + }); }); - }); - test('loading props should override global loading config', async () => { - const { container } = render( - - - , - ); + test('loading props should override global loading config', async () => { + const { container } = render( + + + , + ); - expect(container.querySelector('.t-loading__text')).toHaveTextContent('局部加载中'); - expect(container.querySelector('.t-size-s')).toBeTruthy(); + expect(container.querySelector('.t-loading__text')).toHaveTextContent('局部加载中'); + expect(container.querySelector('.t-size-s')).toBeTruthy(); + }); }); - test('loading plugin works', async () => { - const { container } = render( -
-
- loading({ - attach: () => document.querySelector('#loading-attach'), - }) as any - } - > - container to trigger loading -
-
-
, - ); - fireEvent.click(container.querySelector('.trigger')); + describe('instanceFunctions', () => { + test('loading plugin works', async () => { + const { container } = render( +
+
+ loading({ + attach: () => document.querySelector('#loading-attach'), + }) as any + } + > + container to trigger loading +
+
+
, + ); + fireEvent.click(container.querySelector('.trigger')); - expect(container.querySelector('.t-loading')).toBeTruthy(); + expect(container.querySelector('.t-loading')).toBeTruthy(); + }); }); }); diff --git a/packages/components/menu/__tests__/menu.test.tsx b/packages/components/menu/__tests__/menu.test.tsx index 8eab2e5d36..4d8191b2f7 100644 --- a/packages/components/menu/__tests__/menu.test.tsx +++ b/packages/components/menu/__tests__/menu.test.tsx @@ -4,7 +4,7 @@ import { fireEvent, render, vi, waitFor } from '@test/utils'; import Menu from '../index'; -describe('Menu 组件测试', () => { +describe('Menu', () => { const { HeadMenu, SubMenu, MenuItem, MenuGroup } = Menu; const renderSubmenu = ({ collapsed = false, @@ -34,179 +34,186 @@ describe('Menu 组件测试', () => { , ); - test('menu collapsed works fine', async () => { - const { container } = renderSubmenu({ collapsed: true }); - expect(container.firstChild).toHaveClass('t-is-collapsed'); - expect(container.querySelectorAll('.t-submenu.t-is-opened').length).toBe(0); - const submenu = container.querySelectorAll('.t-submenu').item(1); - fireEvent.mouseEnter(submenu); - expect(document.querySelector('.t-menu__popup')).not.toBeNull(); - expect(document.querySelector('.t-menu__popup')?.className.includes('t-is-opened')).toBeTruthy(); - - fireEvent.mouseLeave(submenu); - const popup = await waitFor(() => document.querySelector('.t-menu__popup')); - expect(popup?.className.includes('t-is-opened')).not.toBeTruthy(); - }); - - test('menu defaultExpanded works fine', () => { - const { container, queryByText } = renderSubmenu({ defaultExpanded: ['1'] }); - expect(container.firstChild).not.toHaveClass('t-is-collapsed'); - expect(container.querySelectorAll('.t-submenu.t-is-opened').length).toBe(1); - expect(queryByText('菜单二').parentElement.parentElement.parentElement.style.maxHeight).not.toBe('0'); - expect(queryByText('二级菜单-1').parentElement.parentElement.parentElement.style.maxHeight).toBe('0'); - }); - - test('menu expanded works fine', () => { - const { container, queryByText } = renderSubmenu({ expanded: ['1'] }); - expect(container.firstChild).not.toHaveClass('t-is-collapsed'); - expect(container.querySelectorAll('.t-submenu.t-is-opened').length).toBe(1); - expect(queryByText('菜单二').parentElement.parentElement.parentElement.style.maxHeight).not.toBe('0'); - expect(queryByText('二级菜单-1').parentElement.parentElement.parentElement.style.maxHeight).toBe('0'); - }); - - test('menu 测试单层导航', () => { - const onItemClickFn = vi.fn(); - const { container, queryByText } = render( - - - 菜单1 - - , - ); - expect(container.querySelector('.t-menu--light')).toBeInTheDocument(); - expect(queryByText('菜单1')).toBeInTheDocument(); - fireEvent.click(queryByText('菜单1')); - expect(onItemClickFn).toHaveBeenCalledTimes(1); - }); - - test('menu 测试双层导航', async () => { - const onChangeFn = vi.fn(); - const onExpandFn = vi.fn(); - const { container, queryByText, getByText } = render( - 我是operations
} - value="4" - > - - 子菜单1 - 子菜单2 - - - 子菜单3 - - 子菜单4 - - - , - ); - expect(container.querySelector('.t-menu--dark')).toBeInTheDocument(); - expect(queryByText('我是operations')).toBeInTheDocument(); - expect(queryByText('菜单1')).toBeInTheDocument(); - - expect(container.querySelector('.t-tabs__nav-item.menu-item-class')).toBeInTheDocument(); - - const element1 = await waitFor(() => container.querySelector('t-head-menu__submenu')); - expect(element1).toBeNull(); - fireEvent.click(getByText('菜单2')); - fireEvent.click(getByText('子菜单3')); - expect(onChangeFn).toHaveBeenCalledTimes(2); - }); - - test('menu 测试单层侧边导航', () => { - const clickFn = vi.fn(); - const { container, queryByText, getByText } = render( - - - 仪表盘 - - - 资源列表 - - , - ); - expect(container.querySelector('.t-default-menu')).toBeInTheDocument(); - expect(queryByText('仪表盘')).toBeInTheDocument(); - fireEvent.click(getByText('资源列表')); - expect(clickFn).toHaveBeenCalledTimes(1); - }); + describe('props', () => { + test('menu defaultExpanded works fine', () => { + const { container, queryByText } = renderSubmenu({ + defaultExpanded: ['1'], + }); + expect(container.firstChild).not.toHaveClass('t-is-collapsed'); + expect(container.querySelectorAll('.t-submenu.t-is-opened').length).toBe(1); + expect(queryByText('菜单二').parentElement.parentElement.parentElement.style.maxHeight).not.toBe('0'); + expect(queryByText('二级菜单-1').parentElement.parentElement.parentElement.style.maxHeight).toBe('0'); + }); + + test('menu expanded works fine', () => { + const { container, queryByText } = renderSubmenu({ expanded: ['1'] }); + expect(container.firstChild).not.toHaveClass('t-is-collapsed'); + expect(container.querySelectorAll('.t-submenu.t-is-opened').length).toBe(1); + expect(queryByText('菜单二').parentElement.parentElement.parentElement.style.maxHeight).not.toBe('0'); + expect(queryByText('二级菜单-1').parentElement.parentElement.parentElement.style.maxHeight).toBe('0'); + }); + test('menu head-menu render icon', async () => { + const { container, getByText } = render( + + }> + + 苹果 + + + , + ); - test('menu 平铺式侧边导航', () => { - const onExpandFn = vi.fn(); - const onChangeFn = vi.fn(); - const { queryByText, getByText } = render( - - 仪表盘 - 资源列表}> - - 菜单二 - - - , - ); - expect(queryByText('仪表盘')).toBeInTheDocument(); - fireEvent.click(getByText('资源列表')); - expect(queryByText('菜单二')).toBeInTheDocument(); - expect(onExpandFn).toHaveBeenCalledTimes(1); + expect(getByText('水果蔬菜')).toBeInTheDocument(); + expect(container.querySelector('.t-icon-user')).toBeInTheDocument(); + }); }); - test('menu 测试分组导航', () => { - const clickFn = vi.fn(); - const { container, queryByText, getByText } = render( - - - 仪表盘 - - + describe('events', () => { + test('menu 測試 menuItem onClick事件', () => { + const clickFn = vi.fn(); + const { getByText } = render( + + + + 仪表盘 + + 基础列表项 - - , - ); - expect(container.querySelector('.t-menu-group')).toBeInTheDocument(); - expect(queryByText('仪表盘')).toBeVisible(); - expect(container.querySelectorAll('.t-submenu').length).toBe(1); - fireEvent.click(getByText('仪表盘')); - expect(clickFn).toHaveBeenCalledTimes(1); - const ulNode = queryByText('基础列表项').parentElement.parentElement; - expect(ulNode.style.maxHeight).toBe('0'); - fireEvent.click(getByText('列表项')); - expect(ulNode.style.maxHeight).not.toBe('0'); + , + ); + fireEvent.click(getByText('仪表盘')); + expect(clickFn).toHaveBeenCalledTimes(1); + }); }); - test('menu 測試 menuItem onClick事件', () => { - const clickFn = vi.fn(); - const { getByText } = render( - - - - 仪表盘 + describe('scenarios', () => { + test('menu collapsed works fine', async () => { + const { container } = renderSubmenu({ collapsed: true }); + expect(container.firstChild).toHaveClass('t-is-collapsed'); + expect(container.querySelectorAll('.t-submenu.t-is-opened').length).toBe(0); + const submenu = container.querySelectorAll('.t-submenu').item(1); + fireEvent.mouseEnter(submenu); + expect(document.querySelector('.t-menu__popup')).not.toBeNull(); + expect(document.querySelector('.t-menu__popup')?.className.includes('t-is-opened')).toBeTruthy(); + + fireEvent.mouseLeave(submenu); + const popup = await waitFor(() => document.querySelector('.t-menu__popup')); + expect(popup?.className.includes('t-is-opened')).not.toBeTruthy(); + }); + + test('menu 测试单层导航', () => { + const onItemClickFn = vi.fn(); + const { container, queryByText } = render( + + + 菜单1 - - - 基础列表项 - - , - ); - fireEvent.click(getByText('仪表盘')); - expect(clickFn).toHaveBeenCalledTimes(1); - }); - - test('menu head-menu render icon', async () => { - const { container, getByText } = render( - - }> - - 苹果 + , + ); + expect(container.querySelector('.t-menu--light')).toBeInTheDocument(); + expect(queryByText('菜单1')).toBeInTheDocument(); + fireEvent.click(queryByText('菜单1')); + expect(onItemClickFn).toHaveBeenCalledTimes(1); + }); + + test('menu 测试双层导航', async () => { + const onChangeFn = vi.fn(); + const onExpandFn = vi.fn(); + const { container, queryByText, getByText } = render( + 我是operations
} + value="4" + > + + 子菜单1 + 子菜单2 + + + 子菜单3 + + 子菜单4 + + + , + ); + expect(container.querySelector('.t-menu--dark')).toBeInTheDocument(); + expect(queryByText('我是operations')).toBeInTheDocument(); + expect(queryByText('菜单1')).toBeInTheDocument(); + + expect(container.querySelector('.t-tabs__nav-item.menu-item-class')).toBeInTheDocument(); + + const element1 = await waitFor(() => container.querySelector('t-head-menu__submenu')); + expect(element1).toBeNull(); + fireEvent.click(getByText('菜单2')); + fireEvent.click(getByText('子菜单3')); + expect(onChangeFn).toHaveBeenCalledTimes(2); + }); + + test('menu 测试单层侧边导航', () => { + const clickFn = vi.fn(); + const { container, queryByText, getByText } = render( + + + 仪表盘 - - , - ); - - expect(getByText('水果蔬菜')).toBeInTheDocument(); - expect(container.querySelector('.t-icon-user')).toBeInTheDocument(); + + 资源列表 + + , + ); + expect(container.querySelector('.t-default-menu')).toBeInTheDocument(); + expect(queryByText('仪表盘')).toBeInTheDocument(); + fireEvent.click(getByText('资源列表')); + expect(clickFn).toHaveBeenCalledTimes(1); + }); + + test('menu 平铺式侧边导航', () => { + const onExpandFn = vi.fn(); + const onChangeFn = vi.fn(); + const { queryByText, getByText } = render( + + 仪表盘 + 资源列表}> + + 菜单二 + + + , + ); + expect(queryByText('仪表盘')).toBeInTheDocument(); + fireEvent.click(getByText('资源列表')); + expect(queryByText('菜单二')).toBeInTheDocument(); + expect(onExpandFn).toHaveBeenCalledTimes(1); + }); + + test('menu 测试分组导航', () => { + const clickFn = vi.fn(); + const { container, queryByText, getByText } = render( + + + 仪表盘 + + + + 基础列表项 + + + , + ); + expect(container.querySelector('.t-menu-group')).toBeInTheDocument(); + expect(queryByText('仪表盘')).toBeVisible(); + expect(container.querySelectorAll('.t-submenu').length).toBe(1); + fireEvent.click(getByText('仪表盘')); + expect(clickFn).toHaveBeenCalledTimes(1); + const ulNode = queryByText('基础列表项').parentElement.parentElement; + expect(ulNode.style.maxHeight).toBe('0'); + fireEvent.click(getByText('列表项')); + expect(ulNode.style.maxHeight).not.toBe('0'); + }); }); }); diff --git a/packages/components/message/__tests__/message.test.jsx b/packages/components/message/__tests__/message.test.jsx deleted file mode 100644 index de705013eb..0000000000 --- a/packages/components/message/__tests__/message.test.jsx +++ /dev/null @@ -1,370 +0,0 @@ -import React from 'react'; -import { fireEvent, mockTimeout, render, vi, waitFor } from '@test/utils'; -import { - CheckCircleFilledIcon, - ErrorCircleFilledIcon, - HelpCircleFilledIcon, - HelpIcon, - InfoCircleFilledIcon, - LoadingIcon, -} from 'tdesign-icons-react'; -import Message, { MessagePlugin } from '../index'; - -const defaultMessage = '默认的message'; - -const THEME_MAP = { - info: InfoCircleFilledIcon, - success: CheckCircleFilledIcon, - warning: ErrorCircleFilledIcon, - error: ErrorCircleFilledIcon, - question: HelpCircleFilledIcon, - loading: LoadingIcon, -}; -const THEME_LIST = Object.keys(THEME_MAP); - -beforeAll(() => { - Element.prototype.animate = function (keyframes, options) { - const animation = { - onfinish: null, - finish: () => { - if (animation.onfinish) { - animation.onfinish(); - } - }, - }; - setTimeout( - () => { - if (animation.onfinish) { - animation.onfinish(); - } - }, - typeof options === 'number' ? options : options?.duration || 0, - ); - return animation; - }; -}); - -describe('Message Component test', () => { - test('pure message contains right classes', async () => { - const { container, getByText, unmount } = render({defaultMessage}); - expect(getByText(defaultMessage)).toBeInTheDocument(); - expect(container.firstChild).toHaveClass('t-message'); - expect(container.firstChild).toHaveClass('t-is-info'); - expect(container.firstChild).not.toHaveClass('t-message__close'); - expect(() => { - unmount(); - }).not.toThrow(); - }); - - test(':theme', () => { - THEME_LIST.forEach((t) => { - const { container, unmount } = render({t}); - expect(container.firstChild).toHaveClass(`t-is-${t}`); - expect(() => { - unmount(); - }).not.toThrow(); - }); - }); - - test(':closeBtn is true, render default close button.', () => { - const { container } = render({defaultMessage}); - expect(container.firstChild).toHaveClass('t-is-closable'); - expect(container.getElementsByClassName('t-message__close').length).toBe(1); - }); - - test(':closeBtn is a string, equal "关闭".', () => { - const closeBtnTxt = '关闭'; - const { container, getByText } = render({defaultMessage}); - expect(container.getElementsByClassName('t-message__close').length).toBe(1); - expect(getByText(closeBtnTxt).textContent).toBe(closeBtnTxt); - }); - - test(':closeBtn is a function, () => VNode.', () => { - const { container, getByText } = render(x}>{defaultMessage}); - expect(container.getElementsByClassName('t-message__close').length).toBe(1); - expect(getByText('x')).toBeInTheDocument(); - }); - - test(':icon is false', () => { - const { container } = render({defaultMessage}); - expect(container.firstChild).not.toHaveClass('t-icon'); - }); - - test(':icon is a function, () => TIconMore', () => { - const { container } = render( }>{defaultMessage}); - // t-icon - expect(container.firstChild).not.toHaveClass('t-icon'); - }); - - test(':style', () => { - const style = { backgroundColor: 'rgb(51, 51, 51)' }; - THEME_LIST.forEach((t) => { - const { container } = render( - - {defaultMessage} - , - ); - expect(container.firstChild).toHaveStyle(`background-color: ${style.backgroundColor};`); - }); - }); - - test(':content as string, should get equal text', () => { - THEME_LIST.forEach((t) => { - const { container } = render(); - expect(container.firstChild).toHaveTextContent(defaultMessage); - }); - }); - - test(':content as ReactNode', () => { - const ReactNode =

{defaultMessage}

; - THEME_LIST.forEach((t) => { - const { container } = render(); - expect(container.querySelector('.wrapper')).not.toBe(null); - expect(container.querySelector('.wrapper')).toHaveTextContent(defaultMessage); - }); - }); - - test(':duration props', async () => { - const onClose = vi.fn(); - const duration = 300; - render( - - {defaultMessage} - , - ); - expect(onClose).not.toHaveBeenCalled(); - await mockTimeout(() => expect(onClose).toHaveBeenCalledTimes(1), duration); - expect(onClose).toHaveBeenCalledWith({ - trigger: 'duration-end', - }); - }); -}); - -describe('Message Functional test', () => { - afterEach(() => { - document.getElementsByTagName('html')[0].innerHTML = ''; - }); - - test('Message 基础函数式调用,消息应该正常展示、隐藏', async () => { - const openText = 'open'; - const closeText = 'close'; - const TestComponent = () => { - let message; - const handleOpen = () => { - message = MessagePlugin.info(defaultMessage); - }; - const handleClose = () => { - MessagePlugin.close(message); - }; - - return ( - <> - - - - ); - }; - - const { getByText } = render(); - expect(document.querySelector('.t-message')).toBe(null); - fireEvent.click(getByText(openText)); - await mockTimeout(() => expect(document.querySelector('.t-message')).not.toBeNull()); - await mockTimeout(() => expect(document.querySelector('.t-message')).toHaveTextContent(defaultMessage)); - fireEvent.click(getByText(closeText)); - await waitFor( - () => { - expect(document.querySelector('.t-message')).toBeNull(); - }, - // 动画缓冲时间 - { timeout: 500 }, - ); - }); - - test('存在关闭按钮,点击关闭按钮应该触发 onCloseBtnClick', async () => { - const openText = 'open'; - const option = { - content: defaultMessage, - duration: 0, - closeBtn:
关闭
, - onCloseBtnClick: () => 1, - }; - const spy = vi.spyOn(option, 'onCloseBtnClick'); - const TestComponent = () => { - const handleOpen = () => { - MessagePlugin.info(option); - }; - - return ( - <> - - - ); - }; - - const { getByText } = render(); - fireEvent.click(getByText(openText)); - await mockTimeout(() => expect(document.querySelector('#testId')).not.toBeNull()); - fireEvent.click(document.querySelector('#testId')); - await mockTimeout(() => expect(spy).toHaveBeenCalled()); - expect(document.querySelector('#testId')).toBeNull(); - }); - - test('传入 duration 大于0,倒计时结束后消息应该隐藏', async () => { - const duration = 2000; - const openText = 'open'; - const { getByText } = render( - , - ); - expect(document.querySelector('.t-message')).toBe(null); - fireEvent.click(getByText(openText)); - await mockTimeout(() => expect(document.querySelector('.t-message')).not.toBeNull()); - await mockTimeout(() => expect(document.querySelector('.t-message')).toBeNull(), duration + 100); - }); - - test('传入 duration 且 onDurationEnd,倒计时结束后应该执行 onDurationEnd', async () => { - const duration = 2000; - const openText = 'open'; - const option = { - content: defaultMessage, - onDurationEnd: () => 1, - }; - const spy = vi.spyOn(option, 'onDurationEnd'); - const { getByText } = render(); - fireEvent.click(getByText(openText)); - await mockTimeout(() => expect(spy).toHaveBeenCalled(), duration + 100); - }); - - test('attach 为 String,应该正确挂载指定的节点', async () => { - const openText = 'open'; - const option = { - content: defaultMessage, - attach: '#testId', - }; - const TestComponent = () => { - const handleOpen = () => MessagePlugin.info(option); - - return ( - <> -
- - - ); - }; - - const { getByText } = render(); - fireEvent.click(getByText(openText)); - await mockTimeout(() => expect(document.querySelector('#testId').querySelector('.t-message')).not.toBeNull()); - }); - - test('attach 为 Function,应该正确挂载指定的节点', async () => { - const openText = 'open'; - const option = { - content: defaultMessage, - attach: () => document.querySelector('#testId'), - }; - const TestComponent = () => { - const handleOpen = () => MessagePlugin.info(option); - - return ( - <> -
- - - ); - }; - - const { getByText } = render(); - fireEvent.click(getByText(openText)); - await mockTimeout(() => expect(document.querySelector('#testId').querySelector('.t-message')).not.toBeNull()); - }); - - test('设置 offset,应该相对于 placement 正确偏移', async () => { - const openText = 'open'; - const [offsetX, offsetY] = [-10, 20]; - const option = { - content: defaultMessage, - offset: [offsetX, offsetY], - }; - const TestComponent = () => { - const handleOpen = () => MessagePlugin.info(option); - - return ( - <> - - - ); - }; - const expectStyle = `left: ${offsetX}px; top: ${offsetY}px;`; - const { getByText } = render(); - fireEvent.click(getByText(openText)); - await mockTimeout(() => expect(document.querySelector('.t-message')).toHaveStyle(expectStyle)); - }); - - test.each(['center', 'top', 'left', 'right', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right'])( - '不同的 placement 值,弹出消息应该出现在对应位置', - async (placement) => { - const option = { - content: defaultMessage, - duration: 0, - placement, - }; - const openText = `open${placement}`; - const TestComponent = () => { - const handleOpen = () => MessagePlugin.info(option); - - return ( - <> - - - ); - }; - - const { getByText } = render(); - fireEvent.click(getByText(openText)); - await mockTimeout(() => expect(document.querySelector(`.t-message-placement--${placement}`)).not.toBeNull()); - await mockTimeout(() => - expect(document.querySelector(`.t-message-placement--${placement}`).querySelector('.t-message')).not.toBeNull(), - ); - }, - ); - - test('鼠标悬停时不自动关闭', async () => { - const openText = 'open'; - const option = { - content: defaultMessage, - duration: 300, - }; - const TestComponent = () => { - const handleOpen = () => { - MessagePlugin.info(option); - }; - - return ; - }; - - const { getByText, queryByText } = render(); - fireEvent.click(getByText(openText)); - - await waitFor(() => { - expect(queryByText(option.content)).toBeInTheDocument(); - }); - - const messageElement = getByText(option.content); - fireEvent.mouseEnter(messageElement); - - await mockTimeout(() => { - const element = queryByText(option.content); - expect(element).toBeInTheDocument(); - }, option.duration); - - fireEvent.mouseLeave(messageElement); - - // 等待 duration + 动画缓冲时间 - await waitFor( - () => { - expect(queryByText(option.content)).not.toBeInTheDocument(); - }, - { timeout: option.duration + 500 }, - ); - }); -}); diff --git a/packages/components/message/__tests__/message.test.tsx b/packages/components/message/__tests__/message.test.tsx new file mode 100644 index 0000000000..ffa548f297 --- /dev/null +++ b/packages/components/message/__tests__/message.test.tsx @@ -0,0 +1,381 @@ +import React from 'react'; +import { + CheckCircleFilledIcon, + ErrorCircleFilledIcon, + HelpCircleFilledIcon, + HelpIcon, + InfoCircleFilledIcon, + LoadingIcon, +} from 'tdesign-icons-react'; +import { fireEvent, mockTimeout, render, vi, waitFor } from '@test/utils'; + +import Message, { MessagePlugin } from '../index'; + +import type { MessageInfoOptions, MessageInstance, MessagePlacementList, MessageThemeList } from '../type'; + +const defaultMessage = '默认的message'; + +const THEME_MAP: Record = { + info: InfoCircleFilledIcon, + success: CheckCircleFilledIcon, + warning: ErrorCircleFilledIcon, + error: ErrorCircleFilledIcon, + question: HelpCircleFilledIcon, + loading: LoadingIcon, +}; +const THEME_LIST = Object.keys(THEME_MAP) as MessageThemeList[]; + +beforeAll(() => { + Element.prototype.animate = function animate( + _keyframes: Keyframe[] | PropertyIndexedKeyframes, + options?: number | KeyframeAnimationOptions, + ) { + const animation = { + onfinish: null as (() => void) | null, + finish: () => { + if (animation.onfinish) { + animation.onfinish(); + } + }, + }; + setTimeout( + () => { + if (animation.onfinish) { + animation.onfinish(); + } + }, + typeof options === 'number' ? options : options?.duration || 0, + ); + return animation as unknown as Animation; + }; +}); + +describe('Message', () => { + describe('props', () => { + test(':theme', () => { + THEME_LIST.forEach((t) => { + const { container, unmount } = render({t}); + expect(container.firstChild).toHaveClass(`t-is-${t}`); + expect(() => { + unmount(); + }).not.toThrow(); + }); + }); + + test(':closeBtn is true, render default close button.', () => { + const { container } = render({defaultMessage}); + expect(container.firstChild).toHaveClass('t-is-closable'); + expect(container.getElementsByClassName('t-message__close').length).toBe(1); + }); + + test(':icon is false', () => { + const { container } = render({defaultMessage}); + expect(container.firstChild).not.toHaveClass('t-icon'); + }); + + test(':style', () => { + const style = { backgroundColor: 'rgb(51, 51, 51)' }; + THEME_LIST.forEach((t) => { + const { container } = render( + + {defaultMessage} + , + ); + expect(container.firstChild).toHaveStyle(`background-color: ${style.backgroundColor};`); + }); + }); + + test(':content as string, should get equal text', () => { + THEME_LIST.forEach((t) => { + const { container } = render(); + expect(container.firstChild).toHaveTextContent(defaultMessage); + }); + }); + test(':closeBtn is a string, equal "关闭".', () => { + const closeBtnTxt = '关闭'; + const { container, getByText } = render({defaultMessage}); + expect(container.getElementsByClassName('t-message__close').length).toBe(1); + expect(getByText(closeBtnTxt).textContent).toBe(closeBtnTxt); + }); + + test(':closeBtn is a function, () => VNode.', () => { + const { container, getByText } = render(x}>{defaultMessage}); + expect(container.getElementsByClassName('t-message__close').length).toBe(1); + expect(getByText('x')).toBeInTheDocument(); + }); + + test(':icon is a function, () => TIconMore', () => { + const { container } = render( }>{defaultMessage}); + // t-icon + expect(container.firstChild).not.toHaveClass('t-icon'); + }); + + test(':content as ReactNode', () => { + const ReactNode =

{defaultMessage}

; + THEME_LIST.forEach((t) => { + const { container } = render(); + expect(container.querySelector('.wrapper')).not.toBe(null); + expect(container.querySelector('.wrapper')).toHaveTextContent(defaultMessage); + }); + }); + }); + + describe('scenarios', () => { + test('pure message contains right classes', async () => { + const { container, getByText, unmount } = render({defaultMessage}); + expect(getByText(defaultMessage)).toBeInTheDocument(); + expect(container.firstChild).toHaveClass('t-message'); + expect(container.firstChild).toHaveClass('t-is-info'); + expect(container.firstChild).not.toHaveClass('t-message__close'); + expect(() => { + unmount(); + }).not.toThrow(); + }); + + test(':duration props', async () => { + const onClose = vi.fn(); + const duration = 300; + render( + + {defaultMessage} + , + ); + expect(onClose).not.toHaveBeenCalled(); + await mockTimeout(() => expect(onClose).toHaveBeenCalledTimes(1), duration); + expect(onClose).toHaveBeenCalledWith({ + trigger: 'duration-end', + }); + }); + }); + + describe('instanceFunctions', () => { + afterEach(() => { + document.getElementsByTagName('html')[0].innerHTML = ''; + }); + + test('Message 基础函数式调用,消息应该正常展示、隐藏', async () => { + const openText = 'open'; + const closeText = 'close'; + const TestComponent = () => { + let message: Promise; + const handleOpen = () => { + message = MessagePlugin.info(defaultMessage); + }; + const handleClose = () => { + MessagePlugin.close(message); + }; + + return ( + <> + + + + ); + }; + + const { getByText } = render(); + expect(document.querySelector('.t-message')).toBe(null); + fireEvent.click(getByText(openText)); + await mockTimeout(() => expect(document.querySelector('.t-message')).not.toBeNull()); + await mockTimeout(() => expect(document.querySelector('.t-message')).toHaveTextContent(defaultMessage)); + fireEvent.click(getByText(closeText)); + await waitFor( + () => { + expect(document.querySelector('.t-message')).toBeNull(); + }, + // 动画缓冲时间 + { timeout: 500 }, + ); + }); + + test('存在关闭按钮,点击关闭按钮应该触发 onCloseBtnClick', async () => { + const openText = 'open'; + const option: MessageInfoOptions = { + content: defaultMessage, + duration: 0, + closeBtn:
关闭
, + onCloseBtnClick: () => 1, + }; + const spy = vi.spyOn(option, 'onCloseBtnClick'); + const TestComponent = () => { + const handleOpen = () => { + MessagePlugin.info(option); + }; + + return ( + <> + + + ); + }; + + const { getByText } = render(); + fireEvent.click(getByText(openText)); + await mockTimeout(() => expect(document.querySelector('#testId')).not.toBeNull()); + fireEvent.click(document.querySelector('#testId')); + await mockTimeout(() => expect(spy).toHaveBeenCalled()); + expect(document.querySelector('#testId')).toBeNull(); + }); + + test('传入 duration 大于0,倒计时结束后消息应该隐藏', async () => { + const duration = 2000; + const openText = 'open'; + const { getByText } = render( + , + ); + expect(document.querySelector('.t-message')).toBe(null); + fireEvent.click(getByText(openText)); + await mockTimeout(() => expect(document.querySelector('.t-message')).not.toBeNull()); + await mockTimeout(() => expect(document.querySelector('.t-message')).toBeNull(), duration + 100); + }); + + test('传入 duration 且 onDurationEnd,倒计时结束后应该执行 onDurationEnd', async () => { + const duration = 2000; + const openText = 'open'; + const option: MessageInfoOptions = { + content: defaultMessage, + onDurationEnd: () => 1, + }; + const spy = vi.spyOn(option, 'onDurationEnd'); + const { getByText } = render(); + fireEvent.click(getByText(openText)); + await mockTimeout(() => expect(spy).toHaveBeenCalled(), duration + 100); + }); + + test('attach 为 String,应该正确挂载指定的节点', async () => { + const openText = 'open'; + const option: MessageInfoOptions = { + content: defaultMessage, + attach: '#testId', + }; + const TestComponent = () => { + const handleOpen = () => MessagePlugin.info(option); + + return ( + <> +
+ + + ); + }; + + const { getByText } = render(); + fireEvent.click(getByText(openText)); + await mockTimeout(() => expect(document.querySelector('#testId').querySelector('.t-message')).not.toBeNull()); + }); + + test('attach 为 Function,应该正确挂载指定的节点', async () => { + const openText = 'open'; + const option: MessageInfoOptions = { + content: defaultMessage, + attach: () => document.querySelector('#testId'), + }; + const TestComponent = () => { + const handleOpen = () => MessagePlugin.info(option); + + return ( + <> +
+ + + ); + }; + + const { getByText } = render(); + fireEvent.click(getByText(openText)); + await mockTimeout(() => expect(document.querySelector('#testId').querySelector('.t-message')).not.toBeNull()); + }); + + test('设置 offset,应该相对于 placement 正确偏移', async () => { + const openText = 'open'; + const [offsetX, offsetY] = [-10, 20]; + const option: MessageInfoOptions = { + content: defaultMessage, + offset: [offsetX, offsetY], + }; + const TestComponent = () => { + const handleOpen = () => MessagePlugin.info(option); + + return ( + <> + + + ); + }; + const expectStyle = `left: ${offsetX}px; top: ${offsetY}px;`; + const { getByText } = render(); + fireEvent.click(getByText(openText)); + await mockTimeout(() => expect(document.querySelector('.t-message')).toHaveStyle(expectStyle)); + }); + + test.each(['center', 'top', 'left', 'right', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right'])( + '不同的 placement 值,弹出消息应该出现在对应位置', + async (placement: MessagePlacementList) => { + const option: MessageInfoOptions = { + content: defaultMessage, + duration: 0, + placement, + }; + const openText = `open${placement}`; + const TestComponent = () => { + const handleOpen = () => MessagePlugin.info(option); + + return ( + <> + + + ); + }; + + const { getByText } = render(); + fireEvent.click(getByText(openText)); + await mockTimeout(() => expect(document.querySelector(`.t-message-placement--${placement}`)).not.toBeNull()); + await mockTimeout(() => + expect( + document.querySelector(`.t-message-placement--${placement}`).querySelector('.t-message'), + ).not.toBeNull(), + ); + }, + ); + + test('鼠标悬停时不自动关闭', async () => { + const openText = 'open'; + const option: MessageInfoOptions = { + content: defaultMessage, + duration: 300, + }; + const TestComponent = () => { + const handleOpen = () => { + MessagePlugin.info(option); + }; + + return ; + }; + + const { getByText, queryByText } = render(); + fireEvent.click(getByText(openText)); + + await waitFor(() => { + expect(queryByText(option.content as string)).toBeInTheDocument(); + }); + + const messageElement = getByText(option.content as string); + fireEvent.mouseEnter(messageElement); + + await mockTimeout(() => { + const element = queryByText(option.content as string); + expect(element).toBeInTheDocument(); + }, option.duration); + + fireEvent.mouseLeave(messageElement); + + // 等待 duration + 动画缓冲时间 + await waitFor( + () => { + expect(queryByText(option.content as string)).not.toBeInTheDocument(); + }, + { timeout: option.duration + 500 }, + ); + }); + }); +}); diff --git a/packages/components/notification/__tests__/notification.test.tsx b/packages/components/notification/__tests__/notification.test.tsx index cc1568b089..9859cc708b 100644 --- a/packages/components/notification/__tests__/notification.test.tsx +++ b/packages/components/notification/__tests__/notification.test.tsx @@ -3,119 +3,126 @@ import { fireEvent, mockTimeout, render } from '@test/utils'; import { Notification, NotificationPlugin } from '../index'; -describe('Notification test', () => { - test('mount and unmount', () => { - const wrapper = render(); +describe('Notification', () => { + describe('scenarios', () => { + test('mount and unmount', () => { + const wrapper = render(); - expect(() => wrapper.unmount()).not.toThrow(); - }); + expect(() => wrapper.unmount()).not.toThrow(); + }); - test('set props', async () => { - const titleText = '标题'; - const contentId = 'content'; - const contentText = '内容'; - const closeBtnId = 'close'; - const closeBtnText = '关闭'; - const footerId = 'footer'; - - const wrapper = render( - {contentText}
} - theme="success" - icon={
test
} - closeBtn={
{closeBtnText}
} - footer={ -
- 确定 -
- } - />, - ); - - /** - * @desc 更改 title 的断言测试。新的 api title 只接受纯字符串,需要修改测试方法 - */ - const title = document.querySelectorAll('.t-notification__title'); - expect(title.length).toBe(1); - expect(title[0].innerHTML).toBe(titleText); - - expect(wrapper.queryByTestId(contentId)).toHaveTextContent(contentText); - expect(wrapper.queryByTestId(closeBtnId)).toHaveTextContent(closeBtnText); - expect(wrapper.queryByTestId(footerId)).not.toBeNull(); + test('set props', async () => { + const titleText = '标题'; + const contentId = 'content'; + const contentText = '内容'; + const closeBtnId = 'close'; + const closeBtnText = '关闭'; + const footerId = 'footer'; + + const wrapper = render( + {contentText}
} + theme="success" + icon={
test
} + closeBtn={
{closeBtnText}
} + footer={ +
+ 确定 +
+ } + />, + ); + + /** + * @desc 更改 title 的断言测试。新的 api title 只接受纯字符串,需要修改测试方法 + */ + const title = document.querySelectorAll('.t-notification__title'); + expect(title.length).toBe(1); + expect(title[0].innerHTML).toBe(titleText); + + expect(wrapper.queryByTestId(contentId)).toHaveTextContent(contentText); + expect(wrapper.queryByTestId(closeBtnId)).toHaveTextContent(closeBtnText); + expect(wrapper.queryByTestId(footerId)).not.toBeNull(); + }); }); - test('open and close', async () => { - NotificationPlugin.closeAll(); + describe('instanceFunctions', () => { + test('open and close', async () => { + NotificationPlugin.closeAll(); - const notification = await NotificationPlugin.info({}); - await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(1), 100); - notification.close(); - await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(0), 100); + const notification = await NotificationPlugin.info({}); + await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(1), 100); + notification.close(); + await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(0), 100); - const notificationPromise = NotificationPlugin.info({}); + const notificationPromise = NotificationPlugin.info({}); - await notificationPromise; + await notificationPromise; - await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(1), 100); - await NotificationPlugin.close(notificationPromise); - await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(0), 100); - }); + await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(1), 100); + await NotificationPlugin.close(notificationPromise); + await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(0), 100); + }); - test('open with theme', async () => { - NotificationPlugin.closeAll(); + test('open with theme', async () => { + NotificationPlugin.closeAll(); - await NotificationPlugin.info({}); - await NotificationPlugin.success({}); - await NotificationPlugin.warning({}); - await NotificationPlugin.error({}); + await NotificationPlugin.info({}); + await NotificationPlugin.success({}); + await NotificationPlugin.warning({}); + await NotificationPlugin.error({}); - await mockTimeout(() => expect(document.querySelectorAll('.t-notification .t-is-info').length).toBe(1), 100); - await mockTimeout(() => expect(document.querySelectorAll('.t-notification .t-is-success').length).toBe(1), 100); - await mockTimeout(() => expect(document.querySelectorAll('.t-notification .t-is-warning').length).toBe(1), 100); - await mockTimeout(() => expect(document.querySelectorAll('.t-notification .t-is-error').length).toBe(1), 100); - }); + await mockTimeout(() => expect(document.querySelectorAll('.t-notification .t-is-info').length).toBe(1), 100); + await mockTimeout(() => expect(document.querySelectorAll('.t-notification .t-is-success').length).toBe(1), 100); + await mockTimeout(() => expect(document.querySelectorAll('.t-notification .t-is-warning').length).toBe(1), 100); + await mockTimeout(() => expect(document.querySelectorAll('.t-notification .t-is-error').length).toBe(1), 100); + }); - test('open with placement', async () => { - NotificationPlugin.closeAll(); - - await NotificationPlugin.info({ placement: 'top-left' }); - await NotificationPlugin.info({ placement: 'top-right' }); - await NotificationPlugin.info({ placement: 'bottom-left' }); - await NotificationPlugin.info({ placement: 'bottom-right' }); - - await mockTimeout(() => expect(document.querySelectorAll('.t-notification__show--top-left').length).toBe(1), 100); - await mockTimeout(() => expect(document.querySelectorAll('.t-notification__show--top-right').length).toBe(1), 100); - await mockTimeout( - () => expect(document.querySelectorAll('.t-notification__show--bottom-left').length).toBe(1), - 100, - ); - await mockTimeout( - () => expect(document.querySelectorAll('.t-notification__show--bottom-right').length).toBe(1), - 100, - ); - }); + test('open with placement', async () => { + NotificationPlugin.closeAll(); + + await NotificationPlugin.info({ placement: 'top-left' }); + await NotificationPlugin.info({ placement: 'top-right' }); + await NotificationPlugin.info({ placement: 'bottom-left' }); + await NotificationPlugin.info({ placement: 'bottom-right' }); + + await mockTimeout(() => expect(document.querySelectorAll('.t-notification__show--top-left').length).toBe(1), 100); + await mockTimeout( + () => expect(document.querySelectorAll('.t-notification__show--top-right').length).toBe(1), + 100, + ); + await mockTimeout( + () => expect(document.querySelectorAll('.t-notification__show--bottom-left').length).toBe(1), + 100, + ); + await mockTimeout( + () => expect(document.querySelectorAll('.t-notification__show--bottom-right').length).toBe(1), + 100, + ); + }); - test('auto close', async () => { - NotificationPlugin.closeAll(); + test('auto close', async () => { + NotificationPlugin.closeAll(); - await NotificationPlugin.info({ duration: 1000 }); + await NotificationPlugin.info({ duration: 1000 }); - await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(1), 100); + await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(1), 100); - await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(0), 1200); - }); + await mockTimeout(() => expect(document.querySelectorAll('.t-notification').length).toBe(0), 1200); + }); - test('click close button', async () => { - NotificationPlugin.closeAll(); + test('click close button', async () => { + NotificationPlugin.closeAll(); - await NotificationPlugin.info({ - closeBtn: 关闭, - }); + await NotificationPlugin.info({ + closeBtn: 关闭, + }); - await mockTimeout(() => { - fireEvent.click(document.querySelector('#close_button')); - return expect(document.querySelectorAll('.t-notification').length).toBe(0); - }, 300); + await mockTimeout(() => { + fireEvent.click(document.querySelector('#close_button')); + return expect(document.querySelectorAll('.t-notification').length).toBe(0); + }, 300); + }); }); }); diff --git a/packages/components/pagination/__tests__/pagination.test.tsx b/packages/components/pagination/__tests__/pagination.test.tsx index 3265095190..2f95ed18ba 100644 --- a/packages/components/pagination/__tests__/pagination.test.tsx +++ b/packages/components/pagination/__tests__/pagination.test.tsx @@ -3,122 +3,127 @@ import { fireEvent, render, vi } from '@test/utils'; import Pagination from '../index'; -describe('Pagination test', () => { - test('mount and unmount', () => { - const wrapper = render(); - - expect(() => wrapper.unmount()).not.toThrow(); +describe('Pagination', () => { + describe('props', () => { + test('totalContent works fine', () => { + const changeFn = vi.fn(); + const { getByText, rerender } = render( + , + ); + + expect(getByText('总条数')).toBeInTheDocument(); + + const totalContentFn = vi.fn() as unknown as React.ReactNode; + rerender(); + expect(totalContentFn).toHaveBeenCalled(); + }); }); - test('click page works fine', () => { - const wrapper = render(); + describe('scenarios', () => { + test('mount and unmount', () => { + const wrapper = render(); - fireEvent.click(wrapper.getByText('20')); - expect(document.querySelector('.t-is-current')).toHaveTextContent('20'); + expect(() => wrapper.unmount()).not.toThrow(); + }); - fireEvent.click(document.querySelector('.t-pagination__btn-next')); - expect(document.querySelector('.t-is-current')).toHaveTextContent('20'); + test('click page works fine', () => { + const wrapper = render(); - fireEvent.click(document.querySelector('.t-pagination__btn-prev')); - expect(document.querySelector('.t-is-current')).toHaveTextContent('19'); - }); + fireEvent.click(wrapper.getByText('20')); + expect(document.querySelector('.t-is-current')).toHaveTextContent('20'); - test('pageSize works fine', async () => { - const changeFn = vi.fn(); - const pageSizeChangeFn = vi.fn(); - const { getByText, container, getByDisplayValue } = render( - , - ); - const pager = container.querySelector('.t-pagination__pager'); - expect(pager.childNodes.length).toBe(7); - - fireEvent.click(getByDisplayValue('5 条/页')); - fireEvent.click(getByText('10 条/页')); - expect(pager.childNodes.length).toBe(10); - - fireEvent.click(getByText('10')); - expect(document.querySelector('.t-is-current')).toHaveTextContent('10'); - - fireEvent.click(getByDisplayValue('10 条/页')); - fireEvent.click(getByText('20 条/页')); - expect(container.querySelector('.t-pagination__pager').childNodes.length).toBe(5); - expect(document.querySelector('.t-is-current')).toHaveTextContent('5'); - }); - test('folded works fine', () => { - const changeFn = vi.fn(); - const pageSizeChangeFn = vi.fn(); - const { getByText, container } = render( - , - ); - expect(container.querySelector('.t-icon-ellipsis')).toBeInTheDocument(); - - fireEvent.click(getByText('2')); - expect(changeFn.mock.calls[0][0]).toEqual({ - current: 2, - previous: 1, - pageSize: 5, - }); - fireEvent.mouseOver(container.querySelector('.t-pagination__number--more')); - expect(container.querySelector('.t-icon-chevron-right-double')).toBeInTheDocument(); - fireEvent.mouseOut(container.querySelector('.t-pagination__number--more')); - expect(container.querySelector('.t-icon-chevron-right-double')).not.toBeInTheDocument(); - - fireEvent.click(container.querySelector('.t-pagination__number--more')); - expect(changeFn.mock.calls[1][0]).toEqual({ - current: 7, - previous: 2, - pageSize: 5, - }); - fireEvent.mouseOver(container.querySelector('.t-pagination__number--more')); - expect(container.querySelector('.t-icon-chevron-left-double')).toBeInTheDocument(); - fireEvent.mouseOut(container.querySelector('.t-pagination__number--more')); - expect(container.querySelector('.t-icon-chevron-left-double')).not.toBeInTheDocument(); - - fireEvent.click(container.querySelector('.t-pagination__number--more')); - expect(document.querySelector('.t-is-current')).toHaveTextContent('2'); - }); - test('theme works fine', () => { - const changeFn = vi.fn(); - render(); + fireEvent.click(document.querySelector('.t-pagination__btn-next')); + expect(document.querySelector('.t-is-current')).toHaveTextContent('20'); - fireEvent.change(document.querySelector('.t-pagination__jump .t-input__inner'), { target: { value: '5' } }); - fireEvent.keyDown(document.querySelector('.t-pagination__jump .t-input__inner'), { keyCode: 13 }); - expect((document.querySelector('.t-pagination__jump .t-input__inner') as HTMLInputElement).value).toEqual('5'); - }); - test('totalContent works fine', () => { - const changeFn = vi.fn(); - const { getByText, rerender } = render( - , - ); + fireEvent.click(document.querySelector('.t-pagination__btn-prev')); + expect(document.querySelector('.t-is-current')).toHaveTextContent('19'); + }); - expect(getByText('总条数')).toBeInTheDocument(); + test('pageSize works fine', async () => { + const changeFn = vi.fn(); + const pageSizeChangeFn = vi.fn(); + const { getByText, container, getByDisplayValue } = render( + , + ); + const pager = container.querySelector('.t-pagination__pager'); + expect(pager.childNodes.length).toBe(7); + + fireEvent.click(getByDisplayValue('5 条/页')); + fireEvent.click(getByText('10 条/页')); + expect(pager.childNodes.length).toBe(10); + + fireEvent.click(getByText('10')); + expect(document.querySelector('.t-is-current')).toHaveTextContent('10'); + + fireEvent.click(getByDisplayValue('10 条/页')); + fireEvent.click(getByText('20 条/页')); + expect(container.querySelector('.t-pagination__pager').childNodes.length).toBe(5); + expect(document.querySelector('.t-is-current')).toHaveTextContent('5'); + }); + test('folded works fine', () => { + const changeFn = vi.fn(); + const pageSizeChangeFn = vi.fn(); + const { getByText, container } = render( + , + ); + expect(container.querySelector('.t-icon-ellipsis')).toBeInTheDocument(); + + fireEvent.click(getByText('2')); + expect(changeFn.mock.calls[0][0]).toEqual({ + current: 2, + previous: 1, + pageSize: 5, + }); + fireEvent.mouseOver(container.querySelector('.t-pagination__number--more')); + expect(container.querySelector('.t-icon-chevron-right-double')).toBeInTheDocument(); + fireEvent.mouseOut(container.querySelector('.t-pagination__number--more')); + expect(container.querySelector('.t-icon-chevron-right-double')).not.toBeInTheDocument(); + + fireEvent.click(container.querySelector('.t-pagination__number--more')); + expect(changeFn.mock.calls[1][0]).toEqual({ + current: 7, + previous: 2, + pageSize: 5, + }); + fireEvent.mouseOver(container.querySelector('.t-pagination__number--more')); + expect(container.querySelector('.t-icon-chevron-left-double')).toBeInTheDocument(); + fireEvent.mouseOut(container.querySelector('.t-pagination__number--more')); + expect(container.querySelector('.t-icon-chevron-left-double')).not.toBeInTheDocument(); + + fireEvent.click(container.querySelector('.t-pagination__number--more')); + expect(document.querySelector('.t-is-current')).toHaveTextContent('2'); + }); + test('theme works fine', () => { + const changeFn = vi.fn(); + render(); - const totalContentFn = vi.fn() as unknown as React.ReactNode; - rerender(); - expect(totalContentFn).toHaveBeenCalled(); - }); - test('jumper works fine', () => { - render(); + fireEvent.change(document.querySelector('.t-pagination__jump .t-input__inner'), { target: { value: '5' } }); + fireEvent.keyDown(document.querySelector('.t-pagination__jump .t-input__inner'), { keyCode: 13 }); + expect((document.querySelector('.t-pagination__jump .t-input__inner') as HTMLInputElement).value).toEqual('5'); + }); + test('jumper works fine', () => { + render(); - fireEvent.change(document.querySelector('.t-pagination__jump .t-input__inner'), { target: { value: '5' } }); - fireEvent.keyDown(document.querySelector('.t-pagination__jump .t-input__inner'), { keyCode: 13 }); - expect(document.querySelector('.t-is-current')).toHaveTextContent('5'); + fireEvent.change(document.querySelector('.t-pagination__jump .t-input__inner'), { target: { value: '5' } }); + fireEvent.keyDown(document.querySelector('.t-pagination__jump .t-input__inner'), { keyCode: 13 }); + expect(document.querySelector('.t-is-current')).toHaveTextContent('5'); - fireEvent.change(document.querySelector('.t-pagination__jump .t-input__inner'), { target: { value: '30' } }); - fireEvent.keyDown(document.querySelector('.t-pagination__jump .t-input__inner'), { keyCode: 13 }); - expect(document.querySelector('.t-is-current')).toHaveTextContent('20'); + fireEvent.change(document.querySelector('.t-pagination__jump .t-input__inner'), { target: { value: '30' } }); + fireEvent.keyDown(document.querySelector('.t-pagination__jump .t-input__inner'), { keyCode: 13 }); + expect(document.querySelector('.t-is-current')).toHaveTextContent('20'); - fireEvent.change(document.querySelector('.t-pagination__jump .t-input__inner'), { target: { value: '-1' } }); - fireEvent.keyDown(document.querySelector('.t-pagination__jump .t-input__inner'), { keyCode: 13 }); - expect(document.querySelector('.t-is-current')).toHaveTextContent('1'); - }); - test('jump input number return integral part', () => { - // 测试输入小数会自动处理整数 - const changeFn = vi.fn(); - render(); - - fireEvent.change(document.querySelector('.t-pagination__jump .t-input__inner'), { target: { value: '5.555' } }); - fireEvent.keyDown(document.querySelector('.t-pagination__jump .t-input__inner'), { keyCode: 13 }); - expect((document.querySelector('.t-pagination__jump .t-input__inner') as HTMLInputElement).value).toEqual('5'); + fireEvent.change(document.querySelector('.t-pagination__jump .t-input__inner'), { target: { value: '-1' } }); + fireEvent.keyDown(document.querySelector('.t-pagination__jump .t-input__inner'), { keyCode: 13 }); + expect(document.querySelector('.t-is-current')).toHaveTextContent('1'); + }); + test('jump input number return integral part', () => { + // 测试输入小数会自动处理整数 + const changeFn = vi.fn(); + render(); + + fireEvent.change(document.querySelector('.t-pagination__jump .t-input__inner'), { target: { value: '5.555' } }); + fireEvent.keyDown(document.querySelector('.t-pagination__jump .t-input__inner'), { keyCode: 13 }); + expect((document.querySelector('.t-pagination__jump .t-input__inner') as HTMLInputElement).value).toEqual('5'); + }); }); }); diff --git a/packages/components/pagination/__tests__/paginationMini.test.tsx b/packages/components/pagination/__tests__/paginationMini.test.tsx index 300202bd6e..32be7fe5f2 100644 --- a/packages/components/pagination/__tests__/paginationMini.test.tsx +++ b/packages/components/pagination/__tests__/paginationMini.test.tsx @@ -3,73 +3,79 @@ import { render, vi } from '@test/utils'; import PaginationMini from '../PaginationMini'; -describe('PaginationMini test', () => { - test('mount and unmount', () => { - const wrapper = render(); +describe('PaginationMini', () => { + describe('props', () => { + test('PaginationMini: test Disabled', () => { + const disabledPrev = { + prev: true, + current: false, + next: false, + }; + const disabledCurrent = { + prev: false, + current: true, + next: false, + }; + const disabledNext = { + prev: false, + current: false, + next: true, + }; + const onChange = vi.fn(); + const { container } = render(); + const { container: container2 } = render(); + const { container: container3 } = render(); + const prev = container.querySelector('.t-pagination-mini__prev'); + const current = container2.querySelector('.t-pagination-mini__current'); + const next = container3.querySelector('.t-pagination-mini__next'); + expect(prev).toHaveClass('t-is-disabled'); + expect(current).toHaveClass('t-is-disabled'); + expect(next).toHaveClass('t-is-disabled'); + }); - expect(() => wrapper.unmount()).not.toThrow(); - }); + test('PaginationMini: test title', () => { + const { container } = render(); + const prev = container.querySelector('.t-pagination-mini__prev'); + const current = container.querySelector('.t-pagination-mini__current'); + const next = container.querySelector('.t-pagination-mini__next'); - test('PaginationMini: test Disabled', () => { - const disabledPrev = { - prev: true, - current: false, - next: false, - }; - const disabledCurrent = { - prev: false, - current: true, - next: false, - }; - const disabledNext = { - prev: false, - current: false, - next: true, - }; - const onChange = vi.fn(); - const { container } = render(); - const { container: container2 } = render(); - const { container: container3 } = render(); - const prev = container.querySelector('.t-pagination-mini__prev'); - const current = container2.querySelector('.t-pagination-mini__current'); - const next = container3.querySelector('.t-pagination-mini__next'); - expect(prev).toHaveClass('t-is-disabled'); - expect(current).toHaveClass('t-is-disabled'); - expect(next).toHaveClass('t-is-disabled'); - }); - - test('PaginationMini: test onChange', () => { - const onChange = vi.fn(); - const { container } = render(); - const prev = container.querySelector('.t-pagination-mini__prev'); - const current = container.querySelector('.t-pagination-mini__current'); - const next = container.querySelector('.t-pagination-mini__next'); - prev?.dispatchEvent(new MouseEvent('click', { bubbles: true })); - expect(onChange.mock.calls[0][0].trigger).toBe('prev'); - current?.dispatchEvent(new MouseEvent('click', { bubbles: true })); - expect(onChange.mock.calls[1][0].trigger).toBe('current'); - next?.dispatchEvent(new MouseEvent('click', { bubbles: true })); - expect(onChange.mock.calls[2][0].trigger).toBe('next'); - expect(onChange).toHaveBeenCalledTimes(3); - }); + expect(prev.getAttribute('title')).toBe('上一页'); + expect(current.getAttribute('title')).toBe('当前'); + expect(next.getAttribute('title')).toBe('下一页'); + }); - test('PaginationMini: test title', () => { - const { container } = render(); - const prev = container.querySelector('.t-pagination-mini__prev'); - const current = container.querySelector('.t-pagination-mini__current'); - const next = container.querySelector('.t-pagination-mini__next'); + test('PaginationMini: size variant', () => { + const { container } = render(); + container.querySelectorAll('.t-button').forEach((item) => { + expect(item).toHaveClass('t-size-s'); + }); - expect(prev.getAttribute('title')).toBe('上一页'); - expect(current.getAttribute('title')).toBe('当前'); - expect(next.getAttribute('title')).toBe('下一页'); + expect(container.querySelectorAll('.t-button').length).toBe(3); + }); }); - test('PaginationMini: size variant', () => { - const { container } = render(); - container.querySelectorAll('.t-button').forEach((item) => { - expect(item).toHaveClass('t-size-s'); + describe('events', () => { + test('PaginationMini: test onChange', () => { + const onChange = vi.fn(); + const { container } = render(); + const prev = container.querySelector('.t-pagination-mini__prev'); + const current = container.querySelector('.t-pagination-mini__current'); + const next = container.querySelector('.t-pagination-mini__next'); + prev?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + expect(onChange.mock.calls[0][0].trigger).toBe('prev'); + current?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + expect(onChange.mock.calls[1][0].trigger).toBe('current'); + next?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + expect(onChange.mock.calls[2][0].trigger).toBe('next'); + expect(onChange).toHaveBeenCalledTimes(3); }); + }); - expect(container.querySelectorAll('.t-button').length).toBe(3); + describe('scenarios', () => { + test('mount and unmount', () => { + const wrapper = render(); + + expect(() => wrapper.unmount()).not.toThrow(); + }); }); }); diff --git a/packages/components/popconfirm/__tests__/popconfirm.test.tsx b/packages/components/popconfirm/__tests__/popconfirm.test.tsx index 515cb3a8f3..5aefc301ce 100644 --- a/packages/components/popconfirm/__tests__/popconfirm.test.tsx +++ b/packages/components/popconfirm/__tests__/popconfirm.test.tsx @@ -3,140 +3,146 @@ import { act, fireEvent, render, vi, waitFor } from '@test/utils'; import Popconfirm from '../Popconfirm'; -describe('Popconfirm 组件测试', () => { +describe('Popconfirm', () => { const testId = 'popup-test-id'; const triggerElement = '触发元素'; const text = '弹出层描述'; - // test('hover 触发测试', async () => { - // const ref = React.createRef(); - - // const { getByText, queryByTestId, asFragment, queryByText } = render( - // {text}
} theme="warning" ref={ref}> - // {triggerElement} - // , - // ); - - // // 鼠标进入前,没有元素存在 - // const element1 = await waitFor(() => queryByTestId(testId)); - // expect(element1).toBeNull(); - // expect(ref.current).toBeNull(); - - // // 模拟鼠标进入 - // act(() => { - // fireEvent.mouseEnter(getByText(triggerElement)); - // }); - - // expect(ref.current).not.toBeNull(); - - // // 鼠标进入后,有元素,而且内容为 popupText + 两个按钮 - // const element2 = await waitFor(() => queryByTestId(testId)); - // expect(element2).not.toBeNull(); - - // const cancelBtn = await waitFor(() => queryByText('取消')); - // expect(cancelBtn).not.toBeNull(); - // const confirmBtn = await waitFor(() => queryByText('确定')); - // expect(confirmBtn).not.toBeNull(); - // }); - - test('click 触发测试', async () => { - const onCancelMock = vi.fn(); - const onConfirmMock = vi.fn(); - const { getByText, queryByTestId, queryByText } = render( - {text}
} - onCancel={onCancelMock} - onConfirm={onConfirmMock} - destroyOnClose={false} - confirmBtn="确认提交" - cancelBtn="取消操作" - > - {triggerElement} - , - ); - - // 鼠标点击前,没有元素存在 - const element1 = await waitFor(() => queryByTestId(testId)); - expect(element1).toBeNull(); - - // 模拟鼠标点击 - act(() => { - fireEvent.click(getByText(triggerElement)); + describe('scenarios', () => { + // test('hover 触发测试', async () => { + // const ref = React.createRef(); + + // const { getByText, queryByTestId, asFragment, queryByText } = render( + // {text}
} theme="warning" ref={ref}> + // {triggerElement} + // , + // ); + + // // 鼠标进入前,没有元素存在 + // const element1 = await waitFor(() => queryByTestId(testId)); + // expect(element1).toBeNull(); + // expect(ref.current).toBeNull(); + + // // 模拟鼠标进入 + // act(() => { + // fireEvent.mouseEnter(getByText(triggerElement)); + // }); + + // expect(ref.current).not.toBeNull(); + + // // 鼠标进入后,有元素,而且内容为 popupText + 两个按钮 + // const element2 = await waitFor(() => queryByTestId(testId)); + // expect(element2).not.toBeNull(); + + // const cancelBtn = await waitFor(() => queryByText('取消')); + // expect(cancelBtn).not.toBeNull(); + // const confirmBtn = await waitFor(() => queryByText('确定')); + // expect(confirmBtn).not.toBeNull(); + // }); + + test('click 触发测试', async () => { + const onCancelMock = vi.fn(); + const onConfirmMock = vi.fn(); + const { getByText, queryByTestId, queryByText } = render( + {text}
} + onCancel={onCancelMock} + onConfirm={onConfirmMock} + destroyOnClose={false} + confirmBtn="确认提交" + cancelBtn="取消操作" + > + {triggerElement} + , + ); + + // 鼠标点击前,没有元素存在 + const element1 = await waitFor(() => queryByTestId(testId)); + expect(element1).toBeNull(); + + // 模拟鼠标点击 + act(() => { + fireEvent.click(getByText(triggerElement)); + }); + + // 鼠标进入后,有元素,而且内容为 popupText + 两个按钮 + const element2 = await waitFor(() => queryByTestId(testId)); + expect(element2).not.toBeNull(); + const cancelBtn = await waitFor(() => queryByText('取消操作')); + expect(cancelBtn).not.toBeNull(); + const confirmBtn = await waitFor(() => queryByText('确认提交')); + expect(confirmBtn).not.toBeNull(); + act(() => { + fireEvent.click(cancelBtn); + }); + + expect(onCancelMock).toHaveBeenCalledTimes(1); + + act(() => { + fireEvent.click(confirmBtn); + }); + + expect(onConfirmMock).toHaveBeenCalledTimes(1); }); - // 鼠标进入后,有元素,而且内容为 popupText + 两个按钮 - const element2 = await waitFor(() => queryByTestId(testId)); - expect(element2).not.toBeNull(); - const cancelBtn = await waitFor(() => queryByText('取消操作')); - expect(cancelBtn).not.toBeNull(); - const confirmBtn = await waitFor(() => queryByText('确认提交')); - expect(confirmBtn).not.toBeNull(); - act(() => { - fireEvent.click(cancelBtn); + test('确认/取消按钮透传 null/object 测试', async () => { + const onCancelMock = vi.fn(); + const onConfirmMock = vi.fn(); + const { getByText, queryByTestId, queryByText, queryAllByRole } = render( + {text}
} + onCancel={onCancelMock} + onConfirm={onConfirmMock} + destroyOnClose={false} + // 传入 null + confirmBtn={null} + // 传入 object + cancelBtn={{ + onClick: onCancelMock, + content: '取消操作', + theme: 'danger', + }} + > + {triggerElement} + , + ); + + // 鼠标点击前,没有元素存在 + const element1 = await waitFor(() => queryByTestId(testId)); + expect(element1).toBeNull(); + + // 模拟鼠标点击 + act(() => { + fireEvent.click(getByText(triggerElement)); + }); + + // 鼠标进入后,有元素,而且内容为 popupText, 且 confirmBtn={null} 只有一个按钮 + const element2 = await waitFor(() => queryByTestId(testId)); + expect(element2).not.toBeNull(); + + // 测试只有一个按钮 + const buttons = await waitFor(() => queryAllByRole('button')); + expect(buttons).toHaveLength(1); + + const cancelBtn = await waitFor(() => queryByText('取消操作')); + // 查询 content 验证透传成功 + expect(cancelBtn).not.toBeNull(); + + // 测试点击事件透传成功 + act(() => { + fireEvent.click(cancelBtn); + }); + expect(onCancelMock).toHaveBeenCalledTimes(1); }); - expect(onCancelMock).toHaveBeenCalledTimes(1); - - act(() => { - fireEvent.click(confirmBtn); - }); - - expect(onConfirmMock).toHaveBeenCalledTimes(1); - }); - - test('确认/取消按钮透传 null/object 测试', async () => { - const onCancelMock = vi.fn(); - const onConfirmMock = vi.fn(); - const { getByText, queryByTestId, queryByText, queryAllByRole } = render( - {text}
} - onCancel={onCancelMock} - onConfirm={onConfirmMock} - destroyOnClose={false} - // 传入 null - confirmBtn={null} - // 传入 object - cancelBtn={{ onClick: onCancelMock, content: '取消操作', theme: 'danger' }} - > - {triggerElement} - , - ); - - // 鼠标点击前,没有元素存在 - const element1 = await waitFor(() => queryByTestId(testId)); - expect(element1).toBeNull(); - - // 模拟鼠标点击 - act(() => { - fireEvent.click(getByText(triggerElement)); - }); - - // 鼠标进入后,有元素,而且内容为 popupText, 且 confirmBtn={null} 只有一个按钮 - const element2 = await waitFor(() => queryByTestId(testId)); - expect(element2).not.toBeNull(); - - // 测试只有一个按钮 - const buttons = await waitFor(() => queryAllByRole('button')); - expect(buttons).toHaveLength(1); - - const cancelBtn = await waitFor(() => queryByText('取消操作')); - // 查询 content 验证透传成功 - expect(cancelBtn).not.toBeNull(); - - // 测试点击事件透传成功 - act(() => { - fireEvent.click(cancelBtn); + test('ref test', () => { + render( + {text}
} theme="warning"> + {triggerElement} + , + ); }); - expect(onCancelMock).toHaveBeenCalledTimes(1); - }); - - test('ref test', () => { - render( - {text}
} theme="warning"> - {triggerElement} - , - ); }); }); diff --git a/packages/components/popup/__tests__/popup.test.tsx b/packages/components/popup/__tests__/popup.test.tsx index 055d2a6169..20c6801e35 100644 --- a/packages/components/popup/__tests__/popup.test.tsx +++ b/packages/components/popup/__tests__/popup.test.tsx @@ -4,482 +4,488 @@ import { act, fireEvent, mockTimeout, render, vi, waitFor } from '@test/utils'; import Input from '../../input'; import Popup from '../Popup'; -describe('Popup 组件测试', () => { +describe('Popup', () => { const popupText = '弹出层内容'; const popupTestId = 'popup-test-id'; const triggerElement = '触发元素'; - test('hover 触发测试', async () => { - const { getByText, queryByTestId } = render( - {popupText}
}> - {triggerElement} - , - ); + describe('props', () => { + test('测试隐藏后销毁', async () => { + const { getByText, queryByTestId } = render( + {popupText}
}> + {triggerElement} + , + ); - // 鼠标进入前,没有元素存在 - const popupElement1 = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement1).toBeNull(); + // 模拟鼠标进入 + act(() => { + fireEvent.mouseEnter(getByText(triggerElement)); + }); - // 模拟鼠标进入 - act(() => { - fireEvent.mouseEnter(getByText(triggerElement)); - }); + // 鼠标进入后,有元素,而且内容为 popupText + expect(queryByTestId(popupTestId)).not.toBeNull(); - // 鼠标进入后,有元素,而且内容为 popupText - const popupElement2 = await waitFor(() => queryByTestId(popupTestId)); + // 模拟鼠标离开 + act(() => { + fireEvent.mouseLeave(getByText(triggerElement)); + }); - expect(popupElement2).not.toBeNull(); - expect(popupElement2).toHaveTextContent(popupText); - expect(popupElement2.parentNode.parentNode).toHaveClass('t-popup--animation-enter-active'); + // 鼠标离开,style 的 display 应该为 none + await mockTimeout(() => expect(queryByTestId(popupTestId)).toBeNull(), 400); + }); + + test('渲染到指定位置', async () => { + // 提前创建一个容器 + const specialId = 'SPECIAL_ID'; + const $container = document.createElement('div'); + $container.id = specialId; + document.body.appendChild($container); + + const { queryByTestId } = render( + $container} + content={
{popupText}
} + > + {triggerElement} +
, + ); - // 模拟鼠标离开 - act(() => { - fireEvent.mouseLeave(getByText(triggerElement)); - }); + // 弹出层的父元素的 id 为 SPECIAL_ID + const popupElement = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement).not.toBeNull(); - // 鼠标离开,style 的 display 应该为 none - const popupElement3 = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement3).not.toBeNull(); - expect(popupElement3.parentNode.parentNode).toHaveClass('t-popup--animation-leave-active'); + expect( + // 注意,这里层级有点多 + // #SPECIAL_ID + // └-> .div + // └-> .t-popup + // └-> div popper 定位层 + // └-> div[data-testid="popup-test-id"] + popupElement.parentElement.parentElement.parentElement.parentElement, + ).toHaveAttribute('id', specialId); + }); }); - test('hover 从弹层移回 trigger 时不关闭', async () => { - const onVisibleChange = vi.fn(); - const { getByText, queryByTestId } = render( - {popupText}
} - > - {triggerElement} - , - ); - - act(() => { - fireEvent.mouseEnter(getByText(triggerElement)); - }); + describe('events', () => { + test('hover 从弹层移回 trigger 时不关闭', async () => { + const onVisibleChange = vi.fn(); + const { getByText, queryByTestId } = render( + {popupText}
} + > + {triggerElement} + , + ); + + act(() => { + fireEvent.mouseEnter(getByText(triggerElement)); + }); - const popupContent = await waitFor(() => queryByTestId(popupTestId)); - expect(popupContent).not.toBeNull(); - const popupRoot = popupContent.parentNode.parentNode as HTMLElement; + const popupContent = await waitFor(() => queryByTestId(popupTestId)); + expect(popupContent).not.toBeNull(); + const popupRoot = popupContent.parentNode.parentNode as HTMLElement; - onVisibleChange.mockClear(); + onVisibleChange.mockClear(); - act(() => { - fireEvent.mouseLeave(popupRoot, { - relatedTarget: getByText(triggerElement), + act(() => { + fireEvent.mouseLeave(popupRoot, { + relatedTarget: getByText(triggerElement), + }); }); - }); - const visibilityChanges = onVisibleChange.mock.calls.map(([visible, ctx]) => ({ - visible, - trigger: ctx?.trigger, - })); - expect(visibilityChanges).not.toContainEqual({ - visible: false, - trigger: 'trigger-element-hover', + const visibilityChanges = onVisibleChange.mock.calls.map(([visible, ctx]) => ({ + visible, + trigger: ctx?.trigger, + })); + expect(visibilityChanges).not.toContainEqual({ + visible: false, + trigger: 'trigger-element-hover', + }); }); }); - test('click 触发测试', async () => { - const { getByText, queryByTestId } = render( - {popupText}
}> - {triggerElement} - , - ); + describe('scenarios', () => { + test('hover 触发测试', async () => { + const { getByText, queryByTestId } = render( + {popupText}
}> + {triggerElement} + , + ); - // 鼠标进入前,没有元素存在 - const popupElement1 = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement1).toBeNull(); + // 鼠标进入前,没有元素存在 + const popupElement1 = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement1).toBeNull(); - // 模拟鼠标点击 - act(() => { - fireEvent.click(getByText(triggerElement)); - }); + // 模拟鼠标进入 + act(() => { + fireEvent.mouseEnter(getByText(triggerElement)); + }); - // 鼠标进入后,有元素,而且内容为 popupText - const popupElement2 = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement2).not.toBeNull(); - expect(popupElement2).toHaveTextContent(popupText); - expect(popupElement2.parentNode.parentNode).toHaveClass('t-popup--animation-enter-active'); + // 鼠标进入后,有元素,而且内容为 popupText + const popupElement2 = await waitFor(() => queryByTestId(popupTestId)); - // 点击浮层 - act(() => { - fireEvent.mouseDown(queryByTestId(popupTestId)); - }); + expect(popupElement2).not.toBeNull(); + expect(popupElement2).toHaveTextContent(popupText); + expect(popupElement2.parentNode.parentNode).toHaveClass('t-popup--animation-enter-active'); - // 点击浮层也不隐藏 - const popupElement3 = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement3.parentNode.parentNode).toHaveClass('t-popup--animation-enter-active'); + // 模拟鼠标离开 + act(() => { + fireEvent.mouseLeave(getByText(triggerElement)); + }); - // 模拟鼠标点击其他地方 - act(() => { - fireEvent.mouseDown(document); + // 鼠标离开,style 的 display 应该为 none + const popupElement3 = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement3).not.toBeNull(); + expect(popupElement3.parentNode.parentNode).toHaveClass('t-popup--animation-leave-active'); }); - // 鼠标离开,style 的 display 应该为 none - expect(queryByTestId(popupTestId)).not.toBeNull(); - }); + test('click 触发测试', async () => { + const { getByText, queryByTestId } = render( + {popupText}
}> + {triggerElement} + , + ); - test('focus 触发测试', async () => { - const inputPlaceholder = 'focus-trigger-input'; - const { getByPlaceholderText, queryByTestId } = render( - {popupText}
}> - - , - ); - - // 聚焦前,没有元素存在 - const popupElement1 = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement1).toBeNull(); - - const inputEl = getByPlaceholderText(inputPlaceholder); - act(() => { - fireEvent.focus(inputEl); - }); + // 鼠标进入前,没有元素存在 + const popupElement1 = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement1).toBeNull(); - // 聚焦后,有元素,而且内容为 popupText - const popupElement2 = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement2).not.toBeNull(); - expect(popupElement2).toHaveTextContent(popupText); - expect(popupElement2.parentNode.parentNode).toHaveClass('t-popup--animation-enter-active'); + // 模拟鼠标点击 + act(() => { + fireEvent.click(getByText(triggerElement)); + }); - act(() => { - fireEvent.blur(inputEl); - }); + // 鼠标进入后,有元素,而且内容为 popupText + const popupElement2 = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement2).not.toBeNull(); + expect(popupElement2).toHaveTextContent(popupText); + expect(popupElement2.parentNode.parentNode).toHaveClass('t-popup--animation-enter-active'); - // 失焦后,浮层进入离开动画 - const popupElement3 = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement3).not.toBeNull(); - expect(popupElement3.parentNode.parentNode).toHaveClass('t-popup--animation-leave-active'); - }); + // 点击浮层 + act(() => { + fireEvent.mouseDown(queryByTestId(popupTestId)); + }); - test('contextMenu 触发测试', async () => { - const { getByText, queryByTestId } = render( - {popupText}
}> - {triggerElement} - , - ); + // 点击浮层也不隐藏 + const popupElement3 = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement3.parentNode.parentNode).toHaveClass('t-popup--animation-enter-active'); - // 鼠标进入前,没有元素存在 - const popupElement1 = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement1).toBeNull(); + // 模拟鼠标点击其他地方 + act(() => { + fireEvent.mouseDown(document); + }); - // 模拟鼠标右键 - act(() => { - fireEvent.contextMenu(getByText(triggerElement)); + // 鼠标离开,style 的 display 应该为 none + expect(queryByTestId(popupTestId)).not.toBeNull(); }); - // 鼠标进入后,有元素,而且内容为 popupText - const popupElement2 = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement2).not.toBeNull(); - expect(popupElement2).toHaveTextContent(popupText); - expect(popupElement2.parentNode.parentNode).toHaveClass('t-popup--animation-enter-active'); + test('focus 触发测试', async () => { + const inputPlaceholder = 'focus-trigger-input'; + const { getByPlaceholderText, queryByTestId } = render( + {popupText}
}> + + , + ); - // 模拟鼠标点击其他地方 - act(() => { - fireEvent.mouseDown(document); - }); + // 聚焦前,没有元素存在 + const popupElement1 = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement1).toBeNull(); - // 鼠标离开,style 的 display 应该为 none - const popupElement3 = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement3).not.toBeNull(); - expect(popupElement3.parentNode.parentNode).toHaveClass('t-popup--animation-leave-active'); - }); + const inputEl = getByPlaceholderText(inputPlaceholder); + act(() => { + fireEvent.focus(inputEl); + }); + + // 聚焦后,有元素,而且内容为 popupText + const popupElement2 = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement2).not.toBeNull(); + expect(popupElement2).toHaveTextContent(popupText); + expect(popupElement2.parentNode.parentNode).toHaveClass('t-popup--animation-enter-active'); - test('测试隐藏后销毁', async () => { - const { getByText, queryByTestId } = render( - {popupText}
}> - {triggerElement} - , - ); + act(() => { + fireEvent.blur(inputEl); + }); - // 模拟鼠标进入 - act(() => { - fireEvent.mouseEnter(getByText(triggerElement)); + // 失焦后,浮层进入离开动画 + const popupElement3 = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement3).not.toBeNull(); + expect(popupElement3.parentNode.parentNode).toHaveClass('t-popup--animation-leave-active'); }); - // 鼠标进入后,有元素,而且内容为 popupText - expect(queryByTestId(popupTestId)).not.toBeNull(); + test('contextMenu 触发测试', async () => { + const { getByText, queryByTestId } = render( + {popupText}
}> + {triggerElement} + , + ); - // 模拟鼠标离开 - act(() => { - fireEvent.mouseLeave(getByText(triggerElement)); - }); + // 鼠标进入前,没有元素存在 + const popupElement1 = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement1).toBeNull(); - // 鼠标离开,style 的 display 应该为 none - await mockTimeout(() => expect(queryByTestId(popupTestId)).toBeNull(), 400); - }); + // 模拟鼠标右键 + act(() => { + fireEvent.contextMenu(getByText(triggerElement)); + }); - test('渲染到指定位置', async () => { - // 提前创建一个容器 - const specialId = 'SPECIAL_ID'; - const $container = document.createElement('div'); - $container.id = specialId; - document.body.appendChild($container); - - const { queryByTestId } = render( - $container} - content={
{popupText}
} - > - {triggerElement} -
, - ); - - // 弹出层的父元素的 id 为 SPECIAL_ID - const popupElement = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement).not.toBeNull(); - - expect( - // 注意,这里层级有点多 - // #SPECIAL_ID - // └-> .div - // └-> .t-popup - // └-> div popper 定位层 - // └-> div[data-testid="popup-test-id"] - popupElement.parentElement.parentElement.parentElement.parentElement, - ).toHaveAttribute('id', specialId); - }); + // 鼠标进入后,有元素,而且内容为 popupText + const popupElement2 = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement2).not.toBeNull(); + expect(popupElement2).toHaveTextContent(popupText); + expect(popupElement2.parentNode.parentNode).toHaveClass('t-popup--animation-enter-active'); + + // 模拟鼠标点击其他地方 + act(() => { + fireEvent.mouseDown(document); + }); - test('受控展示&隐藏', async () => { - const testShowButton = 'test-show-button'; - const testHideButton = 'test-hide-button'; + // 鼠标离开,style 的 display 应该为 none + const popupElement3 = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement3).not.toBeNull(); + expect(popupElement3.parentNode.parentNode).toHaveClass('t-popup--animation-leave-active'); + }); - function TestComponent() { - const [visible, setVisible] = useState(false); - const $content = ( - - ); + test('受控展示&隐藏', async () => { + const testShowButton = 'test-show-button'; + const testHideButton = 'test-hide-button'; - return ( - - - - ); - } + ); - const { queryByTestId } = render(); + return ( + + + + ); + } - // 鼠标点击前,没有元素存在 - expect(queryByTestId(testHideButton)).toBeNull(); + const { queryByTestId } = render(); - // 模拟鼠标点击显示按钮 - act(() => { - fireEvent.click(queryByTestId(testShowButton)); - }); + // 鼠标点击前,没有元素存在 + expect(queryByTestId(testHideButton)).toBeNull(); - // 隐藏的按钮展示出来 - expect(queryByTestId(testHideButton)).not.toBeNull(); + // 模拟鼠标点击显示按钮 + act(() => { + fireEvent.click(queryByTestId(testShowButton)); + }); - // 模拟鼠标点击隐藏按钮 - fireEvent.click(queryByTestId(testHideButton)); + // 隐藏的按钮展示出来 + expect(queryByTestId(testHideButton)).not.toBeNull(); - // 隐藏的按钮消失 - await mockTimeout(() => expect(queryByTestId(testHideButton)).toBeNull()); - }); + // 模拟鼠标点击隐藏按钮 + fireEvent.click(queryByTestId(testHideButton)); - test('测试浮层附加属性及 content 为空', async () => { - const testClassName = 'test-class-name'; - const testStyle = { color: '#ff0000' }; - const { getByText } = render( - - {triggerElement} - , - ); - - // 模拟鼠标进入 - act(() => { - fireEvent.mouseEnter(getByText(triggerElement)); + // 隐藏的按钮消失 + await mockTimeout(() => expect(queryByTestId(testHideButton)).toBeNull()); }); - // 鼠标进入后,没有元素,而且内容为空 - const popupContainer = await waitFor(() => document.querySelector(`.${testClassName}`)); - expect(popupContainer).toBeNull(); - }); - - test('异常情况:浮层隐藏时点击其他地方,浮层不可以展示出来', async () => { - const testClassName = 'test-class-name'; - render( - - {triggerElement} - , - ); - - // 浮层隐藏时,随便点击 - act(() => { - fireEvent.click(document.body); - }); + test('测试浮层附加属性及 content 为空', async () => { + const testClassName = 'test-class-name'; + const testStyle = { color: '#ff0000' }; + const { getByText } = render( + + {triggerElement} + , + ); - // 鼠标进入后,有元素,而且内容为空 - const popupContainer = await waitFor(() => document.querySelector(`.${testClassName}`)); - expect(popupContainer).toBeNull(); - }); + // 模拟鼠标进入 + act(() => { + fireEvent.mouseEnter(getByText(triggerElement)); + }); - test('异常情况:getPopupContainer 传了非 HTMLElement 的元素', async () => { - const testClassName = 'test-class-name'; - const { getByText } = render( - 'xxx' as any}> - {triggerElement} - , - ); - - // 浮层隐藏时,随便点击 - act(() => { - fireEvent.click(getByText(triggerElement)); + // 鼠标进入后,没有元素,而且内容为空 + const popupContainer = await waitFor(() => document.querySelector(`.${testClassName}`)); + expect(popupContainer).toBeNull(); }); - // 有元素,并且是渲染在 body 上 - const popupContainer = await waitFor(() => document.querySelector(`.${testClassName}`)); - expect(popupContainer.parentElement.parentElement).toBe(document.body); - }); + test('异常情况:浮层隐藏时点击其他地方,浮层不可以展示出来', async () => { + const testClassName = 'test-class-name'; + render( + + {triggerElement} + , + ); - test('disabled 子组件触发测试:hover 可触发,click 不可触发', async () => { - const hoverPopupTestId = 'hover-popup-test-id'; - const clickPopupTestId = 'click-popup-test-id'; - const disabledButtonText = 'Disabled Button'; - - const { getByText, queryByTestId } = render( -
- Hover Popup Content
}> - - - - Click Popup Content
}> - - -
, - ); - - const disabledButton = getByText(disabledButtonText); - expect(queryByTestId(hoverPopupTestId)).toBeNull(); - act(() => { - fireEvent.mouseEnter(disabledButton); - }); - const hoverPopup = await waitFor(() => queryByTestId(hoverPopupTestId)); - expect(hoverPopup).not.toBeNull(); - expect(hoverPopup).toHaveTextContent('Hover Popup Content'); - act(() => { - fireEvent.mouseLeave(disabledButton); - }); + // 浮层隐藏时,随便点击 + act(() => { + fireEvent.click(document.body); + }); - const disabledClickButton = getByText('Disabled Click Button'); - expect(queryByTestId(clickPopupTestId)).toBeNull(); - act(() => { - fireEvent.click(disabledClickButton); - }); - await waitFor(() => { - expect(queryByTestId(clickPopupTestId)).toBeNull(); + // 鼠标进入后,有元素,而且内容为空 + const popupContainer = await waitFor(() => document.querySelector(`.${testClassName}`)); + expect(popupContainer).toBeNull(); }); - }); -}); -describe('Popup 嵌套组件测试', () => { - const popupTestId = 'popup-test-id'; - const wrappedPopupTestId = 'wrapped-popup-test-id'; - const triggerElement = '外层触发元素'; - const wrappedTriggerElement = '内层触发元素'; - const wrappedPopupText = '内层浮层内容'; - - const renderNestedPopup = (trigger: 'click' | 'hover') => - render( - {wrappedPopupText}
} - > -
{wrappedTriggerElement}
- - } - > - {triggerElement} - , - ); - - test('trigger="click"', async () => { - const { getByText, queryByTestId } = renderNestedPopup('click'); - - // 初始状态,浮层不存在 - expect(queryByTestId(popupTestId)).toBeNull(); - expect(queryByTestId(wrappedPopupTestId)).toBeNull(); - - // click 外层触发器 - act(() => { - fireEvent.click(getByText(triggerElement)); - }); - const popupElement = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement).not.toBeNull(); + test('异常情况:getPopupContainer 传了非 HTMLElement 的元素', async () => { + const testClassName = 'test-class-name'; + const { getByText } = render( + 'xxx' as any}> + {triggerElement} + , + ); - // click 内层触发器 - act(() => { - fireEvent.click(getByText(wrappedTriggerElement)); - }); - const wrappedPopupElement = await waitFor(() => queryByTestId(wrappedPopupTestId)); - expect(wrappedPopupElement).not.toBeNull(); - expect(wrappedPopupElement).toHaveTextContent(wrappedPopupText); + // 浮层隐藏时,随便点击 + act(() => { + fireEvent.click(getByText(triggerElement)); + }); - // mouseDown 内层内容不关闭 - act(() => { - fireEvent.mouseDown(queryByTestId(wrappedPopupTestId) as HTMLElement); - }); - await waitFor(() => { - expect(popupElement).not.toBeNull(); - expect(wrappedPopupElement).not.toBeNull(); + // 有元素,并且是渲染在 body 上 + const popupContainer = await waitFor(() => document.querySelector(`.${testClassName}`)); + expect(popupContainer.parentElement.parentElement).toBe(document.body); }); - }); - test('trigger="hover"', async () => { - const { getByText, getByTestId, queryByTestId } = renderNestedPopup('hover'); + test('disabled 子组件触发测试:hover 可触发,click 不可触发', async () => { + const hoverPopupTestId = 'hover-popup-test-id'; + const clickPopupTestId = 'click-popup-test-id'; + const disabledButtonText = 'Disabled Button'; - // 初始状态,浮层不存在 - expect(queryByTestId(popupTestId)).toBeNull(); - expect(queryByTestId(wrappedPopupTestId)).toBeNull(); + const { getByText, queryByTestId } = render( +
+ Hover Popup Content
}> + + - // hover 外层触发器 - act(() => { - fireEvent.mouseEnter(getByText(triggerElement)); - }); - const popupElement = await waitFor(() => queryByTestId(popupTestId)); - expect(popupElement).not.toBeNull(); + Click Popup Content
}> + + +
, + ); - // hover 内层触发器 - act(() => { - fireEvent.mouseEnter(getByTestId(popupTestId)); - }); - const wrappedPopupElement = await waitFor(() => queryByTestId(wrappedPopupTestId)); - expect(wrappedPopupElement).not.toBeNull(); - expect(wrappedPopupElement).toHaveTextContent(wrappedPopupText); + const disabledButton = getByText(disabledButtonText); + expect(queryByTestId(hoverPopupTestId)).toBeNull(); + act(() => { + fireEvent.mouseEnter(disabledButton); + }); + const hoverPopup = await waitFor(() => queryByTestId(hoverPopupTestId)); + expect(hoverPopup).not.toBeNull(); + expect(hoverPopup).toHaveTextContent('Hover Popup Content'); + act(() => { + fireEvent.mouseLeave(disabledButton); + }); - // mouseLeave 内层触发器 - act(() => { - fireEvent.mouseLeave(getByTestId(popupTestId)); + const disabledClickButton = getByText('Disabled Click Button'); + expect(queryByTestId(clickPopupTestId)).toBeNull(); + act(() => { + fireEvent.click(disabledClickButton); + }); + await waitFor(() => { + expect(queryByTestId(clickPopupTestId)).toBeNull(); + }); }); - // 等待内层浮层销毁 - await waitFor(() => { - expect(queryByTestId(wrappedPopupTestId)).toBeNull(); - }); + describe('嵌套组件测试', () => { + const popupTestId = 'popup-test-id'; + const wrappedPopupTestId = 'wrapped-popup-test-id'; + const triggerElement = '外层触发元素'; + const wrappedTriggerElement = '内层触发元素'; + const wrappedPopupText = '内层浮层内容'; - // mouseLeave 外层触发器 - act(() => { - fireEvent.mouseLeave(getByText(triggerElement)); - }); + const renderNestedPopup = (trigger: 'click' | 'hover') => + render( + {wrappedPopupText}
} + > +
{wrappedTriggerElement}
+ + } + > + {triggerElement} + , + ); + + test('trigger="click"', async () => { + const { getByText, queryByTestId } = renderNestedPopup('click'); + + // 初始状态,浮层不存在 + expect(queryByTestId(popupTestId)).toBeNull(); + expect(queryByTestId(wrappedPopupTestId)).toBeNull(); + + // click 外层触发器 + act(() => { + fireEvent.click(getByText(triggerElement)); + }); + const popupElement = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement).not.toBeNull(); + + // click 内层触发器 + act(() => { + fireEvent.click(getByText(wrappedTriggerElement)); + }); + const wrappedPopupElement = await waitFor(() => queryByTestId(wrappedPopupTestId)); + expect(wrappedPopupElement).not.toBeNull(); + expect(wrappedPopupElement).toHaveTextContent(wrappedPopupText); + + // mouseDown 内层内容不关闭 + act(() => { + fireEvent.mouseDown(queryByTestId(wrappedPopupTestId) as HTMLElement); + }); + await waitFor(() => { + expect(popupElement).not.toBeNull(); + expect(wrappedPopupElement).not.toBeNull(); + }); + }); - // 等待外层浮层销毁 - await waitFor(() => { - expect(queryByTestId(popupTestId)).toBeNull(); + test('trigger="hover"', async () => { + const { getByText, getByTestId, queryByTestId } = renderNestedPopup('hover'); + + // 初始状态,浮层不存在 + expect(queryByTestId(popupTestId)).toBeNull(); + expect(queryByTestId(wrappedPopupTestId)).toBeNull(); + + // hover 外层触发器 + act(() => { + fireEvent.mouseEnter(getByText(triggerElement)); + }); + const popupElement = await waitFor(() => queryByTestId(popupTestId)); + expect(popupElement).not.toBeNull(); + + // hover 内层触发器 + act(() => { + fireEvent.mouseEnter(getByTestId(popupTestId)); + }); + const wrappedPopupElement = await waitFor(() => queryByTestId(wrappedPopupTestId)); + expect(wrappedPopupElement).not.toBeNull(); + expect(wrappedPopupElement).toHaveTextContent(wrappedPopupText); + + // mouseLeave 内层触发器 + act(() => { + fireEvent.mouseLeave(getByTestId(popupTestId)); + }); + + // 等待内层浮层销毁 + await waitFor(() => { + expect(queryByTestId(wrappedPopupTestId)).toBeNull(); + }); + + // mouseLeave 外层触发器 + act(() => { + fireEvent.mouseLeave(getByText(triggerElement)); + }); + + // 等待外层浮层销毁 + await waitFor(() => { + expect(queryByTestId(popupTestId)).toBeNull(); + }); + }); }); }); }); diff --git a/packages/components/progress/__tests__/progress.test.tsx b/packages/components/progress/__tests__/progress.test.tsx index 1ed6826623..cb11430b59 100644 --- a/packages/components/progress/__tests__/progress.test.tsx +++ b/packages/components/progress/__tests__/progress.test.tsx @@ -37,122 +37,53 @@ Object.defineProperties(HTMLElement.prototype, { }, }); -describe('Progress 组件测试', () => { - test('render theme', async () => { - const testId = 'progress test theme'; - const themes: ThemeEnum[] = ['line', 'plump', 'circle']; - const { getByTestId } = render( -
- {themes?.map((theme, index) => ( - - ))} -
, - ); - - const instance = await waitFor(() => getByTestId(testId)); - - for (let index = 0; index < themes.length; index++) { - const theme = themes[index]; - expect(() => instance.querySelector(`.t-progress--${theme}`)).not.toBe(null); - } - }); - - test('render size', async () => { - // const testId = 'progress test size'; - const sizes: any[] = [ - { name: 'small', size: 72 }, - { name: 'medium', size: 112 }, - { name: 'large', size: 160 }, - { name: 240, size: 240 }, - ]; - - const createProgressSizeTest = async (sizeOjb: any) => { - // const sizeOjb = sizes[0]; - const testId = `progress test size ${sizeOjb.name}`; - const view = render( -
- -
, - ); - const instance = await waitFor(() => view.getByTestId(testId)); - expect(instance.querySelector('.t-progress--circle')).toHaveStyle(`width: ${sizeOjb.size}px`); - }; - - await createProgressSizeTest(sizes[0]); - await createProgressSizeTest(sizes[1]); - await createProgressSizeTest(sizes[2]); - await createProgressSizeTest(sizes[3]); - }); - - describe('plump theme label position', () => { - test('label should be inside when inner width is enough', async () => { - const testId = 'progress plump inside'; - const { getByTestId } = render( -
- -
, - ); - - const instance = await waitFor(() => getByTestId(testId)); - // inner(200) >= label(30) + 8,label 应在内部 - expect(instance.querySelector('.t-progress--over-ten')).toBeTruthy(); - }); - - test('label should be outside when inner width is not enough', async () => { - const testId = 'progress plump outside'; - // Mock 小的 inner 宽度 - Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { - get() { - if (this.classList.contains('t-progress__inner')) { - return 20; // 小于 label 宽度 - } - if (this.classList.contains('t-progress__info')) { - return 30; - } - return 0; - }, - configurable: true, - }); - +describe('Progress', () => { + describe('props', () => { + test('render theme', async () => { + const testId = 'progress test theme'; + const themes: ThemeEnum[] = ['line', 'plump', 'circle']; const { getByTestId } = render(
- + {themes?.map((theme, index) => ( + + ))}
, ); const instance = await waitFor(() => getByTestId(testId)); - // inner(20) < label(30) + 8,label 应在外部 - expect(instance.querySelector('.t-progress--under-ten')).toBeTruthy(); - - // 恢复原始 mock - Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { - get() { - if (this.classList.contains('t-progress__inner')) { - return 200; - } - if (this.classList.contains('t-progress__info')) { - return 30; - } - return 0; - }, - configurable: true, - }); + for (let index = 0; index < themes.length; index++) { + const theme = themes[index]; + expect(() => instance.querySelector(`.t-progress--${theme}`)).not.toBe(null); + } }); - test('label should be hidden when label is false', async () => { - const testId = 'progress plump no label'; - const { getByTestId } = render( -
- -
, - ); - - const instance = await waitFor(() => getByTestId(testId)); - // label 为 false 时不显示 - expect(instance.querySelector('.t-progress__info')).toBeFalsy(); + test('render size', async () => { + // const testId = 'progress test size'; + const sizes: any[] = [ + { name: 'small', size: 72 }, + { name: 'medium', size: 112 }, + { name: 'large', size: 160 }, + { name: 240, size: 240 }, + ]; + + const createProgressSizeTest = async (sizeOjb: any) => { + // const sizeOjb = sizes[0]; + const testId = `progress test size ${sizeOjb.name}`; + const view = render( +
+ +
, + ); + const instance = await waitFor(() => view.getByTestId(testId)); + expect(instance.querySelector('.t-progress--circle')).toHaveStyle(`width: ${sizeOjb.size}px`); + }; + + await createProgressSizeTest(sizes[0]); + await createProgressSizeTest(sizes[1]); + await createProgressSizeTest(sizes[2]); + await createProgressSizeTest(sizes[3]); }); - test('custom label should render correctly', async () => { const testId = 'progress plump custom label'; const customLabel = Custom; @@ -166,4 +97,76 @@ describe('Progress 组件测试', () => { expect(instance.querySelector('[data-testid="custom-label"]')).toBeTruthy(); }); }); + + describe('scenarios', () => { + describe('plump theme label position', () => { + test('label should be inside when inner width is enough', async () => { + const testId = 'progress plump inside'; + const { getByTestId } = render( +
+ +
, + ); + + const instance = await waitFor(() => getByTestId(testId)); + // inner(200) >= label(30) + 8,label 应在内部 + expect(instance.querySelector('.t-progress--over-ten')).toBeTruthy(); + }); + + test('label should be outside when inner width is not enough', async () => { + const testId = 'progress plump outside'; + // Mock 小的 inner 宽度 + Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { + get() { + if (this.classList.contains('t-progress__inner')) { + return 20; // 小于 label 宽度 + } + if (this.classList.contains('t-progress__info')) { + return 30; + } + return 0; + }, + configurable: true, + }); + + const { getByTestId } = render( +
+ +
, + ); + + const instance = await waitFor(() => getByTestId(testId)); + + // inner(20) < label(30) + 8,label 应在外部 + expect(instance.querySelector('.t-progress--under-ten')).toBeTruthy(); + + // 恢复原始 mock + Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { + get() { + if (this.classList.contains('t-progress__inner')) { + return 200; + } + if (this.classList.contains('t-progress__info')) { + return 30; + } + return 0; + }, + configurable: true, + }); + }); + + test('label should be hidden when label is false', async () => { + const testId = 'progress plump no label'; + const { getByTestId } = render( +
+ +
, + ); + + const instance = await waitFor(() => getByTestId(testId)); + // label 为 false 时不显示 + expect(instance.querySelector('.t-progress__info')).toBeFalsy(); + }); + }); + }); }); diff --git a/packages/components/qrcode/__tests__/qrcode.test.tsx b/packages/components/qrcode/__tests__/qrcode.test.tsx index e20fd6be2a..fa03f6a20c 100644 --- a/packages/components/qrcode/__tests__/qrcode.test.tsx +++ b/packages/components/qrcode/__tests__/qrcode.test.tsx @@ -3,103 +3,106 @@ import { render, waitFor } from '@test/utils'; import QRCode from '../QRCode'; -describe('qrcode 组件测试', () => { +describe('QRCode', () => { const qrcodeTestId = 'qrcode-test-id'; - test('canvas 二维码测试', async () => { - const { queryByTestId } = render( -
- -
, - ); + describe('props', () => { + test('svg 二维码测试', async () => { + const { queryByTestId } = render( +
+ +
, + ); - const canvasElement = await waitFor(() => queryByTestId(qrcodeTestId).getElementsByTagName('canvas')); - expect(canvasElement).not.toBeNull(); - }); - - test('svg 二维码测试', async () => { - const { queryByTestId } = render( -
- -
, - ); + const svgElement = await waitFor(() => queryByTestId(qrcodeTestId).getElementsByTagName('svg')); + expect(svgElement).not.toBeNull(); + }); - const svgElement = await waitFor(() => queryByTestId(qrcodeTestId).getElementsByTagName('svg')); - expect(svgElement).not.toBeNull(); - }); + test('二维码状态测试', async () => { + const { queryByTestId } = render( +
+ + console.log('refresh')} value={'https://tdesign.tencent.com/'} /> + +
, + ); - test('二维码尺寸测试', async () => { - const width = 200; - const { queryByTestId } = render( -
- -
, - ); + // 容器元素 + const conElement = await waitFor(() => queryByTestId(qrcodeTestId)); - // 容器元素 - const conElement = await waitFor(() => queryByTestId(qrcodeTestId).getElementsByClassName('qrcode-test')[0]); - // 获取计算后的样式 - const computedStyle = window.getComputedStyle(conElement); - expect(computedStyle.width).toBe(`${width}px`); - expect(computedStyle['background-color']).toBe(`red`); - }); + expect(conElement.querySelector('.t-loading')).not.toBeNull(); + expect(conElement.querySelector('.t-expired__text')).not.toBeNull(); + expect(conElement.querySelector('.t-expired__button')).not.toBeNull(); + expect(conElement.querySelector('.t-scanned')).not.toBeNull(); + }); + test('二维码自定义渲染', async () => { + const customStatusRender = (info) => { + switch (info.status) { + case 'expired': + return
点击刷新
; + case 'loading': + return
loading
; + case 'scanned': + return
已扫描
; + default: + return null; + } + }; - test('二维码状态测试', async () => { - const { queryByTestId } = render( -
- - console.log('refresh')} value={'https://tdesign.tencent.com/'} /> - -
, - ); + const { queryByTestId } = render( +
+ + console.log('refresh')} + value={'https://tdesign.tencent.com/'} + statusRender={customStatusRender} + /> + +
, + ); - // 容器元素 - const conElement = await waitFor(() => queryByTestId(qrcodeTestId)); + // 容器元素 + const conElement = await waitFor(() => queryByTestId(qrcodeTestId)); - expect(conElement.querySelector('.t-loading')).not.toBeNull(); - expect(conElement.querySelector('.t-expired__text')).not.toBeNull(); - expect(conElement.querySelector('.t-expired__button')).not.toBeNull(); - expect(conElement.querySelector('.t-scanned')).not.toBeNull(); + expect(conElement.querySelector('.loading')).not.toBeNull(); + expect(conElement.querySelector('.expired')).not.toBeNull(); + expect(conElement.querySelector('.scanned')).not.toBeNull(); + }); }); - test('二维码自定义渲染', async () => { - const customStatusRender = (info) => { - switch (info.status) { - case 'expired': - return
点击刷新
; - case 'loading': - return
loading
; - case 'scanned': - return
已扫描
; - default: - return null; - } - }; + describe('scenarios', () => { + test('canvas 二维码测试', async () => { + const { queryByTestId } = render( +
+ +
, + ); - const { queryByTestId } = render( -
- - console.log('refresh')} - value={'https://tdesign.tencent.com/'} - statusRender={customStatusRender} - /> - -
, - ); + const canvasElement = await waitFor(() => queryByTestId(qrcodeTestId).getElementsByTagName('canvas')); + expect(canvasElement).not.toBeNull(); + }); - // 容器元素 - const conElement = await waitFor(() => queryByTestId(qrcodeTestId)); + test('二维码尺寸测试', async () => { + const width = 200; + const { queryByTestId } = render( +
+ +
, + ); - expect(conElement.querySelector('.loading')).not.toBeNull(); - expect(conElement.querySelector('.expired')).not.toBeNull(); - expect(conElement.querySelector('.scanned')).not.toBeNull(); + // 容器元素 + const conElement = await waitFor(() => queryByTestId(qrcodeTestId).getElementsByClassName('qrcode-test')[0]); + // 获取计算后的样式 + const computedStyle = window.getComputedStyle(conElement); + expect(computedStyle.width).toBe(`${width}px`); + expect(computedStyle['background-color']).toBe(`red`); + }); }); }); diff --git a/packages/components/radio/__tests__/__snapshots__/vitest-radio.test.jsx.snap b/packages/components/radio/__tests__/__snapshots__/radio-group.test.tsx.snap similarity index 70% rename from packages/components/radio/__tests__/__snapshots__/vitest-radio.test.jsx.snap rename to packages/components/radio/__tests__/__snapshots__/radio-group.test.tsx.snap index 4a10f69bee..f62af79c8c 100644 --- a/packages/components/radio/__tests__/__snapshots__/vitest-radio.test.jsx.snap +++ b/packages/components/radio/__tests__/__snapshots__/radio-group.test.tsx.snap @@ -1,169 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Radio Component > props.checked is equal to true 1`] = ` -
- -
-`; - -exports[`Radio Component > props.checked works fine 1`] = ` -
- -
-`; - -exports[`Radio Component > props.checked works fine 2`] = ` -
- -
-`; - -exports[`Radio Component > props.children works fine 1`] = ` -
- -
-`; - -exports[`Radio Component > props.disabled works fine 1`] = ` -
- -
-`; - -exports[`Radio Component > props.disabled works fine 2`] = ` -
- -
-`; - -exports[`Radio Component > props.label works fine 1`] = ` -
- -
-`; - -exports[`RadioGroup Component > props.disabled is equal true 1`] = ` +exports[`RadioGroup > props > disabled is equal true 1`] = `
props.disabled is equal true 1`] = `
`; -exports[`RadioGroup Component > props.disabled is equal true 2`] = ` +exports[`RadioGroup > props > disabled is equal true with kids 1`] = `
props.disabled is equal true 2`] = `
`; -exports[`RadioGroup Component > props.name is equal to 'custom-radio-name' 1`] = ` +exports[`RadioGroup > props > name is equal to 'custom-radio-name' 1`] = `
props.name is equal to 'custom-radio-name' 1`] =
`; -exports[`RadioGroup Component > props.name is equal to 'custom-radio-name' 2`] = ` +exports[`RadioGroup > props > name is equal to 'custom-radio-name' with kids 1`] = `
props > checked 1`] = ` +
+ +
+`; + +exports[`Radio > props > checked 2`] = ` +
+ +
+`; + +exports[`Radio > props > checked is equal to true 1`] = ` +
+ +
+`; + +exports[`Radio > props > children 1`] = ` +
+ +
+`; + +exports[`Radio > props > disabled 1`] = ` +
+ +
+`; + +exports[`Radio > props > disabled 2`] = ` +
+ +
+`; + +exports[`Radio > props > label 1`] = ` +
+ +
+`; diff --git a/packages/components/radio/__tests__/mount.jsx b/packages/components/radio/__tests__/mount.jsx deleted file mode 100644 index 4190bce676..0000000000 --- a/packages/components/radio/__tests__/mount.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { render } from '@test/utils'; -import { Radio } from '..'; - -// options 写法 -export function getRadioGroupDefaultMount(RadioGroup, props, events) { - const options = [ - { label: 'Radio1', value: 1 }, - { label: 'Radio2', value: '2', allowUncheck: true }, - { label: Radio3, value: 3 }, - { label: 'Radio4', value: 4, disabled: true }, - ]; - return render(); -} - -// 子组件写法 -export function getRadioGroupKidsMount(RadioGroup, props, events) { - return render( - - Radio1 - - Radio2 - - Radio3 - - Radio4 - - , - ); -} diff --git a/packages/components/radio/__tests__/radio-group.test.tsx b/packages/components/radio/__tests__/radio-group.test.tsx new file mode 100644 index 0000000000..24a37af519 --- /dev/null +++ b/packages/components/radio/__tests__/radio-group.test.tsx @@ -0,0 +1,426 @@ +import React, { useEffect, useState } from 'react'; +import { act, fireEvent, mockElementSizes, mockIntersectionObserver, render, vi } from '@test/utils'; + +import Radio, { RadioGroup } from '..'; + +function getRadioGroupDefaultMount(props: Record = {}, events: Record = {}) { + const options = [ + { label: 'Radio1', value: 1 }, + { label: 'Radio2', value: '2', allowUncheck: true }, + { label: Radio3, value: 3 }, + { label: 'Radio4', value: 4, disabled: true }, + ]; + return render(); +} + +function getRadioGroupKidsMount(props: Record = {}, events: Record = {}) { + return render( + + Radio1 + + Radio2 + + Radio3 + + Radio4 + + , + ); +} + +describe('RadioGroup', () => { + describe('props', () => { + test('allowUncheck', () => { + const onChangeFn = vi.fn(); + const { container } = getRadioGroupDefaultMount({ value: 1, allowUncheck: true }, { onChange: onChangeFn }); + fireEvent.click(container.querySelector('.t-radio')); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(undefined); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + }); + + test('allowUncheck with kids', () => { + const onChangeFn = vi.fn(); + const { container } = getRadioGroupKidsMount({ value: 1, allowUncheck: true }, { onChange: onChangeFn }); + fireEvent.click(container.querySelector('.t-radio')); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(undefined); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + }); + + test('disabled is equal true', () => { + const { container } = getRadioGroupDefaultMount({ disabled: true }); + expect(container.querySelectorAll('.t-radio.t-is-disabled').length).toBe(4); + expect(container).toMatchSnapshot(); + }); + + test('disabled is equal true with kids', () => { + const { container } = getRadioGroupKidsMount({ disabled: true }); + expect(container.querySelectorAll('.t-radio.t-is-disabled').length).toBe(4); + expect(container).toMatchSnapshot(); + }); + + test('disabled radio can not trigger change', () => { + const onChangeFn = vi.fn(); + const { container } = getRadioGroupDefaultMount({ disabled: true }, { onChange: onChangeFn }); + fireEvent.click(container.querySelector('.t-radio')); + expect(onChangeFn).not.toHaveBeenCalled(); + }); + + test('disabled radio can not trigger change with kids', () => { + const onChangeFn = vi.fn(); + const { container } = getRadioGroupKidsMount({ disabled: true }, { onChange: onChangeFn }); + fireEvent.click(container.querySelector('.t-radio')); + expect(onChangeFn).not.toHaveBeenCalled(); + }); + + test(`name is equal to 'custom-radio-name'`, () => { + const { container } = getRadioGroupDefaultMount({ + name: 'custom-radio-name', + }); + const domWrapper = container.querySelector('input'); + expect(domWrapper.getAttribute('name')).toBe('custom-radio-name'); + expect(container).toMatchSnapshot(); + }); + + test(`name is equal to 'custom-radio-name' with kids`, () => { + const { container } = getRadioGroupKidsMount({ + name: 'custom-radio-name', + }); + const domWrapper = container.querySelector('input'); + expect(domWrapper.getAttribute('name')).toBe('custom-radio-name'); + expect(container).toMatchSnapshot(); + }); + + test('options works fine. `{".t-radio":4}` should exist', () => { + const { container } = getRadioGroupDefaultMount(); + expect(container.querySelectorAll('.t-radio').length).toBe(4); + }); + + test('options works fine. `".custom-node"` should exist', () => { + const { container } = getRadioGroupDefaultMount(); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + + test('options works fine. `{".t-radio.t-is-disabled":1}` should exist', () => { + const { container } = getRadioGroupDefaultMount(); + expect(container.querySelectorAll('.t-radio.t-is-disabled').length).toBe(1); + }); + + test(`value is equal to '2'`, () => { + const { container } = getRadioGroupDefaultMount({ value: '2' }); + const domWrapper = container.querySelector('.t-radio.t-is-checked input'); + expect(domWrapper.value).toBe('2'); + }); + + test(`value is equal to '2' with kids`, () => { + const { container } = getRadioGroupKidsMount({ value: '2' }); + const domWrapper = container.querySelector('.t-radio.t-is-checked input'); + expect(domWrapper.value).toBe('2'); + }); + + const variantClassNameList = ['t-radio-group__outline', 't-radio-group--primary-filled', 't-radio-group--filled']; + (['outline', 'primary-filled', 'default-filled'] as const).forEach((item, index) => { + test(`variant is equal to ${item}`, () => { + const { container } = render(); + expect(container.firstChild).toHaveClass(variantClassNameList[index]); + }); + }); + + test('value', () => { + const { container } = render( + + 广州 + + 深圳 + + , + ); + expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); + }); + + test('variant', () => { + const { container } = render( + , + ); + expect(container.firstChild.firstChild).toHaveClass('t-radio'); + }); + + test('value is string', () => { + const { container } = render(); + expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); + }); + + test('value is number', () => { + const { container } = render(); + expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); + }); + + test('defaultValue', () => { + const { container } = render( + + 广州 + + 深圳 + + , + ); + expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); + }); + + test('theme radio', () => { + const { container } = render( + , + ); + expect(container.firstChild.firstChild).toHaveClass('t-radio'); + }); + + test('theme button', () => { + const { container } = render( + , + ); + expect(container.firstChild.firstChild).toHaveClass('t-radio-button'); + }); + }); + + describe('events', () => { + test('onChange', () => { + const fn = vi.fn(); + const { container } = render( + + 广州 + + 深圳 + + , + ); + fireEvent.click(container.firstChild.firstChild); + expect(fn).toHaveBeenCalledTimes(1); + }); + }); + + describe('scenarios', () => { + test('options', () => { + const { container } = render( + , + ); + fireEvent.click(container.firstChild.lastChild); + expect(container.firstChild.lastChild).toHaveClass('t-is-checked'); + }); + + describe('primary-filled bg-block', () => { + const MOCK_SIZES = [ + { selector: '.t-radio-group', width: 300, height: 40, left: 0, top: 0 }, + { + selector: '.t-radio-button:nth-child(1)', + width: 60, + height: 32, + left: 4, + top: 4, + }, + { + selector: '.t-radio-button:nth-child(2)', + width: 60, + height: 32, + left: 68, + top: 4, + }, + { + selector: '.t-radio-button:nth-child(3)', + width: 60, + height: 32, + left: 132, + top: 4, + }, + ]; + + // 根据选项索引获取对应按钮的尺寸数据 + const getButtonSize = (optionIndex: number) => { + const buttonSelector = `.t-radio-button:nth-child(${optionIndex + 1})`; + return MOCK_SIZES.find((size) => size.selector === buttonSelector); + }; + + const mockRadioGroupSizes = () => mockElementSizes(MOCK_SIZES); + + test('bg-block not rendered when value changes to empty', async () => { + const cleanup = mockRadioGroupSizes(); + + const TestComponent = () => { + const [value, setValue] = React.useState('1'); + return ( + <> + + + + ); + }; + + const { container, getByTestId } = render(); + + const bgBlock = container.querySelector('.t-radio-group__bg-block') as HTMLElement; + expect(bgBlock).toBeInTheDocument(); + + const expectedSize = getButtonSize(1); + expect(bgBlock.style.left).toBe(`${expectedSize.left}px`); + expect(bgBlock.style.top).toBe(`${expectedSize.top}px`); + expect(bgBlock.style.width).toBe(`${expectedSize.width}px`); + expect(bgBlock.style.height).toBe(`${expectedSize.height}px`); + + await act(async () => { + fireEvent.click(getByTestId('clear-btn')); + }); + + expect(container.querySelector('.t-radio-group__bg-block')).not.toBeInTheDocument(); + + cleanup(); + }); + + test('bg-block updates when options change dynamically', async () => { + const cleanup = mockRadioGroupSizes(); + + const TestComponent = () => { + const [value, setValue] = useState('1'); + const [options, setOptions] = useState(['0']); + + useEffect(() => { + const timer = setTimeout(() => { + setOptions(['0', '1', '2']); + }, 100); + return () => clearTimeout(timer); + }, []); + + return ( + + variant="primary-filled" + theme="button" + value={value} + options={options} + onChange={(val) => setValue(val)} + /> + ); + }; + + const { container } = render(); + + expect(container.querySelector('.t-radio-group__bg-block')).not.toBeInTheDocument(); + + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 150)); + }); + + const bgBlock = container.querySelector('.t-radio-group__bg-block') as HTMLElement; + expect(bgBlock).toBeInTheDocument(); + + const expectedSize = getButtonSize(1); + expect(bgBlock.style.left).toBe(`${expectedSize.left}px`); + expect(bgBlock.style.top).toBe(`${expectedSize.top}px`); + expect(bgBlock.style.width).toBe(`${expectedSize.width}px`); + expect(bgBlock.style.height).toBe(`${expectedSize.height}px`); + + cleanup(); + }); + + test('bg-block updates when component becomes visible from hidden', async () => { + const cleanup = mockRadioGroupSizes(); + + let observerCallback: Function | null = null; + mockIntersectionObserver( + {}, + { + observe: (element: Element, callback: Function) => { + observerCallback = callback; + if (!element.closest('[hidden]')) { + callback([{ isIntersecting: true, target: element }]); + } + }, + }, + ); + + const TestComponent = () => { + const [activeNav, setActiveNav] = useState('nav1'); + const [value, setValue] = useState('1'); + + const navList = [ + { + id: 'nav1', + component:
First Nav Content
, + }, + { + id: 'nav2', + component: ( + setValue(val)} + /> + ), + }, + ]; + + return ( + <> + {navList.map((nav) => ( +
+ + +
+ ))} + + ); + }; + + const { container, getByTestId } = render(); + + await act(async () => { + fireEvent.click(getByTestId('nav-nav2')); + }); + + if (observerCallback) { + await act(async () => { + const radioGroup = container.querySelector('.t-radio-group'); + observerCallback([{ isIntersecting: true, target: radioGroup }]); + }); + } + + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + const bgBlock = container.querySelector('.t-radio-group__bg-block') as HTMLElement; + expect(bgBlock).toBeInTheDocument(); + + const expectedSize = getButtonSize(1); + expect(bgBlock.style.left).toBe(`${expectedSize.left}px`); + expect(bgBlock.style.top).toBe(`${expectedSize.top}px`); + expect(bgBlock.style.width).toBe(`${expectedSize.width}px`); + expect(bgBlock.style.height).toBe(`${expectedSize.height}px`); + + cleanup(); + }); + }); + }); +}); diff --git a/packages/components/radio/__tests__/radio.test.tsx b/packages/components/radio/__tests__/radio.test.tsx index c8e17793a0..c984251131 100644 --- a/packages/components/radio/__tests__/radio.test.tsx +++ b/packages/components/radio/__tests__/radio.test.tsx @@ -1,319 +1,140 @@ -import React, { useEffect, useState } from 'react'; -import { act, fireEvent, mockElementSizes, mockIntersectionObserver, render, vi } from '@test/utils'; - -import Radio from '../Radio'; - -describe('Radio [basic api]', () => { - test('checked & children', () => { - const { container, queryByText } = render(单选框); - expect(container.firstChild).toHaveClass('t-radio', 't-is-checked'); - expect(queryByText('单选框')).toBeInTheDocument(); - }); - - test('defaultChecked', () => { - const { container } = render(); - expect(container.firstChild).toHaveClass('t-radio', 't-is-checked'); - }); - - test('allowUncheck', () => { - const { container } = render(); - fireEvent.click(container.firstChild); - expect(container.firstChild).toHaveClass('t-radio', 't-is-checked'); - }); - - test('disable', () => { - const fn = vi.fn(); - const { container } = render(); - expect(container.firstChild).toHaveClass('t-is-disabled', 't-radio'); - fireEvent.click(container.firstChild); - expect(fn).toHaveBeenCalledTimes(0); - }); - - test('label', () => { - const { queryByText } = render(); - expect(queryByText('选中项')).toBeInTheDocument(); - }); - - test('onChange', () => { - const fn = vi.fn(); - const { container } = render(); - fireEvent.click(container.firstElementChild); - expect(fn).toHaveBeenCalledTimes(0); - }); -}); - -describe('RadioGroup [basic api]', () => { - test('value', () => { - const { container } = render( - - 广州 - - 深圳 - - , - ); - expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); - }); - - test('onChange', () => { - const fn = vi.fn(); - const { container } = render( - - 广州 - - 深圳 - - , - ); - fireEvent.click(container.firstChild.firstChild); - expect(fn).toHaveBeenCalledTimes(1); - }); - - test('options', () => { - const { container } = render( - , - ); - fireEvent.click(container.firstChild.lastChild); - expect(container.firstChild.lastChild).toHaveClass('t-is-checked'); - }); - - test('variant', () => { - const { container } = render( - , - ); - expect(container.firstChild.firstChild).toHaveClass('t-radio'); - }); - - test('value is string', () => { - const { container } = render(); - expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); - }); - - test('value is number', () => { - const { container } = render(); - expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); - }); - - test('defaultValue', () => { - const { container } = render( - - 广州 - - 深圳 - - , - ); - expect(container.firstChild.firstChild).toHaveClass('t-is-checked'); - }); - - test('theme radio', () => { - const { container } = render( - , - ); - expect(container.firstChild.firstChild).toHaveClass('t-radio'); - }); - - test('theme button', () => { - const { container } = render( - , - ); - expect(container.firstChild.firstChild).toHaveClass('t-radio-button'); - }); -}); - -describe('RadioGroup [primary-filled bg-block]', () => { - const MOCK_SIZES = [ - { selector: '.t-radio-group', width: 300, height: 40, left: 0, top: 0 }, - { selector: '.t-radio-button:nth-child(1)', width: 60, height: 32, left: 4, top: 4 }, - { selector: '.t-radio-button:nth-child(2)', width: 60, height: 32, left: 68, top: 4 }, - { selector: '.t-radio-button:nth-child(3)', width: 60, height: 32, left: 132, top: 4 }, - ]; +import React from 'react'; +import { fireEvent, render, vi } from '@test/utils'; + +import { Radio } from '..'; + +describe('Radio', () => { + describe('props', () => { + test('allowUncheck', () => { + const onChangeFn = vi.fn(); + const { container } = render(); + fireEvent.click(container.firstChild); + expect(onChangeFn).toHaveBeenCalled(); + expect(onChangeFn.mock.calls[0][0]).toBe(false); + expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); + }); - // 根据选项索引获取对应按钮的尺寸数据 - const getButtonSize = (optionIndex: number) => { - const buttonSelector = `.t-radio-button:nth-child(${optionIndex + 1})`; - return MOCK_SIZES.find((size) => size.selector === buttonSelector); - }; + test('checked', () => { + // checked default value is false + const { container: container1 } = render(); + expect(container1.querySelector(`.${'t-is-checked'}`)).toBeFalsy(); + // checked = true + const { container: container2 } = render(); + expect(container2.firstChild).toHaveClass('t-is-checked'); + expect(container2).toMatchSnapshot(); + // checked = false + const { container: container3 } = render(); + expect(container3.querySelector(`.${'t-is-checked'}`)).toBeFalsy(); + expect(container3).toMatchSnapshot(); + }); - const mockRadioGroupSizes = () => mockElementSizes(MOCK_SIZES); + test('checked is equal to true', () => { + const { container } = render(); + const domWrapper = container.querySelector('input'); + expect(domWrapper.checked).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); - test('bg-block not rendered when value changes to empty', async () => { - const cleanup = mockRadioGroupSizes(); + test('disabled', () => { + // disabled default value is undefined + const { container: container1 } = render(Text); + expect(container1.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); + // disabled = true + const { container: container2 } = render(Text); + expect(container2.firstChild).toHaveClass('t-is-disabled'); + expect(container2).toMatchSnapshot(); + // disabled = false + const { container: container3 } = render(Text); + expect(container3.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); + expect(container3).toMatchSnapshot(); + }); - const TestComponent = () => { - const [value, setValue] = React.useState('1'); - return ( - <> - - - + test('disabled can not trigger change', () => { + const onChangeFn = vi.fn(); + const { container } = render( + + Text + , ); - }; - - const { container, getByTestId } = render(); - - const bgBlock = container.querySelector('.t-radio-group__bg-block') as HTMLElement; - expect(bgBlock).toBeInTheDocument(); - - const expectedSize = getButtonSize(1); - expect(bgBlock.style.left).toBe(`${expectedSize.left}px`); - expect(bgBlock.style.top).toBe(`${expectedSize.top}px`); - expect(bgBlock.style.width).toBe(`${expectedSize.width}px`); - expect(bgBlock.style.height).toBe(`${expectedSize.height}px`); - - await act(async () => { - fireEvent.click(getByTestId('clear-btn')); + fireEvent.click(container.firstChild); + expect(onChangeFn).not.toHaveBeenCalled(); }); - expect(container.querySelector('.t-radio-group__bg-block')).not.toBeInTheDocument(); - - cleanup(); - }); - - test('bg-block updates when options change dynamically', async () => { - const cleanup = mockRadioGroupSizes(); + test('name', () => { + const wrapper = render(); + const container = wrapper.container.querySelector('input'); + expect(container.getAttribute('name')).toBe('radio-gender-name'); + }); - const TestComponent = () => { - const [value, setValue] = useState('1'); - const [options, setOptions] = useState(['0']); + test(`value is equal to 'radio-value'`, () => { + const { container } = render(); + const domWrapper = container.querySelector('input'); + expect(domWrapper.value).toBe('radio-value'); + }); - useEffect(() => { - const timer = setTimeout(() => { - setOptions(['0', '1', '2']); - }, 100); - return () => clearTimeout(timer); - }, []); + test('defaultChecked', () => { + const { container } = render(); + expect(container.firstChild).toHaveClass('t-radio', 't-is-checked'); + }); - return ( - - variant="primary-filled" - theme="button" - value={value} - options={options} - onChange={(val) => setValue(val)} - /> + test('allowUncheck stays checked', () => { + const { container } = render(); + fireEvent.click(container.firstChild); + expect(container.firstChild).toHaveClass('t-radio', 't-is-checked'); + }); + test('children', () => { + const { container } = render( + + TNode + , ); - }; - - const { container } = render(); - - expect(container.querySelector('.t-radio-group__bg-block')).not.toBeInTheDocument(); - - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 150)); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container).toMatchSnapshot(); }); - const bgBlock = container.querySelector('.t-radio-group__bg-block') as HTMLElement; - expect(bgBlock).toBeInTheDocument(); - - const expectedSize = getButtonSize(1); - expect(bgBlock.style.left).toBe(`${expectedSize.left}px`); - expect(bgBlock.style.top).toBe(`${expectedSize.top}px`); - expect(bgBlock.style.width).toBe(`${expectedSize.width}px`); - expect(bgBlock.style.height).toBe(`${expectedSize.height}px`); + test('label', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + expect(container.querySelector('.t-radio__label')).toBeTruthy(); + expect(container).toMatchSnapshot(); + }); - cleanup(); + test('label text', () => { + const { queryByText } = render(); + expect(queryByText('选中项')).toBeInTheDocument(); + }); }); - test('bg-block updates when component becomes visible from hidden', async () => { - const cleanup = mockRadioGroupSizes(); - - let observerCallback: Function | null = null; - mockIntersectionObserver( - {}, - { - observe: (element: Element, callback: Function) => { - observerCallback = callback; - if (!element.closest('[hidden]')) { - callback([{ isIntersecting: true, target: element }]); - } - }, - }, - ); - - const TestComponent = () => { - const [activeNav, setActiveNav] = useState('nav1'); - const [value, setValue] = useState('1'); - - const navList = [ - { - id: 'nav1', - component:
First Nav Content
, - }, - { - id: 'nav2', - component: ( - setValue(val)} - /> - ), - }, - ]; - - return ( - <> - {navList.map((nav) => ( -
- - -
- ))} - - ); - }; - - const { container, getByTestId } = render(); - - await act(async () => { - fireEvent.click(getByTestId('nav-nav2')); + describe('events', () => { + test('click', () => { + const onClickFn = vi.fn(); + const { container } = render(); + fireEvent.click(container.firstChild); + expect(onClickFn).toHaveBeenCalled(); + expect(onClickFn.mock.calls[0][0].e.stopPropagation).toBeTruthy(); }); - if (observerCallback) { - await act(async () => { - const radioGroup = container.querySelector('.t-radio-group'); - observerCallback([{ isIntersecting: true, target: radioGroup }]); - }); - } - - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 100)); + test('onChange', () => { + const fn = vi.fn(); + const { container } = render(); + fireEvent.click(container.firstElementChild); + expect(fn).toHaveBeenCalledTimes(0); }); + }); - const bgBlock = container.querySelector('.t-radio-group__bg-block') as HTMLElement; - expect(bgBlock).toBeInTheDocument(); - - const expectedSize = getButtonSize(1); - expect(bgBlock.style.left).toBe(`${expectedSize.left}px`); - expect(bgBlock.style.top).toBe(`${expectedSize.top}px`); - expect(bgBlock.style.width).toBe(`${expectedSize.width}px`); - expect(bgBlock.style.height).toBe(`${expectedSize.height}px`); + describe('scenarios', () => { + test('checked & children', () => { + const { container, queryByText } = render(单选框); + expect(container.firstChild).toHaveClass('t-radio', 't-is-checked'); + expect(queryByText('单选框')).toBeInTheDocument(); + }); - cleanup(); + test('disable', () => { + const fn = vi.fn(); + const { container } = render(); + expect(container.firstChild).toHaveClass('t-is-disabled', 't-radio'); + fireEvent.click(container.firstChild); + expect(fn).toHaveBeenCalledTimes(0); + }); }); }); diff --git a/packages/components/radio/__tests__/vitest-radio.test.jsx b/packages/components/radio/__tests__/vitest-radio.test.jsx deleted file mode 100644 index 6fe2edf469..0000000000 --- a/packages/components/radio/__tests__/vitest-radio.test.jsx +++ /dev/null @@ -1,207 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * 该文件由脚本自动生成,如需修改请联系 PMC - * This file generated by scripts of tdesign-api. `npm run api:docs Radio React(PC) vitest,finalProject` - * If you need to modify this file, contact PMC first please. - */ -import React from 'react'; -import { fireEvent, vi, render } from '@test/utils'; -import { Radio, RadioGroup } from '..'; -import { getRadioGroupKidsMount, getRadioGroupDefaultMount } from './mount'; - -describe('Radio Component', () => { - it('props.allowUncheck works fine', () => { - const onChangeFn = vi.fn(); - const { container } = render(); - fireEvent.click(container.firstChild); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(false); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - }); - - it('props.checked works fine', () => { - // checked default value is false - const { container: container1 } = render(); - expect(container1.querySelector(`.${'t-is-checked'}`)).toBeFalsy(); - // checked = true - const { container: container2 } = render(); - expect(container2.firstChild).toHaveClass('t-is-checked'); - expect(container2).toMatchSnapshot(); - // checked = false - const { container: container3 } = render(); - expect(container3.querySelector(`.${'t-is-checked'}`)).toBeFalsy(); - expect(container3).toMatchSnapshot(); - }); - - it(`props.checked is equal to true`, () => { - const { container } = render(); - const domWrapper = container.querySelector('input'); - expect(domWrapper.checked).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.children works fine', () => { - const { container } = render( - - TNode - , - ); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.disabled works fine', () => { - // disabled default value is undefined - const { container: container1 } = render(Text); - expect(container1.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - // disabled = true - const { container: container2 } = render(Text); - expect(container2.firstChild).toHaveClass('t-is-disabled'); - expect(container2).toMatchSnapshot(); - // disabled = false - const { container: container3 } = render(Text); - expect(container3.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - expect(container3).toMatchSnapshot(); - }); - - it('props.disabled works fine', () => { - const onChangeFn = vi.fn(); - const { container } = render( - - Text - , - ); - fireEvent.click(container.firstChild); - expect(onChangeFn).not.toHaveBeenCalled(); - }); - - it('props.label works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - expect(container.querySelector('.t-radio__label')).toBeTruthy(); - expect(container).toMatchSnapshot(); - }); - - it('props.name works fine', () => { - const wrapper = render(); - const container = wrapper.container.querySelector('input'); - expect(container.getAttribute('name')).toBe('radio-gender-name'); - }); - - it(`props.value is equal to 'radio-value'`, () => { - const { container } = render(); - const domWrapper = container.querySelector('input'); - expect(domWrapper.value).toBe('radio-value'); - }); - - it('events.click works fine', () => { - const onClickFn = vi.fn(); - const { container } = render(); - fireEvent.click(container.firstChild); - expect(onClickFn).toHaveBeenCalled(); - expect(onClickFn.mock.calls[0][0].e.stopPropagation).toBeTruthy(); - }); -}); - -describe('RadioGroup Component', () => { - it('props.allowUncheck works fine', () => { - const onChangeFn = vi.fn(); - const { container } = getRadioGroupDefaultMount( - RadioGroup, - { value: 1, allowUncheck: true }, - { onChange: onChangeFn }, - ); - fireEvent.click(container.querySelector('.t-radio')); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(undefined); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - }); - - it('props.allowUncheck works fine', () => { - const onChangeFn = vi.fn(); - const { container } = getRadioGroupKidsMount( - RadioGroup, - { value: 1, allowUncheck: true }, - { onChange: onChangeFn }, - ); - fireEvent.click(container.querySelector('.t-radio')); - expect(onChangeFn).toHaveBeenCalled(); - expect(onChangeFn.mock.calls[0][0]).toBe(undefined); - expect(onChangeFn.mock.calls[0][1].e.type).toBe('click'); - }); - - it('props.disabled is equal true', () => { - const { container } = getRadioGroupDefaultMount(RadioGroup, { disabled: true }); - expect(container.querySelectorAll('.t-radio.t-is-disabled').length).toBe(4); - expect(container).toMatchSnapshot(); - }); - - it('props.disabled is equal true', () => { - const { container } = getRadioGroupKidsMount(RadioGroup, { disabled: true }); - expect(container.querySelectorAll('.t-radio.t-is-disabled').length).toBe(4); - expect(container).toMatchSnapshot(); - }); - - it('props.disabled: disabled radio can not trigger change', () => { - const onChangeFn = vi.fn(); - const { container } = getRadioGroupDefaultMount(RadioGroup, { disabled: true }, { onChange: onChangeFn }); - fireEvent.click(container.querySelector('.t-radio')); - expect(onChangeFn).not.toHaveBeenCalled(); - }); - - it('props.disabled: disabled radio can not trigger change', () => { - const onChangeFn = vi.fn(); - const { container } = getRadioGroupKidsMount(RadioGroup, { disabled: true }, { onChange: onChangeFn }); - fireEvent.click(container.querySelector('.t-radio')); - expect(onChangeFn).not.toHaveBeenCalled(); - }); - - it(`props.name is equal to 'custom-radio-name'`, () => { - const { container } = getRadioGroupDefaultMount(RadioGroup, { name: 'custom-radio-name' }); - const domWrapper = container.querySelector('input'); - expect(domWrapper.getAttribute('name')).toBe('custom-radio-name'); - expect(container).toMatchSnapshot(); - }); - - it(`props.name is equal to 'custom-radio-name'`, () => { - const { container } = getRadioGroupKidsMount(RadioGroup, { name: 'custom-radio-name' }); - const domWrapper = container.querySelector('input'); - expect(domWrapper.getAttribute('name')).toBe('custom-radio-name'); - expect(container).toMatchSnapshot(); - }); - - it('props.options works fine. `{".t-radio":4}` should exist', () => { - const { container } = getRadioGroupDefaultMount(RadioGroup); - expect(container.querySelectorAll('.t-radio').length).toBe(4); - }); - - it('props.options works fine. `".custom-node"` should exist', () => { - const { container } = getRadioGroupDefaultMount(RadioGroup); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - it('props.options works fine. `{".t-radio.t-is-disabled":1}` should exist', () => { - const { container } = getRadioGroupDefaultMount(RadioGroup); - expect(container.querySelectorAll('.t-radio.t-is-disabled').length).toBe(1); - }); - - it(`props.value is equal to '2'`, () => { - const { container } = getRadioGroupDefaultMount(RadioGroup, { value: '2' }); - const domWrapper = container.querySelector('.t-radio.t-is-checked input'); - expect(domWrapper.value).toBe('2'); - }); - - it(`props.value is equal to '2'`, () => { - const { container } = getRadioGroupKidsMount(RadioGroup, { value: '2' }); - const domWrapper = container.querySelector('.t-radio.t-is-checked input'); - expect(domWrapper.value).toBe('2'); - }); - - const variantClassNameList = ['t-radio-group__outline', 't-radio-group--primary-filled', 't-radio-group--filled']; - ['outline', 'primary-filled', 'default-filled'].forEach((item, index) => { - it(`props.variant is equal to ${item}`, () => { - const { container } = render(); - expect(container.firstChild).toHaveClass(variantClassNameList[index]); - }); - }); -}); diff --git a/packages/components/rate/__tests__/rate.test.tsx b/packages/components/rate/__tests__/rate.test.tsx index 589a105795..fadd582b3b 100644 --- a/packages/components/rate/__tests__/rate.test.tsx +++ b/packages/components/rate/__tests__/rate.test.tsx @@ -3,42 +3,50 @@ import { fireEvent, render, vi } from '@test/utils'; import Rate from '../Rate'; -describe('Rate 组件测试', () => { - // 测试渲染 - test('create', async () => { - const { container } = render(); - expect(container.children[0].classList.contains('t-rate')).toBeTruthy(); - expect(document.querySelectorAll('.t-rate__item')).toHaveLength(5); +describe('Rate', () => { + describe('props', () => { + test('count', async () => { + render(); + expect(document.querySelectorAll('.t-rate__item')).toHaveLength(10); + }); + test('icon', () => { + const { container } = render(TNode} />); + expect(container.children[0].classList.contains('t-rate')).toBeTruthy(); + expect(document.querySelectorAll('.custom-node')).toHaveLength(10); + }); }); - // 点击测试 - test('onChange', async () => { - const clickFn = vi.fn(); - render(); - fireEvent.click(document.querySelector('.t-rate__item')); - expect(clickFn).toHaveBeenCalledTimes(1); - expect(clickFn).toHaveBeenCalledTimes(1); + describe('events', () => { + // 点击测试 + test('onChange', async () => { + const clickFn = vi.fn(); + render(); + fireEvent.click(document.querySelector('.t-rate__item')); + expect(clickFn).toHaveBeenCalledTimes(1); + expect(clickFn).toHaveBeenCalledTimes(1); + }); }); - // mouse 事件测试 - test('mouse', async () => { - render(); - expect(document.querySelectorAll('.t-rate__item--full')).toHaveLength(3); + describe('scenarios', () => { + // 测试渲染 + test('create', async () => { + const { container } = render(); + expect(container.children[0].classList.contains('t-rate')).toBeTruthy(); + expect(document.querySelectorAll('.t-rate__item')).toHaveLength(5); + }); - fireEvent.mouseMove(document.querySelector('.t-rate__item')); - expect(document.querySelectorAll('.t-rate__item--full')).toHaveLength(1); + // mouse 事件测试 + test('mouse', async () => { + render(); + expect(document.querySelectorAll('.t-rate__item--full')).toHaveLength(3); - fireEvent.mouseLeave(document.querySelector('.t-rate__item')); - expect(document.querySelectorAll('.t-rate__item--full')).toHaveLength(3); - }); + fireEvent.mouseMove(document.querySelector('.t-rate__item')); + expect(document.querySelectorAll('.t-rate__item--full')).toHaveLength(1); - // 数量测试 - test('count', async () => { - render(); - expect(document.querySelectorAll('.t-rate__item')).toHaveLength(10); - }); + fireEvent.mouseLeave(document.querySelector('.t-rate__item')); + expect(document.querySelectorAll('.t-rate__item--full')).toHaveLength(3); + }); - describe('props', () => { test('disable', async () => { const clickFn = vi.fn(); render(); @@ -46,12 +54,6 @@ describe('Rate 组件测试', () => { expect(clickFn).toHaveBeenCalledTimes(0); }); - test('icon', () => { - const { container } = render(TNode} />); - expect(container.children[0].classList.contains('t-rate')).toBeTruthy(); - expect(document.querySelectorAll('.custom-node')).toHaveLength(10); - }); - test('allowHalf', () => { render(); // 默认展示 diff --git a/packages/components/select-input/__tests__/mount.jsx b/packages/components/select-input/__tests__/mount.jsx deleted file mode 100644 index e26b14b3ed..0000000000 --- a/packages/components/select-input/__tests__/mount.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { render } from '@test/utils'; - -const OPTIONS = [ - { label: 'tdesign-vue', value: 1 }, - { label: 'tdesign-react', value: 2 }, - { label: 'tdesign-miniprogram', value: 3 }, - { label: 'tdesign-angular', value: '4' }, - { label: 'tdesign-mobile-vue', value: '5' }, - { label: 'tdesign-mobile-react', value: '6' }, -]; - -function Panel() { - return ( -
    - {OPTIONS.map(item => ( -
  • - - {item.label} -
  • - ))} -
- ); -} - -const value = [ - { label: 'tdesign-vue', value: 1 }, - { label: 'tdesign-react', value: 2 }, - { label: 'tdesign-miniprogram', value: 3 }, - { label: 'tdesign-mobile-vue', value: 4 }, - { label: 'tdesign-react-vue', value: 5 }, -]; - -// single select -export function getSelectInputMultipleMount(SelectInput, props, events) { - return render( - - )} - {...props} - {...events} - > - ); -} diff --git a/packages/components/select-input/__tests__/select-input.test.tsx b/packages/components/select-input/__tests__/select-input.test.tsx new file mode 100644 index 0000000000..aac713a185 --- /dev/null +++ b/packages/components/select-input/__tests__/select-input.test.tsx @@ -0,0 +1,222 @@ +import React from 'react'; +import { act, fireEvent, mockDelay, render, vi } from '@test/utils'; + +import { SelectInput } from '..'; + +import type { TdSelectInputProps } from '../type'; + +const OPTIONS = [ + { label: 'tdesign-vue', value: 1 }, + { label: 'tdesign-react', value: 2 }, + { label: 'tdesign-miniprogram', value: 3 }, + { label: 'tdesign-angular', value: '4' }, + { label: 'tdesign-mobile-vue', value: '5' }, + { label: 'tdesign-mobile-react', value: '6' }, +]; + +function Panel() { + return ( +
    + {OPTIONS.map((item) => ( +
  • + + {item.label} +
  • + ))} +
+ ); +} + +const multipleValue = [ + { label: 'tdesign-vue', value: 1 }, + { label: 'tdesign-react', value: 2 }, + { label: 'tdesign-miniprogram', value: 3 }, + { label: 'tdesign-mobile-vue', value: 4 }, + { label: 'tdesign-react-vue', value: 5 }, +]; + +function getSelectInputMultipleMount(props?: Partial, events?: Record) { + return render( + } {...props} {...events}>, + ); +} + +describe('SelectInput', () => { + describe('props', () => { + test(`allowInput is equal to true`, () => { + const { container } = render(); + const domWrapper = container.querySelector('.t-input'); + expect(domWrapper.classList.contains('t-is-readonly')).toBeFalsy(); + }); + + test('borderless', () => { + // borderless default value is false + const { container: container1 } = render(); + expect(container1.querySelector(`.${'t-select-input--borderless'}`)).toBeFalsy(); + // borderless = true + const { container: container2 } = render(); + expect(container2.firstChild).toHaveClass('t-select-input--borderless'); + // borderless = false + const { container: container3 } = render(); + expect(container3.querySelector(`.${'t-select-input--borderless'}`)).toBeFalsy(); + }); + + test('clearable: empty value can not show clear icon', async () => { + const { container } = render(); + fireEvent.mouseEnter(container.querySelector('.t-input')); + await mockDelay(); + expect(container.querySelector('.t-input__suffix-clear')).toBeFalsy(); + }); + + test('clearable: show clear icon on mouse enter in single select input', async () => { + const { container } = render(); + fireEvent.mouseEnter(container.querySelector('.t-input')); + await mockDelay(); + expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); + }); + + test('clearable: show clear icon on mouse enter in multiple select input', async () => { + const { container } = render(); + fireEvent.mouseEnter(container.querySelector('.t-input')); + await mockDelay(); + expect(container.querySelector('.t-tag-input__suffix-clear')).toBeTruthy(); + }); + + test('disabled', () => { + // disabled default value is + const wrapper1 = render(); + const container1 = wrapper1.container.querySelector('.t-input'); + expect(container1.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); + // disabled = true + const wrapper2 = render(); + const container2 = wrapper2.container.querySelector('.t-input'); + expect(container2).toHaveClass('t-is-disabled'); + // disabled = false + const wrapper3 = render(); + const container3 = wrapper3.container.querySelector('.t-input'); + expect(container3.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); + }); + + test('loading', () => { + const { container } = render(); + expect(container.querySelector('.t-loading')).toBeTruthy(); + }); + + test('multiple', () => { + // multiple default value is false + const { container } = render(); + expect(container.querySelector('.t-tag-input')).toBeFalsy(); + // multiple = false + const { container: container1 } = render(); + expect(container1.querySelector('.t-tag-input')).toBeFalsy(); + // multiple = true + const { container: container2 } = render(); + expect(container2.querySelector('.t-tag-input')).toBeTruthy(); + }); + + test('placeholder', () => { + const wrapper = render(); + const container = wrapper.container.querySelector('input'); + expect(container.getAttribute('placeholder')).toBe('This is SelectInput placeholder'); + }); + + const statusClassNameList = [{ 't-is-default': false }, 't-is-success', 't-is-warning', 't-is-error']; + (['default', 'success', 'warning', 'error'] as TdSelectInputProps['status'][]).forEach((item, index) => { + test(`status is equal to ${item}`, () => { + const wrapper = render(); + const container = wrapper.container.querySelector('.t-input'); + if (typeof statusClassNameList[index] === 'string') { + expect(container).toHaveClass(statusClassNameList[index]); + } else if (typeof statusClassNameList[index] === 'object') { + const classNameKey = Object.keys(statusClassNameList[index])[0]; + expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); + } + }); + }); + + test('tips is equal this is a tip', () => { + const { container } = render(); + expect(container.querySelectorAll('.t-input__tips').length).toBe(1); + }); + test('collapsedItems', () => { + const { container } = getSelectInputMultipleMount({ + collapsedItems: TNode, + minCollapsedNum: 3, + }); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + + test('collapsedItems is a function with params', () => { + const fn = vi.fn(); + getSelectInputMultipleMount({ collapsedItems: fn, minCollapsedNum: 3 }); + expect(fn).toHaveBeenCalled(); + expect(fn.mock.calls[0][0].count).toBe(2); + }); + + test('label', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + + test('panel', () => { + const { container } = render(TNode}>); + fireEvent.click(container.querySelector('.t-input')); + const customNodeDom = document.querySelector('.custom-node'); + expect(customNodeDom).toBeTruthy(); + }); + + test('suffix', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + + test('suffixIcon', () => { + const { container } = render(TNode}>); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + + test('tag', () => { + const { container } = render( + TNode} + multiple={true} + value={['tdesign-vue']} + >, + ); + expect(container.querySelector('.custom-node')).toBeTruthy(); + }); + + test('tag is a function with params', () => { + const fn = vi.fn(); + render(); + expect(fn).toHaveBeenCalled(); + expect(fn.mock.calls[0][0].value).toBe('tdesign-vue'); + }); + + test('label display', async () => { + const text = 'test-label'; + const { getByText } = await render(); + + act(() => { + expect(getByText(text)).toBeTruthy(); + }); + }); + + test('prefixIcon display', async () => { + const text = 'test-prefixIcon'; + const { getByText } = await render({text}} />); + + act(() => { + expect(getByText(text)).toBeTruthy(); + }); + }); + }); + + describe('scenarios', () => { + test('default select-input readonly', async () => { + const { container } = await render(); + + expect(container.querySelector('input')).toHaveAttribute('readonly'); + }); + }); +}); diff --git a/packages/components/select-input/__tests__/select.test.tsx b/packages/components/select-input/__tests__/select.test.tsx deleted file mode 100644 index 8954d3323e..0000000000 --- a/packages/components/select-input/__tests__/select.test.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { act, render } from '@test/utils'; - -import SelectInput from '../index'; - -describe('SelectInput Test', () => { - test('label display', async () => { - const text = 'test-label'; - const { getByText } = await render(); - - act(() => { - expect(getByText(text)).toBeTruthy(); - }); - }); - - test('prefixIcon display', async () => { - const text = 'test-prefixIcon'; - const { getByText } = await render({text}} />); - - act(() => { - expect(getByText(text)).toBeTruthy(); - }); - }); - - test('default select-input readonly', async () => { - const { container } = await render(); - - expect(container.querySelector('input')).toHaveAttribute('readonly'); - }); -}); diff --git a/packages/components/select-input/__tests__/vitest-select-input.test.jsx b/packages/components/select-input/__tests__/vitest-select-input.test.jsx deleted file mode 100644 index f6b3408f4b..0000000000 --- a/packages/components/select-input/__tests__/vitest-select-input.test.jsx +++ /dev/null @@ -1,163 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * 该文件由脚本自动生成,如需修改请联系 PMC - * This file generated by scripts of tdesign-api. `npm run api:docs SelectInput React(PC) vitest,finalProject` - * If you need to modify this file, contact PMC first please. - */ -import React from 'react'; -import { fireEvent, vi, render, mockDelay } from '@test/utils'; -import { SelectInput } from '..'; -import { getSelectInputMultipleMount } from './mount'; - -describe('SelectInput Component', () => { - it(`props.allowInput is equal to true`, () => { - const { container } = render(); - const domWrapper = container.querySelector('.t-input'); - expect(domWrapper.classList.contains('t-is-readonly')).toBeFalsy(); - }); - - it('props.borderless works fine', () => { - // borderless default value is false - const { container: container1 } = render(); - expect(container1.querySelector(`.${'t-select-input--borderless'}`)).toBeFalsy(); - // borderless = true - const { container: container2 } = render(); - expect(container2.firstChild).toHaveClass('t-select-input--borderless'); - // borderless = false - const { container: container3 } = render(); - expect(container3.querySelector(`.${'t-select-input--borderless'}`)).toBeFalsy(); - }); - - it('props.clearable: empty value can not show clear icon', async () => { - const { container } = render(); - fireEvent.mouseEnter(container.querySelector('.t-input')); - await mockDelay(); - expect(container.querySelector('.t-input__suffix-clear')).toBeFalsy(); - }); - - it('props.clearable: show clear icon on mouse enter in single select input', async () => { - const { container } = render(); - fireEvent.mouseEnter(container.querySelector('.t-input')); - await mockDelay(); - expect(container.querySelector('.t-input__suffix-clear')).toBeTruthy(); - }); - - it('props.clearable: show clear icon on mouse enter in multiple select input', async () => { - const { container } = render(); - fireEvent.mouseEnter(container.querySelector('.t-input')); - await mockDelay(); - expect(container.querySelector('.t-tag-input__suffix-clear')).toBeTruthy(); - }); - - it('props.collapsedItems works fine', () => { - const { container } = getSelectInputMultipleMount(SelectInput, { - collapsedItems: TNode, - minCollapsedNum: 3, - }); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - it('props.collapsedItems is a function with params', () => { - const fn = vi.fn(); - getSelectInputMultipleMount(SelectInput, { collapsedItems: fn, minCollapsedNum: 3 }); - expect(fn).toHaveBeenCalled(); - expect(fn.mock.calls[0][0].count).toBe(2); - }); - - it('props.disabled works fine', () => { - // disabled default value is - const wrapper1 = render(); - const container1 = wrapper1.container.querySelector('.t-input'); - expect(container1.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - // disabled = true - const wrapper2 = render(); - const container2 = wrapper2.container.querySelector('.t-input'); - expect(container2).toHaveClass('t-is-disabled'); - // disabled = false - const wrapper3 = render(); - const container3 = wrapper3.container.querySelector('.t-input'); - expect(container3.querySelector(`.${'t-is-disabled'}`)).toBeFalsy(); - }); - - it('props.label works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - it('props.loading works fine. `".t-loading"` should exist', () => { - const { container } = render(); - expect(container.querySelector('.t-loading')).toBeTruthy(); - }); - - it('props.multiple: SelectInput contains element `.t-tag-input`', () => { - // multiple default value is false - const { container } = render(); - expect(container.querySelector('.t-tag-input')).toBeFalsy(); - // multiple = false - const { container: container1 } = render(); - expect(container1.querySelector('.t-tag-input')).toBeFalsy(); - // multiple = true - const { container: container2 } = render(); - expect(container2.querySelector('.t-tag-input')).toBeTruthy(); - }); - - it('props.panel works fine', () => { - const { container } = render(TNode}>); - fireEvent.click(container.querySelector('.t-input')); - const customNodeDom = document.querySelector('.custom-node'); - expect(customNodeDom).toBeTruthy(); - }); - - it('props.placeholder works fine', () => { - const wrapper = render(); - const container = wrapper.container.querySelector('input'); - expect(container.getAttribute('placeholder')).toBe('This is SelectInput placeholder'); - }); - - const statusClassNameList = [{ 't-is-default': false }, 't-is-success', 't-is-warning', 't-is-error']; - ['default', 'success', 'warning', 'error'].forEach((item, index) => { - it(`props.status is equal to ${item}`, () => { - const wrapper = render(); - const container = wrapper.container.querySelector('.t-input'); - if (typeof statusClassNameList[index] === 'string') { - expect(container).toHaveClass(statusClassNameList[index]); - } else if (typeof statusClassNameList[index] === 'object') { - const classNameKey = Object.keys(statusClassNameList[index])[0]; - expect(container.querySelector(`.${classNameKey}`)).toBeFalsy(); - } - }); - }); - - it('props.suffix works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - it('props.suffixIcon works fine', () => { - const { container } = render(TNode}>); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - it('props.tag works fine', () => { - const { container } = render( - TNode} - multiple={true} - value={['tdesign-vue']} - >, - ); - expect(container.querySelector('.custom-node')).toBeTruthy(); - }); - - it('props.tag is a function with params', () => { - const fn = vi.fn(); - render(); - expect(fn).toHaveBeenCalled(); - expect(fn.mock.calls[0][0].value).toBe('tdesign-vue'); - }); - - it('props.tips is equal this is a tip', () => { - const { container } = render(); - expect(container.querySelectorAll('.t-input__tips').length).toBe(1); - }); -}); diff --git a/packages/components/select/__tests__/closing-animation.test.tsx b/packages/components/select/__tests__/closing-animation.test.tsx deleted file mode 100644 index 231152341b..0000000000 --- a/packages/components/select/__tests__/closing-animation.test.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import React, { useState } from 'react'; -import { act, fireEvent, render, vi } from '@test/utils'; - -import Select from '../index'; - -test.each([ - { source: 'options', destroyOnClose: false }, - { source: 'children', destroyOnClose: false }, - { source: 'options', destroyOnClose: true }, - { source: 'children', destroyOnClose: true }, -])('keeps the filtered list during exit: $source, destroyOnClose=$destroyOnClose', ({ source, destroyOnClose }) => { - vi.useFakeTimers(); - const onChange = vi.fn(); - const onInputChange = vi.fn(); - const Demo = () => { - const [value, setValue] = useState(''); - const options = [ - { label: '选项一', value: '1' }, - { label: '选项二', value: '2' }, - { label: '选项三', value: '3' }, - ]; - return ( - - ); - }; - const { getByRole, getByText, unmount } = render(); - try { - const input = getByRole('textbox'); - fireEvent.click(input); - act(() => vi.advanceTimersByTime(200)); - fireEvent.change(input, { target: { value: '一' } }); - const popup = document.querySelector('.t-popup'); - expect(popup).toHaveTextContent('选项一'); - expect(popup).not.toHaveTextContent('选项二'); - - fireEvent.click(getByText('选项一')); - expect(input).toHaveValue('选项一'); - expect(onChange).toHaveBeenLastCalledWith('1'); - expect(onInputChange).toHaveBeenLastCalledWith('', expect.objectContaining({ trigger: 'blur' })); - expect(popup).toHaveStyle({ display: 'block' }); - expect(popup).not.toHaveTextContent('选项二'); - expect(popup).not.toHaveTextContent('选项三'); - - act(() => vi.advanceTimersByTime(100)); - expect(popup).toHaveStyle({ display: 'block' }); - expect(popup).not.toHaveTextContent('选项二'); - act(() => vi.advanceTimersByTime(100)); - if (destroyOnClose) expect(document.querySelector('.t-popup')).toBeNull(); - else expect(popup).toHaveStyle({ display: 'none' }); - - fireEvent.click(input); - const reopenedPopup = document.querySelector('.t-popup'); - expect(reopenedPopup).toHaveTextContent('选项一'); - expect(reopenedPopup).toHaveTextContent('选项二'); - expect(reopenedPopup).toHaveTextContent('选项三'); - expect(onChange).toHaveBeenCalledTimes(1); - } finally { - unmount(); - vi.useRealTimers(); - } -}); - -const options = [ - { label: '选项一', value: '1' }, - { label: '选项二', value: '2' }, - { label: '选项三', value: '3' }, -]; - -test('reopening during exit uses the latest options and cancels the pending close', () => { - vi.useFakeTimers(); - const onChange = vi.fn(); - const { getByRole, getByText, rerender, unmount } = render( - ); - expect(popup).not.toHaveTextContent('选项四'); - fireEvent.click(input); - expect(input).toHaveValue(''); - expect(popup).toHaveTextContent('选项二'); - expect(popup).toHaveTextContent('选项四'); - act(() => vi.advanceTimersByTime(200)); - expect(popup).toHaveStyle({ display: 'block' }); - expect(onChange).toHaveBeenCalledTimes(1); - } finally { - unmount(); - vi.useRealTimers(); - } -}); - -test.each([false, true])('multiple selection keeps the open list live: reserveKeyword=%s', (reserveKeyword) => { - const { getByRole, getByText } = render( - , - ); - const input = getByRole('textbox'); - fireEvent.change(input, { target: { value: '一' } }); - fireEvent.click(getByText('选项一')); - expect(onPopupVisibleChange).toHaveBeenLastCalledWith(false, expect.anything()); - expect(document.querySelector('.t-popup')).toHaveStyle({ display: 'block' }); - expect(document.querySelector('.t-popup')).toHaveTextContent('选项二'); - fireEvent.change(input, { target: { value: '三' } }); - expect(document.querySelector('.t-popup')).toHaveTextContent('选项三'); - expect(document.querySelector('.t-popup')).not.toHaveTextContent('选项二'); -}); diff --git a/packages/components/select/__tests__/select.test.tsx b/packages/components/select/__tests__/select.test.tsx index 749f4ef8e8..765500f318 100644 --- a/packages/components/select/__tests__/select.test.tsx +++ b/packages/components/select/__tests__/select.test.tsx @@ -11,14 +11,36 @@ import type { SelectProps } from '../index'; const { Option, OptionGroup } = Select; -describe('Select 组件测试', () => { - const selectSelector = '.t-select'; - const popupSelector = '.t-popup'; - const options = [ +const selectSelector = '.t-select'; +const popupSelector = '.t-popup'; + +const options = [ + { + label: 'Apple', + value: 'apple', + disabled: true, + }, + { + label: 'Banana', + value: 'banana', + }, + { + label: 'Orange', + value: 'orange', + }, +]; + +const filterableOptions = [ + { label: '选项一', value: '1' }, + { label: '选项二', value: '2' }, + { label: '选项三', value: '3' }, +]; + +const RemoteSearchSelect = ({ multiple }: { multiple?: boolean }) => { + const defaultOptions = [ { label: 'Apple', value: 'apple', - disabled: true, }, { label: 'Banana', @@ -29,669 +51,729 @@ describe('Select 组件测试', () => { value: 'orange', }, ]; + const [value, setValue] = useState(); + const [loading, setLoading] = useState(false); + const [remoteOptions, setRemoteOptions] = useState(defaultOptions); - const RemoteSearchSelect = ({ multiple }: { multiple?: boolean }) => { - const defaultOptions = [ - { - label: 'Apple', - value: 'apple', - }, - { - label: 'Banana', - value: 'banana', - }, - { - label: 'Orange', - value: 'orange', - }, - ]; - const [value, setValue] = useState(); - const [loading, setLoading] = useState(false); - const [options, setOptions] = useState(defaultOptions); - - const onChange = (value) => { - setValue(value); - }; - - const handleRemoteSearch = (search) => { - setLoading(true); - setTimeout(() => { - setLoading(false); - let options: typeof defaultOptions = []; - if (search) { - options = [ - { - value: `${search}_test1`, - label: `${search}_test1`, - }, - { - value: `${search}_test2`, - label: `${search}_test2`, - }, - { - value: `${search}_test3`, - label: `${search}_test3`, - }, - ]; - } else { - options = defaultOptions; - } - setOptions(options); - }); - }; - - return ( - - ); + const onChange = (nextValue) => { + setValue(nextValue); }; - test('单选测试', async () => { - const SingleSelect = () => { - const [value, setValue] = useState('apple'); - const onChange = (value) => { - setValue(value); - }; - return ( - - ); - }; - const { getByText } = render(); + const handleRemoteSearch = (search) => { + setLoading(true); + setTimeout(() => { + setLoading(false); + let nextOptions: typeof defaultOptions = []; + if (search) { + nextOptions = [ + { + value: `${search}_test1`, + label: `${search}_test1`, + }, + { + value: `${search}_test2`, + label: `${search}_test2`, + }, + { + value: `${search}_test3`, + label: `${search}_test3`, + }, + ]; + } else { + nextOptions = defaultOptions; + } + setRemoteOptions(nextOptions); + }); + }; - // 未点击 input 前,popup 不出现 - expect(document.querySelector(popupSelector)).toBeNull(); + return ( + + ); +}; + +const createCollapsedItems = + (minCollapsedNum: number, extraClassName?: string): SelectProps['collapsedItems'] => + ({ collapsedSelectedItems, onClose }) => ( + + {collapsedSelectedItems.map((item, index) => ( + onClose({ e: context.e, index: minCollapsedNum + index })} + > + {item.label} + + ))} + + } + > + More({collapsedSelectedItems?.length}) + + ); + +describe('Select', () => { + describe('props', () => { + test('valueDisplay', () => { + const { container } = render( + 人工智能, + value: 'ai', + }, + ]} + />, + ); + expect(container.querySelector('.t-input__prefix .custom-label')).toHaveTextContent('人工智能'); + expect(container.querySelector('.t-input__inner')).toHaveValue(' '); }); - expect(document.querySelector(popupSelector)).toHaveTextContent('Apple'); - expect(document.querySelector(popupSelector)).toHaveTextContent('Orange'); - expect(document.querySelector(popupSelector)).toHaveTextContent('Banana'); + test('label', async () => { + const text = 'test-label'; + const { getByText } = await render({text}} />); - test('非过滤单选的字符串 valueDisplay 使用 prefix 展示', () => { - const { container } = render( - 人工智能, - value: 'ai', - }, - ]} - />, - ); - expect(container.querySelector('.t-input__prefix .custom-label')).toHaveTextContent('人工智能'); - expect(container.querySelector('.t-input__inner')).toHaveValue(' '); + test('collapsedItems', async () => { + const minCollapsedNum = 1; + const MultipleSelect = () => { + const selectAllValue = ['apple', 'banana', 'orange']; + + return ( + +