@pie-lib/graphing 4.0.23 → 4.0.24-beta.1

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 (66) hide show
  1. package/CHANGELOG.md +0 -92
  2. package/lib/mark-label.js +7 -197
  3. package/lib/mark-label.js.map +1 -1
  4. package/lib/tools/circle/component.js +3 -34
  5. package/lib/tools/circle/component.js.map +1 -1
  6. package/lib/tools/point/component.js +8 -29
  7. package/lib/tools/point/component.js.map +1 -1
  8. package/lib/tools/polygon/component.js +6 -31
  9. package/lib/tools/polygon/component.js.map +1 -1
  10. package/lib/tools/shared/line/index.js +3 -34
  11. package/lib/tools/shared/line/index.js.map +1 -1
  12. package/package.json +17 -17
  13. package/src/mark-label.jsx +8 -209
  14. package/src/tools/circle/component.jsx +3 -26
  15. package/src/tools/point/component.jsx +8 -21
  16. package/src/tools/polygon/component.jsx +6 -24
  17. package/src/tools/shared/line/index.jsx +3 -26
  18. package/LICENSE.md +0 -5
  19. package/src/__tests__/bg.test.jsx +0 -250
  20. package/src/__tests__/coordinates-label.test.jsx +0 -243
  21. package/src/__tests__/graph-with-controls.test.jsx +0 -198
  22. package/src/__tests__/graph.test.jsx +0 -796
  23. package/src/__tests__/grid-setup.test.jsx +0 -645
  24. package/src/__tests__/grid.test.jsx +0 -22
  25. package/src/__tests__/key-legend.test.jsx +0 -260
  26. package/src/__tests__/label-svg-icon.test.jsx +0 -278
  27. package/src/__tests__/labels.test.jsx +0 -55
  28. package/src/__tests__/mark-label.test.jsx +0 -437
  29. package/src/__tests__/toggle-bar.test.jsx +0 -146
  30. package/src/__tests__/tool-menu.test.jsx +0 -115
  31. package/src/__tests__/undo-redo.test.jsx +0 -24
  32. package/src/__tests__/use-debounce.test.js +0 -21
  33. package/src/__tests__/utils.js +0 -41
  34. package/src/__tests__/utils.test.js +0 -105
  35. package/src/axis/__tests__/arrow.test.jsx +0 -38
  36. package/src/axis/__tests__/axes.test.jsx +0 -216
  37. package/src/container/__tests__/actions.test.js +0 -105
  38. package/src/container/__tests__/index.test.jsx +0 -319
  39. package/src/container/__tests__/marks.test.js +0 -172
  40. package/src/container/__tests__/middleware.test.js +0 -235
  41. package/src/container/__tests__/reducer.test.js +0 -324
  42. package/src/tools/absolute/__tests__/component.test.jsx +0 -53
  43. package/src/tools/circle/__tests__/bg-circle.test.jsx +0 -26
  44. package/src/tools/circle/__tests__/component.test.jsx +0 -494
  45. package/src/tools/circle/__tests__/index.test.js +0 -480
  46. package/src/tools/exponential/__tests__/component.test.jsx +0 -53
  47. package/src/tools/exponential/__tests__/index.test.js +0 -729
  48. package/src/tools/line/__tests__/component.test.jsx +0 -37
  49. package/src/tools/parabola/__tests__/component.test.jsx +0 -48
  50. package/src/tools/parabola/__tests__/index.test.js +0 -470
  51. package/src/tools/point/__tests__/component.test.jsx +0 -475
  52. package/src/tools/point/__tests__/index.test.js +0 -241
  53. package/src/tools/polygon/__tests__/component.test.jsx +0 -471
  54. package/src/tools/polygon/__tests__/index.test.js +0 -294
  55. package/src/tools/polygon/__tests__/line.test.jsx +0 -35
  56. package/src/tools/polygon/__tests__/polygon.test.jsx +0 -61
  57. package/src/tools/ray/__tests__/component.test.jsx +0 -29
  58. package/src/tools/segment/__tests__/component.test.jsx +0 -28
  59. package/src/tools/shared/__tests__/arrow-head.test.jsx +0 -31
  60. package/src/tools/shared/line/__tests__/index.test.jsx +0 -109
  61. package/src/tools/shared/line/__tests__/line-path.test.jsx +0 -53
  62. package/src/tools/shared/line/__tests__/with-root-edge.test.jsx +0 -73
  63. package/src/tools/shared/point/__tests__/arrow-point.test.jsx +0 -91
  64. package/src/tools/shared/point/__tests__/base-point.test.jsx +0 -87
  65. package/src/tools/sine/__tests__/component.test.jsx +0 -51
  66. package/src/tools/vector/__tests__/component.test.jsx +0 -25
