Skip to content

Commit

Permalink
feat: 新增覆盖物组件 AMapRectangle
Browse files Browse the repository at this point in the history
  • Loading branch information
xyy94813 committed Mar 15, 2024
1 parent 492b909 commit be3b9d8
Show file tree
Hide file tree
Showing 6 changed files with 758 additions and 0 deletions.
143 changes: 143 additions & 0 deletions src/components/AMapRectangle/AMapRectangle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/* eslint-disable react/default-props-match-prop-types */
import {
forwardRef,
useImperativeHandle,
useCallback,
useMemo,
} from 'react';

import useAMapPluginInstance from '../../hooks/useAMapPluginInstance';
import useSetter from '../../hooks/useSetter';
import useAMapOverlayBinder from '../../hooks/useAMapOverlayBinder';
import useAMapEventBinder from '../../hooks/useAMapEventBinder';
import useVisible from '../../hooks/useVisible';

import type { AMapRectangleProps } from './interface';

/**
* Origin API see:
* https://lbs.amap.com/api/javascript-api-v2/documentation#rectangle
*/

const defaultProps = {
visible: true,
};

const AMapRectangle = forwardRef<AMap.Rectangle, AMapRectangleProps>(({
bounds,
zIndex,
bubble,
cursor,
draggable,
visible,
extData,
height,

// styles
strokeColor,
strokeOpacity,
strokeWeight,
strokeStyle,
strokeDasharray,
fillColor,
fillOpacity,
// event properties
onShow,
onHide,
onClick,
onDBLClick,
onRightClick,
onMousedown,
onMouseup,
onMouseover,
onMouseout,
onTouchstart,
onTouchmove,
onTouchend,
}, ref) => {
const initInstance = useCallback((AMap) => new AMap!.Rectangle(), []);
const curInstance = useAMapPluginInstance<AMap.Rectangle>(
'Rectangle',
initInstance,
);

useImperativeHandle(ref, () => curInstance!, [curInstance]);

useSetter<Parameters<AMap.Rectangle['setExtData']>>(
curInstance,
'setExtData',
extData!,
);

const options: Parameters<AMap.Rectangle['setOptions']>[0] = useMemo(() => {
const opts = Object.entries({
// style options
zIndex,
cursor,
strokeColor,
strokeOpacity,
strokeWeight,
strokeStyle,
strokeDasharray,
fillColor,
fillOpacity,
// other options
draggable,
bubble,
})
.filter(([, val]) => val !== undefined && val !== null)
.reduce((finallyObj, [key, val]) => {
// eslint-disable-next-line no-param-reassign
finallyObj[key] = val;
return finallyObj;
}, {});
return opts;
}, [
bubble,
cursor,
draggable,
fillColor,
fillOpacity,
strokeColor,
strokeDasharray,
strokeOpacity,
strokeStyle,
strokeWeight,
zIndex,
]);
useSetter<Parameters<AMap.Rectangle['setOptions']>>(
curInstance,
'setOptions',
options!,
);

useSetter<Parameters<AMap.Rectangle['setBounds']>>(
curInstance,
'setBounds',
bounds!,
);
useSetter<Parameters<AMap.Rectangle['setHeight']>>(curInstance, 'setHeight', height || 0);

useVisible(curInstance, !!visible);

useAMapEventBinder(curInstance, 'show', onShow);
useAMapEventBinder(curInstance, 'hide', onHide);
useAMapEventBinder(curInstance, 'click', onClick);
useAMapEventBinder(curInstance, 'dblclick', onDBLClick);
useAMapEventBinder(curInstance, 'rightclick', onRightClick);
useAMapEventBinder(curInstance, 'mousedown', onMousedown);
useAMapEventBinder(curInstance, 'mouseup', onMouseup);
useAMapEventBinder(curInstance, 'mouseover', onMouseover);
useAMapEventBinder(curInstance, 'mouseout', onMouseout);
useAMapEventBinder(curInstance, 'touchstart', onTouchstart);
useAMapEventBinder(curInstance, 'touchmove', onTouchmove);
useAMapEventBinder(curInstance, 'touchend', onTouchend);

useAMapOverlayBinder(curInstance);

return null;
});

AMapRectangle.defaultProps = defaultProps;

