@pie-element/hotspot 10.0.0-beta.0 → 10.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +0 -11
  2. package/configure/CHANGELOG.md +0 -11
  3. package/configure/lib/defaults.js +3 -0
  4. package/configure/lib/defaults.js.map +1 -1
  5. package/configure/lib/hotspot-circle.js +0 -1
  6. package/configure/lib/hotspot-circle.js.map +1 -1
  7. package/configure/lib/hotspot-drawable.js +5 -5
  8. package/configure/lib/hotspot-drawable.js.map +1 -1
  9. package/configure/lib/hotspot-polygon.js +1 -2
  10. package/configure/lib/hotspot-polygon.js.map +1 -1
  11. package/configure/lib/hotspot-rectangle.js +0 -1
  12. package/configure/lib/hotspot-rectangle.js.map +1 -1
  13. package/configure/lib/root.js +4 -4
  14. package/configure/lib/root.js.map +1 -1
  15. package/configure/lib/utils.js +2 -3
  16. package/configure/lib/utils.js.map +1 -1
  17. package/configure/package.json +8 -8
  18. package/configure/src/__tests__/DeleteWidget.test.jsx +366 -0
  19. package/configure/src/__tests__/button.test.jsx +198 -0
  20. package/configure/src/__tests__/hotspot-circle.test.jsx +259 -0
  21. package/configure/src/__tests__/hotspot-palette.test.jsx +71 -0
  22. package/configure/src/__tests__/image-konva.test.jsx +226 -0
  23. package/configure/src/defaults.js +1 -0
  24. package/configure/src/hotspot-circle.jsx +0 -1
  25. package/configure/src/hotspot-drawable.jsx +1 -1
  26. package/configure/src/hotspot-polygon.jsx +0 -1
  27. package/configure/src/hotspot-rectangle.jsx +0 -1
  28. package/configure/src/root.jsx +1 -1
  29. package/configure/src/utils.js +1 -1
  30. package/controller/CHANGELOG.md +0 -11
  31. package/controller/lib/index.js +2 -2
  32. package/controller/lib/index.js.map +1 -1
  33. package/controller/lib/utils.js +3 -5
  34. package/controller/lib/utils.js.map +1 -1
  35. package/controller/package.json +3 -3
  36. package/controller/src/index.js +1 -1
  37. package/controller/src/utils.js +1 -2
  38. package/lib/hotspot/circle.js +1 -2
  39. package/lib/hotspot/circle.js.map +1 -1
  40. package/lib/hotspot/polygon.js +0 -1
  41. package/lib/hotspot/polygon.js.map +1 -1
  42. package/lib/hotspot/rectangle.js +0 -1
  43. package/lib/hotspot/rectangle.js.map +1 -1
  44. package/package.json +10 -10
  45. package/src/hotspot/__tests__/circle.test.jsx +464 -0
  46. package/src/hotspot/__tests__/container.test.jsx +546 -0
  47. package/src/hotspot/__tests__/image-konva-tooltip.test.jsx +510 -0
  48. package/src/hotspot/__tests__/polygon.test.jsx +502 -0
  49. package/src/hotspot/__tests__/rectangle.test.jsx +418 -0
  50. package/src/hotspot/circle.jsx +0 -1
  51. package/src/hotspot/polygon.jsx +0 -1
  52. package/src/hotspot/rectangle.jsx +0 -1
  53. package/configure/node_modules/debug/CHANGELOG.md +0 -395
  54. package/configure/node_modules/debug/LICENSE +0 -19
  55. package/configure/node_modules/debug/README.md +0 -437
  56. package/configure/node_modules/debug/node.js +0 -1
  57. package/configure/node_modules/debug/package.json +0 -51
  58. package/configure/node_modules/debug/src/browser.js +0 -180
  59. package/configure/node_modules/debug/src/common.js +0 -249
  60. package/configure/node_modules/debug/src/index.js +0 -12
  61. package/configure/node_modules/debug/src/node.js +0 -177
