Skip to content

Commit 09c9ab9

Browse files
jiasy1616siyaojiagithub-actions[bot]novlan1
authored
test(color-picker): add ColorPicker tests (#783)
* test(color-picker): add ColorPicker tests * fix(vitest-config): update vitest setup file path * fix(color-picker): update snapshot * chore: update snapshot * chore: update snapshot * chore: update setupFiles import * chore: update coverate --------- Co-authored-by: siyaojia <[email protected]> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: novlan1 <[email protected]>
1 parent 5221762 commit 09c9ab9

File tree

11 files changed

+259
-54
lines changed

11 files changed

+259
-54
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { vi } from 'vitest';
2+
3+
class ResizeObserverMock {
4+
constructor(callback) {
5+
this.callback = callback;
6+
this.observe = vi.fn();
7+
this.unobserve = vi.fn();
8+
this.disconnect = vi.fn();
9+
}
10+
11+
trigger(entries) {
12+
this.callback(entries);
13+
}
14+
}
15+
16+
export default ResizeObserverMock;

script/test/mocks/Touch.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class Touch {
2+
constructor(init) {
3+
this.identifier = init.identifier;
4+
this.target = init.target;
5+
this.clientX = init.clientX;
6+
this.clientY = init.clientY;
7+
this.pageX = init.pageX;
8+
this.pageY = init.pageY;
9+
this.screenX = init.screenX;
10+
this.screenY = init.screenY;
11+
this.radiusX = init.radiusX;
12+
this.radiusY = init.radiusY;
13+
this.rotationAngle = init.rotationAngle;
14+
this.force = init.force;
15+
}
16+
}
17+
18+
export default Touch;

script/test/setup.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { vi } from 'vitest';
2+
import ResizeObserverMock from './mocks/ResizeObserverMock.js';
3+
import Touch from './mocks/Touch.js';
4+
5+
global.Touch = Touch;
6+
7+
vi.stubGlobal('ResizeObserver', ResizeObserverMock);
8+
vi.stubGlobal('Touch', Touch);

site/test-coverage.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module.exports = {
99
cell: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
1010
checkbox: { statements: '99.12%', branches: '98.27%', functions: '100%', lines: '100%' },
1111
collapse: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' },
12-
colorPicker: { statements: '3.03%', branches: '0%', functions: '0%', lines: '3.03%' },
12+
colorPicker: { statements: '97.33%', branches: '90.9%', functions: '97.72%', lines: '97.98%' },
1313
common: { statements: '82.75%', branches: '66.66%', functions: '83.33%', lines: '92%' },
1414
configProvider: { statements: '54.54%', branches: '0%', functions: '0%', lines: '54.54%' },
1515
countDown: { statements: '100%', branches: '90%', functions: '100%', lines: '100%' },

src/color-picker/ColorPicker.tsx

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ export interface ColorPickerProps extends TdColorPickerProps, StyledProps {}
2121
const ColorPicker: FC<ColorPickerProps> = (props) => {
2222
const { format, type, enableAlpha, swatchColors, style, value, defaultValue, fixed, onChange, onPaletteBarChange } =
2323
useDefaultProps(props, colorPickerDefaultProps);
24-
const [formatList, setFormatList] = useState([]);
24+
const [formatList, setFormatList] = useState<[string, Array<string | number>]>(['', []]);
2525
const [innerSwatchList, setInnerSwatchList] = useState([]);
26-
const [showPrimaryColorPreview] = useState(false);
27-
const [previewColor, setPreviewColor] = useState('');
2826
const [sliderInfo, setSliderInfo] = useState(0);
2927
const [panelRect, setPanelRect] = useState<PanelRectType>({
3028
width: SATURATION_PANEL_DEFAULT_WIDTH,
@@ -60,9 +58,7 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
6058
const getSliderThumbStyle = useCallback(
6159
({ value, maxValue }) => {
6260
const { width } = sliderRect;
63-
if (!width) {
64-
return;
65-
}
61+
if (!width) return;
6662
const left = Math.round((value / maxValue) * 100);
6763
return {
6864
left: `${left}%`,
@@ -96,7 +92,6 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
9692
value: color.current.value,
9793
}),
9894
);
99-
setPreviewColor(color.current.rgba);
10095
setFormatList(getFormatList(format, color.current));
10196
},
10297
[getSaturationThumbStyle, getSliderThumbStyle],
@@ -162,10 +157,6 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
162157
color.current = new Color(value || DEFAULT_COLOR);
163158
}, [value]);
164159

165-
useEffect(() => {
166-
setPreviewColor(value);
167-
}, [value]);
168-
169160
useEffect(() => {
170161
setCoreStyle(format);
171162
}, [format, setCoreStyle]);
@@ -210,13 +201,7 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
210201
const { x } = coordinate;
211202
const maxValue = isAlpha ? ALPHA_MAX : HUE_MAX;
212203

213-
let value = Math.round((x / width) * maxValue * 100) / 100;
214-
if (value < 0) {
215-
value = 0;
216-
}
217-
if (value > maxValue) {
218-
value = maxValue;
219-
}
204+
const value = Math.min(maxValue, Math.max(0, Math.round((x / width) * maxValue * 100) / 100));
220205
handleChangeSlider({ value, isAlpha });
221206
}
222207

@@ -249,8 +234,6 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
249234
case 'alpha-slider':
250235
handleSliderDrag(e, true);
251236
break;
252-
default:
253-
break;
254237
}
255238
}
256239

