@pie-element/hotspot 11.1.2-next.5 → 11.1.3

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 +2226 -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 +1963 -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-CFo7dxdy.js +0 -19336
  193. package/dist/browser/ReactKonva-CFo7dxdy.js.map +0 -1
  194. package/dist/browser/author/index.js +0 -47549
  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-CeB-1djc.js +0 -100
  203. package/dist/browser/dist-CeB-1djc.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,546 @@
1
+ import React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react';
3
+ import Konva from 'konva';
4
+ import { Container } from '../container';
5
+
6
+ Konva.isBrowser = false;
7
+
8
+ jest.mock('react-konva', () => {
9
+ const React = require('react');
10
+ return {
11
+ Stage: ({ children, ...props }) => React.createElement('div', { 'data-testid': 'stage', ...props }, children),
12
+ Layer: ({ children, ...props }) => React.createElement('div', { 'data-testid': 'layer', ...props }, children),
13
+ };
14
+ });
15
+
16
+ jest.mock('../rectangle', () => {
17
+ return function Rectangle(props) {
18
+ return (
19
+ <div
20
+ data-testid={`rectangle-${props.id}`}
21
+ data-selected={props.selected}
22
+ data-iscorrect={props.isCorrect}
23
+ data-disabled={props.disabled}
24
+ onClick={() => props.onClick({ id: props.id, selected: !props.selected })}
25
+ />
26
+ );
27
+ };
28
+ });
29
+
30
+ jest.mock('../polygon', () => {
31
+ return function Polygon(props) {
32
+ return (
33
+ <div
34
+ data-testid={`polygon-${props.id}`}
35
+ data-selected={props.selected}
36
+ data-iscorrect={props.isCorrect}
37
+ data-disabled={props.disabled}
38
+ onClick={() => props.onClick({ id: props.id, selected: !props.selected })}
39
+ />
40
+ );
41
+ };
42
+ });
43
+
44
+ jest.mock('../circle', () => {
45
+ return function Circle(props) {
46
+ return (
47
+ <div
48
+ data-testid={`circle-${props.id}`}
49
+ data-selected={props.selected}
50
+ data-iscorrect={props.isCorrect}
51
+ data-disabled={props.disabled}
52
+ onClick={() => props.onClick({ id: props.id, selected: !props.selected })}
53
+ />
54
+ );
55
+ };
56
+ });
57
+
58
+ describe('Container', () => {
59
+ let defaultProps;
60
+
61
+ beforeEach(() => {
62
+ defaultProps = {
63
+ dimensions: { width: 800, height: 600 },
64
+ disabled: false,
65
+ hotspotColor: '#FF0000',
66
+ hoverOutlineColor: '#FFFF00',
67
+ selectedHotspotColor: '#00FF00',
68
+ imageUrl: 'http://example.com/image.png',
69
+ isEvaluateMode: false,
70
+ onSelectChoice: jest.fn(),
71
+ outlineColor: '#0000FF',
72
+ session: { answers: [] },
73
+ shapes: {
74
+ rectangles: [],
75
+ polygons: [],
76
+ circles: [],
77
+ },
78
+ strokeWidth: 5,
79
+ scale: 1,
80
+ showCorrect: false,
81
+ };
82
+ });
83
+
84
+ describe('rendering', () => {
85
+ it('should render without crashing', () => {
86
+ const { container } = render(<Container {...defaultProps} />);
87
+ expect(container).toBeTruthy();
88
+ });
89
+
90
+ it('should render image when imageUrl is provided', () => {
91
+ const { getByAltText } = render(<Container {...defaultProps} />);
92
+ const image = getByAltText('hotspot-image');
93
+ expect(image).toBeInTheDocument();
94
+ expect(image).toHaveAttribute('src', 'http://example.com/image.png');
95
+ });
96
+
97
+ it('should not render image when imageUrl is not provided', () => {
98
+ const { queryByAltText } = render(<Container {...defaultProps} imageUrl="" />);
99
+ expect(queryByAltText('hotspot-image')).not.toBeInTheDocument();
100
+ });
101
+
102
+ it('should render Stage with correct dimensions', () => {
103
+ const { getByTestId } = render(<Container {...defaultProps} />);
104
+ const stage = getByTestId('stage');
105
+ expect(stage).toHaveAttribute('height', '605'); // 600 + strokeWidth
106
+ expect(stage).toHaveAttribute('width', '805'); // 800 + strokeWidth
107
+ });
108
+
109
+ it('should apply scale to dimensions', () => {
110
+ const { getByTestId } = render(<Container {...defaultProps} scale={2} />);
111
+ const stage = getByTestId('stage');
112
+ expect(stage).toHaveAttribute('height', '1205'); // (600 * 2) + strokeWidth
113
+ expect(stage).toHaveAttribute('width', '1605'); // (800 * 2) + strokeWidth
114
+ });
115
+
116
+ it('should render Layer inside Stage', () => {
117
+ const { getByTestId } = render(<Container {...defaultProps} />);
118
+ const layer = getByTestId('layer');
119
+ expect(layer).toBeInTheDocument();
120
+ });
121
+ });
122
+
123
+ describe('rectangle shapes', () => {
124
+ it('should render rectangles', () => {
125
+ const props = {
126
+ ...defaultProps,
127
+ shapes: {
128
+ rectangles: [
129
+ { id: 'rect1', x: 10, y: 20, width: 100, height: 80, correct: false },
130
+ { id: 'rect2', x: 150, y: 200, width: 120, height: 90, correct: false },
131
+ ],
132
+ polygons: [],
133
+ circles: [],
134
+ },
135
+ };
136
+ const { getByTestId } = render(<Container {...props} />);
137
+
138
+ expect(getByTestId('rectangle-rect1')).toBeInTheDocument();
139
+ expect(getByTestId('rectangle-rect2')).toBeInTheDocument();
140
+ });
141
+
142
+ it('should mark rectangle as selected when in session answers', () => {
143
+ const props = {
144
+ ...defaultProps,
145
+ session: { answers: [{ id: 'rect1' }] },
146
+ shapes: {
147
+ rectangles: [
148
+ { id: 'rect1', x: 10, y: 20, width: 100, height: 80, correct: false },
149
+ ],
150
+ polygons: [],
151
+ circles: [],
152
+ },
153
+ };
154
+ const { getByTestId } = render(<Container {...props} />);
155
+
156
+ const rect = getByTestId('rectangle-rect1');
157
+ expect(rect).toHaveAttribute('data-selected', 'true');
158
+ });
159
+
160
+ it('should call onSelectChoice when rectangle is clicked', () => {
161
+ const onSelectChoice = jest.fn();
162
+ const props = {
163
+ ...defaultProps,
164
+ onSelectChoice,
165
+ shapes: {
166
+ rectangles: [
167
+ { id: 'rect1', x: 10, y: 20, width: 100, height: 80, correct: false },
168
+ ],
169
+ polygons: [],
170
+ circles: [],
171
+ },
172
+ };
173
+ const { getByTestId } = render(<Container {...props} />);
174
+
175
+ const rect = getByTestId('rectangle-rect1');
176
+ fireEvent.click(rect);
177
+
178
+ expect(onSelectChoice).toHaveBeenCalledWith({ id: 'rect1', selected: true });
179
+ });
180
+ });
181
+
182
+ describe('polygon shapes', () => {
183
+ it('should render polygons', () => {
184
+ const props = {
185
+ ...defaultProps,
186
+ shapes: {
187
+ rectangles: [],
188
+ polygons: [
189
+ {
190
+ id: 'poly1',
191
+ points: [{ x: 10, y: 10 }, { x: 100, y: 10 }, { x: 50, y: 100 }],
192
+ correct: false,
193
+ },
194
+ ],
195
+ circles: [],
196
+ },
197
+ };
198
+ const { getByTestId } = render(<Container {...props} />);
199
+
200
+ expect(getByTestId('polygon-poly1')).toBeInTheDocument();
201
+ });
202
+
203
+ it('should mark polygon as selected when in session answers', () => {
204
+ const props = {
205
+ ...defaultProps,
206
+ session: { answers: [{ id: 'poly1' }] },
207
+ shapes: {
208
+ rectangles: [],
209
+ polygons: [
210
+ {
211
+ id: 'poly1',
212
+ points: [{ x: 10, y: 10 }, { x: 100, y: 10 }, { x: 50, y: 100 }],
213
+ correct: false,
214
+ },
215
+ ],
216
+ circles: [],
217
+ },
218
+ };
219
+ const { getByTestId } = render(<Container {...props} />);
220
+
221
+ const polygon = getByTestId('polygon-poly1');
222
+ expect(polygon).toHaveAttribute('data-selected', 'true');
223
+ });
224
+
225
+ it('should call onSelectChoice when polygon is clicked', () => {
226
+ const onSelectChoice = jest.fn();
227
+ const props = {
228
+ ...defaultProps,
229
+ onSelectChoice,
230
+ shapes: {
231
+ rectangles: [],
232
+ polygons: [
233
+ {
234
+ id: 'poly1',
235
+ points: [{ x: 10, y: 10 }, { x: 100, y: 10 }, { x: 50, y: 100 }],
236
+ correct: false,
237
+ },
238
+ ],
239
+ circles: [],
240
+ },
241
+ };
242
+ const { getByTestId } = render(<Container {...props} />);
243
+
244
+ const polygon = getByTestId('polygon-poly1');
245
+ fireEvent.click(polygon);
246
+
247
+ expect(onSelectChoice).toHaveBeenCalledWith({ id: 'poly1', selected: true });
248
+ });
249
+ });
250
+
251
+ describe('circle shapes', () => {
252
+ it('should render circles', () => {
253
+ const props = {
254
+ ...defaultProps,
255
+ shapes: {
256
+ rectangles: [],
257
+ polygons: [],
258
+ circles: [
259
+ { id: 'circle1', x: 100, y: 100, radius: 50, correct: false },
260
+ ],
261
+ },
262
+ };
263
+ const { getByTestId } = render(<Container {...props} />);
264
+
265
+ expect(getByTestId('circle-circle1')).toBeInTheDocument();
266
+ });
267
+
268
+ it('should mark circle as selected when in session answers', () => {
269
+ const props = {
270
+ ...defaultProps,
271
+ session: { answers: [{ id: 'circle1' }] },
272
+ shapes: {
273
+ rectangles: [],
274
+ polygons: [],
275
+ circles: [
276
+ { id: 'circle1', x: 100, y: 100, radius: 50, correct: false },
277
+ ],
278
+ },
279
+ };
280
+ const { getByTestId } = render(<Container {...props} />);
281
+
282
+ const circle = getByTestId('circle-circle1');
283
+ expect(circle).toHaveAttribute('data-selected', 'true');
284
+ });
285
+
286
+ it('should call onSelectChoice when circle is clicked', () => {
287
+ const onSelectChoice = jest.fn();
288
+ const props = {
289
+ ...defaultProps,
290
+ onSelectChoice,
291
+ shapes: {
292
+ rectangles: [],
293
+ polygons: [],
294
+ circles: [
295
+ { id: 'circle1', x: 100, y: 100, radius: 50, correct: false },
296
+ ],
297
+ },
298
+ };
299
+ const { getByTestId } = render(<Container {...props} />);
300
+
301
+ const circle = getByTestId('circle-circle1');
302
+ fireEvent.click(circle);
303
+
304
+ expect(onSelectChoice).toHaveBeenCalledWith({ id: 'circle1', selected: true });
305
+ });
306
+ });
307
+
308
+ describe('evaluate mode', () => {
309
+ it('should show correctness when in evaluate mode', () => {
310
+ const props = {
311
+ ...defaultProps,
312
+ isEvaluateMode: true,
313
+ session: { answers: [{ id: 'rect1' }] },
314
+ shapes: {
315
+ rectangles: [
316
+ { id: 'rect1', x: 10, y: 20, width: 100, height: 80, correct: true },
317
+ ],
318
+ polygons: [],
319
+ circles: [],
320
+ },
321
+ };
322
+ const { getByTestId } = render(<Container {...props} />);
323
+
324
+ const rect = getByTestId('rectangle-rect1');
325
+ expect(rect).toHaveAttribute('data-iscorrect', 'true');
326
+ });
327
+
328
+ it('should show incorrect when selected but not correct', () => {
329
+ const props = {
330
+ ...defaultProps,
331
+ isEvaluateMode: true,
332
+ session: { answers: [{ id: 'rect1' }] },
333
+ shapes: {
334
+ rectangles: [
335
+ { id: 'rect1', x: 10, y: 20, width: 100, height: 80, correct: false },
336
+ ],
337
+ polygons: [],
338
+ circles: [],
339
+ },
340
+ };
341
+ const { getByTestId } = render(<Container {...props} />);
342
+
343
+ const rect = getByTestId('rectangle-rect1');
344
+ expect(rect).toHaveAttribute('data-iscorrect', 'false');
345
+ });
346
+
347
+ it('should show incorrect when not selected but correct', () => {
348
+ const props = {
349
+ ...defaultProps,
350
+ isEvaluateMode: true,
351
+ session: { answers: [] },
352
+ shapes: {
353
+ rectangles: [
354
+ { id: 'rect1', x: 10, y: 20, width: 100, height: 80, correct: true },
355
+ ],
356
+ polygons: [],
357
+ circles: [],
358
+ },
359
+ };
360
+ const { getByTestId } = render(<Container {...props} />);
361
+
362
+ const rect = getByTestId('rectangle-rect1');
363
+ expect(rect).toHaveAttribute('data-iscorrect', 'false');
364
+ });
365
+
366
+ it('should show correct when not selected and not correct', () => {
367
+ const props = {
368
+ ...defaultProps,
369
+ isEvaluateMode: true,
370
+ session: { answers: [] },
371
+ shapes: {
372
+ rectangles: [
373
+ { id: 'rect1', x: 10, y: 20, width: 100, height: 80, correct: false },
374
+ ],
375
+ polygons: [],
376
+ circles: [],
377
+ },
378
+ };
379
+ const { getByTestId } = render(<Container {...props} />);
380
+
381
+ const rect = getByTestId('rectangle-rect1');
382
+ expect(rect).toHaveAttribute('data-iscorrect', 'true');
383
+ });
384
+ });
385
+
386
+ describe('disabled state', () => {
387
+ it('should pass disabled prop to shapes', () => {
388
+ const props = {
389
+ ...defaultProps,
390
+ disabled: true,
391
+ shapes: {
392
+ rectangles: [
393
+ { id: 'rect1', x: 10, y: 20, width: 100, height: 80, correct: false },
394
+ ],
395
+ polygons: [],
396
+ circles: [],
397
+ },
398
+ };
399
+ const { getByTestId } = render(<Container {...props} />);
400
+
401
+ const rect = getByTestId('rectangle-rect1');
402
+ expect(rect).toHaveAttribute('data-disabled', 'true');
403
+ });
404
+ });
405
+
406
+ describe('mixed shapes', () => {
407
+ it('should render all types of shapes together', () => {
408
+ const props = {
409
+ ...defaultProps,
410
+ shapes: {
411
+ rectangles: [
412
+ { id: 'rect1', x: 10, y: 20, width: 100, height: 80, correct: false },
413
+ ],
414
+ polygons: [
415
+ {
416
+ id: 'poly1',
417
+ points: [{ x: 10, y: 10 }, { x: 100, y: 10 }, { x: 50, y: 100 }],
418
+ correct: false,
419
+ },
420
+ ],
421
+ circles: [
422
+ { id: 'circle1', x: 100, y: 100, radius: 50, correct: false },
423
+ ],
424
+ },
425
+ };
426
+ const { getByTestId } = render(<Container {...props} />);
427
+
428
+ expect(getByTestId('rectangle-rect1')).toBeInTheDocument();
429
+ expect(getByTestId('polygon-poly1')).toBeInTheDocument();
430
+ expect(getByTestId('circle-circle1')).toBeInTheDocument();
431
+ });
432
+
433
+ it('should handle multiple selections across different shape types', () => {
434
+ const props = {
435
+ ...defaultProps,
436
+ session: { answers: [{ id: 'rect1' }, { id: 'poly1' }, { id: 'circle1' }] },
437
+ shapes: {
438
+ rectangles: [
439
+ { id: 'rect1', x: 10, y: 20, width: 100, height: 80, correct: false },
440
+ ],
441
+ polygons: [
442
+ {
443
+ id: 'poly1',
444
+ points: [{ x: 10, y: 10 }, { x: 100, y: 10 }, { x: 50, y: 100 }],
445
+ correct: false,
446
+ },
447
+ ],
448
+ circles: [
449
+ { id: 'circle1', x: 100, y: 100, radius: 50, correct: false },
450
+ ],
451
+ },
452
+ };
453
+ const { getByTestId } = render(<Container {...props} />);
454
+
455
+ expect(getByTestId('rectangle-rect1')).toHaveAttribute('data-selected', 'true');
456
+ expect(getByTestId('polygon-poly1')).toHaveAttribute('data-selected', 'true');
457
+ expect(getByTestId('circle-circle1')).toHaveAttribute('data-selected', 'true');
458
+ });
459
+ });
460
+
461
+ describe('showCorrect mode', () => {
462
+ it('should pass showCorrect prop to shapes in evaluate mode', () => {
463
+ const props = {
464
+ ...defaultProps,
465
+ isEvaluateMode: true,
466
+ showCorrect: true,
467
+ shapes: {
468
+ rectangles: [
469
+ { id: 'rect1', x: 10, y: 20, width: 100, height: 80, correct: true },
470
+ ],
471
+ polygons: [],
472
+ circles: [],
473
+ },
474
+ };
475
+ const { container } = render(<Container {...props} />);
476
+ expect(container).toBeTruthy();
477
+ });
478
+ });
479
+
480
+ describe('getEvaluateText', () => {
481
+ it('should return correct text for correctly selected', () => {
482
+ const component = new Container(defaultProps);
483
+ const text = component.getEvaluateText(true, true);
484
+ expect(text).toBe('Correctly\nselected');
485
+ });
486
+
487
+ it('should return correct text for incorrectly selected', () => {
488
+ const component = new Container(defaultProps);
489
+ const text = component.getEvaluateText(false, true);
490
+ expect(text).toBe('Should not have\nbeen selected');
491
+ });
492
+
493
+ it('should return correct text for should have been selected', () => {
494
+ const component = new Container(defaultProps);
495
+ const text = component.getEvaluateText(true, false);
496
+ expect(text).toBe('Should have\nbeen selected');
497
+ });
498
+
499
+ it('should return null for correctly not selected', () => {
500
+ const component = new Container(defaultProps);
501
+ const text = component.getEvaluateText(false, false);
502
+ expect(text).toBeNull();
503
+ });
504
+ });
505
+
506
+ describe('correctness calculation', () => {
507
+ it('should calculate correctness properly', () => {
508
+ const component = new Container(defaultProps);
509
+
510
+ expect(component.correctness(true, true)).toBe(true);
511
+ expect(component.correctness(true, false)).toBe(false);
512
+ expect(component.correctness(false, true)).toBe(false);
513
+ expect(component.correctness(false, false)).toBe(true);
514
+ });
515
+ });
516
+
517
+ describe('edge cases', () => {
518
+ it('should handle empty shapes object', () => {
519
+ const props = {
520
+ ...defaultProps,
521
+ shapes: {},
522
+ };
523
+ const { container } = render(<Container {...props} />);
524
+ expect(container).toBeTruthy();
525
+ });
526
+
527
+ it('should handle null imageUrl', () => {
528
+ const props = {
529
+ ...defaultProps,
530
+ imageUrl: null,
531
+ };
532
+ const { queryByAltText } = render(<Container {...props} />);
533
+ expect(queryByAltText('hotspot-image')).not.toBeInTheDocument();
534
+ });
535
+
536
+ it('should handle default scale value', () => {
537
+ const props = {
538
+ ...defaultProps,
539
+ scale: undefined,
540
+ };
541
+ const { getByTestId } = render(<Container {...props} />);
542
+ const stage = getByTestId('stage');
543
+ expect(stage).toBeInTheDocument();
544
+ });
545
+ });
546
+ });