@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.
- package/lib/actions-button.js +0 -5
- package/lib/actions-button.js.map +1 -1
- package/lib/axes.js +0 -23
- package/lib/axes.js.map +1 -1
- package/lib/bars/bar.js +0 -2
- package/lib/bars/bar.js.map +1 -1
- package/lib/bars/common/bars.js +0 -13
- package/lib/bars/common/bars.js.map +1 -1
- package/lib/bars/common/correct-check-icon.js +0 -1
- package/lib/bars/common/correct-check-icon.js.map +1 -1
- package/lib/bars/histogram.js +0 -2
- package/lib/bars/histogram.js.map +1 -1
- package/lib/chart-setup.js +0 -19
- package/lib/chart-setup.js.map +1 -1
- package/lib/chart-type.js +0 -1
- package/lib/chart-type.js.map +1 -1
- package/lib/chart-types.js +0 -1
- package/lib/chart-types.js.map +1 -1
- package/lib/chart.js +2 -18
- package/lib/chart.js.map +1 -1
- package/lib/common/correctness-indicators.js +0 -4
- package/lib/common/correctness-indicators.js.map +1 -1
- package/lib/common/drag-handle.js +0 -1
- package/lib/common/drag-handle.js.map +1 -1
- package/lib/common/drag-icon.js +0 -2
- package/lib/common/drag-icon.js.map +1 -1
- package/lib/common/styles.js +0 -1
- package/lib/common/styles.js.map +1 -1
- package/lib/grid.js +0 -4
- package/lib/grid.js.map +1 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/key-legend.js +0 -1
- package/lib/key-legend.js.map +1 -1
- package/lib/line/common/drag-handle.js +0 -2
- package/lib/line/common/drag-handle.js.map +1 -1
- package/lib/line/common/line.js +2 -19
- package/lib/line/common/line.js.map +1 -1
- package/lib/line/line-cross.js +0 -16
- package/lib/line/line-cross.js.map +1 -1
- package/lib/line/line-dot.js +0 -2
- package/lib/line/line-dot.js.map +1 -1
- package/lib/mark-label.js +0 -21
- package/lib/mark-label.js.map +1 -1
- package/lib/plot/common/plot.js +0 -12
- package/lib/plot/common/plot.js.map +1 -1
- package/lib/plot/dot.js +0 -3
- package/lib/plot/dot.js.map +1 -1
- package/lib/plot/line.js +0 -3
- package/lib/plot/line.js.map +1 -1
- package/lib/tool-menu.js +0 -11
- package/lib/tool-menu.js.map +1 -1
- package/lib/utils.js +0 -12
- package/lib/utils.js.map +1 -1
- package/package.json +12 -9
- package/src/__tests__/actions-button.test.jsx +280 -0
- package/src/__tests__/axes.test.jsx +557 -16
- package/src/__tests__/chart-setup.test.jsx +495 -10
- package/src/__tests__/chart.test.jsx +1 -1
- package/src/__tests__/grid.test.jsx +2 -2
- package/src/__tests__/key-legend.test.jsx +223 -0
- package/src/__tests__/tool-menu.test.jsx +522 -0
- package/src/__tests__/utils.js +1 -1
- package/src/axes.jsx +10 -7
- package/src/bars/common/bars.jsx +32 -50
- package/src/chart-setup.jsx +6 -9
- package/src/chart-type.js +3 -6
- package/src/chart.jsx +2 -2
- package/src/common/__tests__/correctness-indicators.test.jsx +720 -0
- package/src/common/__tests__/drag-handle.test.jsx +0 -1
- package/src/common/correctness-indicators.jsx +8 -13
- package/src/common/drag-handle.jsx +2 -12
- package/src/grid.jsx +1 -1
- package/src/line/__tests__/line-cross.test.jsx +423 -1
- package/src/line/__tests__/utils.js +1 -1
- package/src/line/common/__tests__/drag-handle.test.jsx +1 -2
- package/src/line/common/drag-handle.jsx +2 -11
- package/src/line/common/line.jsx +2 -2
- package/src/line/line-cross.js +7 -13
- package/src/mark-label.jsx +3 -3
- package/src/plot/__tests__/dot.test.jsx +300 -1
- package/src/plot/__tests__/line.test.jsx +331 -1
- package/src/plot/common/plot.jsx +14 -13
- package/src/utils.js +0 -1
- package/NEXT.CHANGELOG.json +0 -16
|
@@ -1,8 +1,69 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render } from '@pie-lib/test-utils';
|
|
3
|
-
import {
|
|
4
|
-
import ChartAxes, {
|
|
5
|
-
import {
|
|
2
|
+
import { fireEvent, render } from '@pie-lib/test-utils';
|
|
3
|
+
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
|
4
|
+
import ChartAxes, { RawChartAxes, TickComponent } from '../axes';
|
|
5
|
+
import { createBandScale, graphProps } from './utils';
|
|
6
|
+
|
|
7
|
+
jest.mock('@visx/axis', () => ({
|
|
8
|
+
AxisLeft: ({ children, ...props }) => (
|
|
9
|
+
<g data-testid="axis-left" {...props}>
|
|
10
|
+
{typeof children === 'function' ? children({ formattedValue: '5' }) : children}
|
|
11
|
+
</g>
|
|
12
|
+
),
|
|
13
|
+
AxisBottom: ({ children, tickValues, ...props }) => (
|
|
14
|
+
<g data-testid="axis-bottom" {...props}>
|
|
15
|
+
{typeof children === 'function'
|
|
16
|
+
? (tickValues || ['0-A', '1-B', '2-C']).map((v, i) => children({ formattedValue: v, index: i }))
|
|
17
|
+
: children}
|
|
18
|
+
</g>
|
|
19
|
+
),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock('../mark-label', () => {
|
|
23
|
+
return function MarkLabel({ mark, disabled, onChange, error, autoFocus, inputRef, correctnessIndicator }) {
|
|
24
|
+
return (
|
|
25
|
+
<div data-testid="mark-label">
|
|
26
|
+
<input
|
|
27
|
+
data-testid="mark-label-input"
|
|
28
|
+
ref={inputRef}
|
|
29
|
+
value={mark?.label || ''}
|
|
30
|
+
disabled={disabled}
|
|
31
|
+
onChange={(e) => onChange && onChange(e.target.value)}
|
|
32
|
+
autoFocus={autoFocus}
|
|
33
|
+
/>
|
|
34
|
+
{error && <span data-testid="mark-label-error">{error}</span>}
|
|
35
|
+
{correctnessIndicator}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
jest.mock('@pie-lib/config-ui', () => ({
|
|
42
|
+
AlertDialog: ({ open, title, text, onClose, onConfirm }) =>
|
|
43
|
+
open ? (
|
|
44
|
+
<div data-testid="alert-dialog">
|
|
45
|
+
<h2>{title}</h2>
|
|
46
|
+
<p>{text}</p>
|
|
47
|
+
<button data-testid="alert-cancel" onClick={onClose}>
|
|
48
|
+
Cancel
|
|
49
|
+
</button>
|
|
50
|
+
<button data-testid="alert-confirm" onClick={onConfirm}>
|
|
51
|
+
Confirm
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
) : null,
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
jest.mock('../common/correctness-indicators', () => ({
|
|
58
|
+
TickCorrectnessIndicator: ({ correctness, interactive }) =>
|
|
59
|
+
correctness && interactive ? (
|
|
60
|
+
<div data-testid="tick-correctness-indicator">{correctness.value === 'correct' ? '✓' : '✗'}</div>
|
|
61
|
+
) : null,
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
jest.mock('@pie-lib/math-rendering', () => ({
|
|
65
|
+
renderMath: jest.fn((text) => text),
|
|
66
|
+
}));
|
|
6
67
|
|
|
7
68
|
const theme = createTheme();
|
|
8
69
|
|
|
@@ -21,7 +82,7 @@ describe('ChartAxes', () => {
|
|
|
21
82
|
<svg>
|
|
22
83
|
<ChartAxes {...props} />
|
|
23
84
|
</svg>
|
|
24
|
-
</ThemeProvider
|
|
85
|
+
</ThemeProvider>,
|
|
25
86
|
);
|
|
26
87
|
};
|
|
27
88
|
|
|
@@ -32,13 +93,35 @@ describe('ChartAxes', () => {
|
|
|
32
93
|
});
|
|
33
94
|
|
|
34
95
|
it('renders SVG with axes group', () => {
|
|
35
|
-
const { container} = renderComponent();
|
|
96
|
+
const { container } = renderComponent();
|
|
36
97
|
const svg = container.querySelector('svg');
|
|
37
98
|
expect(svg).toBeInTheDocument();
|
|
38
|
-
// Component renders StyledAxesGroup which is a <g> element
|
|
39
99
|
const axesGroup = container.querySelector('g');
|
|
40
100
|
expect(axesGroup).toBeInTheDocument();
|
|
41
101
|
});
|
|
102
|
+
|
|
103
|
+
it('renders axes elements', () => {
|
|
104
|
+
const { container } = renderComponent();
|
|
105
|
+
const groups = container.querySelectorAll('g');
|
|
106
|
+
expect(groups.length).toBeGreaterThan(0);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('edge cases', () => {
|
|
111
|
+
it('handles missing xBand', () => {
|
|
112
|
+
const { container } = renderComponent({ xBand: undefined });
|
|
113
|
+
expect(container).toBeInTheDocument();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('handles empty categories', () => {
|
|
117
|
+
const { container } = renderComponent({ categories: [] });
|
|
118
|
+
expect(container).toBeInTheDocument();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('handles null categories', () => {
|
|
122
|
+
const { container } = renderComponent({ categories: null });
|
|
123
|
+
expect(container).toBeInTheDocument();
|
|
124
|
+
});
|
|
42
125
|
});
|
|
43
126
|
});
|
|
44
127
|
|
|
@@ -57,7 +140,7 @@ describe('RawChartAxes', () => {
|
|
|
57
140
|
<svg>
|
|
58
141
|
<RawChartAxes {...props} />
|
|
59
142
|
</svg>
|
|
60
|
-
</ThemeProvider
|
|
143
|
+
</ThemeProvider>,
|
|
61
144
|
);
|
|
62
145
|
};
|
|
63
146
|
|
|
@@ -71,24 +154,42 @@ describe('RawChartAxes', () => {
|
|
|
71
154
|
const { container } = renderComponent();
|
|
72
155
|
const svg = container.querySelector('svg');
|
|
73
156
|
expect(svg).toBeInTheDocument();
|
|
74
|
-
// Component renders StyledAxesGroup which is a <g> element
|
|
75
157
|
const axesGroup = container.querySelector('g');
|
|
76
158
|
expect(axesGroup).toBeInTheDocument();
|
|
77
159
|
});
|
|
160
|
+
|
|
161
|
+
it('renders both axes when leftAxis is true', () => {
|
|
162
|
+
const { getByTestId } = renderComponent({ leftAxis: true });
|
|
163
|
+
expect(getByTestId('axis-left')).toBeInTheDocument();
|
|
164
|
+
expect(getByTestId('axis-bottom')).toBeInTheDocument();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('renders only bottom axis when leftAxis is false', () => {
|
|
168
|
+
const { queryByTestId, getByTestId } = renderComponent({ leftAxis: false });
|
|
169
|
+
expect(queryByTestId('axis-left')).not.toBeInTheDocument();
|
|
170
|
+
expect(getByTestId('axis-bottom')).toBeInTheDocument();
|
|
171
|
+
});
|
|
78
172
|
});
|
|
79
173
|
});
|
|
80
174
|
|
|
81
175
|
describe('TickComponent', () => {
|
|
176
|
+
const defaultCategories = [
|
|
177
|
+
{ value: 1, label: 'Category A', editable: true, interactive: true },
|
|
178
|
+
{ value: 2, label: 'Category B', editable: true, interactive: true },
|
|
179
|
+
];
|
|
180
|
+
|
|
82
181
|
const renderComponent = (extras) => {
|
|
83
182
|
const defaults = {
|
|
84
183
|
graphProps: graphProps(),
|
|
85
184
|
xBand: createBandScale(['a', 'b', 'c'], [0, 400]),
|
|
86
|
-
categories:
|
|
87
|
-
formattedValue: '0-
|
|
185
|
+
categories: defaultCategories,
|
|
186
|
+
formattedValue: '0-Category A',
|
|
88
187
|
bandWidth: 100,
|
|
89
188
|
barWidth: 100,
|
|
90
189
|
x: 50,
|
|
91
190
|
y: 50,
|
|
191
|
+
top: 0,
|
|
192
|
+
onChangeCategory: jest.fn(),
|
|
92
193
|
};
|
|
93
194
|
const props = { ...defaults, ...extras };
|
|
94
195
|
return render(
|
|
@@ -96,7 +197,7 @@ describe('TickComponent', () => {
|
|
|
96
197
|
<svg>
|
|
97
198
|
<TickComponent {...props} />
|
|
98
199
|
</svg>
|
|
99
|
-
</ThemeProvider
|
|
200
|
+
</ThemeProvider>,
|
|
100
201
|
);
|
|
101
202
|
};
|
|
102
203
|
|
|
@@ -105,7 +206,6 @@ describe('TickComponent', () => {
|
|
|
105
206
|
const { container } = renderComponent();
|
|
106
207
|
const svg = container.querySelector('svg');
|
|
107
208
|
expect(svg).toBeInTheDocument();
|
|
108
|
-
// TickComponent renders a <g> element
|
|
109
209
|
expect(container.querySelector('g')).toBeInTheDocument();
|
|
110
210
|
});
|
|
111
211
|
|
|
@@ -118,9 +218,450 @@ describe('TickComponent', () => {
|
|
|
118
218
|
expect(svg).toBeInTheDocument();
|
|
119
219
|
expect(container.querySelector('g')).toBeInTheDocument();
|
|
120
220
|
});
|
|
221
|
+
|
|
222
|
+
it('renders MarkLabel component', () => {
|
|
223
|
+
const { getAllByTestId } = renderComponent();
|
|
224
|
+
const markLabels = getAllByTestId('mark-label');
|
|
225
|
+
expect(markLabels.length).toBeGreaterThan(0);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('renders foreignObject for labels', () => {
|
|
229
|
+
const { container } = renderComponent();
|
|
230
|
+
const foreignObject = container.querySelector('foreignObject');
|
|
231
|
+
expect(foreignObject).toBeInTheDocument();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('does not render when formattedValue is null', () => {
|
|
235
|
+
const { container } = renderComponent({ formattedValue: null });
|
|
236
|
+
const foreignObject = container.querySelector('foreignObject');
|
|
237
|
+
expect(foreignObject).not.toBeInTheDocument();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('does not render when formattedValue is undefined', () => {
|
|
241
|
+
const { container } = renderComponent({ formattedValue: undefined });
|
|
242
|
+
const foreignObject = container.querySelector('foreignObject');
|
|
243
|
+
expect(foreignObject).not.toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('renders hidden label for longest category', () => {
|
|
247
|
+
const { getAllByTestId } = renderComponent();
|
|
248
|
+
const markLabels = getAllByTestId('mark-label');
|
|
249
|
+
expect(markLabels.length).toBeGreaterThan(0);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('category label editing', () => {
|
|
254
|
+
it('allows editing category label', () => {
|
|
255
|
+
const onChangeCategory = jest.fn();
|
|
256
|
+
const { getAllByTestId } = renderComponent({ onChangeCategory });
|
|
257
|
+
|
|
258
|
+
const inputs = getAllByTestId('mark-label-input');
|
|
259
|
+
const input = inputs[inputs.length - 1];
|
|
260
|
+
fireEvent.change(input, { target: { value: 'New Label' } });
|
|
261
|
+
|
|
262
|
+
expect(onChangeCategory).toHaveBeenCalled();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('calls changeCategory with correct parameters', () => {
|
|
266
|
+
const onChangeCategory = jest.fn();
|
|
267
|
+
const { getAllByTestId } = renderComponent({ onChangeCategory });
|
|
268
|
+
|
|
269
|
+
const inputs = getAllByTestId('mark-label-input');
|
|
270
|
+
const input = inputs[inputs.length - 1];
|
|
271
|
+
fireEvent.change(input, { target: { value: 'Updated' } });
|
|
272
|
+
|
|
273
|
+
expect(onChangeCategory).toHaveBeenCalledWith(0, expect.objectContaining({ label: 'Updated' }));
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('disables editing when editable is false', () => {
|
|
277
|
+
const categories = [{ value: 1, label: 'test', editable: false, interactive: true }];
|
|
278
|
+
const { getAllByTestId } = renderComponent({ categories, formattedValue: '0-test' });
|
|
279
|
+
|
|
280
|
+
const inputs = getAllByTestId('mark-label-input');
|
|
281
|
+
const input = inputs[inputs.length - 1];
|
|
282
|
+
expect(input).toBeDisabled();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('enables editing in define chart mode', () => {
|
|
286
|
+
const categories = [{ value: 1, label: 'test', editable: true, interactive: true }];
|
|
287
|
+
const { getAllByTestId } = renderComponent({
|
|
288
|
+
categories,
|
|
289
|
+
formattedValue: '0-test',
|
|
290
|
+
defineChart: true,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const inputs = getAllByTestId('mark-label-input');
|
|
294
|
+
const input = inputs[inputs.length - 1];
|
|
295
|
+
expect(input).not.toBeDisabled();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('respects editable property when not in define chart mode', () => {
|
|
299
|
+
const categories = [{ value: 1, label: 'test', editable: true, interactive: true }];
|
|
300
|
+
const { getAllByTestId } = renderComponent({
|
|
301
|
+
categories,
|
|
302
|
+
formattedValue: '0-test',
|
|
303
|
+
defineChart: false,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const inputs = getAllByTestId('mark-label-input');
|
|
307
|
+
const input = inputs[inputs.length - 1];
|
|
308
|
+
expect(input).not.toBeDisabled();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe('interactive checkbox', () => {
|
|
313
|
+
it('shows warning when disabling interactive with correct answer', () => {
|
|
314
|
+
const categories = [
|
|
315
|
+
{
|
|
316
|
+
value: 1,
|
|
317
|
+
label: 'test',
|
|
318
|
+
editable: true,
|
|
319
|
+
interactive: true,
|
|
320
|
+
correctness: { value: 'correct' },
|
|
321
|
+
},
|
|
322
|
+
];
|
|
323
|
+
const component = new TickComponent({
|
|
324
|
+
categories,
|
|
325
|
+
onChangeCategory: jest.fn(),
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
component.setState = jest.fn();
|
|
329
|
+
component.changeInteractive(0, false);
|
|
330
|
+
|
|
331
|
+
expect(component.setState).toHaveBeenCalledWith(
|
|
332
|
+
expect.objectContaining({
|
|
333
|
+
dialog: expect.objectContaining({
|
|
334
|
+
open: true,
|
|
335
|
+
title: 'Warning',
|
|
336
|
+
}),
|
|
337
|
+
}),
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('changes interactive without warning when enabling', () => {
|
|
342
|
+
const onChangeCategory = jest.fn();
|
|
343
|
+
const categories = [{ value: 1, label: 'test', editable: true, interactive: false }];
|
|
344
|
+
const component = new TickComponent({
|
|
345
|
+
categories,
|
|
346
|
+
onChangeCategory,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
component.changeInteractive(0, true);
|
|
350
|
+
|
|
351
|
+
expect(onChangeCategory).toHaveBeenCalledWith(0, expect.objectContaining({ interactive: true }));
|
|
352
|
+
});
|
|
121
353
|
});
|
|
122
354
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
355
|
+
describe('editable checkbox', () => {
|
|
356
|
+
it('shows warning when disabling editable with correct answer name', () => {
|
|
357
|
+
const categories = [
|
|
358
|
+
{
|
|
359
|
+
value: 1,
|
|
360
|
+
label: 'test',
|
|
361
|
+
editable: true,
|
|
362
|
+
interactive: true,
|
|
363
|
+
correctness: { value: 'correct' },
|
|
364
|
+
},
|
|
365
|
+
];
|
|
366
|
+
const component = new TickComponent({
|
|
367
|
+
categories,
|
|
368
|
+
onChangeCategory: jest.fn(),
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
component.setState = jest.fn();
|
|
372
|
+
component.changeEditable(0, false);
|
|
373
|
+
|
|
374
|
+
expect(component.setState).toHaveBeenCalledWith(
|
|
375
|
+
expect.objectContaining({
|
|
376
|
+
dialog: expect.objectContaining({
|
|
377
|
+
open: true,
|
|
378
|
+
title: 'Warning',
|
|
379
|
+
}),
|
|
380
|
+
}),
|
|
381
|
+
);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('changes editable without warning when enabling', () => {
|
|
385
|
+
const onChangeCategory = jest.fn();
|
|
386
|
+
const categories = [{ value: 1, label: 'test', editable: false, interactive: true }];
|
|
387
|
+
const component = new TickComponent({
|
|
388
|
+
categories,
|
|
389
|
+
onChangeCategory,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
component.changeEditable(0, true);
|
|
393
|
+
|
|
394
|
+
expect(onChangeCategory).toHaveBeenCalledWith(0, expect.objectContaining({ editable: true }));
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
describe('splitText method', () => {
|
|
399
|
+
it('splits text into chunks', () => {
|
|
400
|
+
const component = new TickComponent({ categories: [] });
|
|
401
|
+
const result = component.splitText('This is a long text', 10);
|
|
402
|
+
|
|
403
|
+
expect(result.length).toBeGreaterThan(1);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it('splits at word boundaries', () => {
|
|
407
|
+
const component = new TickComponent({ categories: [] });
|
|
408
|
+
const result = component.splitText('Hello world test', 11);
|
|
409
|
+
|
|
410
|
+
expect(result[0]).toBe('Hello world');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('handles text shorter than max', () => {
|
|
414
|
+
const component = new TickComponent({ categories: [] });
|
|
415
|
+
const result = component.splitText('Short', 20);
|
|
416
|
+
|
|
417
|
+
expect(result).toEqual(['Short']);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('handles empty text', () => {
|
|
421
|
+
const component = new TickComponent({ categories: [] });
|
|
422
|
+
const result = component.splitText('', 10);
|
|
423
|
+
|
|
424
|
+
expect(result).toEqual([]);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('handles null text', () => {
|
|
428
|
+
const component = new TickComponent({ categories: [] });
|
|
429
|
+
const result = component.splitText(null, 10);
|
|
430
|
+
|
|
431
|
+
expect(result).toEqual([]);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('forces split when no spaces', () => {
|
|
435
|
+
const component = new TickComponent({ categories: [] });
|
|
436
|
+
const result = component.splitText('verylongwordwithoutspaces', 10);
|
|
437
|
+
|
|
438
|
+
expect(result.length).toBeGreaterThan(1);
|
|
439
|
+
expect(result[0].length).toBeLessThanOrEqual(10);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
describe('correctness display', () => {
|
|
444
|
+
it('renders correctness indicator when showCorrectness is true', () => {
|
|
445
|
+
const categories = [
|
|
446
|
+
{
|
|
447
|
+
value: 1,
|
|
448
|
+
label: 'test',
|
|
449
|
+
editable: true,
|
|
450
|
+
interactive: true,
|
|
451
|
+
correctness: { value: 'correct', label: 'Correct!' },
|
|
452
|
+
},
|
|
453
|
+
];
|
|
454
|
+
const { getByTestId } = renderComponent({
|
|
455
|
+
categories,
|
|
456
|
+
formattedValue: '0-test',
|
|
457
|
+
showCorrectness: true,
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
expect(getByTestId('tick-correctness-indicator')).toBeInTheDocument();
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('does not render correctness indicator when showCorrectness is false', () => {
|
|
464
|
+
const categories = [
|
|
465
|
+
{
|
|
466
|
+
value: 1,
|
|
467
|
+
label: 'test',
|
|
468
|
+
editable: true,
|
|
469
|
+
interactive: true,
|
|
470
|
+
correctness: { value: 'correct', label: 'Correct!' },
|
|
471
|
+
},
|
|
472
|
+
];
|
|
473
|
+
const { queryByTestId } = renderComponent({
|
|
474
|
+
categories,
|
|
475
|
+
formattedValue: '0-test',
|
|
476
|
+
showCorrectness: false,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
expect(queryByTestId('tick-correctness-indicator')).not.toBeInTheDocument();
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('does not render correctness indicator when not interactive', () => {
|
|
483
|
+
const categories = [
|
|
484
|
+
{
|
|
485
|
+
value: 1,
|
|
486
|
+
label: 'test',
|
|
487
|
+
editable: true,
|
|
488
|
+
interactive: false,
|
|
489
|
+
correctness: { value: 'correct', label: 'Correct!' },
|
|
490
|
+
},
|
|
491
|
+
];
|
|
492
|
+
const { queryByTestId } = renderComponent({
|
|
493
|
+
categories,
|
|
494
|
+
formattedValue: '0-test',
|
|
495
|
+
showCorrectness: true,
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
expect(queryByTestId('tick-correctness-indicator')).not.toBeInTheDocument();
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
describe('error display', () => {
|
|
503
|
+
it('renders error message for first category', () => {
|
|
504
|
+
const error = { 0: 'Error message' };
|
|
505
|
+
const { getByTestId } = renderComponent({
|
|
506
|
+
error,
|
|
507
|
+
formattedValue: '0-test',
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
expect(getByTestId('mark-label-error')).toBeInTheDocument();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('renders distinct error messages', () => {
|
|
514
|
+
const error = { 0: 'Error 1', 1: 'Error 1', 2: 'Error 2' };
|
|
515
|
+
const { container } = renderComponent({
|
|
516
|
+
error,
|
|
517
|
+
formattedValue: '0-test',
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
const errorText = container.querySelector('text');
|
|
521
|
+
expect(errorText).toBeInTheDocument();
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('does not render error when no error prop', () => {
|
|
525
|
+
const { queryByTestId } = renderComponent({ error: null });
|
|
526
|
+
expect(queryByTestId('mark-label-error')).not.toBeInTheDocument();
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
describe('autoFocus handling', () => {
|
|
531
|
+
it('handles autoFocus prop', () => {
|
|
532
|
+
const onAutoFocusUsed = jest.fn();
|
|
533
|
+
const { rerender } = renderComponent({
|
|
534
|
+
autoFocus: false,
|
|
535
|
+
onAutoFocusUsed,
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
rerender(
|
|
539
|
+
<ThemeProvider theme={theme}>
|
|
540
|
+
<svg>
|
|
541
|
+
<TickComponent
|
|
542
|
+
{...{
|
|
543
|
+
graphProps: graphProps(),
|
|
544
|
+
xBand: createBandScale(['a', 'b', 'c'], [0, 400]),
|
|
545
|
+
categories: defaultCategories,
|
|
546
|
+
formattedValue: '0-Category A',
|
|
547
|
+
bandWidth: 100,
|
|
548
|
+
barWidth: 100,
|
|
549
|
+
x: 50,
|
|
550
|
+
y: 50,
|
|
551
|
+
top: 0,
|
|
552
|
+
onChangeCategory: jest.fn(),
|
|
553
|
+
autoFocus: true,
|
|
554
|
+
onAutoFocusUsed,
|
|
555
|
+
}}
|
|
556
|
+
/>
|
|
557
|
+
</svg>
|
|
558
|
+
</ThemeProvider>,
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
expect(onAutoFocusUsed).toHaveBeenCalled();
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
describe('define chart mode', () => {
|
|
566
|
+
it('renders interactive checkboxes when changeInteractiveEnabled', () => {
|
|
567
|
+
const chartingOptions = {
|
|
568
|
+
changeInteractive: { authoringLabel: 'Allow students to change' },
|
|
569
|
+
};
|
|
570
|
+
const { container } = renderComponent({
|
|
571
|
+
defineChart: true,
|
|
572
|
+
chartingOptions,
|
|
573
|
+
changeInteractiveEnabled: true,
|
|
574
|
+
formattedValue: '0-test',
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const text = container.querySelector('text');
|
|
578
|
+
expect(text).toBeInTheDocument();
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it('renders editable checkboxes when changeEditableEnabled', () => {
|
|
582
|
+
const chartingOptions = {
|
|
583
|
+
changeEditable: { authoringLabel: 'Allow label editing' },
|
|
584
|
+
};
|
|
585
|
+
const { container } = renderComponent({
|
|
586
|
+
defineChart: true,
|
|
587
|
+
chartingOptions,
|
|
588
|
+
changeEditableEnabled: true,
|
|
589
|
+
formattedValue: '0-test',
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
const text = container.querySelector('text');
|
|
593
|
+
expect(text).toBeInTheDocument();
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
it('splits long authoring labels', () => {
|
|
597
|
+
const chartingOptions = {
|
|
598
|
+
changeInteractive: { authoringLabel: 'This is a very long label that needs to be split' },
|
|
599
|
+
};
|
|
600
|
+
const { container } = renderComponent({
|
|
601
|
+
defineChart: true,
|
|
602
|
+
chartingOptions,
|
|
603
|
+
changeInteractiveEnabled: true,
|
|
604
|
+
formattedValue: '0-test',
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const tspans = container.querySelectorAll('tspan');
|
|
608
|
+
expect(tspans.length).toBeGreaterThan(1);
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
describe('edge cases', () => {
|
|
613
|
+
it('handles missing category', () => {
|
|
614
|
+
const { container } = renderComponent({
|
|
615
|
+
categories: [],
|
|
616
|
+
formattedValue: '0-missing',
|
|
617
|
+
});
|
|
618
|
+
expect(container).toBeInTheDocument();
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it('handles invalid formattedValue format', () => {
|
|
622
|
+
const { container } = renderComponent({
|
|
623
|
+
formattedValue: 'invalid',
|
|
624
|
+
});
|
|
625
|
+
expect(container).toBeInTheDocument();
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it('handles missing graphProps', () => {
|
|
629
|
+
const { container } = renderComponent({
|
|
630
|
+
graphProps: null,
|
|
631
|
+
});
|
|
632
|
+
expect(container).toBeInTheDocument();
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it('handles zero barWidth', () => {
|
|
636
|
+
const { container } = renderComponent({
|
|
637
|
+
barWidth: 0,
|
|
638
|
+
});
|
|
639
|
+
expect(container).toBeInTheDocument();
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it('handles negative coordinates', () => {
|
|
643
|
+
const { container } = renderComponent({
|
|
644
|
+
x: -10,
|
|
645
|
+
y: -20,
|
|
646
|
+
});
|
|
647
|
+
expect(container).toBeInTheDocument();
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
describe('componentDidUpdate', () => {
|
|
652
|
+
it('calls onAutoFocusUsed when autoFocus changes to true', () => {
|
|
653
|
+
const onAutoFocusUsed = jest.fn();
|
|
654
|
+
const component = new TickComponent({
|
|
655
|
+
autoFocus: false,
|
|
656
|
+
onAutoFocusUsed,
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
component.componentDidUpdate({ autoFocus: false });
|
|
660
|
+
expect(onAutoFocusUsed).not.toHaveBeenCalled();
|
|
661
|
+
|
|
662
|
+
component.props = { autoFocus: true, onAutoFocusUsed };
|
|
663
|
+
component.componentDidUpdate({ autoFocus: false });
|
|
664
|
+
expect(onAutoFocusUsed).toHaveBeenCalled();
|
|
665
|
+
});
|
|
666
|
+
});
|
|
126
667
|
});
|