export default AMapRectangle;
234 changes: 234 additions & 0 deletions src/components/AMapRectangle/__tests__/AMapRectangle.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import * as React from 'react';
import { createRef } from 'react';
import { render, cleanup } from '@testing-library/react';

import useAMapPluginInstance from '../../../hooks/useAMapPluginInstance';

import AMapRectangle from '../index';

const mockInstance = {
setBounds: jest.fn(),
setOptions: jest.fn(),
setExtData: jest.fn(),
setHeight: jest.fn(),
show: jest.fn(),
hide: jest.fn(),
on: jest.fn(),
off: jest.fn(),
};

jest.mock('../../../hooks/useAMapPluginInstance', () => ({
esModule: true,
default: jest.fn((__, cb) => {
cb({
Rectangle: jest.fn(),
}, {});
return mockInstance;
}),
}));

describe('AMapRectangle Component', () => {
const bounds: AMap.BoundsLike = [
116.39, 39.9,
117.39, 40.9,
];

beforeEach(() => {
jest.clearAllMocks();
});
afterEach(cleanup);

test('renders without crashing', () => {
expect(() => {
render(<AMapRectangle bounds={bounds} />);
}).not.toThrowError();
expect(useAMapPluginInstance).toHaveBeenCalledWith('Rectangle', expect.any(Function));
});

test('renders without crashing when instance is null', () => {
(useAMapPluginInstance as jest.Mock).mockReturnValueOnce(null);
expect(() => {
render(<AMapRectangle bounds={bounds} />);
}).not.toThrowError();
});

test('support ref to instance', () => {
(useAMapPluginInstance as jest.Mock)
.mockReturnValueOnce(null);
const $ref = createRef<any>();
const { rerender } = render(<AMapRectangle bounds={bounds} ref={$ref} />);
expect($ref.current).toBe(null);
rerender(<AMapRectangle bounds={bounds} ref={$ref} />);
expect($ref.current).toBe(mockInstance);
});

test('sets the circle center and radius', () => {
render(<AMapRectangle bounds={bounds} />);

expect(mockInstance.setBounds).toHaveBeenCalledWith(bounds);
});

test('sets the extra data', () => {
const extData = { id: 1 };
render(<AMapRectangle bounds={bounds} extData={extData} />);

expect(mockInstance.setExtData).toHaveBeenCalledWith(extData);
});

test('set to invisible', () => {
const { rerender } = render(<AMapRectangle bounds={bounds} />);

expect(mockInstance.show).toBeCalled();

rerender(<AMapRectangle bounds={bounds} visible={false} />);

expect(mockInstance.hide).toBeCalled();
});

test('set height', () => {
render(<AMapRectangle bounds={bounds} height={10} />);
expect(mockInstance.setHeight).toHaveBeenCalledWith(10);
});

test('updates options when props change', () => {
const zIndex = 1;
const bubble = false;
const cursor = '1';
const fillColor = '#ffffff';
const fillOpacity = 1;
const strokeColor = '#000000';
const strokeStyle = 'solid';
const strokeOpacity = 1;
const strokeWeight = 1;
const strokeDasharray: [number, number] = [10, 40];
const draggable = false;

const { rerender } = render(
<AMapRectangle
bounds={bounds}
zIndex={zIndex}
bubble={bubble}
cursor={cursor}
fillColor={fillColor}
fillOpacity={fillOpacity}
strokeColor={strokeColor}
strokeStyle={strokeStyle}
strokeOpacity={strokeOpacity}
strokeWeight={strokeWeight}
strokeDasharray={strokeDasharray}
draggable={draggable}
/>,
);

expect(mockInstance.setOptions).toHaveBeenCalledWith({
zIndex,
bubble,
cursor,
fillColor,
fillOpacity,
strokeColor,
strokeStyle,
strokeOpacity,
strokeWeight,
strokeDasharray,
draggable,
});

const newFillColor = '#00ff00';
const newStrokeColor = '#ffff00';

rerender(
<AMapRectangle
bounds={bounds}
zIndex={zIndex}
bubble={undefined}
cursor={undefined}
fillColor={newFillColor}
fillOpacity={fillOpacity}
strokeColor={newStrokeColor}
strokeStyle={strokeStyle}
strokeOpacity={strokeOpacity}
strokeWeight={strokeWeight}
strokeDasharray={strokeDasharray}
draggable={draggable}
/>,
);

expect(mockInstance.setOptions).toHaveBeenCalledWith({
zIndex,
// bubble,
// cursor,
fillColor: newFillColor,
fillOpacity,
strokeColor: newStrokeColor,
strokeStyle,
strokeOpacity,
strokeWeight,
strokeDasharray,
draggable,
});
});

test('bind event correctly', () => {
const onShow = jest.fn();
const onHide = jest.fn();
const onClick = jest.fn();
const onDBLClick = jest.fn();
const onRightClick = jest.fn();
const onMousedown = jest.fn();
const onMouseup = jest.fn();
const onMouseover = jest.fn();
const onMouseout = jest.fn();
const onTouchstart = jest.fn();
const onTouchmove = jest.fn();
const onTouchend = jest.fn();

const { unmount } = render(
<AMapRectangle
bounds={bounds}
onShow={onShow}
onHide={onHide}
onClick={onClick}
onDBLClick={onDBLClick}
onRightClick={onRightClick}
onMousedown={onMousedown}
onMouseup={onMouseup}
onMouseover={onMouseover}
onMouseout={onMouseout}
onTouchstart={onTouchstart}
onTouchmove={onTouchmove}
onTouchend={onTouchend}
/>,
);

expect(mockInstance.on).toBeCalledTimes(12);
expect(mockInstance.on).toHaveBeenCalledWith('show', onShow);
expect(mockInstance.on).toHaveBeenCalledWith('hide', onHide);
expect(mockInstance.on).toHaveBeenCalledWith('click', onClick);
expect(mockInstance.on).toHaveBeenCalledWith('dblclick', onDBLClick);
expect(mockInstance.on).toHaveBeenCalledWith('rightclick', onRightClick);
expect(mockInstance.on).toHaveBeenCalledWith('mousedown', onMousedown);
expect(mockInstance.on).toHaveBeenCalledWith('mouseup', onMouseup);
expect(mockInstance.on).toHaveBeenCalledWith('mouseover', onMouseover);
expect(mockInstance.on).toHaveBeenCalledWith('mouseout', onMouseout);
expect(mockInstance.on).toHaveBeenCalledWith('touchstart', onTouchstart);
expect(mockInstance.on).toHaveBeenCalledWith('touchmove', onTouchmove);
expect(mockInstance.on).toHaveBeenCalledWith('touchend', onTouchend);

unmount();

expect(mockInstance.off).toBeCalledTimes(12);
expect(mockInstance.off).toHaveBeenCalledWith('show', onShow);
expect(mockInstance.off).toHaveBeenCalledWith('hide', onHide);
expect(mockInstance.off).toHaveBeenCalledWith('click', onClick);
expect(mockInstance.off).toHaveBeenCalledWith('dblclick', onDBLClick);
expect(mockInstance.off).toHaveBeenCalledWith('rightclick', onRightClick);
expect(mockInstance.off).toHaveBeenCalledWith('mousedown', onMousedown);
expect(mockInstance.off).toHaveBeenCalledWith('mouseup', onMouseup);
expect(mockInstance.off).toHaveBeenCalledWith('mouseover', onMouseover);
expect(mockInstance.off).toHaveBeenCalledWith('mouseout', onMouseout);
expect(mockInstance.off).toHaveBeenCalledWith('touchstart', onTouchstart);
expect(mockInstance.off).toHaveBeenCalledWith('touchmove', onTouchmove);
expect(mockInstance.off).toHaveBeenCalledWith('touchend', onTouchend);
});
});
2 changes: 2 additions & 0 deletions src/components/AMapRectangle/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './interface';
export { default } from './AMapRectangle';
17 changes: 17 additions & 0 deletions src/components/AMapRectangle/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export type AMapRectangleProps = AMap.RectangleOptions & {
bounds: AMap.BoundsLike;
visible?: boolean;

onShow?: (event?: any) => void;
onHide?: (event?: any) => void;
onClick?: (event?: any) => void;
onDBLClick?: (event?: any) => void;
onRightClick?: (event?: any) => void;
onMousedown?: (event?: any) => void;
onMouseup?: (event?: any) => void;
onMouseover?: (event?: any) => void;
onMouseout?: (event?: any) => void;
onTouchstart?: (event?: any) => void;
onTouchmove?: (event?: any) => void;
onTouchend?: (event?: any) => void;
};
Loading

0 comments on commit be3b9d8

Please sign in to comment.