mirror of
https://github.com/ant-design/ant-design.git
synced 2025-01-18 14:13:37 +08:00
fix: wave target position switch (#39966)
* fix: Node delay for wave pos * test: test case update * test: test case update * chore: update ignore * test: test case update * chore: resize check for size changing * test: coverage * chore: save code
This commit is contained in:
parent
cfc763bce3
commit
99b32a3c37
1
.gitignore
vendored
1
.gitignore
vendored
@ -48,6 +48,7 @@ server/
|
||||
# Docs templates
|
||||
scripts/previewEditor/index.html
|
||||
components/version/version.ts
|
||||
components/version/version.tsx
|
||||
components/version/token.json
|
||||
components/version/token-meta.json
|
||||
.dumi/tmp
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import { render, fireEvent, getByText, waitFakeTimer } from '../../../tests/utils';
|
||||
import { render, fireEvent, getByText, waitFakeTimer, act } from '../../../tests/utils';
|
||||
import Wave from '../wave';
|
||||
|
||||
(global as any).isVisible = true;
|
||||
@ -13,12 +13,29 @@ jest.mock('rc-util/lib/Dom/isVisible', () => {
|
||||
describe('Wave component', () => {
|
||||
mountTest(Wave);
|
||||
|
||||
let obCnt = 0;
|
||||
let disCnt = 0;
|
||||
|
||||
beforeAll(() => {
|
||||
/* eslint-disable class-methods-use-this */
|
||||
class FakeResizeObserver {
|
||||
observe = () => {
|
||||
obCnt += 1;
|
||||
};
|
||||
|
||||
disconnect = () => {
|
||||
disCnt += 1;
|
||||
};
|
||||
}
|
||||
|
||||
(window as any).ResizeObserver = FakeResizeObserver;
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
expect(obCnt).not.toBe(0);
|
||||
expect(disCnt).not.toBe(0);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@ -47,6 +64,12 @@ describe('Wave component', () => {
|
||||
return styleObj;
|
||||
}
|
||||
|
||||
function waitRaf() {
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(100);
|
||||
});
|
||||
}
|
||||
|
||||
it('work', async () => {
|
||||
const { container, unmount } = render(
|
||||
<Wave>
|
||||
@ -55,6 +78,7 @@ describe('Wave component', () => {
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
waitRaf();
|
||||
expect(document.querySelector('.ant-wave')).toBeTruthy();
|
||||
|
||||
// Match deadline
|
||||
@ -74,6 +98,7 @@ describe('Wave component', () => {
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
waitRaf();
|
||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
||||
|
||||
unmount();
|
||||
@ -92,6 +117,7 @@ describe('Wave component', () => {
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
waitRaf();
|
||||
|
||||
const style = getWaveStyle();
|
||||
|
||||
@ -110,6 +136,7 @@ describe('Wave component', () => {
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
waitRaf();
|
||||
|
||||
const style = getWaveStyle();
|
||||
expect(style['--wave-color']).toEqual('red');
|
||||
@ -125,6 +152,7 @@ describe('Wave component', () => {
|
||||
);
|
||||
|
||||
fireEvent.click(getByText(container, 'button')!);
|
||||
waitRaf();
|
||||
|
||||
const style = getWaveStyle();
|
||||
expect(style['--wave-color']).toEqual('blue');
|
||||
@ -140,6 +168,7 @@ describe('Wave component', () => {
|
||||
);
|
||||
|
||||
fireEvent.click(getByText(container, 'button')!);
|
||||
waitRaf();
|
||||
|
||||
const style = getWaveStyle();
|
||||
expect(style['--wave-color']).toEqual('green');
|
||||
@ -155,6 +184,7 @@ describe('Wave component', () => {
|
||||
);
|
||||
|
||||
fireEvent.click(getByText(container, 'button')!);
|
||||
waitRaf();
|
||||
|
||||
const style = getWaveStyle();
|
||||
expect(style['--wave-color']).toEqual('yellow');
|
||||
@ -172,6 +202,7 @@ describe('Wave component', () => {
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
waitRaf();
|
||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
||||
|
||||
unmount();
|
||||
@ -202,6 +233,7 @@ describe('Wave component', () => {
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
waitRaf();
|
||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
||||
});
|
||||
|
||||
@ -213,6 +245,7 @@ describe('Wave component', () => {
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelector('input')!);
|
||||
waitRaf();
|
||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
||||
});
|
||||
|
||||
@ -224,11 +257,15 @@ describe('Wave component', () => {
|
||||
</Wave>,
|
||||
);
|
||||
fireEvent.click(container);
|
||||
waitRaf();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw when no children', () => {
|
||||
expect(() => render(<Wave />)).not.toThrow();
|
||||
expect(() => {
|
||||
render(<Wave />);
|
||||
waitRaf();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('wave color should inferred if border is transparent and background is not', () => {
|
||||
@ -240,6 +277,7 @@ describe('Wave component', () => {
|
||||
</Wave>,
|
||||
);
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
waitRaf();
|
||||
|
||||
const style = getWaveStyle();
|
||||
expect(style['--wave-color']).toEqual('red');
|
||||
@ -257,6 +295,7 @@ describe('Wave component', () => {
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
waitRaf();
|
||||
|
||||
const style = getWaveStyle();
|
||||
expect(style['--wave-color']).toEqual('red');
|
||||
@ -282,6 +321,7 @@ describe('Wave component', () => {
|
||||
|
||||
// Click should not throw
|
||||
fireEvent.click(elem);
|
||||
waitRaf();
|
||||
|
||||
expect(container.querySelector('.ant-wave')).toBeTruthy();
|
||||
});
|
||||
|
@ -1,23 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import CSSMotion from 'rc-motion';
|
||||
import raf from 'rc-util/lib/raf';
|
||||
import { render, unmount } from 'rc-util/lib/React/render';
|
||||
import classNames from 'classnames';
|
||||
import { getTargetWaveColor } from './util';
|
||||
|
||||
function validateNum(value: number) {
|
||||
return Number.isNaN(value) ? 0 : value;
|
||||
}
|
||||
|
||||
export interface WaveEffectProps {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
color: string | null;
|
||||
className: string;
|
||||
borderRadius: number[];
|
||||
target: HTMLElement;
|
||||
}
|
||||
|
||||
const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
||||
const { className, left, top, width, height, color, borderRadius } = props;
|
||||
const { className, target } = props;
|
||||
const divRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const [color, setWaveColor] = React.useState<string | null>(null);
|
||||
const [borderRadius, setBorderRadius] = React.useState<number[]>([]);
|
||||
const [left, setLeft] = React.useState(0);
|
||||
const [top, setTop] = React.useState(0);
|
||||
const [width, setWidth] = React.useState(0);
|
||||
const [height, setHeight] = React.useState(0);
|
||||
const [enabled, setEnabled] = React.useState(false);
|
||||
|
||||
const waveStyle = {
|
||||
left,
|
||||
top,
|
||||
@ -32,6 +40,65 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
||||
waveStyle['--wave-color'] = color;
|
||||
}
|
||||
|
||||
function syncPos() {
|
||||
const nodeStyle = getComputedStyle(target);
|
||||
|
||||
// Get wave color from target
|
||||
setWaveColor(getTargetWaveColor(target));
|
||||
|
||||
// Rect
|
||||
setLeft(target.offsetLeft);
|
||||
setTop(target.offsetTop);
|
||||
setWidth(target.offsetWidth);
|
||||
setHeight(target.offsetHeight);
|
||||
|
||||
// Get border radius
|
||||
const {
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
} = nodeStyle;
|
||||
|
||||
setBorderRadius(
|
||||
[
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomLeftRadius,
|
||||
].map((radius) => validateNum(parseFloat(radius))),
|
||||
);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (target) {
|
||||
// We need delay to check position here
|
||||
// since UI may change after click
|
||||
const id = raf(() => {
|
||||
syncPos();
|
||||
|
||||
setEnabled(true);
|
||||
});
|
||||
|
||||
// Add resize observer to follow size
|
||||
let resizeObserver: ResizeObserver;
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
resizeObserver = new ResizeObserver(syncPos);
|
||||
|
||||
resizeObserver.observe(target);
|
||||
}
|
||||
|
||||
return () => {
|
||||
raf.cancel(id);
|
||||
resizeObserver?.disconnect();
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CSSMotion
|
||||
visible
|
||||
@ -55,46 +122,13 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
function validateNum(value: number) {
|
||||
return Number.isNaN(value) ? 0 : value;
|
||||
}
|
||||
|
||||
export default function showWaveEffect(node: HTMLElement, className: string) {
|
||||
const nodeStyle = getComputedStyle(node);
|
||||
|
||||
// Get wave color from target
|
||||
const waveColor = getTargetWaveColor(node);
|
||||
|
||||
// Get border radius
|
||||
const {
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
} = nodeStyle;
|
||||
|
||||
// Create holder
|
||||
const holder = document.createElement('div');
|
||||
holder.style.position = 'absolute';
|
||||
holder.style.left = `${node.offsetLeft}px`;
|
||||
holder.style.top = `${node.offsetTop}px`;
|
||||
holder.style.left = `0px`;
|
||||
holder.style.top = `0px`;
|
||||
node.parentElement?.appendChild(holder);
|
||||
|
||||
render(
|
||||
<WaveEffect
|
||||
left={0}
|
||||
top={0}
|
||||
width={node.offsetWidth}
|
||||
height={node.offsetHeight}
|
||||
color={waveColor}
|
||||
className={className}
|
||||
borderRadius={[
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomLeftRadius,
|
||||
].map((radius) => validateNum(parseFloat(radius)))}
|
||||
/>,
|
||||
holder,
|
||||
);
|
||||
render(<WaveEffect target={node} className={className} />, holder);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import Button from '..';
|
||||
import { fireEvent, render } from '../../../tests/utils';
|
||||
import { act, fireEvent, render } from '../../../tests/utils';
|
||||
|
||||
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
||||
const mockFn = () => true;
|
||||
@ -23,6 +23,10 @@ describe('click wave effect', () => {
|
||||
const element = container.firstChild;
|
||||
// https://github.com/testing-library/user-event/issues/833
|
||||
await userEvent.setup({ advanceTimers: jest.advanceTimersByTime }).click(element as Element);
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(100);
|
||||
});
|
||||
|
||||
fireEvent(element!, new Event('transitionstart'));
|
||||
fireEvent(element!, new Event('animationend'));
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import Switch from '..';
|
||||
import focusTest from '../../../tests/shared/focusTest';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { fireEvent, render } from '../../../tests/utils';
|
||||
import { act, fireEvent, render } from '../../../tests/utils';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
|
||||
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
||||
@ -20,6 +20,9 @@ describe('Switch', () => {
|
||||
jest.useFakeTimers();
|
||||
const { container } = render(<Switch />);
|
||||
fireEvent.click(container.querySelector('.ant-switch')!);
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(100);
|
||||
});
|
||||
expect(document.querySelector('.ant-wave')).toBeTruthy();
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
|
Loading…
Reference in New Issue
Block a user