@pie-lib/charting 6.1.1-next.0 → 6.2.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 (85) hide show
  1. package/lib/actions-button.js +0 -5
  2. package/lib/actions-button.js.map +1 -1
  3. package/lib/axes.js +0 -23
  4. package/lib/axes.js.map +1 -1
  5. package/lib/bars/bar.js +0 -2
  6. package/lib/bars/bar.js.map +1 -1
  7. package/lib/bars/common/bars.js +0 -13
  8. package/lib/bars/common/bars.js.map +1 -1
  9. package/lib/bars/common/correct-check-icon.js +0 -1
  10. package/lib/bars/common/correct-check-icon.js.map +1 -1
  11. package/lib/bars/histogram.js +0 -2
  12. package/lib/bars/histogram.js.map +1 -1
  13. package/lib/chart-setup.js +0 -19
  14. package/lib/chart-setup.js.map +1 -1
  15. package/lib/chart-type.js +0 -1
  16. package/lib/chart-type.js.map +1 -1
  17. package/lib/chart-types.js +0 -1
  18. package/lib/chart-types.js.map +1 -1
  19. package/lib/chart.js +2 -18
  20. package/lib/chart.js.map +1 -1
  21. package/lib/common/correctness-indicators.js +0 -4
  22. package/lib/common/correctness-indicators.js.map +1 -1
  23. package/lib/common/drag-handle.js +0 -1
  24. package/lib/common/drag-handle.js.map +1 -1
  25. package/lib/common/drag-icon.js +0 -2
  26. package/lib/common/drag-icon.js.map +1 -1
  27. package/lib/common/styles.js +0 -1
  28. package/lib/common/styles.js.map +1 -1
  29. package/lib/grid.js +0 -4
  30. package/lib/grid.js.map +1 -1
  31. package/lib/index.js +0 -1
  32. package/lib/index.js.map +1 -1
  33. package/lib/key-legend.js +0 -1
  34. package/lib/key-legend.js.map +1 -1
  35. package/lib/line/common/drag-handle.js +0 -2
  36. package/lib/line/common/drag-handle.js.map +1 -1
  37. package/lib/line/common/line.js +2 -19
  38. package/lib/line/common/line.js.map +1 -1
  39. package/lib/line/line-cross.js +0 -16
  40. package/lib/line/line-cross.js.map +1 -1
  41. package/lib/line/line-dot.js +0 -2
  42. package/lib/line/line-dot.js.map +1 -1
  43. package/lib/mark-label.js +0 -21
  44. package/lib/mark-label.js.map +1 -1
  45. package/lib/plot/common/plot.js +0 -12
  46. package/lib/plot/common/plot.js.map +1 -1
  47. package/lib/plot/dot.js +0 -3
  48. package/lib/plot/dot.js.map +1 -1
  49. package/lib/plot/line.js +0 -3
  50. package/lib/plot/line.js.map +1 -1
  51. package/lib/tool-menu.js +0 -11
  52. package/lib/tool-menu.js.map +1 -1
  53. package/lib/utils.js +0 -12
  54. package/lib/utils.js.map +1 -1
  55. package/package.json +12 -9
  56. package/src/__tests__/actions-button.test.jsx +280 -0
  57. package/src/__tests__/axes.test.jsx +557 -16
  58. package/src/__tests__/chart-setup.test.jsx +495 -10
  59. package/src/__tests__/chart.test.jsx +1 -1
  60. package/src/__tests__/grid.test.jsx +2 -2
  61. package/src/__tests__/key-legend.test.jsx +223 -0
  62. package/src/__tests__/tool-menu.test.jsx +522 -0
  63. package/src/__tests__/utils.js +1 -1
  64. package/src/axes.jsx +10 -7
  65. package/src/bars/common/bars.jsx +32 -50
  66. package/src/chart-setup.jsx +6 -9
  67. package/src/chart-type.js +3 -6
  68. package/src/chart.jsx +2 -2
  69. package/src/common/__tests__/correctness-indicators.test.jsx +720 -0
  70. package/src/common/__tests__/drag-handle.test.jsx +0 -1
  71. package/src/common/correctness-indicators.jsx +8 -13
  72. package/src/common/drag-handle.jsx +2 -12
  73. package/src/grid.jsx +1 -1
  74. package/src/line/__tests__/line-cross.test.jsx +423 -1
  75. package/src/line/__tests__/utils.js +1 -1
  76. package/src/line/common/__tests__/drag-handle.test.jsx +1 -2
  77. package/src/line/common/drag-handle.jsx +2 -11
  78. package/src/line/common/line.jsx +2 -2
  79. package/src/line/line-cross.js +7 -13
  80. package/src/mark-label.jsx +3 -3
  81. package/src/plot/__tests__/dot.test.jsx +300 -1
  82. package/src/plot/__tests__/line.test.jsx +331 -1
  83. package/src/plot/common/plot.jsx +14 -13
  84. package/src/utils.js +0 -1
  85. package/NEXT.CHANGELOG.json +0 -16
