diff --git a/jest.config.js b/jest.config.js index 66070ad6..c9024d3e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -16,9 +16,9 @@ module.exports = { }, ], }, + collectCoverage: true, coveragePathIgnorePatterns: ['/scripts/', 'tests/'], coverageDirectory: './.ci/', - coverageReporters: ['json', 'html'], moduleNameMapper: { '^@bifrostui/(styles.*)$': '/tests/mocks/styleMock.js', '^@bifrostui/react$': '/packages/bui-core/src', diff --git a/packages/bui-core/src/Collapse/__tests__/Collapse.snapshot.test.tsx b/packages/bui-core/src/Collapse/__tests__/Collapse.snapshot.test.tsx new file mode 100644 index 00000000..15651c3b --- /dev/null +++ b/packages/bui-core/src/Collapse/__tests__/Collapse.snapshot.test.tsx @@ -0,0 +1,5 @@ +import { snapshotTest } from 'testing'; + +describe('Collapse snapshot', () => { + snapshotTest('Collapse'); +}); diff --git a/packages/bui-core/src/Collapse/__tests__/__snapshots__/Collapse.snapshot.test.tsx.snap b/packages/bui-core/src/Collapse/__tests__/__snapshots__/Collapse.snapshot.test.tsx.snap new file mode 100644 index 00000000..d5b75be1 --- /dev/null +++ b/packages/bui-core/src/Collapse/__tests__/__snapshots__/Collapse.snapshot.test.tsx.snap @@ -0,0 +1,148 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Collapse snapshot Collapse demo snapshot 0 1`] = ` +
+
+ +
+
+
+
+
+`; + +exports[`Collapse snapshot Collapse demo snapshot 0 2`] = ` +
+
+ +
+
+
+
+
+`; + +exports[`Collapse snapshot Collapse demo snapshot 0 3`] = ` +
+
+ +
+
+
+
+
+`; diff --git a/packages/bui-core/src/CollapsePanel/__tests__/CollapsePanel.test.tsx b/packages/bui-core/src/CollapsePanel/__tests__/CollapsePanel.test.tsx index 4c53411c..f6223537 100644 --- a/packages/bui-core/src/CollapsePanel/__tests__/CollapsePanel.test.tsx +++ b/packages/bui-core/src/CollapsePanel/__tests__/CollapsePanel.test.tsx @@ -1,5 +1,5 @@ import React, { ReactElement, useState } from 'react'; -import { render, isConformant, userEvent, screen } from 'testing'; +import { render, isConformant, userEvent, screen, waitFor } from 'testing'; import CollapsePanel, { CollapsePanelItem, CollapsePanelProps } from '../index'; const setup = (props: CollapsePanelProps, children?: ReactElement) => { @@ -86,7 +86,7 @@ describe('CollapsePanel', () => { expect(firstPanel).toHaveClass('bui-collapse-panel-item-active'); expect(secondPanel).not.toHaveClass('bui-collapse-panel-item-active'); }); - it('should only open one panel when use accordion', () => { + it('should only open one panel when use accordion', async () => { const { container } = setup({ defaultActiveKeys: ['1'], accordion: true, @@ -100,8 +100,10 @@ describe('CollapsePanel', () => { )[1]; userEvent.click(screen.getByText('这是面板标题2')); - expect(firstPanel).not.toHaveClass('bui-collapse-panel-item-active'); - expect(secondPanel).toHaveClass('bui-collapse-panel-item-active'); + await waitFor(() => { + expect(firstPanel).not.toHaveClass('bui-collapse-panel-item-active'); + expect(secondPanel).toHaveClass('bui-collapse-panel-item-active'); + }); }); it('should use custom icon', () => { const customIcon =
custom icon
; @@ -119,7 +121,7 @@ describe('CollapsePanel', () => { const icon = screen.getByText('custom icon'); expect(icon).toBeVisible(); }); - it('should use custom icon by function', () => { + it('should use custom icon by function', async () => { setup({ arrowIcon: (active) => { if (active) { @@ -138,10 +140,12 @@ describe('CollapsePanel', () => { const unactivedIcon = screen.getByText('unactived icon'); expect(unactivedIcon).toBeVisible(); userEvent.click(screen.getByText('这是面板标题1')); - const activeIcon = screen.getByText('active icon'); - expect(activeIcon).toBeVisible(); + await waitFor(() => { + const activeIcon = screen.getByText('active icon'); + expect(activeIcon).toBeVisible(); + }); }); - it('should call onChange', () => { + it('should call onChange', async () => { const fakeChange = jest.fn(); const Component = () => { const [activeKeys, setActiveKeys] = useState(['1']); @@ -167,10 +171,12 @@ describe('CollapsePanel', () => { )[1]; userEvent.click(screen.getByText('这是面板标题2')); - expect(fakeChange).toBeCalled(); - expect(fakeChange).toBeCalledWith(['2', '1']); - expect(firstPanel).toHaveClass('bui-collapse-panel-item-active'); - expect(secondPanel).toHaveClass('bui-collapse-panel-item-active'); + await waitFor(() => { + expect(fakeChange).toBeCalled(); + expect(fakeChange).toBeCalledWith(['2', '1']); + expect(firstPanel).toHaveClass('bui-collapse-panel-item-active'); + expect(secondPanel).toHaveClass('bui-collapse-panel-item-active'); + }); }); it('should return null', () => { const { container } = setup({}); @@ -206,7 +212,7 @@ describe('CollapsePanel', () => { expect(firstPanel).toHaveClass('bui-collapse-panel-item-active'); expect(secondPanel).not.toHaveClass('bui-collapse-panel-item-active'); }); - it('should only open one panel when use accordion', () => { + it('should only open one panel when use accordion', async () => { const { container } = setupByElement({ defaultActiveKeys: ['1'], accordion: true, @@ -219,8 +225,10 @@ describe('CollapsePanel', () => { )[1]; userEvent.click(screen.getByText('这是面板标题2')); - expect(firstPanel).not.toHaveClass('bui-collapse-panel-item-active'); - expect(secondPanel).toHaveClass('bui-collapse-panel-item-active'); + await waitFor(() => { + expect(firstPanel).not.toHaveClass('bui-collapse-panel-item-active'); + expect(secondPanel).toHaveClass('bui-collapse-panel-item-active'); + }); }); it('should use custom icon', () => { const customIcon =
custom icon
; @@ -236,7 +244,7 @@ describe('CollapsePanel', () => { const icon = screen.getByText('custom icon'); expect(icon).toBeVisible(); }); - it('should use custom icon by function', () => { + it('should use custom icon by function', async () => { setupByElement( { arrowIcon: (active) => { @@ -251,10 +259,12 @@ describe('CollapsePanel', () => { const unactivedIcon = screen.getByText('unactived icon'); expect(unactivedIcon).toBeVisible(); userEvent.click(screen.getByText('这是面板标题1')); - const activeIcon = screen.getByText('active icon'); - expect(activeIcon).toBeVisible(); + await waitFor(() => { + const activeIcon = screen.getByText('active icon'); + expect(activeIcon).toBeVisible(); + }); }); - it('should call onChange', () => { + it('should call onChange', async () => { const fakeChange = jest.fn(); const Component = () => { const [activeKeys, setActiveKeys] = useState(['1']); @@ -283,10 +293,12 @@ describe('CollapsePanel', () => { )[1]; userEvent.click(screen.getByText('这是面板标题2')); - expect(fakeChange).toBeCalled(); - expect(fakeChange).toBeCalledWith(['2', '1']); - expect(firstPanel).toHaveClass('bui-collapse-panel-item-active'); - expect(secondPanel).toHaveClass('bui-collapse-panel-item-active'); + await waitFor(() => { + expect(fakeChange).toBeCalled(); + expect(fakeChange).toBeCalledWith(['2', '1']); + expect(firstPanel).toHaveClass('bui-collapse-panel-item-active'); + expect(secondPanel).toHaveClass('bui-collapse-panel-item-active'); + }); }); it('should return null', () => { const { container } = setupByElement({}, 0); diff --git a/packages/bui-core/src/Dialog/FunctionalDialog.tsx b/packages/bui-core/src/Dialog/FunctionalDialog.tsx index 751abd32..25e4862f 100644 --- a/packages/bui-core/src/Dialog/FunctionalDialog.tsx +++ b/packages/bui-core/src/Dialog/FunctionalDialog.tsx @@ -119,7 +119,7 @@ const useDialog = () => { const wrapAPI: DialogFunction = ( props: DialogOptions | string, ): DialogPromise => { - const options = { theme: holderRef.current.theme, ...formatProps(props) }; + const options = { theme: holderRef.current?.theme, ...formatProps(props) }; const { onConfirm, onCancel, ...rest } = options; return new Promise((resolve) => { DialogGenerator({ @@ -141,13 +141,13 @@ const useDialog = () => { Dialog({ type: 'confirm', ...formatProps(options), - theme: holderRef.current.theme, + theme: holderRef.current?.theme, }); wrapAPI.prompt = (options: PromptOptions) => Dialog({ type: 'prompt', ...formatProps(options), - theme: holderRef.current.theme, + theme: holderRef.current?.theme, }); return [wrapAPI, ] as [ DialogFunction, diff --git a/packages/bui-core/src/Dialog/__tests__/Dialog.snapshot.test.tsx b/packages/bui-core/src/Dialog/__tests__/Dialog.snapshot.test.tsx new file mode 100644 index 00000000..58b1bb8e --- /dev/null +++ b/packages/bui-core/src/Dialog/__tests__/Dialog.snapshot.test.tsx @@ -0,0 +1,5 @@ +import { snapshotTest } from 'testing'; + +describe('Dialog snapshot', () => { + snapshotTest('Dialog'); +}); diff --git a/packages/bui-core/src/Dialog/__tests__/Dialog.test.tsx b/packages/bui-core/src/Dialog/__tests__/Dialog.test.tsx index dbce8d34..3b87b506 100644 --- a/packages/bui-core/src/Dialog/__tests__/Dialog.test.tsx +++ b/packages/bui-core/src/Dialog/__tests__/Dialog.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { fireEvent, waitFor } from '@testing-library/react'; +import { fireEvent, renderHook, waitFor } from '@testing-library/react'; import { Button } from '@bifrostui/react'; import { render, screen, act } from 'testing'; import Dialog from '../FunctionalDialog'; @@ -7,9 +7,14 @@ import '@testing-library/jest-dom/extend-expect'; describe('Dialog Functional Calls', () => { const rootClass = 'bui-dialog'; + let dialogHook; + beforeEach(() => { document.body.innerHTML = ''; jest.useFakeTimers(); + renderHook(() => { + dialogHook = Dialog.useDialog(); + }); }); afterEach(() => { @@ -142,4 +147,69 @@ describe('Dialog Functional Calls', () => { fireEvent.click(screen.getByText('Delete')); await waitFor(() => expect(promptPromise).resolves.toBe(false)); }); + it('the default type of useDialog is confirm', async () => { + const onConfirm = jest.fn(); + const onCancel = jest.fn(); + const { getByTestId } = render( + , + ); + fireEvent.click(getByTestId('emit-button')); + expect( + document.body.querySelector(`.${rootClass}-body-desc`), + ).toBeInTheDocument(); + expect(screen.getByText('描述内容')).toBeInTheDocument(); + fireEvent.click(screen.getByText('取消')); + await waitFor(async () => { + expect(onCancel).toHaveBeenCalled(); + fireEvent.click(screen.getByText('确定')); + await waitFor(() => { + expect(onConfirm).toHaveBeenCalled(); + }); + }); + }); + + it.each(['confirm', 'prompt'])( + 'should support basic api with useDialog', + async (type) => { + const dialog = dialogHook?.[0]; + const onConfirm = jest.fn(); + const onCancel = jest.fn(); + + render( + , + ); + fireEvent.click(screen.getByText('dialog button')); + expect(screen.getByText(`${type} message`)).toBeInTheDocument(); + fireEvent.click(screen.getByText('取消')); + await waitFor(async () => { + expect(onCancel).toHaveBeenCalled(); + fireEvent.click(screen.getByText('确定')); + await waitFor(() => { + expect(onConfirm).toHaveBeenCalled(); + }); + }); + }, + ); }); diff --git a/packages/bui-core/src/Dialog/__tests__/__snapshots__/Dialog.snapshot.test.tsx.snap b/packages/bui-core/src/Dialog/__tests__/__snapshots__/Dialog.snapshot.test.tsx.snap new file mode 100644 index 00000000..4b093fd3 --- /dev/null +++ b/packages/bui-core/src/Dialog/__tests__/__snapshots__/Dialog.snapshot.test.tsx.snap @@ -0,0 +1,165 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Dialog snapshot Dialog demo snapshot 0 1`] = ` +
+ + + +
+`; + +exports[`Dialog snapshot Dialog demo snapshot 0 2`] = ` +
+ + + +
+`; + +exports[`Dialog snapshot Dialog demo snapshot 0 3`] = ` +
+ + +
+`; + +exports[`Dialog snapshot Dialog demo snapshot 0 4`] = ` +
+ + +
+`; + +exports[`Dialog snapshot Dialog demo snapshot 0 5`] = ` +
+ + +
+`; diff --git a/packages/bui-core/src/Dialog/index.en-US.md b/packages/bui-core/src/Dialog/index.en-US.md index 0b4761cd..76eee815 100644 --- a/packages/bui-core/src/Dialog/index.en-US.md +++ b/packages/bui-core/src/Dialog/index.en-US.md @@ -158,7 +158,7 @@ export default () => { }; return ( - + {contextHolder} @@ -188,7 +188,7 @@ export default () => { const [dialog, contextHolder] = Dialog.useDialog(); return ( - + {contextHolder} diff --git a/packages/bui-core/src/Dialog/index.zh-CN.md b/packages/bui-core/src/Dialog/index.zh-CN.md index 8a69b56b..57fc486f 100644 --- a/packages/bui-core/src/Dialog/index.zh-CN.md +++ b/packages/bui-core/src/Dialog/index.zh-CN.md @@ -158,7 +158,7 @@ export default () => { }; return ( - + {contextHolder} @@ -188,7 +188,7 @@ export default () => { const [dialog, contextHolder] = Dialog.useDialog(); return ( - + {contextHolder} diff --git a/packages/bui-core/src/Input/Input.less b/packages/bui-core/src/Input/Input.less index babaadb9..dc9b7283 100644 --- a/packages/bui-core/src/Input/Input.less +++ b/packages/bui-core/src/Input/Input.less @@ -50,6 +50,7 @@ outline: none; background-color: var(--background-color); font-size: var(--bui-text-size-2); + .ellipsis(); &::placeholder { color: var(--bui-color-fg-subtle); diff --git a/packages/bui-core/src/Popover/Popover.tsx b/packages/bui-core/src/Popover/Popover.tsx index 1549eea1..c18f79be 100644 --- a/packages/bui-core/src/Popover/Popover.tsx +++ b/packages/bui-core/src/Popover/Popover.tsx @@ -4,6 +4,7 @@ import { getStylesAndLocation, triggerEventTransform, useUniqueId, + throttle, } from '@bifrostui/utils'; import Portal from '../Portal'; import './Popover.less'; @@ -24,7 +25,6 @@ const Popover = React.forwardRef((props, ref) => { onOpenChange, open, hideArrow, - PortalProps, ...others } = props; @@ -81,16 +81,7 @@ const Popover = React.forwardRef((props, ref) => { hidePopover(event); }; - useEffect(() => { - if (controlByUser) return; - document.addEventListener('click', clickEventHandler); - // eslint-disable-next-line - return () => { - document.removeEventListener('click', clickEventHandler); - }; - }, []); - - const onRootElementMouted = () => { + const onRootElementMouted = throttle(() => { const result = getStylesAndLocation({ childrenRef, arrowDirection, @@ -107,8 +98,29 @@ const Popover = React.forwardRef((props, ref) => { setArrowLocation(newArrowLocation); } setTooltyles(styles); + }, 100); + + const bindEvent = () => { + if (!controlByUser) { + document.addEventListener('click', clickEventHandler); + } + window.addEventListener('resize', onRootElementMouted); }; + const unbindEvent = () => { + if (!controlByUser) { + document.removeEventListener('click', clickEventHandler); + } + window.removeEventListener('resize', onRootElementMouted); + }; + + useEffect(() => { + bindEvent(); + return () => { + unbindEvent(); + }; + }, []); + if (!title && !content) return null; let triggerEventOption; @@ -128,11 +140,7 @@ const Popover = React.forwardRef((props, ref) => { return ( <> {open || openStatus ? ( - +
{ displayName: 'BuiPopover', className: rootClass, skip: [ - 'PortalProps', 'component-has-root-ref', 'component-handles-classNames', 'component-has-default-className', diff --git a/packages/bui-core/src/Popover/index.en-US.md b/packages/bui-core/src/Popover/index.en-US.md index 46ed688d..97ecf902 100644 --- a/packages/bui-core/src/Popover/index.en-US.md +++ b/packages/bui-core/src/Popover/index.en-US.md @@ -19,7 +19,10 @@ import React from 'react'; export default () => { return ( - + This is a title
} + content={
This is a content
} + > click显示 ); @@ -37,7 +40,10 @@ import React, { useState } from 'react'; export default () => { const [open, setOpen] = useState(true); return ( - + This is a popover
} + open={open} + > setOpen(!open)}>open控制显隐 ); @@ -54,7 +60,10 @@ import React from 'react'; export default () => { return ( - + This is a popover
} + defaultOpen + > defaultOpen默认显示 ); @@ -71,7 +80,11 @@ import React from 'react'; export default () => { return ( - + This is a popover
} + defaultOpen + hideArrow + > defaultOpen默认显示 ); @@ -105,13 +118,25 @@ export default () => { justifyContent: 'space-between', }} > - + This is a popover} + placement="topLeft" + > {packageButton(topLeft)} - + This is a popover} + placement="top" + > {packageButton(top)} - + This is a popover} + placement="topRight" + > {packageButton(topRight)} @@ -123,15 +148,23 @@ export default () => { flexDirection: 'column', }} > - + This is a popover} + placement="leftTop" + > {packageButton(leftTop)} - + This is a popover} + placement="left" + > {packageButton(left)} This is a popover} placement="leftBottom" > {packageButton(leftBottom)} @@ -145,15 +178,23 @@ export default () => { flexDirection: 'column', }} > - + This is a popover} + placement="rightTop" + > {packageButton(rightTop)} - + This is a popover} + placement="right" + > {packageButton(right)} This is a popover} placement="rightBottom" > {packageButton(rightBottom)} @@ -170,17 +211,21 @@ export default () => { > This is a popover} placement="bottomLeft" > {packageButton(bottomLeft)} - + This is a popover} + placement="bottom" + > {packageButton(bottom)} This is a popover} placement="bottomRight" > {packageButton(bottomRight)} @@ -201,7 +246,10 @@ import React from 'react'; export default () => { return ( - + This is a popover} + trigger="hover" + > hover触发方式 ); @@ -220,7 +268,7 @@ export default () => { }; return ( This is a popover} trigger="hover" onOpenChange={onOpenChange} > @@ -241,7 +289,6 @@ export default () => { | hideArrow | Display arrows or not | boolean | false | | placement | Bubble box position | String, the enumeration value is `center` `left` `leftTop` `leftBottom` `right` `rightTop` `rightBottom` `top` `topLeft` `topRight` `bottom` `bottomLeft` `bottomRight` `bottom` | 'top' | | trigger | Trigger behavior | string \|String [], the enumeration value is' click '\|'hover' | 'click' | -| PortalProps | Properties of internal Portal components | PortalCoreProps | - | | onOpenChange | The callback method for bubble floating layer manifestation and concealment | (e: React.MouseEvent,data: {open: boolean}) => void | - | ### Style variables diff --git a/packages/bui-core/src/Popover/index.zh-CN.md b/packages/bui-core/src/Popover/index.zh-CN.md index cfd06184..de952832 100644 --- a/packages/bui-core/src/Popover/index.zh-CN.md +++ b/packages/bui-core/src/Popover/index.zh-CN.md @@ -19,7 +19,10 @@ import React from 'react'; export default () => { return ( - + This is a title} + content={
This is a content
} + > click显示
); @@ -37,7 +40,10 @@ import React, { useState } from 'react'; export default () => { const [open, setOpen] = useState(true); return ( - + This is a popover} + open={open} + > setOpen(!open)}>open控制显隐 ); @@ -54,7 +60,10 @@ import React from 'react'; export default () => { return ( - + This is a popover} + defaultOpen + > defaultOpen默认显示 ); @@ -71,7 +80,11 @@ import React from 'react'; export default () => { return ( - + This is a popover} + defaultOpen + hideArrow + > defaultOpen默认显示 ); @@ -105,13 +118,25 @@ export default () => { justifyContent: 'space-between', }} > - + This is a popover} + placement="topLeft" + > {packageButton(topLeft)} - + This is a popover} + placement="top" + > {packageButton(top)} - + This is a popover} + placement="topRight" + > {packageButton(topRight)} @@ -123,15 +148,23 @@ export default () => { flexDirection: 'column', }} > - + This is a popover} + placement="leftTop" + > {packageButton(leftTop)} - + This is a popover} + placement="left" + > {packageButton(left)} This is a popover} placement="leftBottom" > {packageButton(leftBottom)} @@ -145,15 +178,23 @@ export default () => { flexDirection: 'column', }} > - + This is a popover} + placement="rightTop" + > {packageButton(rightTop)} - + This is a popover} + placement="right" + > {packageButton(right)} This is a popover} placement="rightBottom" > {packageButton(rightBottom)} @@ -170,17 +211,21 @@ export default () => { > This is a popover} placement="bottomLeft" > {packageButton(bottomLeft)} - + This is a popover} + placement="bottom" + > {packageButton(bottom)} This is a popover} placement="bottomRight" > {packageButton(bottomRight)} @@ -201,7 +246,10 @@ import React from 'react'; export default () => { return ( - + This is a popover} + trigger="hover" + > hover触发方式 ); @@ -220,7 +268,7 @@ export default () => { }; return ( This is a popover} trigger="hover" onOpenChange={onOpenChange} > @@ -241,7 +289,6 @@ export default () => { | hideArrow | 是否展示箭头 | boolean | false | | placement | 气泡框位置 | string,枚举值是 `center` `left` `leftTop` `leftBottom` `right` `rightTop` `rightBottom` `top` `topLeft` `topRight` `bottom` `bottomLeft` `bottomRight` `bottom` | 'top' | | trigger | 触发行为 | string \| string[],枚举值是 'click' \| 'hover' | 'click' | -| PortalProps | 内部Portal组件的属性 | PortalCoreProps | - | | onOpenChange | 气泡浮层显隐的回调方法 | (e: React.MouseEvent,data: {open: boolean}) => void | - | ### 样式变量 diff --git a/packages/bui-core/src/Progress/Progress.less b/packages/bui-core/src/Progress/Progress.less index 60d58639..c6b9fa42 100644 --- a/packages/bui-core/src/Progress/Progress.less +++ b/packages/bui-core/src/Progress/Progress.less @@ -1,5 +1,5 @@ .bui-progress { - --fill-color: var(--bui-progress-fill-color, --bui-color-primary); + --fill-color: var(--bui-progress-fill-color, var(--bui-color-primary)); --trail-color: var(--bui-progress-trail-color, rgba(156, 156, 165, 0.2)); --stroke-width: var(--bui-progress-stroke-width, 8px); --width: var(--bui-progress-width, 100%); diff --git a/packages/bui-core/src/Radio/Radio.less b/packages/bui-core/src/Radio/Radio.less index a01f94e8..01e6070b 100644 --- a/packages/bui-core/src/Radio/Radio.less +++ b/packages/bui-core/src/Radio/Radio.less @@ -1,7 +1,7 @@ .bui-radio { - --label-color: var(--bui-radio-label-color, --bui-color-fg-default); - --label-font-size: var(--bui-radio-label-font-size, --bui-text-size-1); - --icon-font-size: var(--bui-radio-icon-font-size, --bui-title-size-2); + --label-color: var(--bui-radio-label-color, var(--bui-color-fg-default)); + --label-font-size: var(--bui-radio-label-font-size, var(--bui-text-size-1)); + --icon-font-size: var(--bui-radio-icon-font-size, var(--bui-title-size-2)); --icon-padding: var(--bui-radio-icon-padding, 5px); @iconSize: 16px; diff --git a/packages/bui-core/src/Slider/Slider.less b/packages/bui-core/src/Slider/Slider.less index d98ea432..cef2f562 100644 --- a/packages/bui-core/src/Slider/Slider.less +++ b/packages/bui-core/src/Slider/Slider.less @@ -1,8 +1,11 @@ @import '@bifrostui/styles/mixins/index.less'; .bui-slider { - --line-color: var(--bui-slider-line-color, --bui-color-primary); - --line-bg-color: var(--bui-slider-line-bg-color, --bui-color-border-default); + --line-color: var(--bui-slider-line-color, var(--bui-color-primary)); + --line-bg-color: var( + --bui-slider-line-bg-color, + var(--bui-color-border-default) + ); --width: var(--bui-slider-width, 100%); --height: var(--bui-slider-height, 2px); --padding: var(--bui-slider-padding, 19px 0); @@ -17,9 +20,9 @@ --tooltip-height: var(--bui-slider-tooltip-height, 24px); --tooltip-line-height: var(--bui-slider-tooltip-line-height, 24px); --tooltip-border-radius: var(--bui-slider-tooltip-border-radius, 24px); - --tooltip-font-size: var(--bui-tooltip-font-size, --bui-text-size-3); - --tooltip-color: var(--bui-tooltip-color, --bui-color-white); - --tooltip-bg-color: var(--bui-tooltip-bg-color, --line-color); + --tooltip-font-size: var(--bui-tooltip-font-size, var(--bui-text-size-3)); + --tooltip-color: var(--bui-tooltip-color, var(--bui-color-white)); + --tooltip-bg-color: var(--bui-tooltip-bg-color, var(--line-color)); position: relative; width: var(--width); @@ -47,7 +50,7 @@ top: 50%; left: 0; width: 100%; - height: var(---height); + height: var(--height); transform: translateY(-50%); background-color: var(--line-bg-color); } diff --git a/packages/bui-core/src/ThemeProvider/ThemeProvider.types.ts b/packages/bui-core/src/ThemeProvider/ThemeProvider.types.ts index c06d14f9..c7b5bd53 100644 --- a/packages/bui-core/src/ThemeProvider/ThemeProvider.types.ts +++ b/packages/bui-core/src/ThemeProvider/ThemeProvider.types.ts @@ -2,17 +2,6 @@ import { ReactNode } from 'react'; import { OverrideProps } from '@bifrostui/types'; import { BaseLang } from '../locales/base'; -export type ThemeProviderProps< - D extends React.ElementType = 'div', - P = {}, -> = OverrideProps< - { - props: P & ThemeProps; - defaultComponent: D; - }, - D ->; - export interface Breakpoints { /** * 超小屏幕 (小于 576px,实际为了处理临界值,断点值为 575.98px) @@ -150,6 +139,17 @@ export type UpdateTokensOptions = Pick< rootString: string; }; +export type ThemeProviderProps< + D extends React.ElementType = 'div', + P = {}, +> = OverrideProps< + { + props: P & ThemeProps; + defaultComponent: D; + }, + D +>; + export { BaseLang, ResponsiveTokenOptions, diff --git a/packages/bui-core/src/ThemeProvider/__tests__/ThemeProvider.test.tsx b/packages/bui-core/src/ThemeProvider/__tests__/ThemeProvider.test.tsx index 8b87eec1..52f7b10c 100644 --- a/packages/bui-core/src/ThemeProvider/__tests__/ThemeProvider.test.tsx +++ b/packages/bui-core/src/ThemeProvider/__tests__/ThemeProvider.test.tsx @@ -1,9 +1,5 @@ import React from 'react'; -import { - render, - // userEvent, - screen, -} from 'testing'; +import { render, userEvent, screen } from 'testing'; import dayjs from 'dayjs/esm/index'; import { ThemeProvider, @@ -11,10 +7,18 @@ import { EN, CN, TW, - // Dialog, + Dialog, + Button, } from '@bifrostui/react'; +import { renderHook } from '@testing-library/react'; describe('ThemeProvider', () => { + let dialogHook; + beforeEach(() => { + renderHook(() => { + dialogHook = Dialog.useDialog(); + }); + }); it.each([ { locale: EN, type: 'EN' }, { locale: CN, type: 'CN' }, @@ -33,21 +37,21 @@ describe('ThemeProvider', () => { expect(screen.getByText(placeHolder[theme.type])).toBeTruthy(); }); - // it('should render function locale theme currently', () => { - // const [dialog, contextHolder] = Dialog.useDialog(); - // render( - // - // {contextHolder} - // - // , - // ); - // userEvent.click(screen.getByText(/Dialog/)); - // expect(screen.getByText('Cancel')).toBeTruthy(); - // }); + it('should render function locale theme currently', () => { + const [dialog, contextHolder] = dialogHook; + render( + + {contextHolder} + + , + ); + userEvent.click(screen.getByText(/Dialog/)); + expect(screen.getByText('Cancel')).toBeTruthy(); + }); }); diff --git a/packages/bui-core/src/Toast/FunctionalToast.tsx b/packages/bui-core/src/Toast/FunctionalToast.tsx index fec35c47..6f4b2324 100644 --- a/packages/bui-core/src/Toast/FunctionalToast.tsx +++ b/packages/bui-core/src/Toast/FunctionalToast.tsx @@ -9,7 +9,7 @@ import { ToastReturnType, } from './Toast.types'; -const toastCloses = []; +let toastCloses = []; /** * 参数格式化,支持直接传文案 @@ -88,7 +88,13 @@ const functionalToast = (props: ToastProps | string) => { toastCloses.push(close); if (duration !== 0 && typeof duration === 'number') { - timer = setTimeout(close, duration); + timer = setTimeout(() => { + close(); + // 不允许共存的场景下,当前Toast关闭后,应清空toastCloses + if (!allowMultiple) { + toastCloses = []; + } + }, duration); } return () => { @@ -156,7 +162,7 @@ const useToast = () => { functionalToast({ type: methodName, ...formatProps(options), - theme: holderRef.current.theme, + theme: holderRef.current?.theme, }); }); diff --git a/packages/bui-core/src/Toast/Toast.less b/packages/bui-core/src/Toast/Toast.less index 277dc5c8..c0cb93ae 100644 --- a/packages/bui-core/src/Toast/Toast.less +++ b/packages/bui-core/src/Toast/Toast.less @@ -3,13 +3,17 @@ .bui-toast { --min-width: var(--bui-toast-min-width, 86px); --max-width: var(--bui-toast-max-width, 80%); + --text-align: var(--bui-toast-text-align, center); --flex-direction: var(--bui-toast-flex-direction, column); --padding: var(--bui-toast-padding, var(--bui-spacing-xl)); --position-top: var(--bui-toast-position-top, 15%); --position-bottom: var(--bui-toast-position-bottom, 85%); --background-color: var(--bui-toast-bg-color, rgba(0, 0, 0, 0.8)); - --border-radius: var(--bui-toast-border-radius, --bui-shape-radius-default); - --icon-margin: var(--bui-toast-icon-margin-bottom, 8px); + --border-radius: var( + --bui-toast-border-radius, + var(--bui-shape-radius-default) + ); + --icon-margin: var(--bui-toast-icon-margin, 0 0 8px); --icon-font-size: var(--bui-toast-icon-font-size, 30px); &.bui-toast-allow-click { @@ -31,7 +35,7 @@ word-break: break-all; white-space: pre-wrap; background-color: var(--background-color); - text-align: center; + text-align: var(--text-align); font-family: var(--bui-font-family); &-center { @@ -55,7 +59,7 @@ align-items: center; .bui-svg-icon { - margin: 0 0 var(--icon-margin); + margin: var(--icon-margin); font-size: var(--icon-font-size); } } diff --git a/packages/bui-core/src/Toast/__tests__/Toast.test.tsx b/packages/bui-core/src/Toast/__tests__/Toast.test.tsx index e03be8e7..35e86756 100644 --- a/packages/bui-core/src/Toast/__tests__/Toast.test.tsx +++ b/packages/bui-core/src/Toast/__tests__/Toast.test.tsx @@ -1,15 +1,20 @@ import React from 'react'; -import { act, fireEvent, render } from 'testing'; +import { act, fireEvent, render, screen } from 'testing'; import { Button } from '@bifrostui/react'; import { ErrorCircleFilledBoldIcon } from '@bifrostui/icons'; +import { renderHook } from '@testing-library/react'; import { Toast } from '../index'; describe('Toast', () => { const rootClass = 'bui-toast'; + let toastHook; beforeEach(() => { document.body.innerHTML = ''; jest.useFakeTimers(); + renderHook(() => { + toastHook = Toast.useToast(); + }); }); afterEach(() => { @@ -346,4 +351,69 @@ describe('Toast', () => { }); expect(document.body.innerHTML.includes('提示内容')).toBeFalsy(); }); + it.each(['warning', 'loading', 'success', 'fail', 'clear'])( + 'should support basic api with useToast', + async (type) => { + const toast = toastHook?.[0]; + if (type === 'clear') { + render( + <> + + + + , + ); + fireEvent.click(screen.getByText('button one')); + fireEvent.click(screen.getByText('button two')); + expect(document.body.innerHTML.split('提示内容').length - 1).toBe(2); + fireEvent.click(screen.getByText('button three')); + await act(async () => { + await jest.runAllTimers(); + }); + expect(document.body.innerHTML.includes('提示内容')).toBeFalsy(); + } else { + render( + , + ); + fireEvent.click(screen.getByText(`${type} button`)); + expect( + document.body.querySelector('.bui-svg-icon'), + ).toBeInTheDocument(); + const toastDom = document.body.querySelector(`.${rootClass}`); + expect(toastDom.innerHTML.includes(`校验${type}`)).toBeTruthy(); + } + }, + ); }); diff --git a/packages/bui-core/src/Toast/index.en-US.md b/packages/bui-core/src/Toast/index.en-US.md index 0df00d43..2be465f2 100644 --- a/packages/bui-core/src/Toast/index.en-US.md +++ b/packages/bui-core/src/Toast/index.en-US.md @@ -87,7 +87,7 @@ export default () => { const theme = useTheme(); return ( - + {contextHolder}