Browse Source

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
pull/39969/head
二货爱吃白萝卜 2 years ago
committed by GitHub
parent
commit
99b32a3c37
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 44
      components/_util/__tests__/wave.test.tsx
  3. 120
      components/_util/wave/WaveEffect.tsx
  4. 6
      components/button/__tests__/wave.test.tsx
  5. 5
      components/switch/__tests__/index.test.tsx

1
.gitignore

@ -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

44
components/_util/__tests__/wave.test.tsx

@ -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();
});

120
components/_util/wave/WaveEffect.tsx

@ -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);
}

6
components/button/__tests__/wave.test.tsx

@ -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'));
}

5
components/switch/__tests__/index.test.tsx

@ -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…
Cancel
Save