From a8fc4a4257e33c4a292d25b3bf2a7094bd45d30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Wed, 21 Dec 2022 14:00:51 +0800 Subject: [PATCH 01/34] fix --- components/_util/wave/index.tsx | 224 ++++++++++------------ components/button/__tests__/wave.test.tsx | 10 +- 2 files changed, 107 insertions(+), 127 deletions(-) diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index c01745fbd415..0a3f4350229c 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -1,7 +1,7 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS'; import { composeRef, supportRef } from 'rc-util/lib/ref'; -import * as React from 'react'; -import { forwardRef } from 'react'; +import React, { useContext, useEffect, useRef } from 'react'; import type { ConfigConsumerProps, CSPConfig } from '../../config-provider'; import { ConfigConsumer, ConfigContext } from '../../config-provider'; import raf from '../raf'; @@ -70,115 +70,82 @@ export interface WaveProps { children?: React.ReactNode; } -export class InternalWave extends React.Component { - static contextType = ConfigContext; - - private instance?: { - cancel: () => void; - }; - - private containerRef = React.createRef(); - - private extraNode: HTMLDivElement; - - private clickWaveTimeoutId: number; - - private animationStartId: number; - - private animationStart: boolean = false; - - private destroyed: boolean = false; - - private csp?: CSPConfig; - - context: ConfigConsumerProps; +const InternalWave: React.FC = (props) => { + const { children, insertExtraNode, disabled } = props; + + const instance = useRef<{ cancel?: () => void }>({}); + const containerRef = useRef(null); + const extraNode = useRef(); + const clickWaveTimeoutId = useRef(null); + const animationStartId = useRef(); + const animationStart = useRef(false); + const destroyed = useRef(false); + const cspRef = useRef({}); + + const { getPrefixCls } = useContext(ConfigContext); + + const attributeName = React.useMemo( + () => + insertExtraNode + ? `${getPrefixCls('')}-click-animating` + : `${getPrefixCls('')}-click-animating-without-extra-node`, + [insertExtraNode], + ); - componentDidMount() { - this.destroyed = false; - const node = this.containerRef.current as HTMLDivElement; - if (!node || node.nodeType !== 1) { + const onTransitionStart = (e: AnimationEvent) => { + if (destroyed.current) { return; } - this.instance = this.bindAnimationEvent(node); - } - - componentWillUnmount() { - if (this.instance) { - this.instance.cancel(); - } - if (this.clickWaveTimeoutId) { - clearTimeout(this.clickWaveTimeoutId); + const node = containerRef.current; + if (!e || e.target !== node || animationStart.current) { + return; } + resetEffect(node as HTMLDivElement); + }; - this.destroyed = true; - } - - onClick = (node: HTMLElement, waveColor: string) => { - const { insertExtraNode, disabled } = this.props; + const onTransitionEnd = (e: AnimationEvent) => { + if (!e || e.animationName !== 'fadeEffect') { + return; + } + resetEffect(e.target as HTMLDivElement); + }; + const onClick = (node: HTMLDivElement, waveColor: string) => { if (disabled || !node || isHidden(node) || node.className.includes('-leave')) { return; } - this.extraNode = document.createElement('div'); - const { extraNode } = this; - const { getPrefixCls } = this.context; - extraNode.className = `${getPrefixCls('')}-click-animating-node`; - const attributeName = this.getAttributeName(); + extraNode.current = document.createElement('div'); + + extraNode.current.className = `${getPrefixCls('')}-click-animating-node`; + node.setAttribute(attributeName, 'true'); // Not white or transparent or grey if (isValidWaveColor(waveColor)) { - extraNode.style.borderColor = waveColor; + extraNode.current.style.borderColor = waveColor; const nodeRoot = node.getRootNode?.() || node.ownerDocument; const nodeBody = getValidateContainer(nodeRoot) ?? nodeRoot; styleForPseudo = updateCSS( ` - [${getPrefixCls('')}-click-animating-without-extra-node='true']::after, .${getPrefixCls('')}-click-animating-node { + [${getPrefixCls()}-click-animating-without-extra-node='true']::after, .${getPrefixCls()}-click-animating-node { --antd-wave-shadow-color: ${waveColor}; }`, 'antd-wave', - { csp: this.csp, attachTo: nodeBody }, + { csp: cspRef.current, attachTo: nodeBody }, ); } if (insertExtraNode) { - node.appendChild(extraNode); + node.appendChild(extraNode.current); } ['transition', 'animation'].forEach((name) => { - node.addEventListener(`${name}start`, this.onTransitionStart); - node.addEventListener(`${name}end`, this.onTransitionEnd); + node.addEventListener(`${name}start`, onTransitionStart); + node.addEventListener(`${name}end`, onTransitionEnd); }); }; - onTransitionStart = (e: AnimationEvent) => { - if (this.destroyed) { - return; - } - - const node = this.containerRef.current as HTMLDivElement; - if (!e || e.target !== node || this.animationStart) { - return; - } - this.resetEffect(node); - }; - - onTransitionEnd = (e: AnimationEvent) => { - if (!e || e.animationName !== 'fadeEffect') { - return; - } - this.resetEffect(e.target as HTMLElement); - }; - - getAttributeName() { - const { getPrefixCls } = this.context; - const { insertExtraNode } = this.props; - return insertExtraNode - ? `${getPrefixCls('')}-click-animating` - : `${getPrefixCls('')}-click-animating-without-extra-node`; - } - - bindAnimationEvent = (node?: HTMLElement) => { + const bindAnimationEvent = (node?: HTMLDivElement) => { if ( !node || !node.getAttribute || @@ -187,75 +154,96 @@ export class InternalWave extends React.Component { ) { return; } - const onClick = (e: MouseEvent) => { + const internalClick = (e: MouseEvent) => { // Fix radio button click twice if ((e.target as HTMLElement).tagName === 'INPUT' || isHidden(e.target as HTMLElement)) { return; } - this.resetEffect(node); + resetEffect(node); // Get wave color from target const waveColor = getTargetWaveColor(node); - this.clickWaveTimeoutId = window.setTimeout(() => this.onClick(node, waveColor), 0); + clickWaveTimeoutId.current = setTimeout(() => { + onClick(node, waveColor); + }, 0); - raf.cancel(this.animationStartId); - this.animationStart = true; + raf.cancel(animationStartId.current); + animationStart.current = true; // Render to trigger transition event cost 3 frames. Let's delay 10 frames to reset this. - this.animationStartId = raf(() => { - this.animationStart = false; + animationStartId.current = raf(() => { + animationStart.current = false; }, 10); }; - node.addEventListener('click', onClick, true); + node.addEventListener('click', internalClick, true); return { - cancel: () => { - node.removeEventListener('click', onClick, true); + cancel() { + node.removeEventListener('click', internalClick, true); }, }; }; - resetEffect(node: HTMLElement) { - if (!node || node === this.extraNode || !(node instanceof Element)) { + function resetEffect(node: HTMLDivElement) { + if (!node || node === extraNode.current || !(node instanceof Element)) { return; } - const { insertExtraNode } = this.props; - const attributeName = this.getAttributeName(); + node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466 if (styleForPseudo) { styleForPseudo.innerHTML = ''; } - if (insertExtraNode && this.extraNode && node.contains(this.extraNode)) { - node.removeChild(this.extraNode); + if (insertExtraNode && extraNode.current && node.contains(extraNode.current)) { + node.removeChild(extraNode.current); } ['transition', 'animation'].forEach((name) => { - node.removeEventListener(`${name}start`, this.onTransitionStart); - node.removeEventListener(`${name}end`, this.onTransitionEnd); + node.removeEventListener(`${name}start`, onTransitionStart); + node.removeEventListener(`${name}end`, onTransitionEnd); }); } - renderWave = ({ csp }: ConfigConsumerProps) => { - const { children } = this.props; - this.csp = csp; - - if (!React.isValidElement(children)) return children; - - let ref: React.Ref = this.containerRef; - if (supportRef(children)) { - ref = composeRef((children as any).ref, this.containerRef as any); + useEffect(() => { + destroyed.current = false; + const node = containerRef.current; + if (!node || node.nodeType !== 1) { + return; } + instance.current = bindAnimationEvent(node)!; + return () => { + if (instance.current) { + instance.current.cancel?.(); + } + if (clickWaveTimeoutId.current) { + clearTimeout(clickWaveTimeoutId.current); + } + destroyed.current = true; + }; + }, []); - return cloneElement(children, { ref }); - }; - - render() { - return {this.renderWave}; - } -} + return ( + + {({ csp }: ConfigConsumerProps) => { + cspRef.current = csp!; + if (!React.isValidElement(children)) { + return children; + } + + let ref: React.Ref = containerRef.current as any; + if (supportRef(children)) { + ref = composeRef((children as any).ref, containerRef.current as any); + } + + return cloneElement(children, { ref }); + }} + + ); +}; -const Wave = forwardRef((props, ref) => { +const Wave: React.FC = (props) => { useStyle(); - return ; -}); + // const prefixCls = getPrefixCls('wave'); + // const [wrapSSR, hashId] = useStyle(prefixCls); + return ; +}; export default Wave; diff --git a/components/button/__tests__/wave.test.tsx b/components/button/__tests__/wave.test.tsx index edeeb3aab23a..7a2b5927439e 100644 --- a/components/button/__tests__/wave.test.tsx +++ b/components/button/__tests__/wave.test.tsx @@ -8,18 +8,10 @@ let waveInstanceMock: any; jest.mock('../../_util/wave', () => { const Wave: typeof import('../../_util/wave') = jest.requireActual('../../_util/wave'); const WaveComponent = Wave.default; - return { ...Wave, __esModule: true, - default: (props: import('../../_util/wave').WaveProps) => ( - { - waveInstanceMock = node; - }} - {...props} - /> - ), + default: (props: import('../../_util/wave').WaveProps) => , }; }); From 1ae81522b795cefabdcc0219f17cf287ed592da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Wed, 21 Dec 2022 14:23:36 +0800 Subject: [PATCH 02/34] refactor[Wave]: CC => FC --- components/_util/__tests__/wave.test.tsx | 37 +--------- components/_util/wave/index.tsx | 21 ++---- components/button/__tests__/wave.test.tsx | 90 ++++++----------------- 3 files changed, 32 insertions(+), 116 deletions(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index e166cb2347bc..55f8cb8abbac 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -3,7 +3,6 @@ import mountTest from '../../../tests/shared/mountTest'; import { render, waitFakeTimer, fireEvent, act } from '../../../tests/utils'; import ConfigProvider from '../../config-provider'; import Wave from '../wave'; -import type { InternalWave } from '../wave'; describe('Wave component', () => { mountTest(Wave); @@ -187,47 +186,13 @@ describe('Wave component', () => { container.querySelector('button')?.click(); await waitFakeTimer(); let styles: HTMLCollectionOf | HTMLStyleElement[] = ( - container.querySelector('button')?.getRootNode() as HTMLButtonElement + container.querySelector('button')?.getRootNode() as HTMLButtonElement ).getElementsByTagName('style'); styles = filterStyles(styles); expect(styles[0].getAttribute('nonce')).toBe('YourNonceCode'); unmount(); }); - it('bindAnimationEvent should return when node is null', () => { - const ref = React.createRef(); - render( - - - , - ); - expect(ref.current?.bindAnimationEvent()).toBe(undefined); - }); - - it('bindAnimationEvent.onClick should return when children is hidden', () => { - const ref = React.createRef(); - render( - - - , - ); - expect(ref.current?.bindAnimationEvent()).toBe(undefined); - }); - - it('bindAnimationEvent.onClick should return when children is input', () => { - const ref = React.createRef(); - render( - - - , - ); - expect(ref.current?.bindAnimationEvent()).toBe(undefined); - }); - it('should not throw when click it', () => { expect(() => { const { container } = render( diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 0a3f4350229c..58db1a0b1ff1 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -73,7 +73,7 @@ export interface WaveProps { const InternalWave: React.FC = (props) => { const { children, insertExtraNode, disabled } = props; - const instance = useRef<{ cancel?: () => void }>({}); + const instanceRef = useRef<{ cancel?: () => void }>({}); const containerRef = useRef(null); const extraNode = useRef(); const clickWaveTimeoutId = useRef(null); @@ -208,15 +208,15 @@ const InternalWave: React.FC = (props) => { if (!node || node.nodeType !== 1) { return; } - instance.current = bindAnimationEvent(node)!; + instanceRef.current = bindAnimationEvent(node)!; return () => { - if (instance.current) { - instance.current.cancel?.(); + destroyed.current = true; + if (instanceRef.current) { + instanceRef.current.cancel?.(); } if (clickWaveTimeoutId.current) { clearTimeout(clickWaveTimeoutId.current); } - destroyed.current = true; }; }, []); @@ -227,12 +227,9 @@ const InternalWave: React.FC = (props) => { if (!React.isValidElement(children)) { return children; } - - let ref: React.Ref = containerRef.current as any; - if (supportRef(children)) { - ref = composeRef((children as any).ref, containerRef.current as any); - } - + const ref = supportRef(children) + ? composeRef((children as any).ref, containerRef) + : containerRef; return cloneElement(children, { ref }); }} @@ -241,8 +238,6 @@ const InternalWave: React.FC = (props) => { const Wave: React.FC = (props) => { useStyle(); - // const prefixCls = getPrefixCls('wave'); - // const [wrapSSR, hashId] = useStyle(prefixCls); return ; }; diff --git a/components/button/__tests__/wave.test.tsx b/components/button/__tests__/wave.test.tsx index 7a2b5927439e..8df5dd2f90cd 100644 --- a/components/button/__tests__/wave.test.tsx +++ b/components/button/__tests__/wave.test.tsx @@ -1,10 +1,8 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import Button from '..'; -import { fireEvent, render, assertsExist } from '../../../tests/utils'; +import { fireEvent, render } from '../../../tests/utils'; -// Mock Wave ref -let waveInstanceMock: any; jest.mock('../../_util/wave', () => { const Wave: typeof import('../../_util/wave') = jest.requireActual('../../_util/wave'); const WaveComponent = Wave.default; @@ -25,97 +23,55 @@ describe('click wave effect', () => { jest.useRealTimers(); }); - async function clickButton(wrapper: any) { - const element = wrapper.container.firstChild; + async function clickButton(container: HTMLElement) { + const element = container.firstChild; // https://github.com/testing-library/user-event/issues/833 - await userEvent.setup({ advanceTimers: jest.advanceTimersByTime }).click(element); - fireEvent(element, new Event('transitionstart')); - fireEvent(element, new Event('animationend')); + await userEvent.setup({ advanceTimers: jest.advanceTimersByTime }).click(element as Element); + fireEvent(element!, new Event('transitionstart')); + fireEvent(element!, new Event('animationend')); } it('should have click wave effect for primary button', async () => { - const wrapper = render(); - await clickButton(wrapper); - expect(wrapper.container.querySelector('.ant-btn')).toHaveAttribute( + const { container } = render(); + await clickButton(container); + expect(container.querySelector('.ant-btn')).toHaveAttribute( 'ant-click-animating-without-extra-node', ); }); it('should have click wave effect for default button', async () => { - const wrapper = render(); - await clickButton(wrapper); - expect(wrapper.container.querySelector('.ant-btn')).toHaveAttribute( + const { container } = render(); + await clickButton(container); + expect(container.querySelector('.ant-btn')).toHaveAttribute( 'ant-click-animating-without-extra-node', ); }); it('should not have click wave effect for link type button', async () => { - const wrapper = render(); - await clickButton(wrapper); - expect(wrapper.container.querySelector('.ant-btn')).not.toHaveAttribute( + const { container } = render(); + await clickButton(container); + expect(container.querySelector('.ant-btn')).not.toHaveAttribute( 'ant-click-animating-without-extra-node', ); }); it('should not have click wave effect for text type button', async () => { - const wrapper = render(); - await clickButton(wrapper); - expect(wrapper.container.querySelector('.ant-btn')).not.toHaveAttribute( + const { container } = render(); + await clickButton(container); + expect(container.querySelector('.ant-btn')).not.toHaveAttribute( 'ant-click-animating-without-extra-node', ); }); it('should handle transitionstart', async () => { - const wrapper = render(); - await clickButton(wrapper); - const buttonNode = wrapper.container.querySelector('.ant-btn')!; + const { container, unmount } = render(); + await clickButton(container); + const buttonNode = container.querySelector('.ant-btn')!; fireEvent(buttonNode, new Event('transitionstart')); - expect(wrapper.container.querySelector('.ant-btn')).toHaveAttribute( + expect(container.querySelector('.ant-btn')).toHaveAttribute( 'ant-click-animating-without-extra-node', ); - wrapper.unmount(); + unmount(); fireEvent(buttonNode, new Event('transitionstart')); }); - - it('should run resetEffect in transitionstart', async () => { - const wrapper = render(); - assertsExist(waveInstanceMock); - const resetEffect = jest.spyOn(waveInstanceMock, 'resetEffect'); - await clickButton(wrapper); - expect(resetEffect).toHaveBeenCalledTimes(1); - await userEvent - .setup({ advanceTimers: jest.advanceTimersByTime }) - .click(wrapper.container.querySelector('.ant-btn')!); - expect(resetEffect).toHaveBeenCalledTimes(2); - waveInstanceMock.animationStart = false; - fireEvent(wrapper.container.querySelector('.ant-btn')!, new Event('transitionstart')); - expect(resetEffect).toHaveBeenCalledTimes(3); - resetEffect.mockRestore(); - }); - - it('should handle transitionend', async () => { - const wrapper = render(); - assertsExist(waveInstanceMock); - const resetEffect = jest.spyOn(waveInstanceMock, 'resetEffect'); - await clickButton(wrapper); - expect(resetEffect).toHaveBeenCalledTimes(1); - const event = new Event('animationend'); - Object.assign(event, { animationName: 'fadeEffect' }); - fireEvent(wrapper.container.querySelector('.ant-btn')!, event); - expect(resetEffect).toHaveBeenCalledTimes(2); - resetEffect.mockRestore(); - }); - - it('Wave on falsy element', async () => { - const { default: Wave } = jest.requireActual('../../_util/wave'); - let waveInstance: any; - render( - { - waveInstance = node; - }} - />, - ); - waveInstance.resetEffect(); - }); }); From 4a320b6d5c592a5c9b5b0d1496ef5b0cfdd95b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Wed, 21 Dec 2022 14:40:06 +0800 Subject: [PATCH 03/34] fix lint --- components/_util/__tests__/wave.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 55f8cb8abbac..77d0ddd6687b 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -27,7 +27,7 @@ describe('Wave component', () => { } }); - function filterStyles(styles: any) { + function filterStyles(styles: HTMLCollectionOf) { return Array.from(styles).filter( (style: HTMLStyleElement) => !style.hasAttribute('data-css-hash'), ); From 61b441a60da60ef8c8c3f83e5d4761b5f262d4d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Wed, 21 Dec 2022 15:02:20 +0800 Subject: [PATCH 04/34] fix --- components/_util/wave/index.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 58db1a0b1ff1..c8f58ef00814 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -70,7 +70,7 @@ export interface WaveProps { children?: React.ReactNode; } -const InternalWave: React.FC = (props) => { +const Wave: React.FC = (props) => { const { children, insertExtraNode, disabled } = props; const instanceRef = useRef<{ cancel?: () => void }>({}); @@ -87,8 +87,8 @@ const InternalWave: React.FC = (props) => { const attributeName = React.useMemo( () => insertExtraNode - ? `${getPrefixCls('')}-click-animating` - : `${getPrefixCls('')}-click-animating-without-extra-node`, + ? `${getPrefixCls()}-click-animating` + : `${getPrefixCls()}-click-animating-without-extra-node`, [insertExtraNode], ); @@ -117,7 +117,7 @@ const InternalWave: React.FC = (props) => { extraNode.current = document.createElement('div'); - extraNode.current.className = `${getPrefixCls('')}-click-animating-node`; + extraNode.current.className = `${getPrefixCls()}-click-animating-node`; node.setAttribute(attributeName, 'true'); // Not white or transparent or grey @@ -219,7 +219,7 @@ const InternalWave: React.FC = (props) => { } }; }, []); - + useStyle(); return ( {({ csp }: ConfigConsumerProps) => { @@ -236,9 +236,4 @@ const InternalWave: React.FC = (props) => { ); }; -const Wave: React.FC = (props) => { - useStyle(); - return ; -}; - export default Wave; From 51dbed8db2e59e8fb847574e9e8fffbecbc1c2d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Wed, 21 Dec 2022 15:22:57 +0800 Subject: [PATCH 05/34] fix --- components/_util/wave/index.tsx | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index c8f58ef00814..13e1926b8603 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -1,9 +1,10 @@ +/* eslint-disable react/jsx-no-useless-fragment */ /* eslint-disable @typescript-eslint/no-use-before-define */ import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS'; import { composeRef, supportRef } from 'rc-util/lib/ref'; import React, { useContext, useEffect, useRef } from 'react'; -import type { ConfigConsumerProps, CSPConfig } from '../../config-provider'; -import { ConfigConsumer, ConfigContext } from '../../config-provider'; +import type { ConfigConsumerProps } from '../../config-provider'; +import { ConfigContext } from '../../config-provider'; import raf from '../raf'; import { cloneElement } from '../reactNode'; import useStyle from './style'; @@ -80,9 +81,8 @@ const Wave: React.FC = (props) => { const animationStartId = useRef(); const animationStart = useRef(false); const destroyed = useRef(false); - const cspRef = useRef({}); - const { getPrefixCls } = useContext(ConfigContext); + const { getPrefixCls, csp } = useContext(ConfigContext); const attributeName = React.useMemo( () => @@ -133,7 +133,7 @@ const Wave: React.FC = (props) => { --antd-wave-shadow-color: ${waveColor}; }`, 'antd-wave', - { csp: cspRef.current, attachTo: nodeBody }, + { csp, attachTo: nodeBody }, ); } if (insertExtraNode) { @@ -219,21 +219,16 @@ const Wave: React.FC = (props) => { } }; }, []); + useStyle(); - return ( - - {({ csp }: ConfigConsumerProps) => { - cspRef.current = csp!; - if (!React.isValidElement(children)) { - return children; - } - const ref = supportRef(children) - ? composeRef((children as any).ref, containerRef) - : containerRef; - return cloneElement(children, { ref }); - }} - - ); + + if (!React.isValidElement(children)) { + return <>{children}; + } + + const ref = supportRef(children) ? composeRef((children as any).ref, containerRef) : containerRef; + + return cloneElement(children, { ref }); }; export default Wave; From 22fd1a1d03895b83ecf1aa379730eb37e2ac1838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Fri, 23 Dec 2022 10:49:18 +0800 Subject: [PATCH 06/34] fix --- components/_util/wave/index.tsx | 55 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 13e1926b8603..616702941ab2 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -73,27 +73,25 @@ export interface WaveProps { const Wave: React.FC = (props) => { const { children, insertExtraNode, disabled } = props; - + const { getPrefixCls, csp } = useContext(ConfigContext); const instanceRef = useRef<{ cancel?: () => void }>({}); - const containerRef = useRef(null); + const containerRef = useRef(); const extraNode = useRef(); const clickWaveTimeoutId = useRef(null); const animationStartId = useRef(); const animationStart = useRef(false); - const destroyed = useRef(false); - - const { getPrefixCls, csp } = useContext(ConfigContext); + const destroyedRef = useRef(false); const attributeName = React.useMemo( () => insertExtraNode ? `${getPrefixCls()}-click-animating` : `${getPrefixCls()}-click-animating-without-extra-node`, - [insertExtraNode], + [getPrefixCls, insertExtraNode], ); const onTransitionStart = (e: AnimationEvent) => { - if (destroyed.current) { + if (destroyedRef.current) { return; } const node = containerRef.current; @@ -110,6 +108,26 @@ const Wave: React.FC = (props) => { resetEffect(e.target as HTMLDivElement); }; + function resetEffect(node: HTMLDivElement) { + if (!node || node === extraNode.current || !(node instanceof Element)) { + return; + } + + node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466 + + if (styleForPseudo) { + styleForPseudo.innerHTML = ''; + } + + if (insertExtraNode && extraNode.current && node.contains(extraNode.current)) { + node.removeChild(extraNode.current); + } + ['transition', 'animation'].forEach((name) => { + node.removeEventListener(`${name}start`, onTransitionStart); + node.removeEventListener(`${name}end`, onTransitionEnd); + }); + } + const onClick = (node: HTMLDivElement, waveColor: string) => { if (disabled || !node || isHidden(node) || node.className.includes('-leave')) { return; @@ -182,35 +200,14 @@ const Wave: React.FC = (props) => { }; }; - function resetEffect(node: HTMLDivElement) { - if (!node || node === extraNode.current || !(node instanceof Element)) { - return; - } - - node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466 - - if (styleForPseudo) { - styleForPseudo.innerHTML = ''; - } - - if (insertExtraNode && extraNode.current && node.contains(extraNode.current)) { - node.removeChild(extraNode.current); - } - ['transition', 'animation'].forEach((name) => { - node.removeEventListener(`${name}start`, onTransitionStart); - node.removeEventListener(`${name}end`, onTransitionEnd); - }); - } - useEffect(() => { - destroyed.current = false; const node = containerRef.current; if (!node || node.nodeType !== 1) { return; } instanceRef.current = bindAnimationEvent(node)!; return () => { - destroyed.current = true; + destroyedRef.current = true; if (instanceRef.current) { instanceRef.current.cancel?.(); } From 1f9d0f6f613ff455b8f74c1ef21cc0c5d9a384b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Fri, 23 Dec 2022 10:58:51 +0800 Subject: [PATCH 07/34] add test case --- components/_util/__tests__/wave.test.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 77d0ddd6687b..87c01ea12bcf 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import mountTest from '../../../tests/shared/mountTest'; import { render, waitFakeTimer, fireEvent, act } from '../../../tests/utils'; import ConfigProvider from '../../config-provider'; +import Button from '../../button'; import Wave from '../wave'; describe('Wave component', () => { @@ -276,4 +277,20 @@ describe('Wave component', () => { jest.useRealTimers(); }); + + it('wave transitionStart and transitionEnd', async () => { + jest.useFakeTimers(); + const { container } = render( + + + , + ); + const event = new Event('animationend'); + Object.assign(event, { animationName: 'fadeEffect' }); + fireEvent.transitionStart(container.querySelector('button')!, event); + await waitFakeTimer(); + fireEvent.transitionEnd(container.querySelector('button')!, event); + await waitFakeTimer(); + jest.useRealTimers(); + }); }); From 2dbb432e46b24a899ddfe2817c27e341f14ab38e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Fri, 23 Dec 2022 11:02:08 +0800 Subject: [PATCH 08/34] add test case --- components/_util/__tests__/wave.test.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 87c01ea12bcf..ee4f80157665 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -278,17 +278,29 @@ describe('Wave component', () => { jest.useRealTimers(); }); - it('wave transitionStart and transitionEnd', async () => { + it('wave transitionStart', async () => { jest.useFakeTimers(); const { container } = render( , ); - const event = new Event('animationend'); + const event = new Event('animationstart'); Object.assign(event, { animationName: 'fadeEffect' }); fireEvent.transitionStart(container.querySelector('button')!, event); await waitFakeTimer(); + jest.useRealTimers(); + }); + + it('wave transitionEnd', async () => { + jest.useFakeTimers(); + const { container } = render( + + + , + ); + const event = new Event('animationend'); + Object.assign(event, { animationName: 'fadeEffect' }); fireEvent.transitionEnd(container.querySelector('button')!, event); await waitFakeTimer(); jest.useRealTimers(); From 40b415a121bd8125df9ef429ffef4c46670e95fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Fri, 23 Dec 2022 11:17:16 +0800 Subject: [PATCH 09/34] fix test --- components/_util/__tests__/wave.test.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index ee4f80157665..1f191c3c1bac 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -2,7 +2,6 @@ import React from 'react'; import mountTest from '../../../tests/shared/mountTest'; import { render, waitFakeTimer, fireEvent, act } from '../../../tests/utils'; import ConfigProvider from '../../config-provider'; -import Button from '../../button'; import Wave from '../wave'; describe('Wave component', () => { @@ -280,29 +279,29 @@ describe('Wave component', () => { it('wave transitionStart', async () => { jest.useFakeTimers(); - const { container } = render( + const { container, unmount } = render( - + , ); - const event = new Event('animationstart'); - Object.assign(event, { animationName: 'fadeEffect' }); - fireEvent.transitionStart(container.querySelector('button')!, event); + fireEvent.transitionStart(container.querySelector('button')!, new Event('transitionstart')); await waitFakeTimer(); jest.useRealTimers(); + unmount(); }); it('wave transitionEnd', async () => { jest.useFakeTimers(); - const { container } = render( + const { container, unmount } = render( - + , ); const event = new Event('animationend'); - Object.assign(event, { animationName: 'fadeEffect' }); - fireEvent.transitionEnd(container.querySelector('button')!, event); + const options = Object.assign(event, { animationName: 'fadeEffect' }); + fireEvent.transitionEnd(container.querySelector('button')!, options); await waitFakeTimer(); jest.useRealTimers(); + unmount(); }); }); From 7d1b928e1cbb865b8273050baa3bc1973bfd215e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Fri, 23 Dec 2022 11:46:16 +0800 Subject: [PATCH 10/34] fix test --- components/_util/__tests__/wave.test.tsx | 86 +++++++++++++++++------- 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 1f191c3c1bac..31d781d93e18 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -193,6 +193,40 @@ describe('Wave component', () => { unmount(); }); + it('bindAnimationEvent should not throw error when children is disabled', () => { + expect(() => { + render( + + + , + ); + }).not.toThrow(); + }); + + it('bindAnimationEvent.onClick should not throw error when children is hidden', () => { + expect(() => { + render( + + + , + ); + }).not.toThrow(); + }); + + it('bindAnimationEvent.onClick should not throw error when children is input', () => { + expect(() => { + render( + + + , + ); + }).not.toThrow(); + }); + it('should not throw when click it', () => { expect(() => { const { container } = render( @@ -277,31 +311,31 @@ describe('Wave component', () => { jest.useRealTimers(); }); - it('wave transitionStart', async () => { - jest.useFakeTimers(); - const { container, unmount } = render( - - - , - ); - fireEvent.transitionStart(container.querySelector('button')!, new Event('transitionstart')); - await waitFakeTimer(); - jest.useRealTimers(); - unmount(); - }); + // it('wave transitionStart', async () => { + // jest.useFakeTimers(); + // const { container, unmount } = render( + // + // + // , + // ); + // fireEvent.transitionStart(container.querySelector('button')!, new Event('transitionstart')); + // await waitFakeTimer(); + // jest.useRealTimers(); + // unmount(); + // }); - it('wave transitionEnd', async () => { - jest.useFakeTimers(); - const { container, unmount } = render( - - - , - ); - const event = new Event('animationend'); - const options = Object.assign(event, { animationName: 'fadeEffect' }); - fireEvent.transitionEnd(container.querySelector('button')!, options); - await waitFakeTimer(); - jest.useRealTimers(); - unmount(); - }); + // it('wave transitionEnd', async () => { + // jest.useFakeTimers(); + // const { container, unmount } = render( + // + // + // , + // ); + // const event = new Event('animationend'); + // const options = Object.assign(event, { animationName: 'fadeEffect' }); + // fireEvent.transitionEnd(container.querySelector('button')!, options); + // await waitFakeTimer(); + // jest.useRealTimers(); + // unmount(); + // }); }); From ad0789737471ca3b0e53ad0011cf6f92c1adc82d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Fri, 23 Dec 2022 13:01:47 +0800 Subject: [PATCH 11/34] test case --- components/_util/__tests__/wave.test.tsx | 54 ++++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 31d781d93e18..0ca35ce083c4 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -311,31 +311,31 @@ describe('Wave component', () => { jest.useRealTimers(); }); - // it('wave transitionStart', async () => { - // jest.useFakeTimers(); - // const { container, unmount } = render( - // - // - // , - // ); - // fireEvent.transitionStart(container.querySelector('button')!, new Event('transitionstart')); - // await waitFakeTimer(); - // jest.useRealTimers(); - // unmount(); - // }); - - // it('wave transitionEnd', async () => { - // jest.useFakeTimers(); - // const { container, unmount } = render( - // - // - // , - // ); - // const event = new Event('animationend'); - // const options = Object.assign(event, { animationName: 'fadeEffect' }); - // fireEvent.transitionEnd(container.querySelector('button')!, options); - // await waitFakeTimer(); - // jest.useRealTimers(); - // unmount(); - // }); + it('wave transitionStart', async () => { + jest.useFakeTimers(); + const { container, unmount } = render( + + + , + ); + fireEvent.transitionStart(container.querySelector('button')!, new Event('transitionstart')); + await waitFakeTimer(); + jest.useRealTimers(); + unmount(); + }); + + it('wave transitionEnd', async () => { + jest.useFakeTimers(); + const { container, unmount } = render( + + + , + ); + const event = new Event('animationend'); + const options = Object.assign(event, { animationName: 'fadeEffect' }); + fireEvent.transitionEnd(container.querySelector('button')!, options); + await waitFakeTimer(); + jest.useRealTimers(); + unmount(); + }); }); From 9598d0e579778f32e78b68f7b0b972a385d0fca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Fri, 23 Dec 2022 13:09:52 +0800 Subject: [PATCH 12/34] add test case --- components/_util/__tests__/wave.test.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 0ca35ce083c4..7a47c2881e03 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -1,3 +1,4 @@ +import userEvent from '@testing-library/user-event'; import React from 'react'; import mountTest from '../../../tests/shared/mountTest'; import { render, waitFakeTimer, fireEvent, act } from '../../../tests/utils'; @@ -33,6 +34,14 @@ describe('Wave component', () => { ); } + async function clickButton(container: HTMLElement) { + const element = container.firstChild; + // https://github.com/testing-library/user-event/issues/833 + await userEvent.setup({ advanceTimers: jest.advanceTimersByTime }).click(element as Element); + fireEvent(element!, new Event('transitionstart')); + fireEvent(element!, new Event('animationend')); + } + it('isHidden works', () => { const TEST_NODE_ENV = process.env.NODE_ENV; process.env.NODE_ENV = 'development'; @@ -318,6 +327,7 @@ describe('Wave component', () => { , ); + await clickButton(container); fireEvent.transitionStart(container.querySelector('button')!, new Event('transitionstart')); await waitFakeTimer(); jest.useRealTimers(); @@ -331,6 +341,7 @@ describe('Wave component', () => { , ); + await clickButton(container); const event = new Event('animationend'); const options = Object.assign(event, { animationName: 'fadeEffect' }); fireEvent.transitionEnd(container.querySelector('button')!, options); From 4cd8f99e5dd4370e67a0b0cbdb09e1872fa32fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Sat, 24 Dec 2022 09:17:00 +0800 Subject: [PATCH 13/34] fix --- components/_util/wave/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 616702941ab2..38d124cf80f9 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -201,6 +201,7 @@ const Wave: React.FC = (props) => { }; useEffect(() => { + destroyedRef.current = false; const node = containerRef.current; if (!node || node.nodeType !== 1) { return; From 4ae80983ef9e128267c1135b785378db96892cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Sat, 24 Dec 2022 09:37:26 +0800 Subject: [PATCH 14/34] fix --- components/_util/wave/index.tsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 38d124cf80f9..cd52aa63dad1 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -74,13 +74,11 @@ export interface WaveProps { const Wave: React.FC = (props) => { const { children, insertExtraNode, disabled } = props; const { getPrefixCls, csp } = useContext(ConfigContext); - const instanceRef = useRef<{ cancel?: () => void }>({}); const containerRef = useRef(); const extraNode = useRef(); const clickWaveTimeoutId = useRef(null); const animationStartId = useRef(); const animationStart = useRef(false); - const destroyedRef = useRef(false); const attributeName = React.useMemo( () => @@ -91,9 +89,6 @@ const Wave: React.FC = (props) => { ); const onTransitionStart = (e: AnimationEvent) => { - if (destroyedRef.current) { - return; - } const node = containerRef.current; if (!e || e.target !== node || animationStart.current) { return; @@ -201,17 +196,12 @@ const Wave: React.FC = (props) => { }; useEffect(() => { - destroyedRef.current = false; const node = containerRef.current; if (!node || node.nodeType !== 1) { return; } - instanceRef.current = bindAnimationEvent(node)!; return () => { - destroyedRef.current = true; - if (instanceRef.current) { - instanceRef.current.cancel?.(); - } + bindAnimationEvent(node)?.cancel?.(); if (clickWaveTimeoutId.current) { clearTimeout(clickWaveTimeoutId.current); } From bf3bd7c8215870dccec1f804df1febb3dee68802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Sat, 24 Dec 2022 09:52:37 +0800 Subject: [PATCH 15/34] fix --- components/_util/wave/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index cd52aa63dad1..f89b987839a5 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -74,6 +74,7 @@ export interface WaveProps { const Wave: React.FC = (props) => { const { children, insertExtraNode, disabled } = props; const { getPrefixCls, csp } = useContext(ConfigContext); + const instanceRef = useRef<{ cancel?: () => void }>({}); const containerRef = useRef(); const extraNode = useRef(); const clickWaveTimeoutId = useRef(null); @@ -200,8 +201,11 @@ const Wave: React.FC = (props) => { if (!node || node.nodeType !== 1) { return; } + instanceRef.current = bindAnimationEvent(node)!; return () => { - bindAnimationEvent(node)?.cancel?.(); + if (instanceRef.current) { + instanceRef.current.cancel?.(); + } if (clickWaveTimeoutId.current) { clearTimeout(clickWaveTimeoutId.current); } From 26acfaf2996b87979c11f92de36ac7aff17c7088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Sat, 24 Dec 2022 10:20:40 +0800 Subject: [PATCH 16/34] fix --- components/_util/wave/index.tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index f89b987839a5..1186a9aab63a 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -74,10 +74,9 @@ export interface WaveProps { const Wave: React.FC = (props) => { const { children, insertExtraNode, disabled } = props; const { getPrefixCls, csp } = useContext(ConfigContext); - const instanceRef = useRef<{ cancel?: () => void }>({}); const containerRef = useRef(); const extraNode = useRef(); - const clickWaveTimeoutId = useRef(null); + const clickTimeout = useRef(null); const animationStartId = useRef(); const animationStart = useRef(false); @@ -176,7 +175,7 @@ const Wave: React.FC = (props) => { resetEffect(node); // Get wave color from target const waveColor = getTargetWaveColor(node); - clickWaveTimeoutId.current = setTimeout(() => { + clickTimeout.current = setTimeout(() => { onClick(node, waveColor); }, 0); @@ -201,13 +200,11 @@ const Wave: React.FC = (props) => { if (!node || node.nodeType !== 1) { return; } - instanceRef.current = bindAnimationEvent(node)!; + bindAnimationEvent(node); return () => { - if (instanceRef.current) { - instanceRef.current.cancel?.(); - } - if (clickWaveTimeoutId.current) { - clearTimeout(clickWaveTimeoutId.current); + bindAnimationEvent(node)?.cancel?.(); + if (clickTimeout.current) { + clearTimeout(clickTimeout.current); } }; }, []); From b5cc24894548f27989cf7cb633f830f00d83f150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Sat, 24 Dec 2022 11:32:54 +0800 Subject: [PATCH 17/34] raname --- components/_util/wave/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 1186a9aab63a..954186deba34 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -78,7 +78,7 @@ const Wave: React.FC = (props) => { const extraNode = useRef(); const clickTimeout = useRef(null); const animationStartId = useRef(); - const animationStart = useRef(false); + const isAnimationStart = useRef(false); const attributeName = React.useMemo( () => @@ -90,7 +90,7 @@ const Wave: React.FC = (props) => { const onTransitionStart = (e: AnimationEvent) => { const node = containerRef.current; - if (!e || e.target !== node || animationStart.current) { + if (!e || e.target !== node || isAnimationStart.current) { return; } resetEffect(node as HTMLDivElement); @@ -180,11 +180,11 @@ const Wave: React.FC = (props) => { }, 0); raf.cancel(animationStartId.current); - animationStart.current = true; + isAnimationStart.current = true; // Render to trigger transition event cost 3 frames. Let's delay 10 frames to reset this. animationStartId.current = raf(() => { - animationStart.current = false; + isAnimationStart.current = false; }, 10); }; node.addEventListener('click', internalClick, true); From b61bc3af06ed07f2ae6a69a66e9201766aaeca7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Sun, 25 Dec 2022 14:15:24 +0800 Subject: [PATCH 18/34] fix --- components/_util/wave/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 954186deba34..6be260c485ef 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -169,7 +169,7 @@ const Wave: React.FC = (props) => { } const internalClick = (e: MouseEvent) => { // Fix radio button click twice - if ((e.target as HTMLElement).tagName === 'INPUT' || isHidden(e.target as HTMLElement)) { + if ((e.target as HTMLElement)?.tagName === 'INPUT' || isHidden(e.target as HTMLElement)) { return; } resetEffect(node); From 62cd45c7b785de59c33376c98eda58102f75515f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Tue, 27 Dec 2022 01:54:27 +0800 Subject: [PATCH 19/34] test case --- components/_util/__tests__/wave.test.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 7a47c2881e03..50516f7ba3e3 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -328,7 +328,7 @@ describe('Wave component', () => { , ); await clickButton(container); - fireEvent.transitionStart(container.querySelector('button')!, new Event('transitionstart')); + fireEvent.transitionStart(container.querySelector('button')!); await waitFakeTimer(); jest.useRealTimers(); unmount(); @@ -342,9 +342,7 @@ describe('Wave component', () => { , ); await clickButton(container); - const event = new Event('animationend'); - const options = Object.assign(event, { animationName: 'fadeEffect' }); - fireEvent.transitionEnd(container.querySelector('button')!, options); + fireEvent.transitionEnd(container.querySelector('button')!, { animationName: 'fadeEffect' }); await waitFakeTimer(); jest.useRealTimers(); unmount(); From 593680a48f6983c260d36fe62e91c5421e620bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Tue, 27 Dec 2022 02:46:27 +0800 Subject: [PATCH 20/34] test case --- components/_util/__tests__/wave.test.tsx | 16 +--------------- components/_util/wave/index.tsx | 2 +- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 50516f7ba3e3..081639e02b34 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -328,21 +328,7 @@ describe('Wave component', () => { , ); await clickButton(container); - fireEvent.transitionStart(container.querySelector('button')!); - await waitFakeTimer(); - jest.useRealTimers(); - unmount(); - }); - - it('wave transitionEnd', async () => { - jest.useFakeTimers(); - const { container, unmount } = render( - - - , - ); - await clickButton(container); - fireEvent.transitionEnd(container.querySelector('button')!, { animationName: 'fadeEffect' }); + fireEvent.click(container.querySelector('button')!); await waitFakeTimer(); jest.useRealTimers(); unmount(); diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 6be260c485ef..9aa0c9effc0b 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -104,7 +104,7 @@ const Wave: React.FC = (props) => { }; function resetEffect(node: HTMLDivElement) { - if (!node || node === extraNode.current || !(node instanceof Element)) { + if (!node || node === extraNode.current || !(node instanceof Element) || disabled) { return; } From 2e921bd0f72328a253dd0e144c88673654bf6bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Tue, 27 Dec 2022 03:13:26 +0800 Subject: [PATCH 21/34] test case --- components/_util/__tests__/wave.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 081639e02b34..3f500b18134e 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -329,6 +329,7 @@ describe('Wave component', () => { ); await clickButton(container); fireEvent.click(container.querySelector('button')!); + fireEvent.click(container.querySelector('button')!); await waitFakeTimer(); jest.useRealTimers(); unmount(); From 3803dd2b089dcc16da5ff8f40cf877bbaa0c5cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Tue, 27 Dec 2022 20:37:00 +0800 Subject: [PATCH 22/34] fix test --- components/_util/__tests__/wave.test.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 3f500b18134e..6480c1b96f88 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -1,7 +1,7 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import mountTest from '../../../tests/shared/mountTest'; -import { render, waitFakeTimer, fireEvent, act } from '../../../tests/utils'; +import { render, waitFakeTimer, fireEvent, act, sleep } from '../../../tests/utils'; import ConfigProvider from '../../config-provider'; import Wave from '../wave'; @@ -34,14 +34,6 @@ describe('Wave component', () => { ); } - async function clickButton(container: HTMLElement) { - const element = container.firstChild; - // https://github.com/testing-library/user-event/issues/833 - await userEvent.setup({ advanceTimers: jest.advanceTimersByTime }).click(element as Element); - fireEvent(element!, new Event('transitionstart')); - fireEvent(element!, new Event('animationend')); - } - it('isHidden works', () => { const TEST_NODE_ENV = process.env.NODE_ENV; process.env.NODE_ENV = 'development'; @@ -327,10 +319,8 @@ describe('Wave component', () => { , ); - await clickButton(container); fireEvent.click(container.querySelector('button')!); - fireEvent.click(container.querySelector('button')!); - await waitFakeTimer(); + await sleep(1000); jest.useRealTimers(); unmount(); }); From 931835d31c813a740363ba12d8a585a7caafc31e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Tue, 27 Dec 2022 22:42:59 +0800 Subject: [PATCH 23/34] test case --- components/_util/__tests__/wave.test.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 6480c1b96f88..ccdbd1e7e92c 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -1,4 +1,3 @@ -import userEvent from '@testing-library/user-event'; import React from 'react'; import mountTest from '../../../tests/shared/mountTest'; import { render, waitFakeTimer, fireEvent, act, sleep } from '../../../tests/utils'; @@ -314,7 +313,7 @@ describe('Wave component', () => { it('wave transitionStart', async () => { jest.useFakeTimers(); - const { container, unmount } = render( + const { container } = render( , @@ -322,6 +321,5 @@ describe('Wave component', () => { fireEvent.click(container.querySelector('button')!); await sleep(1000); jest.useRealTimers(); - unmount(); }); }); From ec537bc588f0d049ceca6b7aae80b96ebd86ab2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 28 Dec 2022 16:49:52 +0800 Subject: [PATCH 24/34] refactor: Use React way --- components/_util/__tests__/wave.test.tsx | 5 +- components/_util/wave/WaveEffect.tsx | 121 +++++++++++++ components/_util/wave/index.tsx | 212 ++++------------------- components/_util/wave/style.ts | 106 +++--------- components/_util/wave/useWave.tsx | 20 +++ components/_util/wave/util.ts | 45 +++++ components/switch/index.tsx | 2 +- components/theme/interface/components.ts | 4 + 8 files changed, 249 insertions(+), 266 deletions(-) create mode 100644 components/_util/wave/WaveEffect.tsx create mode 100644 components/_util/wave/useWave.tsx create mode 100644 components/_util/wave/util.ts diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index ccdbd1e7e92c..9b8d9f521606 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import mountTest from '../../../tests/shared/mountTest'; -import { render, waitFakeTimer, fireEvent, act, sleep } from '../../../tests/utils'; +import { render, waitFakeTimer, fireEvent, act } from '../../../tests/utils'; import ConfigProvider from '../../config-provider'; import Wave from '../wave'; @@ -312,14 +312,11 @@ describe('Wave component', () => { }); it('wave transitionStart', async () => { - jest.useFakeTimers(); const { container } = render( , ); fireEvent.click(container.querySelector('button')!); - await sleep(1000); - jest.useRealTimers(); }); }); diff --git a/components/_util/wave/WaveEffect.tsx b/components/_util/wave/WaveEffect.tsx new file mode 100644 index 000000000000..2824b18185bd --- /dev/null +++ b/components/_util/wave/WaveEffect.tsx @@ -0,0 +1,121 @@ +import * as React from 'react'; +import CSSMotion from 'rc-motion'; +import { render, unmount } from 'rc-util/lib/React/render'; +import classNames from 'classnames'; +import { isValidWaveColor } from './util'; + +function getTargetWaveColor(node: HTMLElement) { + const computedStyle = getComputedStyle(node); + const borderTopColor = computedStyle.getPropertyValue('border-top-color'); + const borderColor = computedStyle.getPropertyValue('border-color'); + const backgroundColor = computedStyle.getPropertyValue('background-color'); + if (isValidWaveColor(borderTopColor)) { + return borderTopColor; + } + if (isValidWaveColor(borderColor)) { + return borderColor; + } + return backgroundColor; +} + +export interface WaveEffectProps { + left: number; + top: number; + width: number; + height: number; + color: string; + className: string; + scale: number; + borderRadius: number[]; +} + +function WaveEffect(props: WaveEffectProps) { + const { className, left, top, width, height, color, borderRadius, scale } = props; + const divRef = React.useRef(null); + + return ( + { + if ((event as TransitionEvent).propertyName === 'opacity') { + const holder = divRef.current?.parentElement!; + unmount(holder).then(() => { + holder.parentElement?.removeChild(holder); + }); + } + + return false; + }} + > + {({ className: motionClassName }) => ( +
`${radius}px`).join(' '), + '--wave-scale': scale, + } as React.CSSProperties & { + '--wave-scale': number; + } + } + /> + )} + + ); +} + +export default function showWaveEffect( + container: HTMLElement, + node: HTMLElement, + className: string, +) { + const nodeStyle = getComputedStyle(node); + const nodeRect = node.getBoundingClientRect(); + + // Get wave color from target + const waveColor = getTargetWaveColor(node); + + // Get border radius + const { + borderTopLeftRadius, + borderTopRightRadius, + borderBottomLeftRadius, + borderBottomRightRadius, + } = nodeStyle; + + // Do scale calc + const { offsetWidth } = node; + const scale = nodeRect.width / offsetWidth; + + // Create holder + const holder = document.createElement('div'); + container.appendChild(holder); + + render( + parseFloat(radius) * scale)} + />, + holder, + ); +} diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 9aa0c9effc0b..0df52fdb4f82 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -1,218 +1,64 @@ -/* eslint-disable react/jsx-no-useless-fragment */ -/* eslint-disable @typescript-eslint/no-use-before-define */ -import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS'; +import classNames from 'classnames'; import { composeRef, supportRef } from 'rc-util/lib/ref'; -import React, { useContext, useEffect, useRef } from 'react'; +import isVisible from 'rc-util/lib/Dom/isVisible'; +import React, { useContext, useRef } from 'react'; import type { ConfigConsumerProps } from '../../config-provider'; import { ConfigContext } from '../../config-provider'; -import raf from '../raf'; import { cloneElement } from '../reactNode'; import useStyle from './style'; - -let styleForPseudo: HTMLStyleElement | null; - -// Where el is the DOM element you'd like to test for visibility -function isHidden(element: HTMLElement) { - if (process.env.NODE_ENV === 'test') { - return false; - } - return !element || element.offsetParent === null || element.hidden; -} - -function getValidateContainer(nodeRoot: Node): Element { - if (nodeRoot instanceof Document) { - return nodeRoot.body; - } - - return Array.from(nodeRoot.childNodes).find( - (ele) => ele?.nodeType === Node.ELEMENT_NODE, - ) as Element; -} - -function isNotGrey(color: string) { - // eslint-disable-next-line no-useless-escape - const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\d.]*)?\)/); - if (match && match[1] && match[2] && match[3]) { - return !(match[1] === match[2] && match[2] === match[3]); - } - return true; -} - -function isValidWaveColor(color: string) { - return ( - color && - color !== '#fff' && - color !== '#ffffff' && - color !== 'rgb(255, 255, 255)' && - color !== 'rgba(255, 255, 255, 1)' && - isNotGrey(color) && - !/rgba\((?:\d*, ){3}0\)/.test(color) && // any transparent rgba color - color !== 'transparent' - ); -} - -function getTargetWaveColor(node: HTMLElement) { - const computedStyle = getComputedStyle(node); - const borderTopColor = computedStyle.getPropertyValue('border-top-color'); - const borderColor = computedStyle.getPropertyValue('border-color'); - const backgroundColor = computedStyle.getPropertyValue('background-color'); - if (isValidWaveColor(borderTopColor)) { - return borderTopColor; - } - if (isValidWaveColor(borderColor)) { - return borderColor; - } - return backgroundColor; -} +import useWave from './useWave'; export interface WaveProps { - insertExtraNode?: boolean; disabled?: boolean; children?: React.ReactNode; } const Wave: React.FC = (props) => { - const { children, insertExtraNode, disabled } = props; - const { getPrefixCls, csp } = useContext(ConfigContext); - const containerRef = useRef(); - const extraNode = useRef(); - const clickTimeout = useRef(null); - const animationStartId = useRef(); - const isAnimationStart = useRef(false); + const { children, disabled } = props; + const { getPrefixCls } = useContext(ConfigContext); + const containerRef = useRef(null); - const attributeName = React.useMemo( - () => - insertExtraNode - ? `${getPrefixCls()}-click-animating` - : `${getPrefixCls()}-click-animating-without-extra-node`, - [getPrefixCls, insertExtraNode], - ); + // ============================== Style =============================== + const prefixCls = getPrefixCls('wave'); + const [, hashId] = useStyle(prefixCls); - const onTransitionStart = (e: AnimationEvent) => { - const node = containerRef.current; - if (!e || e.target !== node || isAnimationStart.current) { - return; - } - resetEffect(node as HTMLDivElement); - }; - - const onTransitionEnd = (e: AnimationEvent) => { - if (!e || e.animationName !== 'fadeEffect') { - return; - } - resetEffect(e.target as HTMLDivElement); - }; - - function resetEffect(node: HTMLDivElement) { - if (!node || node === extraNode.current || !(node instanceof Element) || disabled) { - return; - } + // =============================== Wave =============================== + const showWave = useWave(containerRef, classNames(prefixCls, hashId)); - node.setAttribute(attributeName, 'false'); // edge has bug on `removeAttribute` #14466 - - if (styleForPseudo) { - styleForPseudo.innerHTML = ''; - } - - if (insertExtraNode && extraNode.current && node.contains(extraNode.current)) { - node.removeChild(extraNode.current); - } - ['transition', 'animation'].forEach((name) => { - node.removeEventListener(`${name}start`, onTransitionStart); - node.removeEventListener(`${name}end`, onTransitionEnd); - }); - } - - const onClick = (node: HTMLDivElement, waveColor: string) => { - if (disabled || !node || isHidden(node) || node.className.includes('-leave')) { - return; - } - - extraNode.current = document.createElement('div'); - - extraNode.current.className = `${getPrefixCls()}-click-animating-node`; - - node.setAttribute(attributeName, 'true'); - // Not white or transparent or grey - if (isValidWaveColor(waveColor)) { - extraNode.current.style.borderColor = waveColor; - - const nodeRoot = node.getRootNode?.() || node.ownerDocument; - const nodeBody = getValidateContainer(nodeRoot) ?? nodeRoot; - - styleForPseudo = updateCSS( - ` - [${getPrefixCls()}-click-animating-without-extra-node='true']::after, .${getPrefixCls()}-click-animating-node { - --antd-wave-shadow-color: ${waveColor}; - }`, - 'antd-wave', - { csp, attachTo: nodeBody }, - ); - } - if (insertExtraNode) { - node.appendChild(extraNode.current); - } - ['transition', 'animation'].forEach((name) => { - node.addEventListener(`${name}start`, onTransitionStart); - node.addEventListener(`${name}end`, onTransitionEnd); - }); - }; - - const bindAnimationEvent = (node?: HTMLDivElement) => { + // ============================== Effect ============================== + React.useEffect(() => { + const node = containerRef.current; if ( !node || + node.nodeType !== 1 || !node.getAttribute || node.getAttribute('disabled') || - node.className.includes('disabled') + node.className.includes('disabled') || + disabled ) { return; } - const internalClick = (e: MouseEvent) => { + + // Click handler + const onClick = (e: MouseEvent) => { // Fix radio button click twice - if ((e.target as HTMLElement)?.tagName === 'INPUT' || isHidden(e.target as HTMLElement)) { + if ((e.target as HTMLElement).tagName === 'INPUT' || !isVisible(e.target as HTMLElement)) { return; } - resetEffect(node); - // Get wave color from target - const waveColor = getTargetWaveColor(node); - clickTimeout.current = setTimeout(() => { - onClick(node, waveColor); - }, 0); - - raf.cancel(animationStartId.current); - isAnimationStart.current = true; - // Render to trigger transition event cost 3 frames. Let's delay 10 frames to reset this. - animationStartId.current = raf(() => { - isAnimationStart.current = false; - }, 10); + showWave(); }; - node.addEventListener('click', internalClick, true); - return { - cancel() { - node.removeEventListener('click', internalClick, true); - }, - }; - }; - useEffect(() => { - const node = containerRef.current; - if (!node || node.nodeType !== 1) { - return; - } - bindAnimationEvent(node); + // Bind events + node.addEventListener('click', onClick, true); return () => { - bindAnimationEvent(node)?.cancel?.(); - if (clickTimeout.current) { - clearTimeout(clickTimeout.current); - } + node.removeEventListener('click', onClick, true); }; - }, []); - - useStyle(); + }, [disabled]); + // ============================== Render ============================== if (!React.isValidElement(children)) { - return <>{children}; + return children as unknown as React.ReactElement; } const ref = supportRef(children) ? composeRef((children as any).ref, containerRef) : containerRef; diff --git a/components/_util/wave/style.ts b/components/_util/wave/style.ts index 798735844b38..587bff59bcd0 100644 --- a/components/_util/wave/style.ts +++ b/components/_util/wave/style.ts @@ -1,86 +1,36 @@ -import { Keyframes, useStyleRegister } from '@ant-design/cssinjs'; -import { useContext } from 'react'; -import { ConfigContext } from '../../config-provider'; -import type { AliasToken, GenerateStyle, UseComponentStyleResult } from '../../theme/internal'; -import { useToken } from '../../theme/internal'; +import { genComponentStyleHook } from '../../theme/internal'; +import type { FullToken, GenerateStyle } from '../../theme/internal'; -interface WaveToken extends AliasToken { - hashId: string; - clickAnimatingNode: string; - clickAnimatingTrue: string; - clickAnimatingWithoutExtraNodeTrue: string; - clickAnimatingWithoutExtraNodeTrueAfter: string; -} +export interface ComponentToken {} -const genWaveStyle: GenerateStyle = (token) => { - const waveEffect = new Keyframes('waveEffect', { - '100%': { - boxShadow: `0 0 0 6px var(--antd-wave-shadow-color)`, - }, - }); - - const fadeEffect = new Keyframes('fadeEffect', { - '100%': { - opacity: 0, - }, - }); +export interface WaveToken extends FullToken<'Wave'> {} - return [ - { - [`${token.clickAnimatingWithoutExtraNodeTrue}, - ${token.clickAnimatingTrue}`]: { - '--antd-wave-shadow-color': token.colorPrimary, - '--scroll-bar': 0, - position: 'relative', - }, - [`${token.clickAnimatingWithoutExtraNodeTrueAfter}, - & ${token.clickAnimatingNode}`]: { - position: 'absolute', - top: 0, - insetInlineStart: 0, - insetInlineEnd: 0, - bottom: 0, - display: 'block', - borderRadius: 'inherit', - boxShadow: `0 0 0 0 var(--antd-wave-shadow-color)`, - opacity: 0.2, - animation: { - _skip_check_: true, - value: `${fadeEffect.getName(token.hashId)} 2s ${ - token.motionEaseOutCirc - }, ${waveEffect.getName(token.hashId)} 0.4s ${token.motionEaseOutCirc}`, +const genWaveStyle: GenerateStyle = (token) => { + const { componentCls } = token; + return { + [componentCls]: { + position: 'fixed', + background: 'transparent', + pointerEvents: 'none', + boxSizing: 'border-box', + + boxShadow: `0 0 0 0 currentcolor`, + opacity: 0.2, + + // =================== Motion =================== + '&.wave-motion-appear': { + transition: [ + `box-shadow 0.4s ${token.motionEaseOutCirc}`, + `opacity 2s ${token.motionEaseOutCirc}`, + ].join(','), + + '&-active': { + boxShadow: `0 0 0 calc(6px * var(--wave-scale)) currentcolor`, + opacity: 0, }, - animationFillMode: 'forwards', - content: '""', - pointerEvents: 'none', }, }, - {}, - waveEffect, - fadeEffect, - ]; -}; - -export default (): UseComponentStyleResult => { - const [theme, token, hashId] = useToken(); - const { getPrefixCls } = useContext(ConfigContext); - const rootPrefixCls = getPrefixCls(); - - const clickAnimatingTrue = `[${rootPrefixCls}-click-animating='true']`; - const clickAnimatingWithoutExtraNodeTrue = `[${rootPrefixCls}-click-animating-without-extra-node='true']`; - const clickAnimatingNode = `.${rootPrefixCls}-click-animating-node`; - - const waveToken: WaveToken = { - ...token, - hashId, - clickAnimatingNode, - clickAnimatingTrue, - clickAnimatingWithoutExtraNodeTrue, - clickAnimatingWithoutExtraNodeTrueAfter: `${clickAnimatingWithoutExtraNodeTrue}::after`, }; - - return [ - useStyleRegister({ theme, token, hashId, path: ['wave'] }, () => [genWaveStyle(waveToken)]), - hashId, - ]; }; + +export default genComponentStyleHook('Wave', (token) => [genWaveStyle(token)]); diff --git a/components/_util/wave/useWave.tsx b/components/_util/wave/useWave.tsx new file mode 100644 index 000000000000..ca4f3d242a51 --- /dev/null +++ b/components/_util/wave/useWave.tsx @@ -0,0 +1,20 @@ +import showWaveEffect from './WaveEffect'; + +export default function useWave( + nodeRef: React.RefObject, + className: string, +): VoidFunction { + function showWave() { + const node = nodeRef.current!; + + // Skip if not exist doc + const container = node?.ownerDocument?.body; + if (!container) { + return; + } + + showWaveEffect(container, node, className); + } + + return showWave; +} diff --git a/components/_util/wave/util.ts b/components/_util/wave/util.ts new file mode 100644 index 000000000000..c4616eb9918b --- /dev/null +++ b/components/_util/wave/util.ts @@ -0,0 +1,45 @@ +export function getValidateContainer(nodeRoot: Node): Element { + if (nodeRoot instanceof Document) { + return nodeRoot.body; + } + + return Array.from(nodeRoot.childNodes).find( + (ele) => ele?.nodeType === Node.ELEMENT_NODE, + ) as Element; +} + +export function isNotGrey(color: string) { + // eslint-disable-next-line no-useless-escape + const match = (color || '').match(/rgba?\((\d*), (\d*), (\d*)(, [\d.]*)?\)/); + if (match && match[1] && match[2] && match[3]) { + return !(match[1] === match[2] && match[2] === match[3]); + } + return true; +} + +export function isValidWaveColor(color: string) { + return ( + color && + color !== '#fff' && + color !== '#ffffff' && + color !== 'rgb(255, 255, 255)' && + color !== 'rgba(255, 255, 255, 1)' && + isNotGrey(color) && + !/rgba\((?:\d*, ){3}0\)/.test(color) && // any transparent rgba color + color !== 'transparent' + ); +} + +export function getTargetWaveColor(node: HTMLElement) { + const computedStyle = getComputedStyle(node); + const borderTopColor = computedStyle.getPropertyValue('border-top-color'); + const borderColor = computedStyle.getPropertyValue('border-color'); + const backgroundColor = computedStyle.getPropertyValue('background-color'); + if (isValidWaveColor(borderTopColor)) { + return borderTopColor; + } + if (isValidWaveColor(borderColor)) { + return borderColor; + } + return backgroundColor; +} diff --git a/components/switch/index.tsx b/components/switch/index.tsx index 4db89dc91fa4..1bb73a77ccee 100755 --- a/components/switch/index.tsx +++ b/components/switch/index.tsx @@ -90,7 +90,7 @@ const Switch = React.forwardRef( ); return wrapSSR( - + Date: Wed, 28 Dec 2022 18:52:04 +0800 Subject: [PATCH 25/34] test: coverage --- components/_util/__tests__/wave.test.tsx | 277 ++++++++++------------- components/_util/wave/WaveEffect.tsx | 69 +++--- components/_util/wave/index.tsx | 20 +- components/_util/wave/style.ts | 3 +- components/_util/wave/useWave.tsx | 8 +- components/_util/wave/util.ts | 10 +- 6 files changed, 170 insertions(+), 217 deletions(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 9b8d9f521606..34b8dc81b8e8 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -1,9 +1,15 @@ import React from 'react'; import mountTest from '../../../tests/shared/mountTest'; -import { render, waitFakeTimer, fireEvent, act } from '../../../tests/utils'; -import ConfigProvider from '../../config-provider'; +import { render, fireEvent, getByText, act, waitFakeTimer } from '../../../tests/utils'; import Wave from '../wave'; +(global as any).isVisible = true; + +jest.mock('rc-util/lib/Dom/isVisible', () => { + const mockFn = () => (global as any).isVisible; + return mockFn; +}); + describe('Wave component', () => { mountTest(Wave); @@ -16,6 +22,7 @@ describe('Wave component', () => { }); beforeEach(() => { + (global as any).isVisible = true; document.body.innerHTML = ''; }); @@ -27,46 +34,52 @@ describe('Wave component', () => { } }); - function filterStyles(styles: HTMLCollectionOf) { - return Array.from(styles).filter( - (style: HTMLStyleElement) => !style.hasAttribute('data-css-hash'), - ); + function getWaveStyle() { + const styleObj: Record = {}; + const { style } = document.querySelector('.ant-wave')!; + style.cssText.split(';').forEach((kv) => { + if (kv.trim()) { + const cells = kv.split(':'); + styleObj[cells[0].trim()] = cells[1].trim(); + } + }); + + return styleObj; } - it('isHidden works', () => { - const TEST_NODE_ENV = process.env.NODE_ENV; - process.env.NODE_ENV = 'development'; + it('work', async () => { const { container, unmount } = render( , ); - expect(container.querySelector('button')?.className).toBe(''); - container.querySelector('button')?.click(); + fireEvent.click(container.querySelector('button')!); + expect(document.querySelector('.ant-wave')).toBeTruthy(); + + // Match deadline + await waitFakeTimer(); + + expect(document.querySelector('.ant-wave')).toBeFalsy(); - expect( - container.querySelector('button')?.hasAttribute('ant-click-animating-without-extra-node'), - ).toBeFalsy(); unmount(); - process.env.NODE_ENV = TEST_NODE_ENV; }); - it('isHidden is mocked', () => { + it('invisible in screen', () => { + (global as any).isVisible = false; const { container, unmount } = render( , ); - expect(container.querySelector('button')?.className).toBe(''); - container.querySelector('button')?.click(); - expect( - container.querySelector('button')?.getAttribute('ant-click-animating-without-extra-node'), - ).toBe('false'); + + fireEvent.click(container.querySelector('button')!); + expect(document.querySelector('.ant-wave')).toBeFalsy(); + unmount(); }); - it('wave color is grey', async () => { + it('wave color is grey', () => { const { container, unmount } = render( , ); - container.querySelector('button')?.click(); - await waitFakeTimer(); - let styles: HTMLCollectionOf | HTMLStyleElement[] = ( - container.querySelector('button')?.getRootNode() as HTMLButtonElement - ).getElementsByTagName('style'); - styles = filterStyles(styles); - expect(styles.length).toBe(0); + + fireEvent.click(container.querySelector('button')!); + + const style = getWaveStyle(); + + expect(style['--wave-scale']).toBeTruthy(); + expect(style['--wave-color']).toBeFalsy(); + unmount(); }); - it('wave color is not grey', async () => { + it('wave color is not grey', () => { const { container, unmount } = render( , ); - container.querySelector('button')?.click(); - await waitFakeTimer(); - let styles: HTMLCollectionOf | HTMLStyleElement[] = ( - container.querySelector('button')?.getRootNode() as HTMLButtonElement - ).getElementsByTagName('style'); - styles = filterStyles(styles); - expect(styles.length).toBe(1); - expect(styles[0].innerHTML).toContain('--antd-wave-shadow-color: red;'); + + fireEvent.click(container.querySelector('button')!); + + const style = getWaveStyle(); + expect(style['--wave-color']).toEqual('red'); + unmount(); }); - it('read wave color from border-top-color', async () => { + it('read wave color from border-top-color', () => { const { container, unmount } = render(
button
, ); - container.querySelector('div')?.click(); - await waitFakeTimer(); - let styles: HTMLCollectionOf | HTMLStyleElement[] = ( - container.querySelector('div')?.getRootNode() as HTMLDivElement - ).getElementsByTagName('style'); - styles = filterStyles(styles); - expect(styles.length).toBe(1); - expect(styles[0].innerHTML).toContain('--antd-wave-shadow-color: blue;'); + + fireEvent.click(getByText(container, 'button')!); + + const style = getWaveStyle(); + expect(style['--wave-color']).toEqual('blue'); + unmount(); }); - it('read wave color from background color', async () => { + it('read wave color from background color', () => { const { container, unmount } = render(
button
, ); - container.querySelector('div')?.click(); - await waitFakeTimer(); - let styles: HTMLCollectionOf | HTMLStyleElement[] = ( - container.querySelector('div')?.getRootNode() as HTMLDivElement - ).getElementsByTagName('style'); - styles = filterStyles(styles); - expect(styles.length).toBe(1); - expect(styles[0].innerHTML).toContain('--antd-wave-shadow-color: green;'); + + fireEvent.click(getByText(container, 'button')!); + + const style = getWaveStyle(); + expect(style['--wave-color']).toEqual('green'); + unmount(); }); - it('read wave color from border firstly', async () => { + it('read wave color from border firstly', () => { const { container, unmount } = render(
button
, ); - container.querySelector('div')?.click(); - await waitFakeTimer(); - let styles: HTMLCollectionOf | HTMLStyleElement[] = ( - container.querySelector('div')?.getRootNode() as HTMLDivElement - ).getElementsByTagName('style'); - styles = filterStyles(styles); - expect(styles.length).toBe(1); - expect(styles[0].innerHTML).toContain('--antd-wave-shadow-color: yellow;'); + + fireEvent.click(getByText(container, 'button')!); + + const style = getWaveStyle(); + expect(style['--wave-color']).toEqual('yellow'); + unmount(); }); - it('hidden element with -leave className', async () => { + it('hidden element with -leave className', () => { const { container, unmount } = render( , ); - container.querySelector('button')?.click(); - await waitFakeTimer(); - let styles: HTMLCollectionOf | HTMLStyleElement[] = ( - container.querySelector('button')?.getRootNode() as HTMLButtonElement - ).getElementsByTagName('style'); - styles = filterStyles(styles); - expect(styles.length).toBe(0); + + fireEvent.click(container.querySelector('button')!); + expect(document.querySelector('.ant-wave')).toBeFalsy(); + unmount(); }); - it('ConfigProvider csp', async () => { - const { container, unmount } = render( - - - - - , + it('not show when disabled', () => { + const { container } = render( + + + , ); - container.querySelector('button')?.click(); - await waitFakeTimer(); - let styles: HTMLCollectionOf | HTMLStyleElement[] = ( - container.querySelector('button')?.getRootNode() as HTMLButtonElement - ).getElementsByTagName('style'); - styles = filterStyles(styles); - expect(styles[0].getAttribute('nonce')).toBe('YourNonceCode'); - unmount(); - }); - it('bindAnimationEvent should not throw error when children is disabled', () => { - expect(() => { - render( - - - , - ); - }).not.toThrow(); + fireEvent.click(container.querySelector('button')!); + expect(document.querySelector('.ant-wave')).toBeFalsy(); }); - it('bindAnimationEvent.onClick should not throw error when children is hidden', () => { - expect(() => { - render( - - - , - ); - }).not.toThrow(); + it('not show when hidden', () => { + (global as any).isVisible = false; + + const { container } = render( + + + , + ); + + fireEvent.click(container.querySelector('button')!); + expect(document.querySelector('.ant-wave')).toBeFalsy(); }); - it('bindAnimationEvent.onClick should not throw error when children is input', () => { - expect(() => { - render( - - - , - ); - }).not.toThrow(); + it('not show when is input', () => { + const { container } = render( + + + , + ); + + fireEvent.click(container.querySelector('input')!); + expect(document.querySelector('.ant-wave')).toBeFalsy(); }); it('should not throw when click it', () => { @@ -242,7 +232,7 @@ describe('Wave component', () => { expect(() => render()).not.toThrow(); }); - it('wave color should inferred if border is transparent and background is not', async () => { + it('wave color should inferred if border is transparent and background is not', () => { const { container, unmount } = render( , ); + fireEvent.click(container.querySelector('button')!); - await waitFakeTimer(); - let styles = (container.querySelector('button')!.getRootNode() as any).getElementsByTagName( - 'style', - ); - styles = filterStyles(styles); - expect(styles[0].innerHTML).toContain('--antd-wave-shadow-color: red;'); + const style = getWaveStyle(); + expect(style['--wave-color']).toEqual('red'); + unmount(); }); it('Wave style should append to validate element', () => { - jest.useFakeTimers(); const { container } = render(
@@ -294,29 +278,12 @@ describe('Wave component', () => { fakeDoc.appendChild(document.createElement('span')); expect(fakeDoc.childNodes).toHaveLength(2); - const elem = container.querySelector('.bamboo'); + const elem = container.querySelector('.bamboo')!; + elem.getRootNode = () => fakeDoc; - if (elem) { - elem.getRootNode = () => fakeDoc; + // Click should not throw + fireEvent.click(elem); - // Click should not throw - fireEvent.click(elem); - act(() => { - jest.runAllTimers(); - }); - - expect(fakeDoc.querySelector('style')).toBeTruthy(); - } - - jest.useRealTimers(); - }); - - it('wave transitionStart', async () => { - const { container } = render( - - - , - ); - fireEvent.click(container.querySelector('button')!); + expect(fakeDoc.querySelector('.ant-wave')).toBeTruthy(); }); }); diff --git a/components/_util/wave/WaveEffect.tsx b/components/_util/wave/WaveEffect.tsx index 2824b18185bd..fbec885d86eb 100644 --- a/components/_util/wave/WaveEffect.tsx +++ b/components/_util/wave/WaveEffect.tsx @@ -2,28 +2,14 @@ import * as React from 'react'; import CSSMotion from 'rc-motion'; import { render, unmount } from 'rc-util/lib/React/render'; import classNames from 'classnames'; -import { isValidWaveColor } from './util'; - -function getTargetWaveColor(node: HTMLElement) { - const computedStyle = getComputedStyle(node); - const borderTopColor = computedStyle.getPropertyValue('border-top-color'); - const borderColor = computedStyle.getPropertyValue('border-color'); - const backgroundColor = computedStyle.getPropertyValue('background-color'); - if (isValidWaveColor(borderTopColor)) { - return borderTopColor; - } - if (isValidWaveColor(borderColor)) { - return borderColor; - } - return backgroundColor; -} +import { getTargetWaveColor, getValidateContainer } from './util'; export interface WaveEffectProps { left: number; top: number; width: number; height: number; - color: string; + color: string | null; className: string; scale: number; borderRadius: number[]; @@ -33,6 +19,21 @@ function WaveEffect(props: WaveEffectProps) { const { className, left, top, width, height, color, borderRadius, scale } = props; const divRef = React.useRef(null); + const waveStyle = { + left, + top, + width, + height, + borderRadius: borderRadius.map((radius) => `${radius}px`).join(' '), + '--wave-scale': scale, + } as React.CSSProperties & { + [name: string]: number | string; + }; + + if (color) { + waveStyle['--wave-color'] = color; + } + return ( { - if ((event as TransitionEvent).propertyName === 'opacity') { + if (event.deadline || (event as TransitionEvent).propertyName === 'opacity') { const holder = divRef.current?.parentElement!; unmount(holder).then(() => { holder.parentElement?.removeChild(holder); @@ -51,33 +52,17 @@ function WaveEffect(props: WaveEffectProps) { }} > {({ className: motionClassName }) => ( -
`${radius}px`).join(' '), - '--wave-scale': scale, - } as React.CSSProperties & { - '--wave-scale': number; - } - } - /> +
)} ); } -export default function showWaveEffect( - container: HTMLElement, - node: HTMLElement, - className: string, -) { +function validateNum(value: number) { + return Number.isNaN(value) ? 0 : value; +} + +export default function showWaveEffect(container: Node, node: HTMLElement, className: string) { const nodeStyle = getComputedStyle(node); const nodeRect = node.getBoundingClientRect(); @@ -94,11 +79,11 @@ export default function showWaveEffect( // Do scale calc const { offsetWidth } = node; - const scale = nodeRect.width / offsetWidth; + const scale = validateNum(nodeRect.width / offsetWidth); // Create holder const holder = document.createElement('div'); - container.appendChild(holder); + getValidateContainer(container).appendChild(holder); render( parseFloat(radius) * scale)} + ].map((radius) => validateNum(parseFloat(radius) * scale))} />, holder, ); diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index 0df52fdb4f82..bc44d0ee9cf3 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -28,21 +28,23 @@ const Wave: React.FC = (props) => { // ============================== Effect ============================== React.useEffect(() => { const node = containerRef.current; - if ( - !node || - node.nodeType !== 1 || - !node.getAttribute || - node.getAttribute('disabled') || - node.className.includes('disabled') || - disabled - ) { + if (!node || node.nodeType !== 1 || disabled) { return; } // Click handler const onClick = (e: MouseEvent) => { // Fix radio button click twice - if ((e.target as HTMLElement).tagName === 'INPUT' || !isVisible(e.target as HTMLElement)) { + if ( + (e.target as HTMLElement).tagName === 'INPUT' || + !isVisible(e.target as HTMLElement) || + // No need wave + !node.getAttribute || + node.getAttribute('disabled') || + (node as HTMLInputElement).disabled || + node.className.includes('disabled') || + node.className.includes('-leave') + ) { return; } diff --git a/components/_util/wave/style.ts b/components/_util/wave/style.ts index 587bff59bcd0..825344dedaec 100644 --- a/components/_util/wave/style.ts +++ b/components/_util/wave/style.ts @@ -6,13 +6,14 @@ export interface ComponentToken {} export interface WaveToken extends FullToken<'Wave'> {} const genWaveStyle: GenerateStyle = (token) => { - const { componentCls } = token; + const { componentCls, colorPrimary } = token; return { [componentCls]: { position: 'fixed', background: 'transparent', pointerEvents: 'none', boxSizing: 'border-box', + color: `var(--wave-color, ${colorPrimary})`, boxShadow: `0 0 0 0 currentcolor`, opacity: 0.2, diff --git a/components/_util/wave/useWave.tsx b/components/_util/wave/useWave.tsx index ca4f3d242a51..0b23ed4b37a1 100644 --- a/components/_util/wave/useWave.tsx +++ b/components/_util/wave/useWave.tsx @@ -8,12 +8,10 @@ export default function useWave( const node = nodeRef.current!; // Skip if not exist doc - const container = node?.ownerDocument?.body; - if (!container) { - return; + const container = node.getRootNode?.() || node?.ownerDocument; + if (container) { + showWaveEffect(container, node, className); } - - showWaveEffect(container, node, className); } return showWave; diff --git a/components/_util/wave/util.ts b/components/_util/wave/util.ts index c4616eb9918b..9f6b1f0a1d1e 100644 --- a/components/_util/wave/util.ts +++ b/components/_util/wave/util.ts @@ -31,15 +31,15 @@ export function isValidWaveColor(color: string) { } export function getTargetWaveColor(node: HTMLElement) { - const computedStyle = getComputedStyle(node); - const borderTopColor = computedStyle.getPropertyValue('border-top-color'); - const borderColor = computedStyle.getPropertyValue('border-color'); - const backgroundColor = computedStyle.getPropertyValue('background-color'); + const { borderTopColor, borderColor, backgroundColor } = getComputedStyle(node); if (isValidWaveColor(borderTopColor)) { return borderTopColor; } if (isValidWaveColor(borderColor)) { return borderColor; } - return backgroundColor; + if (isValidWaveColor(backgroundColor)) { + return backgroundColor; + } + return null; } From b272be71a924279f5f9928ccf923a2733163a749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 28 Dec 2022 18:54:07 +0800 Subject: [PATCH 26/34] chore: clean up --- components/_util/__tests__/wave.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/_util/__tests__/wave.test.tsx b/components/_util/__tests__/wave.test.tsx index 34b8dc81b8e8..c3308b8694f5 100644 --- a/components/_util/__tests__/wave.test.tsx +++ b/components/_util/__tests__/wave.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import mountTest from '../../../tests/shared/mountTest'; -import { render, fireEvent, getByText, act, waitFakeTimer } from '../../../tests/utils'; +import { render, fireEvent, getByText, waitFakeTimer } from '../../../tests/utils'; import Wave from '../wave'; (global as any).isVisible = true; From 9bf3b120cb96360e18a7f9931c8bfef9117120fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=97=E5=98=89=E7=94=B7?= <574980606@qq.com> Date: Wed, 28 Dec 2022 19:35:33 +0800 Subject: [PATCH 27/34] rerun fail ci --- components/switch/__tests__/index.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/switch/__tests__/index.test.tsx b/components/switch/__tests__/index.test.tsx index 21870ef3cfe4..a5d33cafc37a 100644 --- a/components/switch/__tests__/index.test.tsx +++ b/components/switch/__tests__/index.test.tsx @@ -16,7 +16,7 @@ describe('Switch', () => { const { container } = render(); fireEvent.click(container.querySelector('.ant-switch')!); await waitFakeTimer(); - expect(container.querySelector('button')!.getAttribute('ant-click-animating')).toBe('true'); + expect(container.querySelector('button')?.getAttribute('ant-click-animating')).toBe('true'); jest.clearAllTimers(); jest.useRealTimers(); }); From ad28afb94e0d4e6384b57a3aa77543c4e7eb159f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 28 Dec 2022 20:40:51 +0800 Subject: [PATCH 28/34] fix: React 17 error --- components/_util/wave/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/_util/wave/index.tsx b/components/_util/wave/index.tsx index bc44d0ee9cf3..f32405e1c728 100644 --- a/components/_util/wave/index.tsx +++ b/components/_util/wave/index.tsx @@ -60,7 +60,7 @@ const Wave: React.FC = (props) => { // ============================== Render ============================== if (!React.isValidElement(children)) { - return children as unknown as React.ReactElement; + return (children ?? null) as unknown as React.ReactElement; } const ref = supportRef(children) ? composeRef((children as any).ref, containerRef) : containerRef; From ee5aadc95055580d0383d4d9043126e6d46d7a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 28 Dec 2022 20:53:07 +0800 Subject: [PATCH 29/34] test: fix test case --- components/switch/__tests__/index.test.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/components/switch/__tests__/index.test.tsx b/components/switch/__tests__/index.test.tsx index a5d33cafc37a..47c1c476f04c 100644 --- a/components/switch/__tests__/index.test.tsx +++ b/components/switch/__tests__/index.test.tsx @@ -3,20 +3,24 @@ import Switch from '..'; import focusTest from '../../../tests/shared/focusTest'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; -import { waitFakeTimer, fireEvent, render } from '../../../tests/utils'; +import { fireEvent, render } from '../../../tests/utils'; import { resetWarned } from '../../_util/warning'; +jest.mock('rc-util/lib/Dom/isVisible', () => { + const mockFn = () => true; + return mockFn; +}); + describe('Switch', () => { focusTest(Switch, { refFocus: true }); mountTest(Switch); rtlTest(Switch); - it('should has click wave effect', async () => { + it('should has click wave effect', () => { jest.useFakeTimers(); const { container } = render(); fireEvent.click(container.querySelector('.ant-switch')!); - await waitFakeTimer(); - expect(container.querySelector('button')?.getAttribute('ant-click-animating')).toBe('true'); + expect(document.querySelector('.ant-wave')).toBeTruthy(); jest.clearAllTimers(); jest.useRealTimers(); }); From bd0605a645014aa7d7d8df68c1047912aa92292c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Wed, 28 Dec 2022 21:38:20 +0800 Subject: [PATCH 30/34] test: fix test case --- components/button/__tests__/wave.test.tsx | 40 ++++--------------- .../__snapshots__/index.test.tsx.snap | 1 - 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/components/button/__tests__/wave.test.tsx b/components/button/__tests__/wave.test.tsx index 8df5dd2f90cd..71d76e28ae11 100644 --- a/components/button/__tests__/wave.test.tsx +++ b/components/button/__tests__/wave.test.tsx @@ -3,14 +3,9 @@ import React from 'react'; import Button from '..'; import { fireEvent, render } from '../../../tests/utils'; -jest.mock('../../_util/wave', () => { - const Wave: typeof import('../../_util/wave') = jest.requireActual('../../_util/wave'); - const WaveComponent = Wave.default; - return { - ...Wave, - __esModule: true, - default: (props: import('../../_util/wave').WaveProps) => , - }; +jest.mock('rc-util/lib/Dom/isVisible', () => { + const mockFn = () => true; + return mockFn; }); describe('click wave effect', () => { @@ -21,6 +16,7 @@ describe('click wave effect', () => { afterEach(() => { jest.clearAllTimers(); jest.useRealTimers(); + document.body.innerHTML = ''; }); async function clickButton(container: HTMLElement) { @@ -34,44 +30,24 @@ describe('click wave effect', () => { it('should have click wave effect for primary button', async () => { const { container } = render(); await clickButton(container); - expect(container.querySelector('.ant-btn')).toHaveAttribute( - 'ant-click-animating-without-extra-node', - ); + expect(document.querySelector('.ant-wave')).toBeTruthy(); }); it('should have click wave effect for default button', async () => { const { container } = render(); await clickButton(container); - expect(container.querySelector('.ant-btn')).toHaveAttribute( - 'ant-click-animating-without-extra-node', - ); + expect(document.querySelector('.ant-wave')).toBeTruthy(); }); it('should not have click wave effect for link type button', async () => { const { container } = render(); await clickButton(container); - expect(container.querySelector('.ant-btn')).not.toHaveAttribute( - 'ant-click-animating-without-extra-node', - ); + expect(document.querySelector('.ant-wave')).toBeFalsy(); }); it('should not have click wave effect for text type button', async () => { const { container } = render(); await clickButton(container); - expect(container.querySelector('.ant-btn')).not.toHaveAttribute( - 'ant-click-animating-without-extra-node', - ); - }); - - it('should handle transitionstart', async () => { - const { container, unmount } = render(); - await clickButton(container); - const buttonNode = container.querySelector('.ant-btn')!; - fireEvent(buttonNode, new Event('transitionstart')); - expect(container.querySelector('.ant-btn')).toHaveAttribute( - 'ant-click-animating-without-extra-node', - ); - unmount(); - fireEvent(buttonNode, new Event('transitionstart')); + expect(document.querySelector('.ant-wave')).toBeFalsy(); }); }); diff --git a/components/tour/__tests__/__snapshots__/index.test.tsx.snap b/components/tour/__tests__/__snapshots__/index.test.tsx.snap index e9c0370f6c26..1a9e5b434ab7 100644 --- a/components/tour/__tests__/__snapshots__/index.test.tsx.snap +++ b/components/tour/__tests__/__snapshots__/index.test.tsx.snap @@ -589,7 +589,6 @@ exports[`Tour step support Primary 1`] = `