@@ -0,0 +1,720 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { createTheme, ThemeProvider } from '@mui/material/styles';
4
+ import { CorrectnessIndicator, SmallCorrectPointIndicator, TickCorrectnessIndicator } from '../correctness-indicators';
5
+
6
+ jest.mock('@pie-lib/render-ui', () => ({
7
+ color: {
8
+ correct: () => '#00ff00',
9
+ incorrectWithIcon: () => '#ff0000',
10
+ defaults: {
11
+ WHITE: '#ffffff',
12
+ },
13
+ },
14
+ }));
15
+
16
+ jest.mock('@mui/icons-material/Check', () => {
17
+ return function Check(props) {
18
+ return <svg data-testid="check-icon" title={props.title} className={props.className} />;
19
+ };
20
+ });
21
+
22
+ jest.mock('@mui/icons-material/Close', () => {
23
+ return function Close(props) {
24
+ return <svg data-testid="close-icon" title={props.title} className={props.className} />;
25
+ };
26
+ });
27
+
28
+ let theme;
29
+
30
+ beforeAll(() => {
31
+ theme = createTheme();
32
+ });
33
+
34
+ describe('CorrectnessIndicator', () => {
35
+ const mockScale = {
36
+ x: jest.fn((val) => val * 10),
37
+ y: jest.fn((val) => val * 10),
38
+ };
39
+
40
+ beforeEach(() => {
41
+ jest.clearAllMocks();
42
+ });
43
+
44
+ const renderComponent = (extras = {}) => {
45
+ const defaults = {
46
+ scale: mockScale,
47
+ x: 5,
48
+ y: 10,
49
+ correctness: { value: 'correct', label: 'Correct Answer' },
50
+ interactive: true,
51
+ };
52
+ const props = { ...defaults, ...extras };
53
+ return render(
54
+ <ThemeProvider theme={theme}>
55
+ <svg>
56
+ <CorrectnessIndicator {...props} />
57
+ </svg>
58
+ </ThemeProvider>,
59
+ );
60
+ };
61
+
62
+ describe('rendering', () => {
63
+ it('should render without crashing', () => {
64
+ const { container } = renderComponent();
65
+ expect(container).toBeInTheDocument();
66
+ });
67
+
68
+ it('should render foreignObject element', () => {
69
+ const { container } = renderComponent();
70
+ const foreignObject = container.querySelector('foreignObject');
71
+ expect(foreignObject).toBeInTheDocument();
72
+ });
73
+
74
+ it('should render check icon for correct answer', () => {
75
+ const { getByTestId } = renderComponent({
76
+ correctness: { value: 'correct', label: 'Correct' },
77
+ });
78
+ expect(getByTestId('check-icon')).toBeInTheDocument();
79
+ });
80
+
81
+ it('should render close icon for incorrect answer', () => {
82
+ const { getByTestId } = renderComponent({
83
+ correctness: { value: 'incorrect', label: 'Incorrect' },
84
+ });
85
+ expect(getByTestId('close-icon')).toBeInTheDocument();
86
+ });
87
+
88
+ it('should not render when correctness is null', () => {
89
+ const { container } = renderComponent({ correctness: null });
90
+ const foreignObject = container.querySelector('foreignObject');
91
+ expect(foreignObject).not.toBeInTheDocument();
92
+ });
93
+
94
+ it('should not render when correctness is undefined', () => {
95
+ const { container } = renderComponent({ correctness: undefined });
96
+ const foreignObject = container.querySelector('foreignObject');
97
+ expect(foreignObject).not.toBeInTheDocument();
98
+ });
99
+
100
+ it('should not render when interactive is false', () => {
101
+ const { container } = renderComponent({ interactive: false });
102
+ const foreignObject = container.querySelector('foreignObject');
103
+ expect(foreignObject).not.toBeInTheDocument();
104
+ });
105
+
106
+ it('should render with correct title for correct answer', () => {
107
+ const { getByTestId } = renderComponent({
108
+ correctness: { value: 'correct', label: 'Well done!' },
109
+ });
110
+ const icon = getByTestId('check-icon');
111
+ expect(icon).toHaveAttribute('title', 'Well done!');
112
+ });
113
+
114
+ it('should render with correct title for incorrect answer', () => {
115
+ const { getByTestId } = renderComponent({
116
+ correctness: { value: 'incorrect', label: 'Try again' },
117
+ });
118
+ const icon = getByTestId('close-icon');
119
+ expect(icon).toHaveAttribute('title', 'Try again');
120
+ });
121
+ });
122
+
123
+ describe('positioning with scale', () => {
124
+ it('should use scale to calculate position', () => {
125
+ const { container } = renderComponent({
126
+ scale: mockScale,
127
+ x: 5,
128
+ y: 10,
129
+ });
130
+ const foreignObject = container.querySelector('foreignObject');
131
+ expect(mockScale.x).toHaveBeenCalledWith(5);
132
+ expect(mockScale.y).toHaveBeenCalledWith(10);
133
+ // position is scaled value - 11 (half of 22px icon size)
134
+ expect(foreignObject).toHaveAttribute('x', '39'); // 50 - 11
135
+ expect(foreignObject).toHaveAttribute('y', '89'); // 100 - 11
136
+ });
137
+
138
+ it('should have correct size attributes', () => {
139
+ const { container } = renderComponent();
140
+ const foreignObject = container.querySelector('foreignObject');
141
+ expect(foreignObject).toHaveAttribute('width', '22');
142
+ expect(foreignObject).toHaveAttribute('height', '22');
143
+ });
144
+
145
+ it('should handle different x and y values', () => {
146
+ const { container } = renderComponent({
147
+ x: 20,
148
+ y: 30,
149
+ });
150
+ expect(mockScale.x).toHaveBeenCalledWith(20);
151
+ expect(mockScale.y).toHaveBeenCalledWith(30);
152
+ });
153
+
154
+ it('should use raw values when scale is not provided', () => {
155
+ const { container } = renderComponent({
156
+ scale: null,
157
+ x: 50,
158
+ y: 100,
159
+ });
160
+ const foreignObject = container.querySelector('foreignObject');
161
+ expect(foreignObject).toHaveAttribute('x', '39'); // 50 - 11
162
+ expect(foreignObject).toHaveAttribute('y', '89'); // 100 - 11
163
+ });
164
+ });
165
+
166
+ describe('edge cases', () => {
167
+ it('should handle zero coordinates', () => {
168
+ const { container } = renderComponent({
169
+ x: 0,
170
+ y: 0,
171
+ });
172
+ const foreignObject = container.querySelector('foreignObject');
173
+ expect(foreignObject).toHaveAttribute('x', '-11');
174
+ expect(foreignObject).toHaveAttribute('y', '-11');
175
+ });
176
+
177
+ it('should handle negative coordinates', () => {
178
+ const { container } = renderComponent({
179
+ x: -5,
180
+ y: -10,
181
+ });
182
+ expect(mockScale.x).toHaveBeenCalledWith(-5);
183
+ expect(mockScale.y).toHaveBeenCalledWith(-10);
184
+ });
185
+
186
+ it('should handle large coordinates', () => {
187
+ const { container } = renderComponent({
188
+ x: 1000,
189
+ y: 2000,
190
+ });
191
+ expect(mockScale.x).toHaveBeenCalledWith(1000);
192
+ expect(mockScale.y).toHaveBeenCalledWith(2000);
193
+ });
194
+
195
+ it('should handle correctness with empty label', () => {
196
+ const { getByTestId } = renderComponent({
197
+ correctness: { value: 'correct', label: '' },
198
+ });
199
+ const icon = getByTestId('check-icon');
200
+ expect(icon).toHaveAttribute('title', '');
201
+ });
202
+
203
+ it('should handle correctness with undefined label', () => {
204
+ const { getByTestId } = renderComponent({
205
+ correctness: { value: 'correct', label: undefined },
206
+ });
207
+ expect(getByTestId('check-icon')).toBeInTheDocument();
208
+ });
209
+
210
+ it('should render when both correctness and interactive are true', () => {
211
+ const { container } = renderComponent({
212
+ correctness: { value: 'correct', label: 'Test' },
213
+ interactive: true,
214
+ });
215
+ expect(container.querySelector('foreignObject')).toBeInTheDocument();
216
+ });
217
+
218
+ it('should not render when interactive is false even with correctness', () => {
219
+ const { container } = renderComponent({
220
+ correctness: { value: 'correct', label: 'Test' },
221
+ interactive: false,
222
+ });
223
+ expect(container.querySelector('foreignObject')).not.toBeInTheDocument();
224
+ });
225
+ });
226
+ });
227
+
228
+ describe('SmallCorrectPointIndicator', () => {
229
+ const mockScale = {
230
+ x: jest.fn((val) => val * 10),
231
+ y: jest.fn((val) => val * 10),
232
+ };
233
+
234
+ const correctData = [
235
+ { label: 'A', value: 10 },
236
+ { label: 'B', value: 20 },
237
+ { label: 'C', value: '30' },
238
+ ];
239
+
240
+ beforeEach(() => {
241
+ jest.clearAllMocks();
242
+ });
243
+
244
+ const renderComponent = (extras = {}) => {
245
+ const defaults = {
246
+ scale: mockScale,
247
+ x: 5,
248
+ correctness: { value: 'incorrect', label: 'Incorrect' },
249
+ correctData,
250
+ label: 'A',
251
+ };
252
+ const props = { ...defaults, ...extras };
253
+ return render(
254
+ <ThemeProvider theme={theme}>
255
+ <svg>
256
+ <SmallCorrectPointIndicator {...props} />
257
+ </svg>
258
+ </ThemeProvider>,
259
+ );
260
+ };
261
+
262
+ describe('rendering', () => {
263
+ it('should render without crashing', () => {
264
+ const { container } = renderComponent();
265
+ expect(container).toBeInTheDocument();
266
+ });
267
+
268
+ it('should render foreignObject when incorrect', () => {
269
+ const { container } = renderComponent({
270
+ correctness: { value: 'incorrect', label: 'Wrong' },
271
+ label: 'A',
272
+ });
273
+ const foreignObject = container.querySelector('foreignObject');
274
+ expect(foreignObject).toBeInTheDocument();
275
+ });
276
+
277
+ it('should render small check icon when incorrect', () => {
278
+ const { getByTestId } = renderComponent({
279
+ correctness: { value: 'incorrect', label: 'Wrong' },
280
+ label: 'A',
281
+ });
282
+ const icon = getByTestId('check-icon');
283
+ expect(icon).toBeInTheDocument();
284
+ expect(icon).toHaveClass('small');
285
+ });
286
+
287
+ it('should not render when correctness is correct', () => {
288
+ const { container } = renderComponent({
289
+ correctness: { value: 'correct', label: 'Correct' },
290
+ });
291
+ const foreignObject = container.querySelector('foreignObject');
292
+ expect(foreignObject).not.toBeInTheDocument();
293
+ });
294
+
295
+ it('should not render when correctness is null', () => {
296
+ const { container } = renderComponent({ correctness: null });
297
+ const foreignObject = container.querySelector('foreignObject');
298
+ expect(foreignObject).not.toBeInTheDocument();
299
+ });
300
+
301
+ it('should not render when correctness is undefined', () => {
302
+ const { container } = renderComponent({ correctness: undefined });
303
+ const foreignObject = container.querySelector('foreignObject');
304
+ expect(foreignObject).not.toBeInTheDocument();
305
+ });
306
+ });
307
+
308
+ describe('positioning', () => {
309
+ it('should calculate correct position for label A', () => {
310
+ const { container } = renderComponent({
311
+ x: 5,
312
+ label: 'A',
313
+ });
314
+ const foreignObject = container.querySelector('foreignObject');
315
+ expect(mockScale.x).toHaveBeenCalledWith(5);
316
+ expect(mockScale.y).toHaveBeenCalledWith(10); // value from correctData for label A
317
+ // Position is scaled value - 7.5 (half of 15px small icon size)
318
+ expect(foreignObject).toHaveAttribute('x', '42.5'); // 50 - 7.5
319
+ expect(foreignObject).toHaveAttribute('y', '92.5'); // 100 - 7.5
320
+ });
321
+
322
+ it('should calculate correct position for label B', () => {
323
+ const { container } = renderComponent({
324
+ x: 5,
325
+ label: 'B',
326
+ });
327
+ expect(mockScale.y).toHaveBeenCalledWith(20);
328
+ });
329
+
330
+ it('should calculate correct position for label C with string value', () => {
331
+ const { container } = renderComponent({
332
+ x: 5,
333
+ label: 'C',
334
+ });
335
+ expect(mockScale.y).toHaveBeenCalledWith(30);
336
+ });
337
+
338
+ it('should have correct size attributes for small icon', () => {
339
+ const { container } = renderComponent({
340
+ label: 'A',
341
+ });
342
+ const foreignObject = container.querySelector('foreignObject');
343
+ expect(foreignObject).toHaveAttribute('width', '15');
344
+ expect(foreignObject).toHaveAttribute('height', '15');
345
+ });
346
+ });
347
+
348
+ describe('correctData handling', () => {
349
+ it('should find correct value from correctData by label', () => {
350
+ const { container } = renderComponent({
351
+ label: 'B',
352
+ });
353
+ const foreignObject = container.querySelector('foreignObject');
354
+ expect(foreignObject).toBeInTheDocument();
355
+ expect(mockScale.y).toHaveBeenCalledWith(20);
356
+ });
357
+
358
+ it('should not render when label is not in correctData', () => {
359
+ const { container } = renderComponent({
360
+ label: 'Z',
361
+ });
362
+ const foreignObject = container.querySelector('foreignObject');
363
+ expect(foreignObject).not.toBeInTheDocument();
364
+ });
365
+
366
+ it('should not render when correctData value is NaN', () => {
367
+ const { container } = renderComponent({
368
+ correctData: [{ label: 'A', value: 'invalid' }],
369
+ label: 'A',
370
+ });
371
+ const foreignObject = container.querySelector('foreignObject');
372
+ expect(foreignObject).not.toBeInTheDocument();
373
+ });
374
+
375
+ it('should handle empty correctData array', () => {
376
+ const { container } = renderComponent({
377
+ correctData: [],
378
+ label: 'A',
379
+ });
380
+ const foreignObject = container.querySelector('foreignObject');
381
+ expect(foreignObject).not.toBeInTheDocument();
382
+ });
383
+
384
+ it('should not render when correctData is undefined (correctness not incorrect)', () => {
385
+ const { container } = renderComponent({
386
+ correctness: { value: 'correct', label: 'Correct' },
387
+ correctData: undefined,
388
+ label: 'A',
389
+ });
390
+ const foreignObject = container.querySelector('foreignObject');
391
+ expect(foreignObject).not.toBeInTheDocument();
392
+ });
393
+
394
+ it('should parse string values correctly', () => {
395
+ const { container } = renderComponent({
396
+ correctData: [{ label: 'D', value: '45.5' }],
397
+ label: 'D',
398
+ });
399
+ const foreignObject = container.querySelector('foreignObject');
400
+ expect(foreignObject).toBeInTheDocument();
401
+ expect(mockScale.y).toHaveBeenCalledWith(45.5);
402
+ });
403
+
404
+ it('should handle zero values', () => {
405
+ const { container } = renderComponent({
406
+ correctData: [{ label: 'E', value: 0 }],
407
+ label: 'E',
408
+ });
409
+ const foreignObject = container.querySelector('foreignObject');
410
+ expect(foreignObject).toBeInTheDocument();
411
+ expect(mockScale.y).toHaveBeenCalledWith(0);
412
+ });
413
+
414
+ it('should handle negative values', () => {
415
+ const { container } = renderComponent({
416
+ correctData: [{ label: 'F', value: -10 }],
417
+ label: 'F',
418
+ });
419
+ const foreignObject = container.querySelector('foreignObject');
420
+ expect(foreignObject).toBeInTheDocument();
421
+ expect(mockScale.y).toHaveBeenCalledWith(-10);
422
+ });
423
+ });
424
+
425
+ describe('icon properties', () => {
426
+ it('should render icon with correct title', () => {
427
+ const { getByTestId } = renderComponent({
428
+ correctness: { value: 'incorrect', label: 'Not quite right' },
429
+ label: 'A',
430
+ });
431
+ const icon = getByTestId('check-icon');
432
+ expect(icon).toHaveAttribute('title', 'Not quite right');
433
+ });
434
+
435
+ it('should render icon with small className', () => {
436
+ const { getByTestId } = renderComponent({
437
+ label: 'A',
438
+ });
439
+ const icon = getByTestId('check-icon');
440
+ expect(icon).toHaveClass('small');
441
+ });
442
+ });
443
+
444
+ describe('edge cases', () => {
445
+ it('should handle missing label prop', () => {
446
+ const { container } = renderComponent({
447
+ label: undefined,
448
+ });
449
+ const foreignObject = container.querySelector('foreignObject');
450
+ expect(foreignObject).not.toBeInTheDocument();
451
+ });
452
+
453
+ it('should handle empty string label', () => {
454
+ const { container } = renderComponent({
455
+ label: '',
456
+ });
457
+ const foreignObject = container.querySelector('foreignObject');
458
+ expect(foreignObject).not.toBeInTheDocument();
459
+ });
460
+
461
+ it('should handle very large values in correctData', () => {
462
+ const { container } = renderComponent({
463
+ correctData: [{ label: 'G', value: 999999 }],
464
+ label: 'G',
465
+ });
466
+ const foreignObject = container.querySelector('foreignObject');
467
+ expect(foreignObject).toBeInTheDocument();
468
+ expect(mockScale.y).toHaveBeenCalledWith(999999);
469
+ });
470
+
471
+ it('should only render for incorrect answers', () => {
472
+ const { container: incorrectContainer } = renderComponent({
473
+ correctness: { value: 'incorrect', label: 'Wrong' },
474
+ label: 'A',
475
+ });
476
+ expect(incorrectContainer.querySelector('foreignObject')).toBeInTheDocument();
477
+
478
+ const { container: correctContainer } = renderComponent({
479
+ correctness: { value: 'correct', label: 'Right' },
480
+ label: 'A',
481
+ });
482
+ expect(correctContainer.querySelector('foreignObject')).not.toBeInTheDocument();
483
+ });
484
+ });
485
+ });
486
+
487
+ describe('TickCorrectnessIndicator', () => {
488
+ beforeEach(() => {
489
+ jest.clearAllMocks();
490
+ });
491
+
492
+ const renderComponent = (extras = {}) => {
493
+ const defaults = {
494
+ correctness: { value: 'correct', label: 'Correct Answer' },
495
+ interactive: true,
496
+ };
497
+ const props = { ...defaults, ...extras };
498
+ return render(
499
+ <ThemeProvider theme={theme}>
500
+ <TickCorrectnessIndicator {...props} />
501
+ </ThemeProvider>,
502
+ );
503
+ };
504
+
505
+ describe('rendering', () => {
506
+ it('should render without crashing', () => {
507
+ const { container } = renderComponent();
508
+ expect(container).toBeInTheDocument();
509
+ });
510
+
511
+ it('should render check icon for correct answer', () => {
512
+ const { getByTestId } = renderComponent({
513
+ correctness: { value: 'correct', label: 'Correct' },
514
+ });
515
+ expect(getByTestId('check-icon')).toBeInTheDocument();
516
+ });
517
+
518
+ it('should render close icon for incorrect answer', () => {
519
+ const { getByTestId } = renderComponent({
520
+ correctness: { value: 'incorrect', label: 'Incorrect' },
521
+ });
522
+ expect(getByTestId('close-icon')).toBeInTheDocument();
523
+ });
524
+
525
+ it('should not render when correctness is null', () => {
526
+ const { queryByTestId } = renderComponent({ correctness: null });
527
+ expect(queryByTestId('check-icon')).not.toBeInTheDocument();
528
+ expect(queryByTestId('close-icon')).not.toBeInTheDocument();
529
+ });
530
+
531
+ it('should not render when correctness is undefined', () => {
532
+ const { queryByTestId } = renderComponent({ correctness: undefined });
533
+ expect(queryByTestId('check-icon')).not.toBeInTheDocument();
534
+ expect(queryByTestId('close-icon')).not.toBeInTheDocument();
535
+ });
536
+
537
+ it('should not render when interactive is false', () => {
538
+ const { queryByTestId } = renderComponent({ interactive: false });
539
+ expect(queryByTestId('check-icon')).not.toBeInTheDocument();
540
+ expect(queryByTestId('close-icon')).not.toBeInTheDocument();
541
+ });
542
+
543
+ it('should render with correct title for correct answer', () => {
544
+ const { getByTestId } = renderComponent({
545
+ correctness: { value: 'correct', label: 'Great job!' },
546
+ });
547
+ const icon = getByTestId('check-icon');
548
+ expect(icon).toHaveAttribute('title', 'Great job!');
549
+ });
550
+
551
+ it('should render with correct title for incorrect answer', () => {
552
+ const { getByTestId } = renderComponent({
553
+ correctness: { value: 'incorrect', label: 'Not correct' },
554
+ });
555
+ const icon = getByTestId('close-icon');
556
+ expect(icon).toHaveAttribute('title', 'Not correct');
557
+ });
558
+ });
559
+
560
+ describe('interactive flag', () => {
561
+ it('should render when interactive is true', () => {
562
+ const { getByTestId } = renderComponent({
563
+ interactive: true,
564
+ correctness: { value: 'correct', label: 'Test' },
565
+ });
566
+ expect(getByTestId('check-icon')).toBeInTheDocument();
567
+ });
568
+
569
+ it('should not render when interactive is false', () => {
570
+ const { queryByTestId } = renderComponent({
571
+ interactive: false,
572
+ correctness: { value: 'correct', label: 'Test' },
573
+ });
574
+ expect(queryByTestId('check-icon')).not.toBeInTheDocument();
575
+ });
576
+
577
+ it('should not render when interactive is undefined', () => {
578
+ const { queryByTestId } = renderComponent({
579
+ interactive: undefined,
580
+ correctness: { value: 'correct', label: 'Test' },
581
+ });
582
+ expect(queryByTestId('check-icon')).not.toBeInTheDocument();
583
+ });
584
+
585
+ it('should not render when interactive is null', () => {
586
+ const { queryByTestId } = renderComponent({
587
+ interactive: null,
588
+ correctness: { value: 'correct', label: 'Test' },
589
+ });
590
+ expect(queryByTestId('check-icon')).not.toBeInTheDocument();
591
+ });
592
+ });
593
+
594
+ describe('correctness values', () => {
595
+ it('should render check icon when value is "correct"', () => {
596
+ const { getByTestId, queryByTestId } = renderComponent({
597
+ correctness: { value: 'correct', label: 'Test' },
598
+ });
599
+ expect(getByTestId('check-icon')).toBeInTheDocument();
600
+ expect(queryByTestId('close-icon')).not.toBeInTheDocument();
601
+ });
602
+
603
+ it('should render close icon when value is "incorrect"', () => {
604
+ const { getByTestId, queryByTestId } = renderComponent({
605
+ correctness: { value: 'incorrect', label: 'Test' },
606
+ });
607
+ expect(getByTestId('close-icon')).toBeInTheDocument();
608
+ expect(queryByTestId('check-icon')).not.toBeInTheDocument();
609
+ });
610
+
611
+ it('should render close icon for any value other than "correct"', () => {
612
+ const { getByTestId } = renderComponent({
613
+ correctness: { value: 'partial', label: 'Test' },
614
+ });
615
+ expect(getByTestId('close-icon')).toBeInTheDocument();
616
+ });
617
+
618
+ it('should handle empty value string', () => {
619
+ const { getByTestId } = renderComponent({
620
+ correctness: { value: '', label: 'Test' },
621
+ });
622
+ expect(getByTestId('close-icon')).toBeInTheDocument();
623
+ });
624
+ });
625
+
626
+ describe('edge cases', () => {
627
+ it('should handle correctness with empty label', () => {
628
+ const { getByTestId } = renderComponent({
629
+ correctness: { value: 'correct', label: '' },
630
+ });
631
+ const icon = getByTestId('check-icon');
632
+ expect(icon).toHaveAttribute('title', '');
633
+ });
634
+
635
+ it('should handle correctness with undefined label', () => {
636
+ const { getByTestId } = renderComponent({
637
+ correctness: { value: 'correct', label: undefined },
638
+ });
639
+ expect(getByTestId('check-icon')).toBeInTheDocument();
640
+ });
641
+
642
+ it('should handle correctness with null value', () => {
643
+ const { queryByTestId } = renderComponent({
644
+ correctness: { value: null, label: 'Test' },
645
+ });
646
+ expect(queryByTestId('close-icon')).toBeInTheDocument();
647
+ });
648
+
649
+ it('should require both correctness and interactive to render', () => {
650
+ const { queryByTestId: query1 } = renderComponent({
651
+ correctness: { value: 'correct', label: 'Test' },
652
+ interactive: false,
653
+ });
654
+ expect(query1('check-icon')).not.toBeInTheDocument();
655
+
656
+ const { queryByTestId: query2 } = renderComponent({
657
+ correctness: null,
658
+ interactive: true,
659
+ });
660
+ expect(query2('check-icon')).not.toBeInTheDocument();
661
+ });
662
+
663
+ it('should handle special characters in label', () => {
664
+ const { getByTestId } = renderComponent({
665
+ correctness: { value: 'correct', label: 'Test <>&"' },
666
+ });
667
+ const icon = getByTestId('check-icon');
668
+ expect(icon).toHaveAttribute('title', 'Test <>&"');
669
+ });
670
+
671
+ it('should handle unicode characters in label', () => {
672
+ const { getByTestId } = renderComponent({
673
+ correctness: { value: 'correct', label: 'Test 你好 🎉' },
674
+ });
675
+ const icon = getByTestId('check-icon');
676
+ expect(icon).toHaveAttribute('title', 'Test 你好 🎉');
677
+ });
678
+
679
+ it('should handle very long labels', () => {
680
+ const longLabel = 'This is a very long label that might cause layout issues in the UI';
681
+ const { getByTestId } = renderComponent({
682
+ correctness: { value: 'correct', label: longLabel },
683
+ });
684
+ const icon = getByTestId('check-icon');
685
+ expect(icon).toHaveAttribute('title', longLabel);
686
+ });
687
+ });
688
+
689
+ describe('component consistency', () => {
690
+ it('should render same icon type consistently for same correctness value', () => {
691
+ const { getByTestId, unmount } = renderComponent({
692
+ correctness: { value: 'correct', label: 'Test 1' },
693
+ });
694
+
695
+ expect(getByTestId('check-icon')).toBeInTheDocument();
696
+ unmount();
697
+
698
+ const { getByTestId: get2 } = renderComponent({
699
+ correctness: { value: 'correct', label: 'Test 2' },
700
+ });
701
+
702
+ expect(get2('check-icon')).toBeInTheDocument();
703
+ });
704
+
705
+ it('should render different icons for different correctness values', () => {
706
+ const { getByTestId, unmount } = renderComponent({
707
+ correctness: { value: 'correct', label: 'Correct' },
708
+ });
709
+
710
+ expect(getByTestId('check-icon')).toBeInTheDocument();
711
+ unmount();
712
+
713
+ const { getByTestId: get2 } = renderComponent({
714
+ correctness: { value: 'incorrect', label: 'Incorrect' },
715
+ });
716
+
717
+ expect(get2('close-icon')).toBeInTheDocument();
718
+ });
719
+ });
720
+ });