@@ -286,12 +269,6 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
286269
};
287270

288271
const renderPicker = () => {
289-
const renderPreviewColorContent = () => (
290-
<div className={classNames(`${rootClassName}__sliders-preview`, `${rootClassName}--bg-alpha`)}>
291-
<div className={`${rootClassName}__sliders-preview-inner`} style={{ background: previewColor }}></div>
292-
</div>
293-
);
294-
295272
const renderAlphaContent = () => (
296273
<div className={classNames(`${rootClassName}__slider-wrapper`, `${rootClassName}__slider-wrapper--alpha-type`)}>
297274
<div
@@ -336,15 +313,6 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
336313
}
337314
: { top: `${saturationThumbStyle.top}`, left: `${saturationThumbStyle.left}` }
338315
}
339-
onTouchStart={(e) => {
340-
e.stopPropagation();
341-
}}
342-
onTouchMove={(e) => {
343-
e.stopPropagation();
344-
}}
345-
onTouchEnd={(e) => {
346-
e.stopPropagation();
347-
}}
348316
/>
349317
</div>
350318
<div className={`${rootClassName}__sliders-wrapper`}>
@@ -368,15 +336,14 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
368336
</div>
369337
{enableAlpha ? renderAlphaContent() : null}
370338
</div>
371-
{showPrimaryColorPreview ? renderPreviewColorContent() : null}
372339
</div>
373340
<div className={`${rootClassName}__format`}>
374341
<div className={classNames(`${rootClassName}__format-item`, `${rootClassName}__format-item--first`)}>
375-
{format}
342+
{formatList[0]}
376343
</div>
377344
<div className={classNames(`${rootClassName}__format-item`, `${rootClassName}__format-item--second`)}>
378345
<div className={`${rootClassName}__format-inputs`}>
379-
{formatList.map((item, index) => (
346+
{formatList[1]?.map((item, index) => (
380347
<div
381348
key={index}
382349
className={classNames(
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import { act, describe, expect, fireEvent, it, render, vi } from '@test/utils';
2+
import React from 'react';
3+
4+
import { Color, getColorObject } from '../../_common/js/color-picker';
5+
import ColorPicker, { ColorPickerProps } from '../index';
6+
import { ColorFormat, TypeEnum } from '../type';
7+
8+
const prefix = 't';
9+
const name = `.${prefix}-color-picker`;
10+
11+
const makeTouch = (
12+
el: Element,
13+
eventName: string,
14+
touchPosition?: { clientX?: number; clientY?: number; pageX?: number; pageY?: number },
15+
) => {
16+
const touchInit = {
17+
changedTouches: [
18+
new Touch({
19+
identifier: 0,
20+
target: el,
21+
clientX: touchPosition?.clientX || 0,
22+
clientY: touchPosition?.clientY || 0,
23+
pageX: touchPosition?.pageX || 0,
24+
pageY: touchPosition?.pageY || 0,
25+
}),
26+
],
27+
bubbles: true,
28+
cancelable: true,
29+
};
30+
31+
const event = new TouchEvent(eventName, touchInit);
32+
el.dispatchEvent(event);
33+
};
34+
35+
const mockBoundingClientRect = (info) => {
36+
window.HTMLElement.prototype.getBoundingClientRect = () => info;
37+
};
38+
39+
const renderColorPicker = (props: ColorPickerProps) => render(<ColorPicker {...props} />);
40+
41+
describe('ColorPicker', () => {
42+
describe('props', () => {
43+
it(': multiple', () => {
44+
const testCurrentProp = (type: TypeEnum, target: number) => {
45+
const { container } = renderColorPicker({ type });
46+
const dom = container.querySelectorAll(`${name}__saturation`);
47+
expect(dom).toHaveLength(target);
48+
};
49+
testCurrentProp(undefined, 0);
50+
testCurrentProp('base', 0);
51+
testCurrentProp('multiple', 1);
52+
});
53+
54+
it(': enableAlpha', () => {
55+
const testEnableAlpha = (enableAlpha: boolean) => {
56+
const { container } = renderColorPicker({ enableAlpha, type: 'multiple' });
57+
const alphaDom = container.querySelectorAll(`${name}__slider-wrapper--alpha-type`);
58+
expect(alphaDom).toHaveLength(enableAlpha ? 1 : 0);
59+
};
60+
testEnableAlpha(false);
61+
testEnableAlpha(true);
62+
});
63+
64+
it(': swatchColors', () => {
65+
const testSwatchColors = (swatchColors: Array<string> | null, target: number) => {
66+
const { container } = renderColorPicker({ swatchColors });
67+
const dom = container.querySelectorAll(`${name}__swatches-item`);
68+
expect(dom).toHaveLength(target);
69+
};
70+
testSwatchColors(null, 0);
71+
testSwatchColors([], 0);
72+
testSwatchColors(undefined, 10);
73+
testSwatchColors(['red', 'blur'], 2);
74+
});
75+
76+
it(': format', () => {
77+
const testFormat = (format: string, target: ColorFormat) => {
78+
const { container } = renderColorPicker({ format: format as ColorFormat, type: 'multiple' });
79+
const dom = container.querySelector(`${name}__format-item--first`);
80+
expect(dom.innerHTML).toBe(target);
81+
};
82+
testFormat('RGB', 'RGB');
83+
testFormat('123', 'RGB');
84+
testFormat('HEX', 'HEX');
85+
});
86+
});
87+
88+
describe('events', () => {
89+
it(': preset change', async () => {
90+
const onChange = vi.fn();
91+
const { container } = renderColorPicker({ onChange });
92+
const swatch = container.querySelector(`${name}__swatches-item`);
93+
94+
fireEvent.click(swatch);
95+
const result = 'rgb(236, 242, 254)';
96+
97+
expect(onChange).toHaveBeenCalledTimes(1);
98+
expect(onChange).toHaveBeenLastCalledWith(result, {
99+
trigger: 'preset',
100+
color: getColorObject(new Color(result)),
101+
});
102+
});
103+
104+
it(': saturation change', async () => {
105+
const testSaturation = async (fixed = false) => {
106+
const onPaletteBarChange = vi.fn();
107+
const { container } = renderColorPicker({ onPaletteBarChange, type: 'multiple', fixed });
108+
const el = container.querySelector('.t-color-picker__saturation');
109+
110+
mockBoundingClientRect({
111+
left: 0,
112+
top: 0,
113+
width: 300,
114+
height: 50,
115+
});
116+
117+
act(() => {
118+
makeTouch(el, 'touchstart');
119+
makeTouch(el, 'touchmove', { pageY: 40, pageX: 0, clientY: 40 });
120+
makeTouch(el, 'touchmove', { pageY: 40, pageX: 0, clientY: 40 });
121+
makeTouch(el, 'touchmove', { pageY: 30, pageX: 0, clientY: 30 });
122+
makeTouch(el, 'touchend', { pageY: 30, pageX: 30, clientY: 20 });
123+
});
124+
125+
expect(onPaletteBarChange).toHaveBeenCalledTimes(3);
126+
const result = 'rgba(80, 80, 80, 1)';
127+
const color = new Color(result);
128+
color.saturation = 0;
129+
color.value = fixed ? 0.4 : 0.8214285714285714;
130+
131+
expect(onPaletteBarChange).toHaveBeenLastCalledWith({
132+
color: getColorObject(color),
133+
});
134+
};
135+
136+
await testSaturation();
137+
await testSaturation(true);
138+
});
139+
140+
it(': hue slider change', async () => {
141+
const onChange = vi.fn();
142+
const { container } = renderColorPicker({ onChange, type: 'multiple' });
143+
const el = container.querySelector('.t-color-picker__slider');
144+
145+
mockBoundingClientRect({
146+
left: 0,
147+
top: 0,
148+
width: 300,
149+
height: 50,
150+
});
151+
152+
act(() => {
153+
makeTouch(el, 'touchstart', { pageY: 0, pageX: 0, clientY: 30 });
154+
makeTouch(el, 'touchmove', { pageY: 0, pageX: 30, clientY: 30 });
155+
makeTouch(el, 'touchend', { pageY: 30, pageX: 40, clientY: 30 });
156+
});
157+
158+
expect(onChange).toHaveBeenCalledTimes(2);
159+
const result = 'rgb(151, 91, 0)';
160+
expect(onChange).toHaveBeenLastCalledWith(result, {
161+
trigger: 'palette-hue-bar',
162+
color: getColorObject(new Color(result)),
163+
});
164+
});
165+
166+
it(': alpha slider change', async () => {
167+
const onChange = vi.fn();
168+
const { container } = renderColorPicker({ onChange, type: 'multiple', enableAlpha: true });
169+
const el = container.querySelector('.t-color-picker__slider-wrapper--alpha-type .t-color-picker__slider');
170+
171+
mockBoundingClientRect({
172+
left: 0,
173+
top: 0,
174+
width: 300,
175+
height: 50,
176+
});
177+
act(() => {
178+
makeTouch(el, 'touchstart', { pageY: 0, pageX: 0, clientY: 30 });
179+
makeTouch(el, 'touchmove', { pageY: 0, pageX: 40, clientY: 30 });
180+
makeTouch(el, 'touchend', { pageY: 30, pageX: 40, clientY: 30 });
181+
});
182+
183+
expect(onChange).toHaveBeenCalledTimes(2);
184+
const result = 'rgb(0, 31, 151)';
185+
const color = new Color(result);
186+
color.alpha = 0.13;
187+
expect(onChange).toHaveBeenLastCalledWith(result, {
188+
trigger: 'palette-alpha-bar',
189+
color: getColorObject(color),
190+
});
191+
});
192+
});
193+
});

src/color-picker/helper/format.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const getCoordinate = (e: TouchEvent, rect: PanelRectType, isFixed?: bool
1313
};
1414
};
1515

16-
export const getFormatList = (format: ColorPickerProps['format'], color: Color) => {
16+
export const getFormatList = (format: ColorPickerProps['format'], color: Color): [string, Array<string | number>] => {
1717
const FORMAT_MAP = {
1818
HSV: Object.values(color.getHsva()),
1919
HSVA: Object.values(color.getHsva()),
@@ -32,9 +32,9 @@ export const getFormatList = (format: ColorPickerProps['format'], color: Color)
3232

3333
const cur = FORMAT_MAP[format];
3434
if (cur) {
35-
return [...cur.slice(0, cur.length - 1), `${Math.round(color.alpha * 100)}%`];
35+
return [format, [...cur.slice(0, cur.length - 1), `${Math.round(color.alpha * 100)}%`]];
3636
}
37-
return FORMAT_MAP.RGB;
37+
return ['RGB', FORMAT_MAP.RGB];
3838
};
3939

4040
export const genSwatchList = (prop: ColorPickerProps['swatchColors']) => {

src/color-picker/type.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export type TypeEnum = 'base' | 'multiple';
6262

6363
export type ColorPickerChangeTrigger = 'palette-hue-bar' | 'palette-alpha-bar' | 'preset';
6464

65+
export type ColorFormat = 'RGB' | 'RGBA' | 'HSL' | 'HSLA' | 'HSB' | 'HSV' | 'HSVA' | 'HEX' | 'CMYK' | 'CSS';
66+
6567
export type ColorPickerTrigger = 'overlay';
6668

6769
export interface ColorObject {

0 commit comments

Comments
 (0)