@@ -0,0 +1,198 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react';
3
+ import RawButton from '../button';
4
+
5
+ describe('RawButton', () => {
6
+ describe('rendering', () => {
7
+ it('should render without crashing', () => {
8
+ const { container } = render(<RawButton />);
9
+ expect(container).toBeTruthy();
10
+ });
11
+
12
+ it('should render with default label', () => {
13
+ const { getByText } = render(<RawButton />);
14
+ expect(getByText('Add')).toBeInTheDocument();
15
+ });
16
+
17
+ it('should render with custom label', () => {
18
+ const { getByText } = render(<RawButton label="Custom Label" />);
19
+ expect(getByText('Custom Label')).toBeInTheDocument();
20
+ });
21
+
22
+ it('should render as a button element', () => {
23
+ const { getByRole } = render(<RawButton label="Test Button" />);
24
+ expect(getByRole('button')).toBeInTheDocument();
25
+ });
26
+
27
+ it('should apply custom className', () => {
28
+ const { getByRole } = render(<RawButton label="Test" className="custom-class" />);
29
+ const button = getByRole('button');
30
+ expect(button).toHaveClass('custom-class');
31
+ });
32
+ });
33
+
34
+ describe('interactions', () => {
35
+ it('should call onClick when clicked', () => {
36
+ const onClick = jest.fn();
37
+ const { getByRole } = render(<RawButton label="Click Me" onClick={onClick} />);
38
+
39
+ const button = getByRole('button');
40
+ fireEvent.click(button);
41
+
42
+ expect(onClick).toHaveBeenCalledTimes(1);
43
+ });
44
+
45
+ it('should not call onClick when disabled', () => {
46
+ const onClick = jest.fn();
47
+ const { getByRole } = render(<RawButton label="Disabled" onClick={onClick} disabled={true} />);
48
+
49
+ const button = getByRole('button');
50
+ fireEvent.click(button);
51
+
52
+ expect(onClick).not.toHaveBeenCalled();
53
+ });
54
+
55
+ it('should call onClick multiple times', () => {
56
+ const onClick = jest.fn();
57
+ const { getByRole } = render(<RawButton label="Multi Click" onClick={onClick} />);
58
+
59
+ const button = getByRole('button');
60
+ fireEvent.click(button);
61
+ fireEvent.click(button);
62
+ fireEvent.click(button);
63
+
64
+ expect(onClick).toHaveBeenCalledTimes(3);
65
+ });
66
+ });
67
+
68
+ describe('disabled state', () => {
69
+ it('should be enabled by default', () => {
70
+ const { getByRole } = render(<RawButton label="Test" />);
71
+ const button = getByRole('button');
72
+ expect(button).not.toBeDisabled();
73
+ });
74
+
75
+ it('should be disabled when disabled prop is true', () => {
76
+ const { getByRole } = render(<RawButton label="Test" disabled={true} />);
77
+ const button = getByRole('button');
78
+ expect(button).toBeDisabled();
79
+ });
80
+
81
+ it('should be enabled when disabled prop is false', () => {
82
+ const { getByRole } = render(<RawButton label="Test" disabled={false} />);
83
+ const button = getByRole('button');
84
+ expect(button).not.toBeDisabled();
85
+ });
86
+ });
87
+
88
+ describe('default props', () => {
89
+ it('should use default onClick when not provided', () => {
90
+ const { getByRole } = render(<RawButton label="Test" />);
91
+ const button = getByRole('button');
92
+
93
+ // Should not throw error when clicked
94
+ expect(() => fireEvent.click(button)).not.toThrow();
95
+ });
96
+
97
+ it('should use default label "Add" when not provided', () => {
98
+ const { getByText } = render(<RawButton />);
99
+ expect(getByText('Add')).toBeInTheDocument();
100
+ });
101
+
102
+ it('should use empty className by default', () => {
103
+ const { getByRole } = render(<RawButton label="Test" />);
104
+ const button = getByRole('button');
105
+ expect(button.className).toBeTruthy(); // Will have MUI classes
106
+ });
107
+
108
+ it('should be enabled by default', () => {
109
+ const { getByRole } = render(<RawButton label="Test" />);
110
+ const button = getByRole('button');
111
+ expect(button).not.toBeDisabled();
112
+ });
113
+ });
114
+
115
+ describe('variant and size', () => {
116
+ it('should render with contained variant', () => {
117
+ const { getByRole } = render(<RawButton label="Test" />);
118
+ const button = getByRole('button');
119
+ expect(button).toHaveClass('MuiButton-contained');
120
+ });
121
+
122
+ it('should render with small size', () => {
123
+ const { getByRole } = render(<RawButton label="Test" />);
124
+ const button = getByRole('button');
125
+ expect(button).toHaveClass('MuiButton-sizeSmall');
126
+ });
127
+ });
128
+
129
+ describe('edge cases', () => {
130
+ it('should handle empty label', () => {
131
+ const { getByRole } = render(<RawButton label="" />);
132
+ const button = getByRole('button');
133
+ expect(button).toBeInTheDocument();
134
+ expect(button.textContent).toBe('');
135
+ });
136
+
137
+ it('should handle very long label', () => {
138
+ const longLabel = 'This is a very long button label that might wrap or overflow';
139
+ const { getByText } = render(<RawButton label={longLabel} />);
140
+ expect(getByText(longLabel)).toBeInTheDocument();
141
+ });
142
+
143
+ it('should handle special characters in label', () => {
144
+ const specialLabel = '!@#$%^&*()_+-=[]{}|;:",.<>?/~`';
145
+ const { getByText } = render(<RawButton label={specialLabel} />);
146
+ expect(getByText(specialLabel)).toBeInTheDocument();
147
+ });
148
+
149
+ it('should handle Unicode characters in label', () => {
150
+ const unicodeLabel = '🚀 Launch 你好 مرحبا';
151
+ const { getByText } = render(<RawButton label={unicodeLabel} />);
152
+ expect(getByText(unicodeLabel)).toBeInTheDocument();
153
+ });
154
+
155
+ it('should handle null onClick gracefully with default', () => {
156
+ const { getByRole } = render(<RawButton label="Test" onClick={null} />);
157
+ const button = getByRole('button');
158
+
159
+ // Should use default onClick
160
+ expect(() => fireEvent.click(button)).not.toThrow();
161
+ });
162
+ });
163
+
164
+ describe('prop updates', () => {
165
+ it('should update label when prop changes', () => {
166
+ const { getByText, rerender } = render(<RawButton label="Initial" />);
167
+ expect(getByText('Initial')).toBeInTheDocument();
168
+
169
+ rerender(<RawButton label="Updated" />);
170
+ expect(getByText('Updated')).toBeInTheDocument();
171
+ });
172
+
173
+ it('should update disabled state when prop changes', () => {
174
+ const { getByRole, rerender } = render(<RawButton label="Test" disabled={false} />);
175
+ const button = getByRole('button');
176
+ expect(button).not.toBeDisabled();
177
+
178
+ rerender(<RawButton label="Test" disabled={true} />);
179
+ expect(button).toBeDisabled();
180
+ });
181
+
182
+ it('should update onClick handler when prop changes', () => {
183
+ const onClick1 = jest.fn();
184
+ const onClick2 = jest.fn();
185
+ const { getByRole, rerender } = render(<RawButton label="Test" onClick={onClick1} />);
186
+
187
+ const button = getByRole('button');
188
+ fireEvent.click(button);
189
+ expect(onClick1).toHaveBeenCalledTimes(1);
190
+ expect(onClick2).not.toHaveBeenCalled();
191
+
192
+ rerender(<RawButton label="Test" onClick={onClick2} />);
193
+ fireEvent.click(button);
194
+ expect(onClick1).toHaveBeenCalledTimes(1);
195
+ expect(onClick2).toHaveBeenCalledTimes(1);
196
+ });
197
+ });
198
+ });
@@ -0,0 +1,259 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react';
3
+ import Konva from 'konva';
4
+ import CircleComponent from '../hotspot-circle';
5
+
6
+ Konva.isBrowser = false;
7
+
8
+ jest.mock('react-konva', () => {
9
+ const React = require('react');
10
+ return {
11
+ Circle: React.forwardRef(({ onClick, onTap, onMouseEnter, onMouseLeave, onDragStart, onDragEnd, onTransformStart, onTransformEnd, ...props }, ref) => {
12
+ const handleClick = (e) => {
13
+ if (onClick) onClick(e);
14
+ if (onTap) onTap(e);
15
+ };
16
+ return React.createElement('div', {
17
+ ref,
18
+ 'data-testid': 'circle',
19
+ onClick: handleClick,
20
+ onMouseEnter,
21
+ onMouseLeave,
22
+ ...props,
23
+ });
24
+ }),
25
+ Group: ({ children, onMouseEnter, onMouseLeave, ...props }) =>
26
+ React.createElement('div', { 'data-testid': 'group', onMouseEnter, onMouseLeave, ...props }, children),
27
+ Transformer: React.forwardRef(({ borderStroke, ...props }, ref) => {
28
+ return React.createElement('div', { ref, 'data-testid': 'transformer', 'data-border-stroke': borderStroke, ...props });
29
+ }),
30
+ };
31
+ });
32
+
33
+ jest.mock('../DeleteWidget', () => {
34
+ return function DeleteWidget({ id, handleWidgetClick }) {
35
+ return (
36
+ <div
37
+ data-testid="delete-widget"
38
+ data-id={id}
39
+ onClick={() => handleWidgetClick(id)}
40
+ />
41
+ );
42
+ };
43
+ });
44
+
45
+ describe('CircleComponent', () => {
46
+ let defaultProps;
47
+
48
+ beforeEach(() => {
49
+ defaultProps = {
50
+ id: 'circle1',
51
+ x: 100,
52
+ y: 150,
53
+ radius: 50,
54
+ hotspotColor: '#FF0000',
55
+ selectedHotspotColor: '#00FF00',
56
+ outlineColor: '#0000FF',
57
+ hoverOutlineColor: '#FFFF00',
58
+ correct: false,
59
+ isDrawing: false,
60
+ onClick: jest.fn(),
61
+ onDeleteShape: jest.fn(),
62
+ onDragEnd: jest.fn(),
63
+ strokeWidth: 5,
64
+ };
65
+
66
+ document.body.style.cursor = 'default';
67
+ });
68
+
69
+ afterEach(() => {
70
+ document.body.style.cursor = 'default';
71
+ });
72
+
73
+ describe('rendering', () => {
74
+ it('should render without crashing', () => {
75
+ const { container } = render(<CircleComponent {...defaultProps} />);
76
+ expect(container).toBeTruthy();
77
+ });
78
+
79
+ it('should render Group and Circle', () => {
80
+ const { getByTestId } = render(<CircleComponent {...defaultProps} />);
81
+ expect(getByTestId('group')).toBeInTheDocument();
82
+ expect(getByTestId('circle')).toBeInTheDocument();
83
+ });
84
+
85
+ it('should render with correct radius', () => {
86
+ const { getByTestId } = render(<CircleComponent {...defaultProps} radius={75} />);
87
+ const circle = getByTestId('circle');
88
+ expect(circle).toHaveAttribute('radius', '75');
89
+ });
90
+
91
+ it('should use minimum radius of 5 for invalid radius', () => {
92
+ const { getByTestId } = render(<CircleComponent {...defaultProps} radius={0} />);
93
+ const circle = getByTestId('circle');
94
+ expect(circle).toHaveAttribute('radius', '5');
95
+ });
96
+
97
+ it('should use minimum radius of 5 for NaN radius', () => {
98
+ const { getByTestId } = render(<CircleComponent {...defaultProps} radius={NaN} />);
99
+ const circle = getByTestId('circle');
100
+ expect(circle).toHaveAttribute('radius', '5');
101
+ });
102
+
103
+ it('should use minimum radius of 5 for negative radius', () => {
104
+ const { getByTestId } = render(<CircleComponent {...defaultProps} radius={-10} />);
105
+ const circle = getByTestId('circle');
106
+ expect(circle).toHaveAttribute('radius', '5');
107
+ });
108
+
109
+ it('should render with hotspot color when not correct', () => {
110
+ const { getByTestId } = render(<CircleComponent {...defaultProps} correct={false} />);
111
+ const circle = getByTestId('circle');
112
+ expect(circle).toHaveAttribute('fill', '#FF0000');
113
+ });
114
+
115
+ it('should render with selected color when correct', () => {
116
+ const { getByTestId } = render(<CircleComponent {...defaultProps} correct={true} />);
117
+ const circle = getByTestId('circle');
118
+ expect(circle).toHaveAttribute('fill', '#00FF00');
119
+ });
120
+ });
121
+
122
+ describe('interactions', () => {
123
+ it('should call onClick when clicked', () => {
124
+ const onClick = jest.fn();
125
+ const { getByTestId } = render(<CircleComponent {...defaultProps} onClick={onClick} />);
126
+ const circle = getByTestId('circle');
127
+
128
+ fireEvent.click(circle);
129
+
130
+ expect(onClick).toHaveBeenCalledWith('circle1');
131
+ });
132
+
133
+ it('should not call onClick when radius is 0 and isDrawing', () => {
134
+ const onClick = jest.fn();
135
+ const { getByTestId } = render(
136
+ <CircleComponent {...defaultProps} radius={0} isDrawing={true} onClick={onClick} />
137
+ );
138
+ const circle = getByTestId('circle');
139
+
140
+ fireEvent.click(circle);
141
+
142
+ expect(onClick).not.toHaveBeenCalled();
143
+ });
144
+ });
145
+
146
+ describe('hover state', () => {
147
+ it('should not show Transformer when not hovered', () => {
148
+ const { queryByTestId } = render(<CircleComponent {...defaultProps} />);
149
+
150
+ expect(queryByTestId('transformer')).not.toBeInTheDocument();
151
+ });
152
+ });
153
+
154
+ describe('drag functionality', () => {
155
+ it('should call onDragEnd with new position', () => {
156
+ const onDragEnd = jest.fn();
157
+ const { getByTestId } = render(
158
+ <CircleComponent {...defaultProps} onDragEnd={onDragEnd} />
159
+ );
160
+ const circle = getByTestId('circle');
161
+
162
+ const mockEvent = {
163
+ target: {
164
+ x: () => 200,
165
+ y: () => 250,
166
+ },
167
+ };
168
+
169
+ if (circle.onDragEnd) {
170
+ circle.onDragEnd(mockEvent);
171
+ }
172
+ });
173
+ });
174
+
175
+ describe('resize functionality', () => {
176
+ it('should handle resize with minimum radius constraint', () => {
177
+ const onDragEnd = jest.fn();
178
+ const { getByTestId } = render(
179
+ <CircleComponent {...defaultProps} onDragEnd={onDragEnd} />
180
+ );
181
+
182
+ // This tests the component's ability to handle transforms
183
+ // In actual usage, the transformer would modify the node's scale
184
+ const transformer = getByTestId('group');
185
+ expect(transformer).toBeInTheDocument();
186
+ });
187
+ });
188
+
189
+ describe('edge cases', () => {
190
+ it('should handle very large radius', () => {
191
+ const { getByTestId } = render(<CircleComponent {...defaultProps} radius={500} />);
192
+ const circle = getByTestId('circle');
193
+ expect(circle).toHaveAttribute('radius', '500');
194
+ });
195
+
196
+ it('should handle position at origin', () => {
197
+ const { getByTestId } = render(<CircleComponent {...defaultProps} x={0} y={0} />);
198
+ const circle = getByTestId('circle');
199
+ expect(circle).toHaveAttribute('x', '0');
200
+ expect(circle).toHaveAttribute('y', '0');
201
+ });
202
+
203
+ it('should handle negative positions', () => {
204
+ const { getByTestId } = render(<CircleComponent {...defaultProps} x={-50} y={-75} />);
205
+ const circle = getByTestId('circle');
206
+ expect(circle).toHaveAttribute('x', '-50');
207
+ expect(circle).toHaveAttribute('y', '-75');
208
+ });
209
+
210
+ it('should use default correct value of false', () => {
211
+ const { getByTestId } = render(<CircleComponent {...defaultProps} correct={undefined} />);
212
+ const circle = getByTestId('circle');
213
+ expect(circle).toHaveAttribute('fill', '#FF0000');
214
+ });
215
+
216
+ it('should handle missing selectedHotspotColor', () => {
217
+ const { getByTestId } = render(
218
+ <CircleComponent {...defaultProps} correct={true} selectedHotspotColor={undefined} />
219
+ );
220
+ const circle = getByTestId('circle');
221
+ // Should fall back to hotspotColor
222
+ expect(circle).toHaveAttribute('fill', '#FF0000');
223
+ });
224
+ });
225
+
226
+ describe('prop updates', () => {
227
+ it('should update radius when prop changes', () => {
228
+ const { getByTestId, rerender } = render(<CircleComponent {...defaultProps} radius={50} />);
229
+ let circle = getByTestId('circle');
230
+ expect(circle).toHaveAttribute('radius', '50');
231
+
232
+ rerender(<CircleComponent {...defaultProps} radius={75} />);
233
+ circle = getByTestId('circle');
234
+ expect(circle).toHaveAttribute('radius', '75');
235
+ });
236
+
237
+ it('should update color when correct prop changes', () => {
238
+ const { getByTestId, rerender } = render(<CircleComponent {...defaultProps} correct={false} />);
239
+ let circle = getByTestId('circle');
240
+ expect(circle).toHaveAttribute('fill', '#FF0000');
241
+
242
+ rerender(<CircleComponent {...defaultProps} correct={true} />);
243
+ circle = getByTestId('circle');
244
+ expect(circle).toHaveAttribute('fill', '#00FF00');
245
+ });
246
+
247
+ it('should update position when props change', () => {
248
+ const { getByTestId, rerender } = render(<CircleComponent {...defaultProps} x={100} y={150} />);
249
+ let circle = getByTestId('circle');
250
+ expect(circle).toHaveAttribute('x', '100');
251
+ expect(circle).toHaveAttribute('y', '150');
252
+
253
+ rerender(<CircleComponent {...defaultProps} x={200} y={250} />);
254
+ circle = getByTestId('circle');
255
+ expect(circle).toHaveAttribute('x', '200');
256
+ expect(circle).toHaveAttribute('y', '250');
257
+ });
258
+ });
259
+ });
@@ -0,0 +1,71 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react';
3
+ import Palette from '../hotspot-palette';
4
+
5
+ describe('Palette', () => {
6
+ let defaultProps;
7
+
8
+ beforeEach(() => {
9
+ defaultProps = {
10
+ hotspotColor: '#FF0000',
11
+ outlineColor: '#0000FF',
12
+ hotspotList: ['#FF0000', '#00FF00', '#0000FF', '#FFFF00'],
13
+ outlineList: ['#000000', '#0000FF', '#FF0000', '#00FF00'],
14
+ onHotspotColorChange: jest.fn(),
15
+ onOutlineColorChange: jest.fn(),
16
+ };
17
+ });
18
+
19
+ describe('rendering', () => {
20
+ it('should render without crashing', () => {
21
+ const { container } = render(<Palette {...defaultProps} />);
22
+ expect(container).toBeTruthy();
23
+ });
24
+ });
25
+
26
+ describe('edge cases', () => {
27
+ it('should handle empty hotspot list gracefully', () => {
28
+ const { container } = render(<Palette {...defaultProps} hotspotList={[]} />);
29
+ expect(container).toBeTruthy();
30
+ });
31
+
32
+ it('should handle empty outline list gracefully', () => {
33
+ const { container } = render(<Palette {...defaultProps} outlineList={[]} />);
34
+ expect(container).toBeTruthy();
35
+ });
36
+ });
37
+
38
+ describe('onChange handler', () => {
39
+ it('should create onChange handler for hotspot', () => {
40
+ const component = new Palette(defaultProps);
41
+ const handler = component.onChange('hotspot');
42
+ expect(typeof handler).toBe('function');
43
+ });
44
+
45
+ it('should create onChange handler for outline', () => {
46
+ const component = new Palette(defaultProps);
47
+ const handler = component.onChange('outline');
48
+ expect(typeof handler).toBe('function');
49
+ });
50
+
51
+ it('should call onHotspotColorChange when hotspot handler is invoked', () => {
52
+ const onHotspotColorChange = jest.fn();
53
+ const component = new Palette({ ...defaultProps, onHotspotColorChange });
54
+ const handler = component.onChange('hotspot');
55
+
56
+ handler({ target: { value: '#00FF00' } });
57
+
58
+ expect(onHotspotColorChange).toHaveBeenCalledWith('#00FF00');
59
+ });
60
+
61
+ it('should call onOutlineColorChange when outline handler is invoked', () => {
62
+ const onOutlineColorChange = jest.fn();
63
+ const component = new Palette({ ...defaultProps, onOutlineColorChange });
64
+ const handler = component.onChange('outline');
65
+
66
+ handler({ target: { value: '#FF0000' } });
67
+
68
+ expect(onOutlineColorChange).toHaveBeenCalledWith('#FF0000');
69
+ });
70
+ });
71
+ });