@pie-element/hotspot 11.1.2-next.2 → 11.1.2

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 (234) hide show
  1. package/CHANGELOG.json +997 -0
  2. package/CHANGELOG.md +2220 -0
  3. package/LICENSE.md +5 -0
  4. package/README.md +1 -0
  5. package/configure/CHANGELOG.json +682 -0
  6. package/configure/CHANGELOG.md +1957 -0
  7. package/configure/lib/DeleteWidget.js +64 -0
  8. package/configure/lib/DeleteWidget.js.map +1 -0
  9. package/configure/lib/button.js +42 -0
  10. package/configure/lib/button.js.map +1 -0
  11. package/configure/lib/buttons/circle.js +33 -0
  12. package/configure/lib/buttons/circle.js.map +1 -0
  13. package/configure/lib/buttons/polygon.js +39 -0
  14. package/configure/lib/buttons/polygon.js.map +1 -0
  15. package/configure/lib/buttons/rectangle.js +39 -0
  16. package/configure/lib/buttons/rectangle.js.map +1 -0
  17. package/configure/lib/defaults.js +155 -0
  18. package/configure/lib/defaults.js.map +1 -0
  19. package/configure/lib/hotspot-circle.js +192 -0
  20. package/configure/lib/hotspot-circle.js.map +1 -0
  21. package/configure/lib/hotspot-container.js +320 -0
  22. package/configure/lib/hotspot-container.js.map +1 -0
  23. package/configure/lib/hotspot-drawable.js +519 -0
  24. package/configure/lib/hotspot-drawable.js.map +1 -0
  25. package/configure/lib/hotspot-palette.js +107 -0
  26. package/configure/lib/hotspot-palette.js.map +1 -0
  27. package/configure/lib/hotspot-polygon.js +293 -0
  28. package/configure/lib/hotspot-polygon.js.map +1 -0
  29. package/configure/lib/hotspot-rectangle.js +190 -0
  30. package/configure/lib/hotspot-rectangle.js.map +1 -0
  31. package/configure/lib/icons.js +7 -0
  32. package/configure/lib/icons.js.map +1 -0
  33. package/configure/lib/image-konva.js +66 -0
  34. package/configure/lib/image-konva.js.map +1 -0
  35. package/configure/lib/index.js +194 -0
  36. package/configure/lib/index.js.map +1 -0
  37. package/configure/lib/root.js +330 -0
  38. package/configure/lib/root.js.map +1 -0
  39. package/configure/lib/shapes/circle.js +84 -0
  40. package/configure/lib/shapes/circle.js.map +1 -0
  41. package/configure/lib/shapes/index.js +50 -0
  42. package/configure/lib/shapes/index.js.map +1 -0
  43. package/configure/lib/shapes/polygon.js +82 -0
  44. package/configure/lib/shapes/polygon.js.map +1 -0
  45. package/configure/lib/shapes/rectagle.js +84 -0
  46. package/configure/lib/shapes/rectagle.js.map +1 -0
  47. package/configure/lib/shapes/utils.js +21 -0
  48. package/configure/lib/shapes/utils.js.map +1 -0
  49. package/configure/lib/upload-control.js +41 -0
  50. package/configure/lib/upload-control.js.map +1 -0
  51. package/configure/lib/utils.js +185 -0
  52. package/configure/lib/utils.js.map +1 -0
  53. package/configure/package.json +26 -0
  54. package/configure/src/DeleteWidget.jsx +51 -0
  55. package/configure/src/__tests__/DeleteWidget.test.jsx +366 -0
  56. package/configure/src/__tests__/button.test.jsx +198 -0
  57. package/configure/src/__tests__/hotspot-circle.test.jsx +259 -0
  58. package/configure/src/__tests__/hotspot-container.test.js +366 -0
  59. package/configure/src/__tests__/hotspot-drawable.test.js +271 -0
  60. package/configure/src/__tests__/hotspot-palette.test.jsx +71 -0
  61. package/configure/src/__tests__/image-konva.test.jsx +226 -0
  62. package/configure/src/__tests__/index.test.js +329 -0
  63. package/configure/src/__tests__/root.test.js +400 -0
  64. package/configure/src/__tests__/utils.test.js +241 -0
  65. package/configure/src/button.jsx +35 -0
  66. package/configure/src/buttons/circle.jsx +18 -0
  67. package/configure/src/buttons/polygon.jsx +29 -0
  68. package/configure/src/buttons/rectangle.jsx +29 -0
  69. package/configure/src/defaults.js +109 -0
  70. package/configure/src/hotspot-circle.jsx +183 -0
  71. package/configure/src/hotspot-container.jsx +330 -0
  72. package/configure/src/hotspot-drawable.jsx +527 -0
  73. package/configure/src/hotspot-palette.jsx +90 -0
  74. package/configure/src/hotspot-polygon.jsx +294 -0
  75. package/configure/src/hotspot-rectangle.jsx +169 -0
  76. package/configure/src/icons.js +5 -0
  77. package/configure/src/image-konva.jsx +63 -0
  78. package/configure/src/index.js +208 -0
  79. package/configure/src/root.jsx +346 -0
  80. package/configure/src/shapes/circle.js +81 -0
  81. package/configure/src/shapes/index.js +4 -0
  82. package/configure/src/shapes/polygon.js +81 -0
  83. package/configure/src/shapes/rectagle.js +82 -0
  84. package/configure/src/shapes/utils.js +16 -0
  85. package/configure/src/upload-control.jsx +33 -0
  86. package/configure/src/utils.js +210 -0
  87. package/controller/CHANGELOG.json +362 -0
  88. package/controller/CHANGELOG.md +1304 -0
  89. package/controller/lib/defaults.js +33 -0
  90. package/controller/lib/defaults.js.map +1 -0
  91. package/controller/lib/index.js +341 -0
  92. package/controller/lib/index.js.map +1 -0
  93. package/controller/lib/utils.js +32 -0
  94. package/controller/lib/utils.js.map +1 -0
  95. package/controller/package.json +18 -0
  96. package/controller/src/__tests__/index.test.js +419 -0
  97. package/controller/src/__tests__/utils.test.js +5 -0
  98. package/controller/src/defaults.js +19 -0
  99. package/controller/src/index.js +328 -0
  100. package/controller/src/utils.js +29 -0
  101. package/docs/config-schema.json +2023 -0
  102. package/docs/config-schema.json.md +1495 -0
  103. package/docs/demo/config.js +8 -0
  104. package/docs/demo/generate.js +118 -0
  105. package/docs/demo/index.html +1 -0
  106. package/docs/demo/session.js +11 -0
  107. package/docs/pie-schema.json +1204 -0
  108. package/docs/pie-schema.json.md +851 -0
  109. package/lib/hotspot/circle.js +156 -0
  110. package/lib/hotspot/circle.js.map +1 -0
  111. package/lib/hotspot/container.js +206 -0
  112. package/lib/hotspot/container.js.map +1 -0
  113. package/lib/hotspot/icons.js +8 -0
  114. package/lib/hotspot/icons.js.map +1 -0
  115. package/lib/hotspot/image-konva-tooltip.js +86 -0
  116. package/lib/hotspot/image-konva-tooltip.js.map +1 -0
  117. package/lib/hotspot/index.js +163 -0
  118. package/lib/hotspot/index.js.map +1 -0
  119. package/lib/hotspot/polygon.js +203 -0
  120. package/lib/hotspot/polygon.js.map +1 -0
  121. package/lib/hotspot/rectangle.js +175 -0
  122. package/lib/hotspot/rectangle.js.map +1 -0
  123. package/lib/index.js +213 -0
  124. package/lib/index.js.map +1 -0
  125. package/lib/session-updater.js +42 -0
  126. package/lib/session-updater.js.map +1 -0
  127. package/package.json +18 -83
  128. package/src/__tests__/container.test.jsx +58 -0
  129. package/src/__tests__/index.test.js +123 -0
  130. package/src/__tests__/session-updater.test.jsx +69 -0
  131. package/src/hotspot/__tests__/circle.test.jsx +464 -0
  132. package/src/hotspot/__tests__/container.test.jsx +546 -0
  133. package/src/hotspot/__tests__/image-konva-tooltip.test.jsx +510 -0
  134. package/src/hotspot/__tests__/polygon.test.jsx +502 -0
  135. package/src/hotspot/__tests__/rectangle.test.jsx +418 -0
  136. package/src/hotspot/circle.jsx +152 -0
  137. package/src/hotspot/container.jsx +217 -0
  138. package/src/hotspot/icons.js +7 -0
  139. package/src/hotspot/image-konva-tooltip.jsx +76 -0
  140. package/src/hotspot/index.jsx +165 -0
  141. package/src/hotspot/polygon.jsx +195 -0
  142. package/src/hotspot/rectangle.jsx +171 -0
  143. package/src/index.js +226 -0
  144. package/src/session-updater.js +29 -0
  145. package/configure.js +0 -2
  146. package/controller.js +0 -1
  147. package/dist/author/DeleteWidget.d.ts +0 -38
  148. package/dist/author/DeleteWidget.js +0 -46
  149. package/dist/author/button.d.ts +0 -31
  150. package/dist/author/button.js +0 -27
  151. package/dist/author/buttons/circle.d.ts +0 -18
  152. package/dist/author/buttons/circle.js +0 -25
  153. package/dist/author/buttons/polygon.d.ts +0 -18
  154. package/dist/author/buttons/polygon.js +0 -36
  155. package/dist/author/buttons/rectangle.d.ts +0 -18
  156. package/dist/author/buttons/rectangle.js +0 -36
  157. package/dist/author/defaults.d.ts +0 -157
  158. package/dist/author/defaults.js +0 -119
  159. package/dist/author/hotspot-circle.d.ts +0 -21
  160. package/dist/author/hotspot-circle.js +0 -124
  161. package/dist/author/hotspot-container.d.ts +0 -29
  162. package/dist/author/hotspot-container.js +0 -210
  163. package/dist/author/hotspot-drawable.d.ts +0 -31
  164. package/dist/author/hotspot-drawable.js +0 -312
  165. package/dist/author/hotspot-palette.d.ts +0 -14
  166. package/dist/author/hotspot-palette.js +0 -72
  167. package/dist/author/hotspot-polygon.d.ts +0 -38
  168. package/dist/author/hotspot-polygon.js +0 -200
  169. package/dist/author/hotspot-rectangle.d.ts +0 -20
  170. package/dist/author/hotspot-rectangle.js +0 -119
  171. package/dist/author/icons.d.ts +0 -9
  172. package/dist/author/icons.js +0 -4
  173. package/dist/author/image-konva.d.ts +0 -19
  174. package/dist/author/image-konva.js +0 -49
  175. package/dist/author/index.d.ts +0 -52
  176. package/dist/author/index.js +0 -143
  177. package/dist/author/root.d.ts +0 -15
  178. package/dist/author/root.js +0 -215
  179. package/dist/author/shapes/circle.d.ts +0 -18
  180. package/dist/author/shapes/circle.js +0 -47
  181. package/dist/author/shapes/index.d.ts +0 -12
  182. package/dist/author/shapes/polygon.d.ts +0 -19
  183. package/dist/author/shapes/polygon.js +0 -51
  184. package/dist/author/shapes/rectagle.d.ts +0 -18
  185. package/dist/author/shapes/rectagle.js +0 -57
  186. package/dist/author/shapes/utils.d.ts +0 -19
  187. package/dist/author/shapes/utils.js +0 -16
  188. package/dist/author/upload-control.d.ts +0 -29
  189. package/dist/author/upload-control.js +0 -28
  190. package/dist/author/utils.d.ts +0 -24
  191. package/dist/author/utils.js +0 -83
  192. package/dist/browser/ReactKonva-DI5WIo8o.js +0 -19336
  193. package/dist/browser/ReactKonva-DI5WIo8o.js.map +0 -1
  194. package/dist/browser/author/index.js +0 -41646
  195. package/dist/browser/author/index.js.map +0 -1
  196. package/dist/browser/browser-CfnAFove.js +0 -219
  197. package/dist/browser/browser-CfnAFove.js.map +0 -1
  198. package/dist/browser/controller/index.js +0 -198
  199. package/dist/browser/controller/index.js.map +0 -1
  200. package/dist/browser/delivery/index.js +0 -2460
  201. package/dist/browser/delivery/index.js.map +0 -1
  202. package/dist/browser/dist-C78LDz6R.js +0 -96
  203. package/dist/browser/dist-C78LDz6R.js.map +0 -1
  204. package/dist/browser/hotspot.css +0 -2
  205. package/dist/controller/defaults.d.ts +0 -35
  206. package/dist/controller/defaults.js +0 -29
  207. package/dist/controller/index.d.ts +0 -22
  208. package/dist/controller/index.js +0 -154
  209. package/dist/controller/utils.d.ts +0 -10
  210. package/dist/controller/utils.js +0 -12
  211. package/dist/delivery/hotspot/circle.d.ts +0 -19
  212. package/dist/delivery/hotspot/circle.js +0 -100
  213. package/dist/delivery/hotspot/container.d.ts +0 -16
  214. package/dist/delivery/hotspot/container.js +0 -150
  215. package/dist/delivery/hotspot/icons.d.ts +0 -10
  216. package/dist/delivery/hotspot/icons.js +0 -4
  217. package/dist/delivery/hotspot/image-konva-tooltip.d.ts +0 -19
  218. package/dist/delivery/hotspot/image-konva-tooltip.js +0 -66
  219. package/dist/delivery/hotspot/index.d.ts +0 -17
  220. package/dist/delivery/hotspot/index.js +0 -114
  221. package/dist/delivery/hotspot/polygon.d.ts +0 -21
  222. package/dist/delivery/hotspot/polygon.js +0 -108
  223. package/dist/delivery/hotspot/rectangle.d.ts +0 -19
  224. package/dist/delivery/hotspot/rectangle.js +0 -104
  225. package/dist/delivery/index.d.ts +0 -20
  226. package/dist/delivery/index.js +0 -107
  227. package/dist/delivery/session-updater.d.ts +0 -10
  228. package/dist/delivery/session-updater.js +0 -14
  229. package/dist/index.d.ts +0 -1
  230. package/dist/index.iife.d.ts +0 -8
  231. package/dist/index.iife.js +0 -169
  232. package/dist/index.js +0 -2
  233. package/dist/runtime-support.d.ts +0 -12
  234. package/dist/runtime-support.js +0 -12
