@pie-lib/graphing 4.0.22 → 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 -88
  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,475 +0,0 @@
1
- import { fireEvent, render } from '@pie-lib/test-utils';
2
- import React from 'react';
3
- import { graphProps, xy } from '../../../__tests__/utils';
4
-
5
- import Component from '../component';
6
-
7
- describe('Component', () => {
8
- let onChange;
9
- let onClick;
10
-
11
- beforeEach(() => {
12
- onChange = jest.fn();
13
- onClick = jest.fn();
14
- });
15
-
16
- const renderComponent = (extras) => {
17
- const defaults = {
18
- classes: {},
19
- className: 'className',
20
- onChange,
21
- onClick,
22
- graphProps: graphProps(),
23
- mark: xy(0, 0),
24
- };
25
- const props = { ...defaults, ...extras };
26
- return render(<Component {...props} />);
27
- };
28
-
29
- describe('rendering', () => {
30
- it('renders without crashing', () => {
31
- const { container } = renderComponent();
32
- expect(container.firstChild).toBeInTheDocument();
33
- });
34
-
35
- it('renders with mark', () => {
36
- const { container } = renderComponent({ mark: { ...xy(0, 0) } });
37
- expect(container.firstChild).toBeInTheDocument();
38
- });
39
-
40
- it('renders with label', () => {
41
- const { container } = renderComponent({ mark: { label: 'foo', ...xy(0, 0) } });
42
- expect(container.firstChild).toBeInTheDocument();
43
- });
44
-
45
- it('renders with different coordinates', () => {
46
- const { container } = renderComponent({ mark: { ...xy(5, -3) } });
47
- expect(container.firstChild).toBeInTheDocument();
48
- });
49
-
50
- it('renders with empty label', () => {
51
- const { container } = renderComponent({ mark: { label: '', ...xy(1, 1) } });
52
- expect(container.firstChild).toBeInTheDocument();
53
- });
54
-
55
- it('renders with negative coordinates', () => {
56
- const { container } = renderComponent({ mark: { ...xy(-5, -5) } });
57
- expect(container.firstChild).toBeInTheDocument();
58
- });
59
-
60
- it('renders with decimal coordinates', () => {
61
- const { container } = renderComponent({ mark: { ...xy(1.5, 2.5) } });
62
- expect(container.firstChild).toBeInTheDocument();
63
- });
64
-
65
- it('renders with zero coordinates', () => {
66
- const { container } = renderComponent({ mark: { ...xy(0, 0) } });
67
- expect(container.firstChild).toBeInTheDocument();
68
- });
69
-
70
- it('renders with large coordinate values', () => {
71
- const { container } = renderComponent({ mark: { ...xy(1000, 1000) } });
72
- expect(container.firstChild).toBeInTheDocument();
73
- });
74
- });
75
-
76
- describe('state properties', () => {
77
- it('renders with disabled state', () => {
78
- const { container } = renderComponent({ mark: { ...xy(1, 1), disabled: true } });
79
- expect(container.firstChild).toBeInTheDocument();
80
- });
81
-
82
- it('renders with correctness state - correct', () => {
83
- const { container } = renderComponent({ mark: { ...xy(1, 1), correctness: 'correct' } });
84
- expect(container.firstChild).toBeInTheDocument();
85
- });
86
-
87
- it('renders with correctness state - incorrect', () => {
88
- const { container } = renderComponent({ mark: { ...xy(1, 1), correctness: 'incorrect' } });
89
- expect(container.firstChild).toBeInTheDocument();
90
- });
91
-
92
- it('renders with correctness state - missing', () => {
93
- const { container } = renderComponent({ mark: { ...xy(1, 1), correctness: 'missing' } });
94
- expect(container.firstChild).toBeInTheDocument();
95
- });
96
-
97
- it('renders with fill color', () => {
98
- const { container } = renderComponent({ mark: { ...xy(1, 1), fill: '#ff0000' } });
99
- expect(container.firstChild).toBeInTheDocument();
100
- });
101
- });
102
-
103
- describe('label functionality', () => {
104
- it('renders label portal when labelNode is provided', () => {
105
- const labelNode = document.createElement('foreignObject');
106
- const { container } = renderComponent({
107
- mark: { ...xy(1, 1), label: 'Test' },
108
- labelNode,
109
- });
110
- expect(container.firstChild).toBeInTheDocument();
111
- });
112
-
113
- it('renders without label portal when labelNode is not provided', () => {
114
- const { container } = renderComponent({
115
- mark: { ...xy(1, 1), label: 'Test' },
116
- });
117
- expect(container.firstChild).toBeInTheDocument();
118
- });
119
-
120
- it('does not render label portal when mark has no label property', () => {
121
- const labelNode = document.createElement('foreignObject');
122
- const { container } = renderComponent({
123
- mark: { ...xy(1, 1) },
124
- labelNode,
125
- });
126
- expect(container.firstChild).toBeInTheDocument();
127
- });
128
-
129
- it('renders with labelModeEnabled', () => {
130
- const labelNode = document.createElement('foreignObject');
131
- const { container } = renderComponent({
132
- mark: { ...xy(1, 1), label: 'Test' },
133
- labelNode,
134
- labelModeEnabled: true,
135
- });
136
- expect(container.firstChild).toBeInTheDocument();
137
- });
138
-
139
- it('renders with labelModeEnabled disabled', () => {
140
- const labelNode = document.createElement('foreignObject');
141
- const { container } = renderComponent({
142
- mark: { ...xy(1, 1), label: 'Test' },
143
- labelNode,
144
- labelModeEnabled: false,
145
- });
146
- expect(container.firstChild).toBeInTheDocument();
147
- });
148
-
149
- it('handles empty string label', () => {
150
- const labelNode = document.createElement('foreignObject');
151
- const { container } = renderComponent({
152
- mark: { ...xy(1, 1), label: '' },
153
- labelNode,
154
- labelModeEnabled: true,
155
- });
156
- expect(container.firstChild).toBeInTheDocument();
157
- });
158
-
159
- it('handles long label text', () => {
160
- const labelNode = document.createElement('foreignObject');
161
- const { container } = renderComponent({
162
- mark: { ...xy(1, 1), label: 'This is a very long label text for testing' },
163
- labelNode,
164
- });
165
- expect(container.firstChild).toBeInTheDocument();
166
- });
167
-
168
- it('handles label with special characters', () => {
169
- const labelNode = document.createElement('foreignObject');
170
- const { container } = renderComponent({
171
- mark: { ...xy(1, 1), label: 'Test@#$%' },
172
- labelNode,
173
- });
174
- expect(container.firstChild).toBeInTheDocument();
175
- });
176
- });
177
-
178
- describe('coordinates on hover', () => {
179
- it('renders with coordinatesOnHover enabled', () => {
180
- const { container } = renderComponent({
181
- mark: { ...xy(1, 1) },
182
- coordinatesOnHover: true,
183
- });
184
- expect(container.firstChild).toBeInTheDocument();
185
- });
186
-
187
- it('renders with coordinatesOnHover disabled', () => {
188
- const { container } = renderComponent({
189
- mark: { ...xy(1, 1) },
190
- coordinatesOnHover: false,
191
- });
192
- expect(container.firstChild).toBeInTheDocument();
193
- });
194
- });
195
-
196
- describe('interaction modes', () => {
197
- it('renders in label mode', () => {
198
- const labelNode = document.createElement('foreignObject');
199
- const { container } = renderComponent({
200
- mark: { ...xy(1, 1), label: 'A' },
201
- labelModeEnabled: true,
202
- labelNode,
203
- });
204
- expect(container.firstChild).toBeInTheDocument();
205
- });
206
-
207
- it('renders in normal mode', () => {
208
- const { container } = renderComponent({
209
- mark: { ...xy(1, 1) },
210
- labelModeEnabled: false,
211
- });
212
- expect(container.firstChild).toBeInTheDocument();
213
- });
214
-
215
- it('handles disabled point in label mode', () => {
216
- const labelNode = document.createElement('foreignObject');
217
- const { container } = renderComponent({
218
- mark: { ...xy(1, 1), label: 'A', disabled: true },
219
- labelModeEnabled: true,
220
- labelNode,
221
- });
222
- expect(container.firstChild).toBeInTheDocument();
223
- });
224
- });
225
-
226
- describe('edge cases', () => {
227
- it('handles mark updates', () => {
228
- const { rerender, container } = renderComponent({ mark: xy(1, 1) });
229
- expect(container.firstChild).toBeInTheDocument();
230
-
231
- rerender(<Component mark={xy(2, 2)} onChange={onChange} onClick={onClick} graphProps={graphProps()} />);
232
- expect(container.firstChild).toBeInTheDocument();
233
- });
234
-
235
- it('handles mark with all properties', () => {
236
- const labelNode = document.createElement('foreignObject');
237
- const { container } = renderComponent({
238
- mark: {
239
- ...xy(3, 4),
240
- label: 'Complete',
241
- disabled: false,
242
- correctness: 'correct',
243
- fill: '#00ff00',
244
- },
245
- labelNode,
246
- labelModeEnabled: true,
247
- coordinatesOnHover: true,
248
- });
249
- expect(container.firstChild).toBeInTheDocument();
250
- });
251
-
252
- it('handles mark without any optional properties', () => {
253
- const { container } = renderComponent({ mark: xy(1, 1) });
254
- expect(container.firstChild).toBeInTheDocument();
255
- });
256
-
257
- it('handles rapid mark position changes', () => {
258
- const { rerender, container } = renderComponent({ mark: xy(0, 0) });
259
-
260
- for (let i = 1; i <= 5; i++) {
261
- rerender(<Component mark={xy(i, i)} onChange={onChange} onClick={onClick} graphProps={graphProps()} />);
262
- }
263
-
264
- expect(container.firstChild).toBeInTheDocument();
265
- });
266
- });
267
-
268
- describe('component lifecycle', () => {
269
- it('initializes with empty state', () => {
270
- const { container } = renderComponent({ mark: xy(1, 1) });
271
- expect(container.firstChild).toBeInTheDocument();
272
- });
273
-
274
- it('updates when mark prop changes', () => {
275
- const { container, rerender } = renderComponent({ mark: xy(1, 1) });
276
- expect(container.firstChild).toBeInTheDocument();
277
-
278
- rerender(<Component mark={xy(5, 5)} onChange={onChange} onClick={onClick} graphProps={graphProps()} />);
279
- expect(container.firstChild).toBeInTheDocument();
280
- });
281
-
282
- it('handles label addition', () => {
283
- const labelNode = document.createElement('foreignObject');
284
- const { container, rerender } = renderComponent({
285
- mark: xy(1, 1),
286
- labelNode,
287
- });
288
-
289
- rerender(
290
- <Component
291
- mark={{ ...xy(1, 1), label: 'New' }}
292
- onChange={onChange}
293
- onClick={onClick}
294
- graphProps={graphProps()}
295
- labelNode={labelNode}
296
- />,
297
- );
298
- expect(container.firstChild).toBeInTheDocument();
299
- });
300
-
301
- it('handles label removal', () => {
302
- const labelNode = document.createElement('foreignObject');
303
- const { container, rerender } = renderComponent({
304
- mark: { ...xy(1, 1), label: 'Remove' },
305
- labelNode,
306
- });
307
-
308
- rerender(
309
- <Component
310
- mark={xy(1, 1)}
311
- onChange={onChange}
312
- onClick={onClick}
313
- graphProps={graphProps()}
314
- labelNode={labelNode}
315
- />,
316
- );
317
- expect(container.firstChild).toBeInTheDocument();
318
- });
319
- });
320
-
321
- describe('graphProps variations', () => {
322
- it('handles different domain and range', () => {
323
- const customGraphProps = graphProps(0, 10, 0, 10);
324
- const { container } = renderComponent({
325
- mark: xy(5, 5),
326
- graphProps: customGraphProps,
327
- });
328
- expect(container.firstChild).toBeInTheDocument();
329
- });
330
-
331
- it('handles negative domain and range', () => {
332
- const customGraphProps = graphProps(-10, 0, -10, 0);
333
- const { container } = renderComponent({
334
- mark: xy(-5, -5),
335
- graphProps: customGraphProps,
336
- });
337
- expect(container.firstChild).toBeInTheDocument();
338
- });
339
-
340
- it('handles large domain and range', () => {
341
- const customGraphProps = graphProps(-100, 100, -100, 100);
342
- const { container } = renderComponent({
343
- mark: xy(50, 50),
344
- graphProps: customGraphProps,
345
- });
346
- expect(container.firstChild).toBeInTheDocument();
347
- });
348
- });
349
-
350
- describe('label focus', () => {
351
- let labelNode;
352
-
353
- // the label is rendered into a portal - it has to be in the document for it to take the focus
354
- beforeEach(() => {
355
- labelNode = document.createElement('div');
356
- document.body.appendChild(labelNode);
357
- });
358
-
359
- afterEach(() => {
360
- document.body.removeChild(labelNode);
361
- });
362
-
363
- // the mark is owned by the container, so the tool only sees a new label once it is handed back
364
- const Controlled = ({ initialMark, labelModeEnabled }) => {
365
- const [mark, setMark] = React.useState(initialMark);
366
-
367
- return (
368
- <Component
369
- mark={mark}
370
- onChange={(current, update) => setMark(update)}
371
- onClick={onClick}
372
- graphProps={graphProps()}
373
- labelNode={labelNode}
374
- labelModeEnabled={labelModeEnabled}
375
- />
376
- );
377
- };
378
-
379
- const clickPoint = (container) => fireEvent.click(container.querySelector('circle'));
380
-
381
- it('focuses the new label when a point is clicked in label mode', () => {
382
- const { container } = render(<Controlled initialMark={xy(1, 1)} labelModeEnabled={true} />);
383
-
384
- expect(labelNode.querySelector('input')).toBe(null);
385
-
386
- clickPoint(container);
387
-
388
- const input = labelNode.querySelector('input');
389
- expect(input).not.toBe(null);
390
- expect(document.activeElement).toBe(input);
391
- });
392
-
393
- it('does not focus the label when a point is clicked outside of label mode', () => {
394
- const { container } = render(<Controlled initialMark={{ ...xy(1, 1), label: 'A' }} labelModeEnabled={false} />);
395
-
396
- clickPoint(container);
397
-
398
- expect(onClick).toHaveBeenCalled();
399
- expect(document.activeElement).not.toBe(labelNode.querySelector('input'));
400
- });
401
-
402
- it('does not focus the label of a disabled mark', () => {
403
- const { container } = render(
404
- <Controlled initialMark={{ ...xy(1, 1), label: 'A', disabled: true }} labelModeEnabled={true} />,
405
- );
406
-
407
- clickPoint(container);
408
-
409
- expect(document.activeElement).not.toBe(labelNode.querySelector('input'));
410
- });
411
-
412
- // the blur that the DNA env causes by moving the focus is not the user leaving the label
413
- it('keeps the focus on a new empty label that something else blurred', () => {
414
- const { container } = render(<Controlled initialMark={xy(1, 1)} labelModeEnabled={true} />);
415
-
416
- clickPoint(container);
417
- const input = labelNode.querySelector('input');
418
- expect(document.activeElement).toBe(input);
419
-
420
- fireEvent.blur(input);
421
- fireEvent.change(input, { target: { value: 'A' } });
422
-
423
- expect(document.activeElement).toBe(input);
424
- });
425
-
426
- it('stops auto focusing the label once it has text and is blurred', () => {
427
- const { container } = render(<Controlled initialMark={xy(1, 1)} labelModeEnabled={true} />);
428
-
429
- clickPoint(container);
430
- const input = labelNode.querySelector('input');
431
-
432
- fireEvent.change(input, { target: { value: 'A' } });
433
- fireEvent.blur(input);
434
- input.blur();
435
-
436
- // a re-render must not pull the focus back into the label the user just left
437
- fireEvent.change(input, { target: { value: 'AB' } });
438
- expect(document.activeElement).not.toBe(input);
439
- });
440
-
441
- it('closes the label that is being edited when label mode is left', () => {
442
- const ref = React.createRef();
443
- const { container, rerender } = render(
444
- <Component
445
- ref={ref}
446
- mark={xy(1, 1)}
447
- onChange={onChange}
448
- onClick={onClick}
449
- graphProps={graphProps()}
450
- labelNode={labelNode}
451
- labelModeEnabled={true}
452
- />,
453
- );
454
-
455
- clickPoint(container);
456
- expect(ref.current.state.editingLabel).toBe('label');
457
-
458
- rerender(
459
- <Component
460
- ref={ref}
461
- mark={xy(1, 1)}
462
- onChange={onChange}
463
- onClick={onClick}
464
- graphProps={graphProps()}
465
- labelNode={labelNode}
466
- labelModeEnabled={false}
467
- />,
468
- );
469
-
470
- // there is no user visible path for this one: a real blur resets it through onBlur, this is
471
- // the safety net for a label that is still marked as edited when label mode goes away
472
- expect(ref.current.state.editingLabel).toBe(null);
473
- });
474
- });
475
- });
@@ -1,241 +0,0 @@
1
- import { tool } from '../index';
2
- import Point from '../component';
3
-
4
- describe('point tool', () => {
5
- let pointTool;
6
-
7
- beforeEach(() => {
8
- pointTool = tool();
9
- });
10
-
11
- describe('tool structure', () => {
12
- it('returns an object with required properties', () => {
13
- expect(pointTool).toHaveProperty('label', 'Point');
14
- expect(pointTool).toHaveProperty('type', 'point');
15
- expect(pointTool).toHaveProperty('Component', Point);
16
- expect(pointTool).toHaveProperty('addPoint');
17
- });
18
-
19
- it('has correct label', () => {
20
- expect(pointTool.label).toBe('Point');
21
- });
22
-
23
- it('has correct type', () => {
24
- expect(pointTool.type).toBe('point');
25
- });
26
-
27
- it('uses Point component', () => {
28
- expect(pointTool.Component).toBe(Point);
29
- });
30
-
31
- it('does not have complete method', () => {
32
- expect(pointTool.complete).toBeUndefined();
33
- });
34
- });
35
-
36
- describe('addPoint', () => {
37
- it('creates a point mark from coordinates', () => {
38
- const point = { x: 1, y: 2 };
39
- const result = pointTool.addPoint(point);
40
-
41
- expect(result).toEqual({
42
- type: 'point',
43
- x: 1,
44
- y: 2,
45
- });
46
- });
47
-
48
- it('creates point with integer coordinates', () => {
49
- const point = { x: 5, y: 10 };
50
- const result = pointTool.addPoint(point);
51
-
52
- expect(result).toEqual({
53
- type: 'point',
54
- x: 5,
55
- y: 10,
56
- });
57
- });
58
-
59
- it('creates point with decimal coordinates', () => {
60
- const point = { x: 1.5, y: 2.75 };
61
- const result = pointTool.addPoint(point);
62
-
63
- expect(result).toEqual({
64
- type: 'point',
65
- x: 1.5,
66
- y: 2.75,
67
- });
68
- });
69
-
70
- it('creates point with negative coordinates', () => {
71
- const point = { x: -3, y: -7 };
72
- const result = pointTool.addPoint(point);
73
-
74
- expect(result).toEqual({
75
- type: 'point',
76
- x: -3,
77
- y: -7,
78
- });
79
- });
80
-
81
- it('creates point with zero coordinates', () => {
82
- const point = { x: 0, y: 0 };
83
- const result = pointTool.addPoint(point);
84
-
85
- expect(result).toEqual({
86
- type: 'point',
87
- x: 0,
88
- y: 0,
89
- });
90
- });
91
-
92
- it('creates point at origin', () => {
93
- const result = pointTool.addPoint({ x: 0, y: 0 });
94
-
95
- expect(result.x).toBe(0);
96
- expect(result.y).toBe(0);
97
- });
98
-
99
- it('spreads all point properties', () => {
100
- const point = { x: 1, y: 2, label: 'A', color: 'red' };
101
- const result = pointTool.addPoint(point);
102
-
103
- expect(result).toEqual({
104
- type: 'point',
105
- x: 1,
106
- y: 2,
107
- label: 'A',
108
- color: 'red',
109
- });
110
- });
111
-
112
- it('preserves additional properties from point object', () => {
113
- const point = {
114
- x: 3,
115
- y: 4,
116
- id: 'point-1',
117
- fill: 'blue',
118
- stroke: 'black',
119
- };
120
- const result = pointTool.addPoint(point);
121
-
122
- expect(result.type).toBe('point');
123
- expect(result.x).toBe(3);
124
- expect(result.y).toBe(4);
125
- expect(result.id).toBe('point-1');
126
- expect(result.fill).toBe('blue');
127
- expect(result.stroke).toBe('black');
128
- });
129
-
130
- it('ignores existing mark parameter', () => {
131
- const point = { x: 1, y: 2 };
132
- const existingMark = { type: 'point', x: 5, y: 5 };
133
- const result = pointTool.addPoint(point, existingMark);
134
-
135
- // Point tool always creates new mark, ignoring existing
136
- expect(result).toEqual({
137
- type: 'point',
138
- x: 1,
139
- y: 2,
140
- });
141
- });
142
-
143
- it('creates new point even with same coordinates', () => {
144
- const point = { x: 1, y: 2 };
145
- const result1 = pointTool.addPoint(point);
146
- const result2 = pointTool.addPoint(point);
147
-
148
- expect(result1).toEqual(result2);
149
- expect(result1).not.toBe(result2); // Different object references
150
- });
151
-
152
- it('handles very large coordinates', () => {
153
- const point = { x: 1000000, y: -1000000 };
154
- const result = pointTool.addPoint(point);
155
-
156
- expect(result.x).toBe(1000000);
157
- expect(result.y).toBe(-1000000);
158
- });
159
-
160
- it('handles very small coordinates', () => {
161
- const point = { x: 0.0001, y: 0.0002 };
162
- const result = pointTool.addPoint(point);
163
-
164
- expect(result.x).toBe(0.0001);
165
- expect(result.y).toBe(0.0002);
166
- });
167
- });
168
-
169
- describe('edge cases', () => {
170
- it('handles point with only x coordinate', () => {
171
- const point = { x: 5 };
172
- const result = pointTool.addPoint(point);
173
-
174
- expect(result.type).toBe('point');
175
- expect(result.x).toBe(5);
176
- expect(result.y).toBeUndefined();
177
- });
178
-
179
- it('handles point with only y coordinate', () => {
180
- const point = { y: 5 };
181
- const result = pointTool.addPoint(point);
182
-
183
- expect(result.type).toBe('point');
184
- expect(result.x).toBeUndefined();
185
- expect(result.y).toBe(5);
186
- });
187
-
188
- it('handles empty point object', () => {
189
- const point = {};
190
- const result = pointTool.addPoint(point);
191
-
192
- expect(result).toEqual({
193
- type: 'point',
194
- });
195
- });
196
-
197
- it('handles NaN coordinates', () => {
198
- const point = { x: NaN, y: NaN };
199
- const result = pointTool.addPoint(point);
200
-
201
- expect(result.type).toBe('point');
202
- expect(Number.isNaN(result.x)).toBe(true);
203
- expect(Number.isNaN(result.y)).toBe(true);
204
- });
205
-
206
- it('handles Infinity coordinates', () => {
207
- const point = { x: Infinity, y: -Infinity };
208
- const result = pointTool.addPoint(point);
209
-
210
- expect(result.x).toBe(Infinity);
211
- expect(result.y).toBe(-Infinity);
212
- });
213
- });
214
-
215
- describe('multiple points', () => {
216
- it('creates multiple distinct points', () => {
217
- const points = [
218
- { x: 1, y: 1 },
219
- { x: 2, y: 2 },
220
- { x: 3, y: 3 },
221
- ];
222
-
223
- const results = points.map((p) => pointTool.addPoint(p));
224
-
225
- expect(results).toHaveLength(3);
226
- results.forEach((result, i) => {
227
- expect(result.type).toBe('point');
228
- expect(result.x).toBe(points[i].x);
229
- expect(result.y).toBe(points[i].y);
230
- });
231
- });
232
-
233
- it('each point is independent', () => {
234
- const point1 = pointTool.addPoint({ x: 1, y: 1 });
235
- const point2 = pointTool.addPoint({ x: 2, y: 2 });
236
-
237
- point1.label = 'A';
238
- expect(point2.label).toBeUndefined();
239
- });
240
- });
241
- });