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
|
# Docs templates
|
||||||
scripts/previewEditor/index.html
|
scripts/previewEditor/index.html
|
||||||
components/version/version.ts
|
components/version/version.ts
|
||||||
|
components/version/version.tsx
|
||||||
components/version/token.json
|
components/version/token.json
|
||||||
components/version/token-meta.json
|
components/version/token-meta.json
|
||||||
.dumi/tmp
|
.dumi/tmp
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import mountTest from '../../../tests/shared/mountTest';
|
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';
|
import Wave from '../wave';
|
||||||
|
|
||||||
(global as any).isVisible = true;
|
(global as any).isVisible = true;
|
||||||
@ -13,12 +13,29 @@ jest.mock('rc-util/lib/Dom/isVisible', () => {
|
|||||||
describe('Wave component', () => {
|
describe('Wave component', () => {
|
||||||
mountTest(Wave);
|
mountTest(Wave);
|
||||||
|
|
||||||
|
let obCnt = 0;
|
||||||
|
let disCnt = 0;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
/* eslint-disable class-methods-use-this */
|
||||||
|
class FakeResizeObserver {
|
||||||
|
observe = () => {
|
||||||
|
obCnt += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
disconnect = () => {
|
||||||
|
disCnt += 1;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
(window as any).ResizeObserver = FakeResizeObserver;
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
|
expect(obCnt).not.toBe(0);
|
||||||
|
expect(disCnt).not.toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -47,6 +64,12 @@ describe('Wave component', () => {
|
|||||||
return styleObj;
|
return styleObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function waitRaf() {
|
||||||
|
act(() => {
|
||||||
|
jest.advanceTimersByTime(100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it('work', async () => {
|
it('work', async () => {
|
||||||
const { container, unmount } = render(
|
const { container, unmount } = render(
|
||||||
<Wave>
|
<Wave>
|
||||||
@ -55,6 +78,7 @@ describe('Wave component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector('button')!);
|
fireEvent.click(container.querySelector('button')!);
|
||||||
|
waitRaf();
|
||||||
expect(document.querySelector('.ant-wave')).toBeTruthy();
|
expect(document.querySelector('.ant-wave')).toBeTruthy();
|
||||||
|
|
||||||
// Match deadline
|
// Match deadline
|
||||||
@ -74,6 +98,7 @@ describe('Wave component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector('button')!);
|
fireEvent.click(container.querySelector('button')!);
|
||||||
|
waitRaf();
|
||||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
@ -92,6 +117,7 @@ describe('Wave component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector('button')!);
|
fireEvent.click(container.querySelector('button')!);
|
||||||
|
waitRaf();
|
||||||
|
|
||||||
const style = getWaveStyle();
|
const style = getWaveStyle();
|
||||||
|
|
||||||
@ -110,6 +136,7 @@ describe('Wave component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector('button')!);
|
fireEvent.click(container.querySelector('button')!);
|
||||||
|
waitRaf();
|
||||||
|
|
||||||
const style = getWaveStyle();
|
const style = getWaveStyle();
|
||||||
expect(style['--wave-color']).toEqual('red');
|
expect(style['--wave-color']).toEqual('red');
|
||||||
@ -125,6 +152,7 @@ describe('Wave component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(getByText(container, 'button')!);
|
fireEvent.click(getByText(container, 'button')!);
|
||||||
|
waitRaf();
|
||||||
|
|
||||||
const style = getWaveStyle();
|
const style = getWaveStyle();
|
||||||
expect(style['--wave-color']).toEqual('blue');
|
expect(style['--wave-color']).toEqual('blue');
|
||||||
@ -140,6 +168,7 @@ describe('Wave component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(getByText(container, 'button')!);
|
fireEvent.click(getByText(container, 'button')!);
|
||||||
|
waitRaf();
|
||||||
|
|
||||||
const style = getWaveStyle();
|
const style = getWaveStyle();
|
||||||
expect(style['--wave-color']).toEqual('green');
|
expect(style['--wave-color']).toEqual('green');
|
||||||
@ -155,6 +184,7 @@ describe('Wave component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(getByText(container, 'button')!);
|
fireEvent.click(getByText(container, 'button')!);
|
||||||
|
waitRaf();
|
||||||
|
|
||||||
const style = getWaveStyle();
|
const style = getWaveStyle();
|
||||||
expect(style['--wave-color']).toEqual('yellow');
|
expect(style['--wave-color']).toEqual('yellow');
|
||||||
@ -172,6 +202,7 @@ describe('Wave component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector('button')!);
|
fireEvent.click(container.querySelector('button')!);
|
||||||
|
waitRaf();
|
||||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
@ -202,6 +233,7 @@ describe('Wave component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector('button')!);
|
fireEvent.click(container.querySelector('button')!);
|
||||||
|
waitRaf();
|
||||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -213,6 +245,7 @@ describe('Wave component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector('input')!);
|
fireEvent.click(container.querySelector('input')!);
|
||||||
|
waitRaf();
|
||||||
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
expect(document.querySelector('.ant-wave')).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -224,11 +257,15 @@ describe('Wave component', () => {
|
|||||||
</Wave>,
|
</Wave>,
|
||||||
);
|
);
|
||||||
fireEvent.click(container);
|
fireEvent.click(container);
|
||||||
|
waitRaf();
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw when no children', () => {
|
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', () => {
|
it('wave color should inferred if border is transparent and background is not', () => {
|
||||||
@ -240,6 +277,7 @@ describe('Wave component', () => {
|
|||||||
</Wave>,
|
</Wave>,
|
||||||
);
|
);
|
||||||
fireEvent.click(container.querySelector('button')!);
|
fireEvent.click(container.querySelector('button')!);
|
||||||
|
waitRaf();
|
||||||
|
|
||||||
const style = getWaveStyle();
|
const style = getWaveStyle();
|
||||||
expect(style['--wave-color']).toEqual('red');
|
expect(style['--wave-color']).toEqual('red');
|
||||||
@ -257,6 +295,7 @@ describe('Wave component', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector('button')!);
|
fireEvent.click(container.querySelector('button')!);
|
||||||
|
waitRaf();
|
||||||
|
|
||||||
const style = getWaveStyle();
|
const style = getWaveStyle();
|
||||||
expect(style['--wave-color']).toEqual('red');
|
expect(style['--wave-color']).toEqual('red');
|
||||||
@ -282,6 +321,7 @@ describe('Wave component', () => {
|
|||||||
|
|
||||||
// Click should not throw
|
// Click should not throw
|
||||||
fireEvent.click(elem);
|
fireEvent.click(elem);
|
||||||
|
waitRaf();
|
||||||
|
|
||||||
expect(container.querySelector('.ant-wave')).toBeTruthy();
|
expect(container.querySelector('.ant-wave')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -1,23 +1,31 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import CSSMotion from 'rc-motion';
|
import CSSMotion from 'rc-motion';
|
||||||
|
import raf from 'rc-util/lib/raf';
|
||||||
import { render, unmount } from 'rc-util/lib/React/render';
|
import { render, unmount } from 'rc-util/lib/React/render';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { getTargetWaveColor } from './util';
|
import { getTargetWaveColor } from './util';
|
||||||
|
|
||||||
|
function validateNum(value: number) {
|
||||||
|
return Number.isNaN(value) ? 0 : value;
|
||||||
|
}
|
||||||
|
|
||||||
export interface WaveEffectProps {
|
export interface WaveEffectProps {
|
||||||
left: number;
|
|
||||||
top: number;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
color: string | null;
|
|
||||||
className: string;
|
className: string;
|
||||||
borderRadius: number[];
|
target: HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
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 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 = {
|
const waveStyle = {
|
||||||
left,
|
left,
|
||||||
top,
|
top,
|
||||||
@ -32,6 +40,65 @@ const WaveEffect: React.FC<WaveEffectProps> = (props) => {
|
|||||||
waveStyle['--wave-color'] = color;
|
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 (
|
return (
|
||||||
<CSSMotion
|
<CSSMotion
|
||||||
visible
|
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) {
|
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
|
// Create holder
|
||||||
const holder = document.createElement('div');
|
const holder = document.createElement('div');
|
||||||
holder.style.position = 'absolute';
|
holder.style.position = 'absolute';
|
||||||
holder.style.left = `${node.offsetLeft}px`;
|
holder.style.left = `0px`;
|
||||||
holder.style.top = `${node.offsetTop}px`;
|
holder.style.top = `0px`;
|
||||||
node.parentElement?.appendChild(holder);
|
node.parentElement?.appendChild(holder);
|
||||||
|
|
||||||
render(
|
render(<WaveEffect target={node} className={className} />, holder);
|
||||||
<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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from '..';
|
import Button from '..';
|
||||||
import { fireEvent, render } from '../../../tests/utils';
|
import { act, fireEvent, render } from '../../../tests/utils';
|
||||||
|
|
||||||
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
||||||
const mockFn = () => true;
|
const mockFn = () => true;
|
||||||
@ -23,6 +23,10 @@ describe('click wave effect', () => {
|
|||||||
const element = container.firstChild;
|
const element = container.firstChild;
|
||||||
// https://github.com/testing-library/user-event/issues/833
|
// https://github.com/testing-library/user-event/issues/833
|
||||||
await userEvent.setup({ advanceTimers: jest.advanceTimersByTime }).click(element as Element);
|
await userEvent.setup({ advanceTimers: jest.advanceTimersByTime }).click(element as Element);
|
||||||
|
act(() => {
|
||||||
|
jest.advanceTimersByTime(100);
|
||||||
|
});
|
||||||
|
|
||||||
fireEvent(element!, new Event('transitionstart'));
|
fireEvent(element!, new Event('transitionstart'));
|
||||||
fireEvent(element!, new Event('animationend'));
|
fireEvent(element!, new Event('animationend'));
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import Switch from '..';
|
|||||||
import focusTest from '../../../tests/shared/focusTest';
|
import focusTest from '../../../tests/shared/focusTest';
|
||||||
import mountTest from '../../../tests/shared/mountTest';
|
import mountTest from '../../../tests/shared/mountTest';
|
||||||
import rtlTest from '../../../tests/shared/rtlTest';
|
import rtlTest from '../../../tests/shared/rtlTest';
|
||||||
import { fireEvent, render } from '../../../tests/utils';
|
import { act, fireEvent, render } from '../../../tests/utils';
|
||||||
import { resetWarned } from '../../_util/warning';
|
import { resetWarned } from '../../_util/warning';
|
||||||
|
|
||||||
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
jest.mock('rc-util/lib/Dom/isVisible', () => {
|
||||||
@ -20,6 +20,9 @@ describe('Switch', () => {
|
|||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
const { container } = render(<Switch />);
|
const { container } = render(<Switch />);
|
||||||
fireEvent.click(container.querySelector('.ant-switch')!);
|
fireEvent.click(container.querySelector('.ant-switch')!);
|
||||||
|
act(() => {
|
||||||
|
jest.advanceTimersByTime(100);
|
||||||
|
});
|
||||||
expect(document.querySelector('.ant-wave')).toBeTruthy();
|
expect(document.querySelector('.ant-wave')).toBeTruthy();
|
||||||
jest.clearAllTimers();
|
jest.clearAllTimers();
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
|
Loading…
Reference in New Issue
Block a user