@@ -0,0 +1,271 @@
1
+ import { render } from '@testing-library/react';
2
+ import React from 'react';
3
+ import { Drawable } from '../hotspot-drawable';
4
+
5
+ jest.mock('react-konva', () => {
6
+ const React = require('react');
7
+ return {
8
+ Stage: ({ children, ...props }) => React.createElement('div', { 'data-testid': 'stage', ...props }, children),
9
+ Layer: ({ children, ...props }) => React.createElement('div', { 'data-testid': 'layer', ...props }, children),
10
+ Rect: (props) => React.createElement('div', { 'data-testid': 'rect', ...props }),
11
+ Circle: (props) => React.createElement('div', { 'data-testid': 'circle', ...props }),
12
+ Line: (props) => React.createElement('div', { 'data-testid': 'line', ...props }),
13
+ Group: ({ children, ...props }) => React.createElement('div', { 'data-testid': 'group', ...props }, children),
14
+ Image: (props) => React.createElement('div', { 'data-testid': 'image', ...props }),
15
+ };
16
+ });
17
+
18
+ const model = () => ({
19
+ imageUrl: 'https://cdn.fluence.net/image/0240eb1455ce4c4bb6180232347b6aef_W',
20
+ shapes: [
21
+ {
22
+ id: '0',
23
+ height: 140,
24
+ width: 130,
25
+ x: 1,
26
+ y: 1,
27
+ correct: true,
28
+ group: 'rectangles',
29
+ },
30
+ {
31
+ id: '1',
32
+ height: 140,
33
+ width: 130,
34
+ x: 140,
35
+ y: 1,
36
+ group: 'rectangles',
37
+ },
38
+ {
39
+ id: '2',
40
+ height: 140,
41
+ width: 130,
42
+ x: 280,
43
+ y: 1,
44
+ group: 'rectangles',
45
+ },
46
+ {
47
+ id: '3',
48
+ points: [
49
+ { x: 1, y: 148 },
50
+ { x: 1, y: 288 },
51
+ { y: 288, x: 129 },
52
+ { y: 148, x: 129 },
53
+ ],
54
+ correct: true,
55
+ group: 'polygons',
56
+ },
57
+ {
58
+ id: '4',
59
+ points: [
60
+ { y: 151, x: 141 },
61
+ { y: 289, x: 141 },
62
+ { y: 289, x: 269 },
63
+ { x: 269, y: 151 },
64
+ ],
65
+ correct: false,
66
+ group: 'polygons',
67
+ },
68
+ {
69
+ id: '5',
70
+ points: [
71
+ { x: 279, y: 150 },
72
+ { x: 279, y: 289 },
73
+ { x: 407, y: 289 },
74
+ { x: 407, y: 150 },
75
+ ],
76
+ correct: false,
77
+ group: 'polygons',
78
+ },
79
+ ],
80
+ dimensions: {
81
+ height: 291,
82
+ width: 410,
83
+ },
84
+ hotspotColor: 'rgba(137, 183, 244, 0.65)',
85
+ outlineColor: 'blue',
86
+ multipleCorrect: true,
87
+ });
88
+
89
+ describe('HotspotDrawable', () => {
90
+ let w,
91
+ handleDisableDrag = jest.fn(),
92
+ handleEnableDrag = jest.fn(),
93
+ onUpdateImageDimension = jest.fn(),
94
+ onUpdateShapes = jest.fn(),
95
+ initialModel = model();
96
+ beforeEach(() => {
97
+ w = (extras) => {
98
+ const props = {
99
+ classes: {},
100
+ dimensions: initialModel.dimensions,
101
+ disableDrag: handleDisableDrag,
102
+ enableDrag: handleEnableDrag,
103
+ imageUrl: initialModel.imageUrl,
104
+ hotspotColor: initialModel.hotspotColor,
105
+ multipleCorrect: initialModel.multipleCorrect,
106
+ onUpdateImageDimension: onUpdateImageDimension,
107
+ onUpdateShapes: onUpdateShapes,
108
+ outlineColor: initialModel.outlineColor,
109
+ shapes: initialModel.shapes,
110
+ strokeWidth: 5,
111
+ shapeType: 'rectangle',
112
+ handleFinishDrawing: jest.fn(),
113
+ onDeleteShape: jest.fn(),
114
+ ...extras,
115
+ };
116
+
117
+ return render(<Drawable {...props} />);
118
+ };
119
+ });
120
+
121
+ describe('logic', () => {
122
+ const createInstance = () => {
123
+ const props = {
124
+ classes: {},
125
+ dimensions: initialModel.dimensions,
126
+ disableDrag: handleDisableDrag,
127
+ enableDrag: handleEnableDrag,
128
+ imageUrl: initialModel.imageUrl,
129
+ hotspotColor: initialModel.hotspotColor,
130
+ multipleCorrect: initialModel.multipleCorrect,
131
+ onUpdateImageDimension: onUpdateImageDimension,
132
+ onUpdateShapes: onUpdateShapes,
133
+ outlineColor: initialModel.outlineColor,
134
+ shapes: initialModel.shapes,
135
+ strokeWidth: 5,
136
+ shapeType: 'rectangle',
137
+ handleFinishDrawing: jest.fn(),
138
+ onDeleteShape: jest.fn(),
139
+ };
140
+ return new Drawable(props);
141
+ };
142
+
143
+ it('handleOnMouseDown target != Stage', () => {
144
+ const instance = createInstance();
145
+ instance.state.stateShapes = initialModel.shapes;
146
+
147
+ const event = {
148
+ target: 'Line',
149
+ currentTarget: 'Stage',
150
+ evt: {
151
+ layerX: 20,
152
+ layerY: 30,
153
+ },
154
+ };
155
+
156
+ instance.handleOnMouseDown(event);
157
+
158
+ expect(onUpdateShapes).not.toBeCalled();
159
+ });
160
+
161
+ it('handleOnMouseDown target = Stage', () => {
162
+ const instance = createInstance();
163
+ const event = {
164
+ target: 'Stage',
165
+ currentTarget: 'Stage',
166
+ evt: {
167
+ layerX: 20,
168
+ layerY: 30,
169
+ },
170
+ };
171
+
172
+ instance.handleOnMouseDown(event);
173
+
174
+ expect(onUpdateShapes).toHaveBeenCalledWith([
175
+ ...initialModel.shapes,
176
+ {
177
+ id: '6',
178
+ height: 0,
179
+ width: 0,
180
+ x: 20,
181
+ y: 30,
182
+ group: 'rectangles',
183
+ index: 6,
184
+ },
185
+ ]);
186
+ });
187
+
188
+ it('handleOnMouseUp isDrawing = true', () => {
189
+ const instance = createInstance();
190
+ instance.state.isDrawing = true;
191
+ instance.state.shapes = initialModel.shapes.slice(0, 2);
192
+
193
+ instance.handleOnMouseUp({
194
+ evt: {
195
+ layerX: 20,
196
+ layerY: 30,
197
+ },
198
+ });
199
+
200
+ expect(onUpdateShapes).toHaveBeenCalledWith([...initialModel.shapes.slice(0, 2)]);
201
+
202
+ // at this point, state.stateShapes is false, so we don't want to update shapes with false (onUpdateShapes)
203
+ expect(instance.state.stateShapes).toEqual(false);
204
+
205
+ instance.handleOnMouseUp({
206
+ evt: {
207
+ layerX: 20,
208
+ layerY: 30,
209
+ },
210
+ });
211
+
212
+ expect(onUpdateShapes).not.toBeCalledWith(false);
213
+
214
+ instance.handleOnMouseUp({});
215
+
216
+ expect(onUpdateShapes).toHaveBeenCalledWith(initialModel.shapes.slice(0, 2));
217
+ });
218
+
219
+ it('handleOnSetAsCorrect correct', () => {
220
+ const instance = createInstance();
221
+ instance.handleOnSetAsCorrect({
222
+ id: '1',
223
+ });
224
+
225
+ expect(onUpdateShapes).toHaveBeenCalledWith([
226
+ initialModel.shapes[0],
227
+ {
228
+ ...initialModel.shapes[1],
229
+ correct: true,
230
+ },
231
+ ...initialModel.shapes.slice(2),
232
+ ]);
233
+ });
234
+
235
+ it('handleOnSetAsCorrect incorrect', () => {
236
+ const instance = createInstance();
237
+ instance.handleOnSetAsCorrect({
238
+ id: '0',
239
+ });
240
+
241
+ expect(onUpdateShapes).toHaveBeenCalledWith([
242
+ {
243
+ ...initialModel.shapes[0],
244
+ correct: false,
245
+ },
246
+ ...initialModel.shapes.slice(1),
247
+ ]);
248
+ });
249
+
250
+ it('handleOnDragEnd', () => {
251
+ const instance = createInstance();
252
+ instance.handleOnDragEnd('0', { x: 1, y: 1 });
253
+
254
+ expect(onUpdateShapes).toHaveBeenCalledWith([
255
+ {
256
+ ...initialModel.shapes[0],
257
+ x: 1,
258
+ y: 1,
259
+ },
260
+ ...initialModel.shapes.slice(1),
261
+ ]);
262
+ });
263
+
264
+ it('handleOnDragEnd unexistent id', () => {
265
+ const instance = createInstance();
266
+ instance.handleOnDragEnd('10', { x: 1, y: 1 });
267
+
268
+ expect(onUpdateShapes).toHaveBeenCalledWith(initialModel.shapes);
269
+ });
270
+ });
271
+ });
@@ -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
+ });
@@ -0,0 +1,226 @@
1
+ import React from 'react';
2
+ import { render, waitFor } from '@testing-library/react';
3
+ import Konva from 'konva';
4
+ import ImageComponent from '../image-konva';
5
+
6
+ Konva.isBrowser = false;
7
+
8
+ jest.mock('react-konva', () => {
9
+ const React = require('react');
10
+ return {
11
+ Image: (props) => React.createElement('div', { 'data-testid': 'image', ...props }),
12
+ };
13
+ });
14
+
15
+ describe('ImageComponent', () => {
16
+ let defaultProps;
17
+
18
+ beforeEach(() => {
19
+ defaultProps = {
20
+ src: 'test-image.png',
21
+ x: 100,
22
+ y: 150,
23
+ };
24
+ });
25
+
26
+ describe('rendering', () => {
27
+ it('should render without crashing', () => {
28
+ const { container } = render(<ImageComponent {...defaultProps} />);
29
+ expect(container).toBeTruthy();
30
+ });
31
+
32
+ it('should render Image component', () => {
33
+ const { getByTestId } = render(<ImageComponent {...defaultProps} />);
34
+ expect(getByTestId('image')).toBeInTheDocument();
35
+ });
36
+
37
+ it('should render with correct position', () => {
38
+ const { getByTestId } = render(<ImageComponent {...defaultProps} x={200} y={250} />);
39
+ const image = getByTestId('image');
40
+ expect(image).toHaveAttribute('x', '200');
41
+ expect(image).toHaveAttribute('y', '250');
42
+ });
43
+
44
+ it('should render with fixed dimensions', () => {
45
+ const { getByTestId } = render(<ImageComponent {...defaultProps} />);
46
+ const image = getByTestId('image');
47
+ expect(image).toHaveAttribute('width', '20');
48
+ expect(image).toHaveAttribute('height', '20');
49
+ });
50
+
51
+ it('should render at origin (0, 0)', () => {
52
+ const { getByTestId } = render(<ImageComponent {...defaultProps} x={0} y={0} />);
53
+ const image = getByTestId('image');
54
+ expect(image).toHaveAttribute('x', '0');
55
+ expect(image).toHaveAttribute('y', '0');
56
+ });
57
+ });
58
+
59
+ describe('image loading', () => {
60
+ it('should load image on mount', async () => {
61
+ const { getByTestId } = render(<ImageComponent {...defaultProps} />);
62
+
63
+ const image = getByTestId('image');
64
+ expect(image).toBeInTheDocument();
65
+ });
66
+
67
+ it('should call loadImage on mount', () => {
68
+ const { container } = render(<ImageComponent {...defaultProps} src="custom-image.jpg" />);
69
+
70
+ expect(container).toBeTruthy();
71
+ });
72
+ });
73
+
74
+ describe('component lifecycle', () => {
75
+ it('should reload image when src changes', async () => {
76
+ const { rerender, container } = render(<ImageComponent {...defaultProps} src="image1.png" />);
77
+
78
+ expect(container).toBeTruthy();
79
+
80
+ rerender(<ImageComponent {...defaultProps} src="image2.png" />);
81
+
82
+ expect(container).toBeTruthy();
83
+ });
84
+
85
+ it('should not reload image when src stays the same', async () => {
86
+ const { rerender, getByTestId } = render(<ImageComponent {...defaultProps} src="same-image.png" />);
87
+
88
+ const image = getByTestId('image');
89
+ expect(image).toBeInTheDocument();
90
+
91
+ rerender(<ImageComponent {...defaultProps} src="same-image.png" x={200} y={250} />);
92
+
93
+ const updatedImage = getByTestId('image');
94
+ expect(updatedImage).toHaveAttribute('x', '200');
95
+ expect(updatedImage).toHaveAttribute('y', '250');
96
+ });
97
+
98
+ it('should clean up event listener on unmount', () => {
99
+ const { unmount } = render(<ImageComponent {...defaultProps} />);
100
+
101
+ expect(() => {
102
+ unmount();
103
+ }).not.toThrow();
104
+ });
105
+
106
+ it('should handle multiple mount/unmount cycles', () => {
107
+ const { unmount } = render(<ImageComponent {...defaultProps} />);
108
+ unmount();
109
+
110
+ expect(() => {
111
+ render(<ImageComponent {...defaultProps} />);
112
+ }).not.toThrow();
113
+ });
114
+ });
115
+
116
+ describe('position updates', () => {
117
+ it('should update x position', () => {
118
+ const { getByTestId, rerender } = render(<ImageComponent {...defaultProps} x={100} />);
119
+ let image = getByTestId('image');
120
+ expect(image).toHaveAttribute('x', '100');
121
+
122
+ rerender(<ImageComponent {...defaultProps} x={200} />);
123
+ image = getByTestId('image');
124
+ expect(image).toHaveAttribute('x', '200');
125
+ });
126
+
127
+ it('should update y position', () => {
128
+ const { getByTestId, rerender } = render(<ImageComponent {...defaultProps} y={150} />);
129
+ let image = getByTestId('image');
130
+ expect(image).toHaveAttribute('y', '150');
131
+
132
+ rerender(<ImageComponent {...defaultProps} y={250} />);
133
+ image = getByTestId('image');
134
+ expect(image).toHaveAttribute('y', '250');
135
+ });
136
+
137
+ it('should update both x and y positions', () => {
138
+ const { getByTestId, rerender } = render(<ImageComponent {...defaultProps} x={100} y={150} />);
139
+ let image = getByTestId('image');
140
+ expect(image).toHaveAttribute('x', '100');
141
+ expect(image).toHaveAttribute('y', '150');
142
+
143
+ rerender(<ImageComponent {...defaultProps} x={300} y={400} />);
144
+ image = getByTestId('image');
145
+ expect(image).toHaveAttribute('x', '300');
146
+ expect(image).toHaveAttribute('y', '400');
147
+ });
148
+ });
149
+
150
+ describe('edge cases', () => {
151
+ it('should handle negative positions', () => {
152
+ const { getByTestId } = render(<ImageComponent {...defaultProps} x={-50} y={-75} />);
153
+ const image = getByTestId('image');
154
+ expect(image).toHaveAttribute('x', '-50');
155
+ expect(image).toHaveAttribute('y', '-75');
156
+ });
157
+
158
+ it('should handle very large positions', () => {
159
+ const { getByTestId } = render(<ImageComponent {...defaultProps} x={10000} y={20000} />);
160
+ const image = getByTestId('image');
161
+ expect(image).toHaveAttribute('x', '10000');
162
+ expect(image).toHaveAttribute('y', '20000');
163
+ });
164
+
165
+ it('should handle data URI as src', () => {
166
+ const dataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
167
+ const { container } = render(<ImageComponent {...defaultProps} src={dataUri} />);
168
+
169
+ expect(container).toBeTruthy();
170
+ });
171
+
172
+ it('should handle SVG as src', () => {
173
+ const { container } = render(<ImageComponent {...defaultProps} src="icon.svg" />);
174
+ expect(container).toBeTruthy();
175
+ });
176
+
177
+ it('should handle empty src', () => {
178
+ const { container } = render(<ImageComponent {...defaultProps} src="" />);
179
+ expect(container).toBeTruthy();
180
+ });
181
+
182
+ it('should handle URL with query parameters', () => {
183
+ const srcWithParams = 'image.png?width=100&height=100';
184
+ const { container } = render(<ImageComponent {...defaultProps} src={srcWithParams} />);
185
+
186
+ expect(container).toBeTruthy();
187
+ });
188
+
189
+ it('should handle relative paths', () => {
190
+ const { container } = render(<ImageComponent {...defaultProps} src="./images/icon.png" />);
191
+ expect(container).toBeTruthy();
192
+ });
193
+
194
+ it('should handle absolute paths', () => {
195
+ const { container } = render(<ImageComponent {...defaultProps} src="/assets/icon.png" />);
196
+ expect(container).toBeTruthy();
197
+ });
198
+ });
199
+
200
+ describe('image state', () => {
201
+ it('should initially render without image loaded', () => {
202
+ const { getByTestId } = render(<ImageComponent {...defaultProps} />);
203
+ const image = getByTestId('image');
204
+ expect(image).toBeInTheDocument();
205
+ });
206
+
207
+ it('should render component regardless of image load state', () => {
208
+ const { getByTestId } = render(<ImageComponent {...defaultProps} />);
209
+ const image = getByTestId('image');
210
+ expect(image).toBeInTheDocument();
211
+ });
212
+ });
213
+
214
+ describe('error handling', () => {
215
+ it('should handle image load error gracefully', () => {
216
+ const { container } = render(<ImageComponent {...defaultProps} src="nonexistent.png" />);
217
+
218
+ const imgElements = document.querySelectorAll('img[src="nonexistent.png"]');
219
+ imgElements.forEach(img => {
220
+ img.dispatchEvent(new Event('error'));
221
+ });
222
+
223
+ expect(container).toBeTruthy();
224
+ });
225
+ });
226
+ });