@@ -1,437 +0,0 @@
1
- import { act, fireEvent, render } from '@pie-lib/test-utils';
2
- import React from 'react';
3
- import { coordinates, MarkLabel, position } from '../mark-label';
4
- import { graphProps as getGraphProps } from './utils';
5
-
6
- describe('MarkLabel', () => {
7
- let onChange = jest.fn();
8
- let inputRef = jest.fn();
9
- const renderComponent = (extras) => {
10
- const defaults = {
11
- classes: {},
12
- className: 'className',
13
- onChange,
14
- inputRef,
15
- mark: { x: 1, y: 1 },
16
- graphProps: getGraphProps(0, 10, 0, 10),
17
- };
18
- const props = { ...defaults, ...extras };
19
- return render(<MarkLabel {...props} />);
20
- };
21
- describe('rendering', () => {
22
- it('renders without crashing', () => {
23
- const { container } = renderComponent();
24
- expect(container.firstChild).toBeInTheDocument();
25
- });
26
- it('renders with different mark position', () => {
27
- const { container } = renderComponent({ mark: { x: 10, y: 10 } });
28
- expect(container.firstChild).toBeInTheDocument();
29
- });
30
- });
31
- });
32
-
33
- describe('MarkLabel - editing', () => {
34
- let onChange;
35
- let onBlur;
36
-
37
- beforeEach(() => {
38
- onChange = jest.fn();
39
- onBlur = jest.fn();
40
- });
41
-
42
- // the label input is the only input MarkLabel renders (react-input-autosize)
43
- const input = (container) => container.querySelector('input');
44
-
45
- const renderComponent = (extras) => {
46
- const defaults = {
47
- onChange,
48
- onBlur,
49
- inputRef: jest.fn(),
50
- mark: { x: 1, y: 1, label: '' },
51
- graphProps: getGraphProps(0, 10, 0, 10),
52
- };
53
- const props = { ...defaults, ...extras };
54
- const result = render(<MarkLabel {...props} />);
55
-
56
- return {
57
- ...result,
58
- input: input(result.container),
59
- rerenderWith: (next) => result.rerender(<MarkLabel {...props} {...next} />),
60
- };
61
- };
62
-
63
- describe('autoFocus', () => {
64
- it('focuses the input so a new label can be typed into right away', () => {
65
- const { input } = renderComponent({ autoFocus: true });
66
-
67
- expect(document.activeElement).toBe(input);
68
- });
69
-
70
- it('leaves the caret at the end of an existing label', () => {
71
- const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'AB' } });
72
-
73
- expect(document.activeElement).toBe(input);
74
- expect(input.selectionStart).toBe(2);
75
- expect(input.selectionEnd).toBe(2);
76
- });
77
-
78
- it('does not focus the input of an existing label when autoFocus is not set', () => {
79
- const { input } = renderComponent({ mark: { x: 1, y: 1, label: 'A' } });
80
-
81
- expect(document.activeElement).not.toBe(input);
82
- });
83
-
84
- it('does not focus the input when disabled', () => {
85
- const { input } = renderComponent({ autoFocus: true, disabled: true, mark: { x: 1, y: 1, label: 'A' } });
86
-
87
- expect(document.activeElement).not.toBe(input);
88
- });
89
-
90
- it('does not focus the input of a disabled mark', () => {
91
- const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'A', disabled: true } });
92
-
93
- expect(document.activeElement).not.toBe(input);
94
- });
95
-
96
- // the focus can go away without this component hearing about it - the input node gets replaced
97
- // by a render higher up, so no blur is fired and autoFocus is still on
98
- it('takes the focus back on the next render while it is still being edited', () => {
99
- const { input, rerenderWith } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'A' } });
100
-
101
- expect(document.activeElement).toBe(input);
102
-
103
- input.blur();
104
- expect(document.activeElement).not.toBe(input);
105
-
106
- rerenderWith({ mark: { x: 1, y: 1, label: 'A' } });
107
-
108
- expect(document.activeElement).toBe(input);
109
- });
110
-
111
- it('leaves the caret where the user put it while the input has the focus', () => {
112
- const { input, rerenderWith } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'ABC' } });
113
-
114
- input.setSelectionRange(1, 1);
115
-
116
- rerenderWith({ mark: { x: 1, y: 1, label: 'ABC' } });
117
-
118
- expect(document.activeElement).toBe(input);
119
- expect(input.selectionStart).toBe(1);
120
- });
121
-
122
- it('focuses the input once it is no longer disabled', () => {
123
- const { rerenderWith, container } = renderComponent({ autoFocus: true, disabled: true });
124
-
125
- rerenderWith({ disabled: false });
126
-
127
- expect(document.activeElement).toBe(input(container));
128
- });
129
-
130
- // the host saves the model with an api call and the model that comes back can replace the
131
- // input node - the label has to keep the focus across that remount
132
- it('re-focuses the input when it is remounted while being edited', () => {
133
- const props = {
134
- autoFocus: true,
135
- onChange,
136
- inputRef: jest.fn(),
137
- mark: { x: 1, y: 1, label: 'A' },
138
- graphProps: getGraphProps(0, 10, 0, 10),
139
- };
140
- const { container, rerender } = render(<MarkLabel key="first" {...props} />);
141
- const first = input(container);
142
-
143
- expect(document.activeElement).toBe(first);
144
-
145
- rerender(<MarkLabel key="second" {...props} />);
146
- const second = input(container);
147
-
148
- expect(second).not.toBe(first);
149
- expect(document.activeElement).toBe(second);
150
- });
151
- });
152
-
153
- // the tool asks for the focus with autoFocus, but its state can be gone before the input renders:
154
- // the marks come back from the host asynchronously and can remount the tool. An empty label in
155
- // label mode is a label the user has just added, so the input can tell on its own.
156
- describe('a label that was just added', () => {
157
- it('focuses itself without the tool asking', () => {
158
- const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
159
-
160
- expect(document.activeElement).toBe(input);
161
- });
162
-
163
- it('focuses itself again when it is remounted before it was typed into', () => {
164
- const props = {
165
- onChange,
166
- onBlur,
167
- inputRef: jest.fn(),
168
- mark: { x: 1, y: 1, label: '' },
169
- graphProps: getGraphProps(0, 10, 0, 10),
170
- };
171
- const { container, rerender } = render(<MarkLabel key="first" {...props} />);
172
- const first = input(container);
173
-
174
- rerender(<MarkLabel key="second" {...props} />);
175
- const second = input(container);
176
-
177
- expect(second).not.toBe(first);
178
- expect(document.activeElement).toBe(second);
179
- });
180
-
181
- it('stops chasing the focus once it has been left', () => {
182
- const { input, rerenderWith } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
183
-
184
- expect(document.activeElement).toBe(input);
185
-
186
- input.blur();
187
- rerenderWith({ mark: { x: 1, y: 1, label: '' } });
188
-
189
- expect(document.activeElement).not.toBe(input);
190
- });
191
-
192
- // outside of label mode the input is disabled, which is how an empty label that was loaded with
193
- // the item is told apart from one the user has just added - label mode always starts off
194
- // the config draws the background marks into the correct answer graphs as disabled marks
195
- it('is not rendered at all while it cannot be edited', () => {
196
- const { container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: '' } });
197
-
198
- expect(container.querySelector('input')).toBe(null);
199
- });
200
-
201
- it('is still rendered uneditable once it has text', () => {
202
- const { container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: 'A' } });
203
-
204
- expect(container.querySelector('input').value).toBe('A');
205
- });
206
-
207
- it('is not focused when label mode is turned on afterwards', () => {
208
- const { rerenderWith, container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: '' } });
209
-
210
- rerenderWith({ disabled: false });
211
-
212
- expect(input(container)).not.toBe(null);
213
- expect(document.activeElement).not.toBe(input(container));
214
- });
215
-
216
- it('is not rendered for a disabled mark while it is empty', () => {
217
- const { container } = renderComponent({ mark: { x: 1, y: 1, label: '', disabled: true } });
218
-
219
- expect(container.querySelector('input')).toBe(null);
220
- });
221
-
222
- // an input sized to its content is 2px wide while it is empty, and chrome paints the caret of
223
- // an input inside an svg foreignObject as a hairline that never blinks - so an empty label that
224
- // has the focus has to be wide enough to be seen as a box waiting for text
225
- it('is wide enough to be seen while it is empty', () => {
226
- const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
227
-
228
- expect(parseInt(input.style.width, 10)).toBeGreaterThanOrEqual(30);
229
- });
230
-
231
- it('is only as wide as its text once it cannot be edited', () => {
232
- const { container } = renderComponent({ disabled: true, mark: { x: 1, y: 1, label: 'A' } });
233
-
234
- expect(parseInt(container.querySelector('input').style.width, 10)).toBeLessThan(30);
235
- });
236
- });
237
-
238
- describe('syncing mark.label into the input', () => {
239
- it('picks up a new mark.label while the input does not have the focus', () => {
240
- const { rerenderWith, container } = renderComponent({ mark: { x: 1, y: 1, label: 'A' } });
241
-
242
- rerenderWith({ mark: { x: 1, y: 1, label: 'updated' } });
243
-
244
- expect(input(container).value).toBe('updated');
245
- });
246
-
247
- // a mark coming back from an (async) model save can carry an older label - applying it would
248
- // wipe out the characters typed in the meantime
249
- it('keeps what the user typed while the input has the focus', () => {
250
- const { input, rerenderWith } = renderComponent({ autoFocus: true });
251
-
252
- expect(document.activeElement).toBe(input);
253
-
254
- fireEvent.change(input, { target: { value: 'AB' } });
255
- expect(input.value).toBe('AB');
256
-
257
- // the host echoes back the label as it was one keystroke ago
258
- rerenderWith({ mark: { x: 1, y: 1, label: 'A' } });
259
-
260
- expect(input.value).toBe('AB');
261
- });
262
- });
263
-
264
- // the characters land at the document's caret, not at the focused node. In the authoring
265
- // environment the input ends up focused with no caret anywhere: keypress fires and nothing is
266
- // inserted. jsdom never reports a caret either, so this is that state - the focus cycle that
267
- // repairs it must not hand the label over to the blur handling.
268
- describe('a focused input with no caret', () => {
269
- it('is repaired without reporting the label as removed', () => {
270
- const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: '' } });
271
-
272
- expect(document.activeElement).toBe(input);
273
- expect(onChange).not.toHaveBeenCalled();
274
- expect(onBlur).not.toHaveBeenCalled();
275
- });
276
-
277
- it('leaves the caret at the end of a label that already has text', () => {
278
- const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'AB' } });
279
-
280
- expect(document.activeElement).toBe(input);
281
- expect(input.selectionStart).toBe(2);
282
- expect(onChange).not.toHaveBeenCalled();
283
- });
284
- });
285
-
286
- // clicking another window, the devtools, or the host moving the focus while it saves the model:
287
- // the label is not being left, so it must survive and take the focus back
288
- describe('while the window is away', () => {
289
- const windowGoesAway = () => window.dispatchEvent(new Event('blur'));
290
- const windowComesBack = () => window.dispatchEvent(new Event('focus'));
291
-
292
- it('keeps an empty label instead of reporting it as removed', () => {
293
- const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
294
-
295
- windowGoesAway();
296
- fireEvent.blur(input);
297
-
298
- expect(onChange).not.toHaveBeenCalled();
299
- expect(onBlur).not.toHaveBeenCalled();
300
- });
301
-
302
- it('takes the focus back when the window comes back', () => {
303
- const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: '' } });
304
-
305
- // the focus is moved to another input rather than with input.blur(): jsdom fires a window
306
- // focus event as part of blur(), which is the very thing being tested here
307
- const elsewhere = document.createElement('input');
308
-
309
- document.body.appendChild(elsewhere);
310
-
311
- windowGoesAway();
312
- elsewhere.focus();
313
- expect(document.activeElement).toBe(elsewhere);
314
-
315
- windowComesBack();
316
-
317
- expect(document.activeElement).toBe(input);
318
-
319
- elsewhere.remove();
320
- });
321
-
322
- it('still reports a label the user leaves once the window is back', () => {
323
- const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
324
-
325
- windowGoesAway();
326
- windowComesBack();
327
- fireEvent.blur(input);
328
-
329
- expect(onChange).toHaveBeenCalledWith('');
330
- });
331
- });
332
-
333
- describe('onBlur', () => {
334
- it('is called so the tool stops auto focusing the label', () => {
335
- const { input } = renderComponent({ autoFocus: true, mark: { x: 1, y: 1, label: 'A' } });
336
-
337
- fireEvent.blur(input);
338
-
339
- expect(onBlur).toHaveBeenCalled();
340
- });
341
-
342
- // clicking a button or anything else interactive blurs the label while the debounce is pending
343
- it('saves what was typed straight away', () => {
344
- const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
345
-
346
- fireEvent.change(input, { target: { value: 'AB' } });
347
- fireEvent.blur(input);
348
-
349
- expect(onChange).toHaveBeenCalledWith('AB');
350
- });
351
-
352
- it('does not save it twice when the debounce catches up', () => {
353
- jest.useFakeTimers();
354
- const { input } = renderComponent({ mark: { x: 1, y: 1, label: '' } });
355
-
356
- fireEvent.change(input, { target: { value: 'AB' } });
357
- fireEvent.blur(input);
358
- act(() => jest.advanceTimersByTime(1000));
359
-
360
- expect(onChange).toHaveBeenCalledTimes(1);
361
- expect(onChange).toHaveBeenCalledWith('AB');
362
- jest.useRealTimers();
363
- });
364
-
365
- it('reports an empty label on blur', () => {
366
- const { input } = renderComponent({ mark: { x: 1, y: 1, label: 'A' } });
367
-
368
- fireEvent.change(input, { target: { value: '' } });
369
- fireEvent.blur(input);
370
-
371
- expect(onChange).toHaveBeenCalledWith('');
372
- });
373
- });
374
-
375
- describe('debounce', () => {
376
- beforeEach(() => jest.useFakeTimers());
377
- afterEach(() => jest.useRealTimers());
378
-
379
- it('does not report the typed label before the debounce delay elapses', () => {
380
- const { input } = renderComponent({ autoFocus: true });
381
-
382
- fireEvent.change(input, { target: { value: 'A' } });
383
-
384
- act(() => jest.advanceTimersByTime(499));
385
- expect(onChange).not.toHaveBeenCalled();
386
-
387
- act(() => jest.advanceTimersByTime(1));
388
- expect(onChange).toHaveBeenCalledWith('A');
389
- });
390
-
391
- it('reports the label once for a burst of keystrokes', () => {
392
- const { input } = renderComponent({ autoFocus: true });
393
-
394
- fireEvent.change(input, { target: { value: 'A' } });
395
- act(() => jest.advanceTimersByTime(300));
396
- fireEvent.change(input, { target: { value: 'AB' } });
397
- act(() => jest.advanceTimersByTime(300));
398
- fireEvent.change(input, { target: { value: 'ABC' } });
399
- act(() => jest.advanceTimersByTime(500));
400
-
401
- expect(onChange).toHaveBeenCalledTimes(1);
402
- expect(onChange).toHaveBeenCalledWith('ABC');
403
- });
404
- });
405
- });
406
-
407
- describe('position', () => {
408
- const assertPosition = (mark, rect, expected) => {
409
- it(`${mark.x},${mark.y} + ${rect.width},${rect.height} => ${expected}`, () => {
410
- // we set range.min to a value because in pixels - the greater the Y the lower down on the screen.
411
- const graphProps = getGraphProps(0, 12, 12, 0);
412
- const result = position(graphProps, mark, rect);
413
- expect(result).toEqual(expected);
414
- });
415
- };
416
-
417
- assertPosition({ x: 0, y: 0 }, { width: 10, height: 10 }, 'top-left');
418
- assertPosition({ x: 0, y: 0 }, { width: 1, height: 1 }, 'bottom-right');
419
- assertPosition({ x: 0, y: 0 }, { width: 10, height: 0 }, 'bottom-left');
420
- assertPosition({ x: 0, y: 0 }, { width: 0, height: 10 }, 'top-right');
421
- });
422
-
423
- describe('coordinates', () => {
424
- const assertCoordinates = (mark, rect, pos, expected) => {
425
- it(`${mark.x}, ${mark.y} -> ${pos} = ${expected.left}, ${expected.top}`, () => {
426
- const result = coordinates(getGraphProps(), mark, rect, pos);
427
- expect(result).toEqual(expected);
428
- });
429
- };
430
- assertCoordinates({ x: 0, y: 0 }, { width: 0, height: 0 }, 'top-left', { left: -5, top: -5 });
431
- assertCoordinates({ x: 0, y: 0 }, { width: 0, height: 0 }, 'bottom-left', { left: -5, top: 5 });
432
- assertCoordinates({ x: 0, y: 0 }, { width: 0, height: 0 }, 'top-right', { left: 5, top: -5 });
433
- assertCoordinates({ x: 0, y: 0 }, { width: 0, height: 0 }, 'bottom-right', {
434
- left: 5,
435
- top: 5,
436
- });
437
- });
@@ -1,146 +0,0 @@
1
- import { render, screen } from '@pie-lib/test-utils';
2
- import userEvent from '@testing-library/user-event';
3
- import React from 'react';
4
-
5
- import { ToggleBar } from '../toggle-bar';
6
-
7
- jest.mock('@pie-lib/drag', () => ({
8
- DragProvider: ({ children }) => <div data-testid="drag-provider">{children}</div>,
9
- }));
10
-
11
- jest.mock('@dnd-kit/core', () => ({
12
- useDraggable: jest.fn(() => ({
13
- attributes: {
14
- role: 'button',
15
- tabIndex: 0,
16
- },
17
- listeners: {
18
- onPointerDown: jest.fn(),
19
- },
20
- setNodeRef: jest.fn(),
21
- transform: null,
22
- transition: null,
23
- isDragging: false,
24
- })),
25
- useDroppable: jest.fn(() => ({
26
- setNodeRef: jest.fn(),
27
- isOver: false,
28
- active: null,
29
- })),
30
- }));
31
-
32
- jest.mock('@dnd-kit/utilities', () => ({
33
- CSS: {
34
- Transform: {
35
- toString: jest.fn((transform) => (transform ? 'translate3d(0, 0, 0)' : '')),
36
- },
37
- },
38
- }));
39
-
40
- jest.mock('@dnd-kit/sortable', () => ({
41
- arrayMove: jest.fn((array, from, to) => {
42
- const newArray = [...array];
43
- const [removed] = newArray.splice(from, 1);
44
- newArray.splice(to, 0, removed);
45
- return newArray;
46
- }),
47
- }));
48
-
49
- jest.mock('@pie-lib/translator', () => ({
50
- translator: {
51
- t: (key) => {
52
- const parts = key.split('.');
53
- return parts[parts.length - 1];
54
- },
55
- },
56
- }));
57
-
58
- describe('ToggleBar', () => {
59
- let onChange = jest.fn();
60
- let onChangeToolsOrder = jest.fn();
61
-
62
- beforeEach(() => {
63
- onChange.mockClear();
64
- onChangeToolsOrder.mockClear();
65
- });
66
-
67
- const renderComponent = (extras) => {
68
- const defaults = {
69
- className: 'className',
70
- onChange,
71
- onChangeToolsOrder,
72
- options: ['point', 'line'],
73
- language: 'en',
74
- };
75
- const props = { ...defaults, ...extras };
76
- return render(<ToggleBar {...props} />);
77
- };
78
-
79
- describe('rendering', () => {
80
- it('renders without crashing', () => {
81
- const { container } = renderComponent();
82
- expect(container.firstChild).toBeInTheDocument();
83
- });
84
-
85
- it('renders tool buttons for valid tools', () => {
86
- renderComponent({ options: ['point', 'line', 'circle'] });
87
- expect(screen.getByText(/point/i)).toBeInTheDocument();
88
- expect(screen.getByText(/line/i)).toBeInTheDocument();
89
- expect(screen.getByText(/circle/i)).toBeInTheDocument();
90
- });
91
-
92
- it('does not render invalid tool options', () => {
93
- renderComponent({ options: ['point', 'invalid-tool', 'line'] });
94
- expect(screen.getByText(/point/i)).toBeInTheDocument();
95
- expect(screen.getByText(/line/i)).toBeInTheDocument();
96
- expect(screen.queryByText(/invalid-tool/i)).not.toBeInTheDocument();
97
- });
98
-
99
- it('highlights selected tool', () => {
100
- const { container } = renderComponent({ selectedToolType: 'line' });
101
- const selectedButton = screen.getByText(/line/i).closest('button');
102
- expect(selectedButton).toHaveAttribute('value', 'line');
103
- });
104
- });
105
-
106
- describe('interactions', () => {
107
- it('calls onChange when tool button is clicked', async () => {
108
- const user = userEvent.setup();
109
- renderComponent({ options: ['point', 'line'] });
110
-
111
- const pointButton = screen.getByText(/point/i).closest('button');
112
- await user.click(pointButton);
113
-
114
- expect(onChange).toHaveBeenCalledWith('point');
115
- });
116
-
117
- it('calls onChange with selected tool type', async () => {
118
- const user = userEvent.setup();
119
- renderComponent({ options: ['point', 'line'], selectedToolType: 'point' });
120
-
121
- const lineButton = screen.getByText(/line/i).closest('button');
122
- await user.click(lineButton);
123
-
124
- expect(onChange).toHaveBeenCalledWith('line');
125
- });
126
-
127
- it('disables buttons when disabled prop is true', () => {
128
- renderComponent({ disabled: true, options: ['point', 'line'] });
129
-
130
- const pointButton = screen.getByText(/point/i).closest('button');
131
- const lineButton = screen.getByText(/line/i).closest('button');
132
-
133
- expect(pointButton).toBeDisabled();
134
- expect(lineButton).toBeDisabled();
135
- });
136
-
137
- it('does not call onChange when disabled', () => {
138
- renderComponent({ disabled: true, options: ['point'] });
139
-
140
- const pointButton = screen.getByText(/point/i).closest('button');
141
- expect(pointButton).toBeDisabled();
142
- // Note: Cannot test click on disabled button with pointer-events: none
143
- // The disabled state is verified above
144
- });
145
- });
146
- });
@@ -1,115 +0,0 @@
1
- import { render, screen } from '@pie-lib/test-utils';
2
- import userEvent from '@testing-library/user-event';
3
- import React from 'react';
4
-
5
- import ToolMenu from '../tool-menu';
6
-
7
- jest.mock('@pie-lib/drag', () => ({
8
- DragProvider: ({ children }) => <div data-testid="drag-provider">{children}</div>,
9
- }));
10
-
11
- jest.mock('@dnd-kit/core', () => ({
12
- useDraggable: jest.fn(() => ({
13
- attributes: {
14
- role: 'button',
15
- tabIndex: 0,
16
- },
17
- listeners: {
18
- onPointerDown: jest.fn(),
19
- },
20
- setNodeRef: jest.fn(),
21
- transform: null,
22
- transition: null,
23
- isDragging: false,
24
- })),
25
- useDroppable: jest.fn(() => ({
26
- setNodeRef: jest.fn(),
27
- isOver: false,
28
- active: null,
29
- })),
30
- }));
31
-
32
- jest.mock('@dnd-kit/utilities', () => ({
33
- CSS: {
34
- Transform: {
35
- toString: jest.fn((transform) => (transform ? 'translate3d(0, 0, 0)' : '')),
36
- },
37
- },
38
- }));
39
-
40
- jest.mock('@dnd-kit/sortable', () => ({
41
- arrayMove: jest.fn((array, from, to) => {
42
- const newArray = [...array];
43
- const [removed] = newArray.splice(from, 1);
44
- newArray.splice(to, 0, removed);
45
- return newArray;
46
- }),
47
- }));
48
-
49
- describe('ToolMenu', () => {
50
- let onChange = jest.fn();
51
- let onChangeTools = jest.fn();
52
-
53
- beforeEach(() => {
54
- onChange.mockClear();
55
- onChangeTools.mockClear();
56
- });
57
-
58
- const renderComponent = (extras) => {
59
- const defaults = {
60
- className: 'className',
61
- onChange,
62
- onChangeTools,
63
- currentToolType: 'point',
64
- toolbarTools: ['point', 'line'],
65
- language: 'en',
66
- };
67
- const props = { ...defaults, ...extras };
68
- return render(<ToolMenu {...props} />);
69
- };
70
-
71
- describe('rendering', () => {
72
- it('renders without crashing', () => {
73
- const { container } = renderComponent();
74
- expect(container.firstChild).toBeInTheDocument();
75
- });
76
-
77
- it('renders ToggleBar with correct props', () => {
78
- renderComponent({ toolbarTools: ['point', 'line', 'circle'] });
79
- expect(screen.getByText(/point/i)).toBeInTheDocument();
80
- expect(screen.getByText(/line/i)).toBeInTheDocument();
81
- expect(screen.getByText(/circle/i)).toBeInTheDocument();
82
- });
83
-
84
- it('passes currentToolType to ToggleBar', () => {
85
- renderComponent({ currentToolType: 'line' });
86
- const selectedButton = screen.getByText(/line/i).closest('button');
87
- expect(selectedButton).toBeInTheDocument();
88
- });
89
- });
90
-
91
- describe('interactions', () => {
92
- it('calls onChange when tool is selected', async () => {
93
- const user = userEvent.setup();
94
- renderComponent();
95
-
96
- const lineButton = screen.getByText(/line/i).closest('button');
97
- await user.click(lineButton);
98
-
99
- expect(onChange).toHaveBeenCalledWith('line');
100
- });
101
-
102
- it('calls onChangeTools when tools order changes', () => {
103
- renderComponent({ draggableTools: true });
104
- // Note: Drag-and-drop testing requires more complex setup with @dnd-kit/test-utils
105
- // For now, we verify the component renders with draggableTools enabled
106
- expect(screen.getByText(/point/i)).toBeInTheDocument();
107
- });
108
-
109
- it('disables tools when disabled prop is true', () => {
110
- renderComponent({ disabled: true });
111
- const pointButton = screen.getByText(/point/i).closest('button');
112
- expect(pointButton).toBeDisabled();
113
- });
114
- });
115
- });