@pie-lib/graphing 3.1.0-next.5 → 3.1.1-next.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 (114) hide show
  1. package/NEXT.CHANGELOG.json +16 -1
  2. package/lib/axis/axes.js.map +1 -1
  3. package/lib/axis/index.js.map +1 -1
  4. package/lib/bg.js +9 -6
  5. package/lib/bg.js.map +1 -1
  6. package/lib/container/index.js +4 -4
  7. package/lib/container/index.js.map +1 -1
  8. package/lib/coordinates-label.js.map +1 -1
  9. package/lib/graph-with-controls.js +3 -4
  10. package/lib/graph-with-controls.js.map +1 -1
  11. package/lib/graph.js +8 -9
  12. package/lib/graph.js.map +1 -1
  13. package/lib/grid-setup.js.map +1 -1
  14. package/lib/key-legend.js +1 -1
  15. package/lib/key-legend.js.map +1 -1
  16. package/lib/mark-label.js.map +1 -1
  17. package/lib/toggle-bar.js.map +1 -1
  18. package/lib/tools/absolute/component.js.map +1 -1
  19. package/lib/tools/circle/bg-circle.js.map +1 -1
  20. package/lib/tools/circle/component.js +2 -2
  21. package/lib/tools/circle/component.js.map +1 -1
  22. package/lib/tools/exponential/component.js.map +1 -1
  23. package/lib/tools/line/component.js.map +1 -1
  24. package/lib/tools/parabola/component.js.map +1 -1
  25. package/lib/tools/point/component.js +3 -4
  26. package/lib/tools/point/component.js.map +1 -1
  27. package/lib/tools/polygon/component.js +5 -7
  28. package/lib/tools/polygon/component.js.map +1 -1
  29. package/lib/tools/polygon/line.js.map +1 -1
  30. package/lib/tools/polygon/polygon.js.map +1 -1
  31. package/lib/tools/ray/component.js.map +1 -1
  32. package/lib/tools/segment/component.js.map +1 -1
  33. package/lib/tools/shared/arrow-head.js.map +1 -1
  34. package/lib/tools/shared/line/index.js +7 -9
  35. package/lib/tools/shared/line/index.js.map +1 -1
  36. package/lib/tools/shared/line/line-path.js.map +1 -1
  37. package/lib/tools/shared/point/arrow-point.js.map +1 -1
  38. package/lib/tools/sine/component.js.map +1 -1
  39. package/lib/tools/vector/component.js.map +1 -1
  40. package/lib/undo-redo.js.map +1 -1
  41. package/lib/use-debounce.js.map +1 -1
  42. package/lib/utils.js +9 -13
  43. package/lib/utils.js.map +1 -1
  44. package/package.json +14 -14
  45. package/src/__tests__/bg.test.jsx +250 -0
  46. package/src/__tests__/coordinates-label.test.jsx +243 -0
  47. package/src/__tests__/graph-with-controls.test.jsx +46 -12
  48. package/src/__tests__/graph.test.jsx +560 -5
  49. package/src/__tests__/grid-setup.test.jsx +645 -0
  50. package/src/__tests__/grid.test.jsx +1 -1
  51. package/src/__tests__/key-legend.test.jsx +260 -0
  52. package/src/__tests__/label-svg-icon.test.jsx +278 -0
  53. package/src/__tests__/mark-label.test.jsx +1 -1
  54. package/src/__tests__/toggle-bar.test.jsx +38 -3
  55. package/src/__tests__/tool-menu.test.jsx +38 -1
  56. package/src/__tests__/use-debounce.test.js +1 -1
  57. package/src/__tests__/utils.test.js +15 -61
  58. package/src/axis/__tests__/axes.test.jsx +1 -1
  59. package/src/axis/axes.jsx +7 -21
  60. package/src/axis/index.js +1 -0
  61. package/src/bg.jsx +5 -5
  62. package/src/container/__tests__/actions.test.js +105 -0
  63. package/src/container/__tests__/index.test.jsx +319 -0
  64. package/src/container/__tests__/marks.test.js +172 -0
  65. package/src/container/__tests__/middleware.test.js +235 -0
  66. package/src/container/__tests__/reducer.test.js +324 -0
  67. package/src/container/index.jsx +2 -3
  68. package/src/coordinates-label.jsx +1 -7
  69. package/src/graph-with-controls.jsx +8 -6
  70. package/src/graph.jsx +2 -3
  71. package/src/grid-setup.jsx +1 -1
  72. package/src/key-legend.jsx +2 -1
  73. package/src/mark-label.jsx +7 -24
  74. package/src/toggle-bar.jsx +8 -1
  75. package/src/tools/absolute/__tests__/component.test.jsx +1 -2
  76. package/src/tools/absolute/component.jsx +2 -2
  77. package/src/tools/circle/__tests__/component.test.jsx +438 -0
  78. package/src/tools/circle/__tests__/index.test.js +480 -0
  79. package/src/tools/circle/bg-circle.jsx +2 -2
  80. package/src/tools/circle/component.jsx +10 -12
  81. package/src/tools/exponential/__tests__/component.test.jsx +0 -1
  82. package/src/tools/exponential/__tests__/index.test.js +729 -0
  83. package/src/tools/exponential/component.jsx +1 -1
  84. package/src/tools/line/__tests__/component.test.jsx +1 -0
  85. package/src/tools/line/component.jsx +4 -11
  86. package/src/tools/parabola/__tests__/component.test.jsx +0 -1
  87. package/src/tools/parabola/__tests__/index.test.js +470 -0
  88. package/src/tools/parabola/component.jsx +1 -1
  89. package/src/tools/point/__tests__/component.test.jsx +310 -2
  90. package/src/tools/point/__tests__/index.test.js +241 -0
  91. package/src/tools/point/component.jsx +1 -2
  92. package/src/tools/polygon/__tests__/component.test.jsx +391 -2
  93. package/src/tools/polygon/__tests__/index.test.js +237 -8
  94. package/src/tools/polygon/__tests__/line.test.jsx +13 -0
  95. package/src/tools/polygon/__tests__/polygon.test.jsx +19 -1
  96. package/src/tools/polygon/component.jsx +4 -14
  97. package/src/tools/polygon/line.jsx +1 -1
  98. package/src/tools/polygon/polygon.jsx +1 -1
  99. package/src/tools/ray/__tests__/component.test.jsx +1 -0
  100. package/src/tools/ray/component.jsx +3 -5
  101. package/src/tools/segment/__tests__/component.test.jsx +1 -0
  102. package/src/tools/segment/component.jsx +1 -1
  103. package/src/tools/shared/arrow-head.jsx +11 -6
  104. package/src/tools/shared/line/__tests__/index.test.jsx +1 -1
  105. package/src/tools/shared/line/__tests__/line-path.test.jsx +3 -3
  106. package/src/tools/shared/line/__tests__/with-root-edge.test.jsx +2 -2
  107. package/src/tools/shared/line/index.jsx +4 -6
  108. package/src/tools/shared/line/line-path.jsx +2 -8
  109. package/src/tools/shared/point/arrow-point.jsx +2 -5
  110. package/src/tools/sine/component.jsx +2 -2
  111. package/src/tools/vector/component.jsx +1 -1
  112. package/src/undo-redo.jsx +3 -9
  113. package/src/use-debounce.js +1 -1
  114. package/src/utils.js +1 -5
@@ -0,0 +1,729 @@
1
+ import { tool } from '../index';
2
+ import { equalPoints, sameAxes } from '../../../utils';
3
+
4
+ jest.mock('../../../utils', () => ({
5
+ equalPoints: jest.fn(),
6
+ sameAxes: jest.fn(),
7
+ }));
8
+
9
+ jest.mock('debug', () => {
10
+ return jest.fn(() => jest.fn());
11
+ });
12
+
13
+ describe('exponential tool', () => {
14
+ let exponentialTool;
15
+
16
+ beforeEach(() => {
17
+ jest.clearAllMocks();
18
+ exponentialTool = tool();
19
+ });
20
+
21
+ describe('tool configuration', () => {
22
+ it('creates a tool object', () => {
23
+ expect(exponentialTool).toBeDefined();
24
+ expect(typeof exponentialTool).toBe('object');
25
+ });
26
+
27
+ it('has type exponential', () => {
28
+ expect(exponentialTool.type).toBe('exponential');
29
+ });
30
+
31
+ it('has a Component', () => {
32
+ expect(exponentialTool.Component).toBeDefined();
33
+ });
34
+
35
+ it('has complete function', () => {
36
+ expect(typeof exponentialTool.complete).toBe('function');
37
+ });
38
+
39
+ it('has addPoint function', () => {
40
+ expect(typeof exponentialTool.addPoint).toBe('function');
41
+ });
42
+
43
+ it('does not have hover function', () => {
44
+ expect(exponentialTool.hover).toBeUndefined();
45
+ });
46
+ });
47
+
48
+ describe('complete', () => {
49
+ it('marks exponential as complete', () => {
50
+ const data = { x: 5, y: 10 };
51
+ const mark = {
52
+ type: 'exponential',
53
+ root: { x: 0, y: 1 },
54
+ edge: { x: 5, y: 10 },
55
+ building: true,
56
+ closed: false,
57
+ };
58
+
59
+ const result = exponentialTool.complete(data, mark);
60
+
61
+ expect(result).toEqual({
62
+ ...mark,
63
+ building: false,
64
+ closed: true,
65
+ });
66
+ });
67
+
68
+ it('sets building to false', () => {
69
+ const mark = {
70
+ type: 'exponential',
71
+ root: { x: 0, y: 1 },
72
+ building: true,
73
+ };
74
+
75
+ const result = exponentialTool.complete({}, mark);
76
+
77
+ expect(result.building).toBe(false);
78
+ });
79
+
80
+ it('sets closed to true', () => {
81
+ const mark = {
82
+ type: 'exponential',
83
+ root: { x: 0, y: 1 },
84
+ closed: false,
85
+ };
86
+
87
+ const result = exponentialTool.complete({}, mark);
88
+
89
+ expect(result.closed).toBe(true);
90
+ });
91
+
92
+ it('preserves mark properties', () => {
93
+ const mark = {
94
+ type: 'exponential',
95
+ root: { x: 0, y: 1 },
96
+ edge: { x: 5, y: 10 },
97
+ label: 'Exp A',
98
+ correctness: { value: 'correct' },
99
+ };
100
+
101
+ const result = exponentialTool.complete({}, mark);
102
+
103
+ expect(result.label).toBe('Exp A');
104
+ expect(result.correctness).toEqual({ value: 'correct' });
105
+ expect(result.root).toEqual(mark.root);
106
+ expect(result.edge).toEqual(mark.edge);
107
+ });
108
+
109
+ it('does not mutate original mark', () => {
110
+ const mark = {
111
+ type: 'exponential',
112
+ root: { x: 0, y: 1 },
113
+ building: true,
114
+ closed: false,
115
+ };
116
+ const originalMark = { ...mark };
117
+
118
+ exponentialTool.complete({}, mark);
119
+
120
+ expect(mark).toEqual(originalMark);
121
+ });
122
+ });
123
+
124
+ describe('addPoint', () => {
125
+ describe('first point (creating root)', () => {
126
+ it('creates new exponential mark when no mark exists', () => {
127
+ const point = { x: 0, y: 5 };
128
+ const mark = null;
129
+ equalPoints.mockReturnValue(false);
130
+ sameAxes.mockReturnValue(false);
131
+
132
+ const result = exponentialTool.addPoint(point, mark);
133
+
134
+ expect(result).toEqual({
135
+ type: 'exponential',
136
+ root: point,
137
+ edge: undefined,
138
+ closed: false,
139
+ building: true,
140
+ });
141
+ });
142
+
143
+ it('creates mark with building true', () => {
144
+ const point = { x: 2, y: 3 };
145
+ equalPoints.mockReturnValue(false);
146
+ sameAxes.mockReturnValue(false);
147
+
148
+ const result = exponentialTool.addPoint(point, null);
149
+
150
+ expect(result.building).toBe(true);
151
+ });
152
+
153
+ it('creates mark with closed false', () => {
154
+ const point = { x: 2, y: 3 };
155
+ equalPoints.mockReturnValue(false);
156
+ sameAxes.mockReturnValue(false);
157
+
158
+ const result = exponentialTool.addPoint(point, null);
159
+
160
+ expect(result.closed).toBe(false);
161
+ });
162
+
163
+ it('creates mark with undefined edge', () => {
164
+ const point = { x: 2, y: 3 };
165
+ equalPoints.mockReturnValue(false);
166
+ sameAxes.mockReturnValue(false);
167
+
168
+ const result = exponentialTool.addPoint(point, null);
169
+
170
+ expect(result.edge).toBeUndefined();
171
+ });
172
+
173
+ it('works with positive coordinates', () => {
174
+ const point = { x: 5, y: 10 };
175
+ equalPoints.mockReturnValue(false);
176
+ sameAxes.mockReturnValue(false);
177
+
178
+ const result = exponentialTool.addPoint(point, null);
179
+
180
+ expect(result.root).toEqual(point);
181
+ });
182
+
183
+ it('works with negative y coordinate', () => {
184
+ const point = { x: 5, y: -10 };
185
+ equalPoints.mockReturnValue(false);
186
+ sameAxes.mockReturnValue(false);
187
+
188
+ const result = exponentialTool.addPoint(point, null);
189
+
190
+ expect(result.root).toEqual(point);
191
+ });
192
+
193
+ it('works with decimal coordinates', () => {
194
+ const point = { x: 3.5, y: 7.25 };
195
+ equalPoints.mockReturnValue(false);
196
+ sameAxes.mockReturnValue(false);
197
+
198
+ const result = exponentialTool.addPoint(point, null);
199
+
200
+ expect(result.root).toEqual(point);
201
+ });
202
+ });
203
+
204
+ describe('second point (completing exponential)', () => {
205
+ it('completes exponential when adding edge point', () => {
206
+ const root = { x: 0, y: 1 };
207
+ const edge = { x: 5, y: 10 };
208
+ const mark = {
209
+ type: 'exponential',
210
+ root,
211
+ edge: undefined,
212
+ closed: false,
213
+ building: true,
214
+ };
215
+ equalPoints.mockReturnValue(false);
216
+ sameAxes.mockReturnValue(false);
217
+
218
+ const result = exponentialTool.addPoint(edge, mark);
219
+
220
+ expect(result).toEqual({
221
+ ...mark,
222
+ edge,
223
+ closed: true,
224
+ building: false,
225
+ });
226
+ });
227
+
228
+ it('sets building to false', () => {
229
+ const mark = {
230
+ type: 'exponential',
231
+ root: { x: 0, y: 1 },
232
+ building: true,
233
+ };
234
+ const point = { x: 5, y: 10 };
235
+ equalPoints.mockReturnValue(false);
236
+ sameAxes.mockReturnValue(false);
237
+
238
+ const result = exponentialTool.addPoint(point, mark);
239
+
240
+ expect(result.building).toBe(false);
241
+ });
242
+
243
+ it('sets closed to true', () => {
244
+ const mark = {
245
+ type: 'exponential',
246
+ root: { x: 0, y: 1 },
247
+ closed: false,
248
+ };
249
+ const point = { x: 5, y: 10 };
250
+ equalPoints.mockReturnValue(false);
251
+ sameAxes.mockReturnValue(false);
252
+
253
+ const result = exponentialTool.addPoint(point, mark);
254
+
255
+ expect(result.closed).toBe(true);
256
+ });
257
+
258
+ it('preserves root when adding edge', () => {
259
+ const root = { x: 1, y: 2 };
260
+ const mark = {
261
+ type: 'exponential',
262
+ root,
263
+ building: true,
264
+ };
265
+ const edge = { x: 5, y: 8 };
266
+ equalPoints.mockReturnValue(false);
267
+ sameAxes.mockReturnValue(false);
268
+
269
+ const result = exponentialTool.addPoint(edge, mark);
270
+
271
+ expect(result.root).toEqual(root);
272
+ expect(result.edge).toEqual(edge);
273
+ });
274
+
275
+ it('preserves mark properties', () => {
276
+ const mark = {
277
+ type: 'exponential',
278
+ root: { x: 0, y: 1 },
279
+ label: 'Exp 1',
280
+ correctness: { value: 'correct' },
281
+ };
282
+ const point = { x: 5, y: 10 };
283
+ equalPoints.mockReturnValue(false);
284
+ sameAxes.mockReturnValue(false);
285
+
286
+ const result = exponentialTool.addPoint(point, mark);
287
+
288
+ expect(result.label).toBe('Exp 1');
289
+ expect(result.correctness).toEqual({ value: 'correct' });
290
+ });
291
+ });
292
+
293
+ describe('same point handling', () => {
294
+ it('returns same mark when clicking equal point', () => {
295
+ const point = { x: 5, y: 10 };
296
+ const mark = {
297
+ type: 'exponential',
298
+ root: point,
299
+ building: true,
300
+ };
301
+ equalPoints.mockReturnValue(true);
302
+ sameAxes.mockReturnValue(false);
303
+
304
+ const result = exponentialTool.addPoint(point, mark);
305
+
306
+ expect(result).toBe(mark);
307
+ });
308
+
309
+ it('returns same mark when clicking point on same axis', () => {
310
+ const mark = {
311
+ type: 'exponential',
312
+ root: { x: 5, y: 10 },
313
+ building: true,
314
+ };
315
+ const point = { x: 8, y: 10 }; // same y
316
+ equalPoints.mockReturnValue(false);
317
+ sameAxes.mockReturnValue(true);
318
+
319
+ const result = exponentialTool.addPoint(point, mark);
320
+
321
+ expect(result).toBe(mark);
322
+ });
323
+
324
+ it('calls equalPoints with correct arguments', () => {
325
+ const point = { x: 5, y: 10 };
326
+ const mark = {
327
+ type: 'exponential',
328
+ root: { x: 5, y: 10 },
329
+ building: true,
330
+ };
331
+ equalPoints.mockReturnValue(true);
332
+
333
+ exponentialTool.addPoint(point, mark);
334
+
335
+ expect(equalPoints).toHaveBeenCalledWith(mark.root, point);
336
+ });
337
+
338
+ it('calls sameAxes with correct arguments', () => {
339
+ const point = { x: 8, y: 10 };
340
+ const mark = {
341
+ type: 'exponential',
342
+ root: { x: 5, y: 10 },
343
+ building: true,
344
+ };
345
+ equalPoints.mockReturnValue(false);
346
+ sameAxes.mockReturnValue(true);
347
+
348
+ exponentialTool.addPoint(point, mark);
349
+
350
+ expect(sameAxes).toHaveBeenCalledWith(mark.root, point);
351
+ });
352
+ });
353
+
354
+ describe('opposite sign y-coordinates', () => {
355
+ it('returns same mark when root and point have opposite sign y', () => {
356
+ const mark = {
357
+ type: 'exponential',
358
+ root: { x: 0, y: 5 }, // positive y
359
+ building: true,
360
+ };
361
+ const point = { x: 5, y: -10 }; // negative y
362
+ equalPoints.mockReturnValue(false);
363
+ sameAxes.mockReturnValue(false);
364
+
365
+ const result = exponentialTool.addPoint(point, mark);
366
+
367
+ expect(result).toBe(mark);
368
+ });
369
+
370
+ it('returns same mark when root is negative and point is positive', () => {
371
+ const mark = {
372
+ type: 'exponential',
373
+ root: { x: 0, y: -5 }, // negative y
374
+ building: true,
375
+ };
376
+ const point = { x: 5, y: 10 }; // positive y
377
+ equalPoints.mockReturnValue(false);
378
+ sameAxes.mockReturnValue(false);
379
+
380
+ const result = exponentialTool.addPoint(point, mark);
381
+
382
+ expect(result).toBe(mark);
383
+ });
384
+
385
+ it('allows points with same sign y (both positive)', () => {
386
+ const mark = {
387
+ type: 'exponential',
388
+ root: { x: 0, y: 5 },
389
+ building: true,
390
+ };
391
+ const point = { x: 5, y: 10 };
392
+ equalPoints.mockReturnValue(false);
393
+ sameAxes.mockReturnValue(false);
394
+
395
+ const result = exponentialTool.addPoint(point, mark);
396
+
397
+ expect(result.edge).toEqual(point);
398
+ expect(result.building).toBe(false);
399
+ });
400
+
401
+ it('allows points with same sign y (both negative)', () => {
402
+ const mark = {
403
+ type: 'exponential',
404
+ root: { x: 0, y: -5 },
405
+ building: true,
406
+ };
407
+ const point = { x: 5, y: -10 };
408
+ equalPoints.mockReturnValue(false);
409
+ sameAxes.mockReturnValue(false);
410
+
411
+ const result = exponentialTool.addPoint(point, mark);
412
+
413
+ expect(result.edge).toEqual(point);
414
+ expect(result.building).toBe(false);
415
+ });
416
+ });
417
+
418
+ describe('zero y-coordinate handling', () => {
419
+ it('returns same mark when point has y = 0', () => {
420
+ const mark = {
421
+ type: 'exponential',
422
+ root: { x: 0, y: 5 },
423
+ building: true,
424
+ };
425
+ const point = { x: 5, y: 0 };
426
+ equalPoints.mockReturnValue(false);
427
+ sameAxes.mockReturnValue(false);
428
+
429
+ const result = exponentialTool.addPoint(point, mark);
430
+
431
+ expect(result).toBe(mark);
432
+ });
433
+
434
+ it('prevents adding point with zero y', () => {
435
+ const point = { x: 5, y: 0 };
436
+ const mark = {
437
+ type: 'exponential',
438
+ root: { x: 1, y: 2 },
439
+ building: true,
440
+ };
441
+ equalPoints.mockReturnValue(false);
442
+ sameAxes.mockReturnValue(false);
443
+
444
+ const result = exponentialTool.addPoint(point, mark);
445
+
446
+ expect(result).toBe(mark);
447
+ expect(result.edge).toBeUndefined();
448
+ });
449
+
450
+ it('prevents creating root with zero y', () => {
451
+ const point = { x: 5, y: 0 };
452
+ equalPoints.mockReturnValue(false);
453
+ sameAxes.mockReturnValue(false);
454
+
455
+ const result = exponentialTool.addPoint(point, null);
456
+
457
+ expect(result).toBeNull();
458
+ });
459
+
460
+ it('allows positive y values close to zero', () => {
461
+ const point = { x: 5, y: 0.001 };
462
+ equalPoints.mockReturnValue(false);
463
+ sameAxes.mockReturnValue(false);
464
+
465
+ const result = exponentialTool.addPoint(point, null);
466
+
467
+ expect(result.root).toEqual(point);
468
+ });
469
+
470
+ it('allows negative y values close to zero', () => {
471
+ const point = { x: 5, y: -0.001 };
472
+ equalPoints.mockReturnValue(false);
473
+ sameAxes.mockReturnValue(false);
474
+
475
+ const result = exponentialTool.addPoint(point, null);
476
+
477
+ expect(result.root).toEqual(point);
478
+ });
479
+ });
480
+
481
+ describe('error handling', () => {
482
+ it('throws error when mark has no root', () => {
483
+ const mark = {
484
+ type: 'exponential',
485
+ building: true,
486
+ // root is missing
487
+ };
488
+ const point = { x: 5, y: 10 };
489
+ equalPoints.mockReturnValue(false);
490
+ sameAxes.mockReturnValue(false);
491
+
492
+ expect(() => {
493
+ exponentialTool.addPoint(point, mark);
494
+ }).toThrow(); // Will throw TypeError when accessing undefined.y
495
+ });
496
+
497
+ it('throws error when mark root is null', () => {
498
+ const mark = {
499
+ type: 'exponential',
500
+ root: null,
501
+ building: true,
502
+ };
503
+ const point = { x: 5, y: 10 };
504
+ equalPoints.mockReturnValue(false);
505
+ sameAxes.mockReturnValue(false);
506
+
507
+ expect(() => {
508
+ exponentialTool.addPoint(point, mark);
509
+ }).toThrow(); // Will throw TypeError when accessing null.y
510
+ });
511
+
512
+ it('throws error when mark root is undefined', () => {
513
+ const mark = {
514
+ type: 'exponential',
515
+ root: undefined,
516
+ building: true,
517
+ };
518
+ const point = { x: 5, y: 10 };
519
+ equalPoints.mockReturnValue(false);
520
+ sameAxes.mockReturnValue(false);
521
+
522
+ expect(() => {
523
+ exponentialTool.addPoint(point, mark);
524
+ }).toThrow(); // Will throw TypeError when accessing undefined.y
525
+ });
526
+ });
527
+
528
+ describe('edge cases', () => {
529
+ it('handles undefined mark on first click', () => {
530
+ const point = { x: 5, y: 10 };
531
+ equalPoints.mockReturnValue(false);
532
+ sameAxes.mockReturnValue(false);
533
+
534
+ const result = exponentialTool.addPoint(point, undefined);
535
+
536
+ expect(result.type).toBe('exponential');
537
+ expect(result.root).toEqual(point);
538
+ expect(result.building).toBe(true);
539
+ });
540
+
541
+ it('handles very large positive y', () => {
542
+ const point = { x: 5, y: 10000 };
543
+ equalPoints.mockReturnValue(false);
544
+ sameAxes.mockReturnValue(false);
545
+
546
+ const result = exponentialTool.addPoint(point, null);
547
+
548
+ expect(result.root).toEqual(point);
549
+ });
550
+
551
+ it('handles very large negative y', () => {
552
+ const point = { x: 5, y: -10000 };
553
+ equalPoints.mockReturnValue(false);
554
+ sameAxes.mockReturnValue(false);
555
+
556
+ const result = exponentialTool.addPoint(point, null);
557
+
558
+ expect(result.root).toEqual(point);
559
+ });
560
+
561
+ it('handles negative x coordinates', () => {
562
+ const point = { x: -5, y: 10 };
563
+ equalPoints.mockReturnValue(false);
564
+ sameAxes.mockReturnValue(false);
565
+
566
+ const result = exponentialTool.addPoint(point, null);
567
+
568
+ expect(result.root).toEqual(point);
569
+ });
570
+
571
+ it('handles mark with existing edge', () => {
572
+ const mark = {
573
+ type: 'exponential',
574
+ root: { x: 0, y: 1 },
575
+ edge: { x: 3, y: 3 },
576
+ building: false,
577
+ closed: true,
578
+ };
579
+ const newEdge = { x: 5, y: 5 };
580
+ equalPoints.mockReturnValue(false);
581
+ sameAxes.mockReturnValue(false);
582
+
583
+ const result = exponentialTool.addPoint(newEdge, mark);
584
+
585
+ expect(result.edge).toEqual(newEdge);
586
+ expect(result.closed).toBe(true);
587
+ expect(result.building).toBe(false);
588
+ });
589
+ });
590
+
591
+ describe('immutability', () => {
592
+ it('does not mutate original mark when adding edge', () => {
593
+ const mark = {
594
+ type: 'exponential',
595
+ root: { x: 0, y: 1 },
596
+ building: true,
597
+ };
598
+ const originalMark = { ...mark };
599
+ const point = { x: 5, y: 10 };
600
+ equalPoints.mockReturnValue(false);
601
+ sameAxes.mockReturnValue(false);
602
+
603
+ const result = exponentialTool.addPoint(point, mark);
604
+
605
+ expect(mark).toEqual(originalMark);
606
+ expect(result).not.toBe(mark);
607
+ });
608
+
609
+ it('does not mutate original mark when returning same', () => {
610
+ const mark = {
611
+ type: 'exponential',
612
+ root: { x: 5, y: 10 },
613
+ building: true,
614
+ };
615
+ const point = { x: 5, y: 10 };
616
+ equalPoints.mockReturnValue(true);
617
+
618
+ const result = exponentialTool.addPoint(point, mark);
619
+
620
+ expect(result).toBe(mark);
621
+ });
622
+ });
623
+ });
624
+
625
+ describe('integration scenarios', () => {
626
+ it('handles full exponential creation flow', () => {
627
+ equalPoints.mockReturnValue(false);
628
+ sameAxes.mockReturnValue(false);
629
+
630
+ // First click - create root
631
+ const firstPoint = { x: 0, y: 1 };
632
+ const mark1 = exponentialTool.addPoint(firstPoint, null);
633
+ expect(mark1.root).toEqual(firstPoint);
634
+ expect(mark1.building).toBe(true);
635
+ expect(mark1.closed).toBe(false);
636
+
637
+ // Second click - complete exponential
638
+ const secondPoint = { x: 5, y: 10 };
639
+ const mark2 = exponentialTool.addPoint(secondPoint, mark1);
640
+ expect(mark2.root).toEqual(firstPoint);
641
+ expect(mark2.edge).toEqual(secondPoint);
642
+ expect(mark2.building).toBe(false);
643
+ expect(mark2.closed).toBe(true);
644
+
645
+ // Complete
646
+ const mark3 = exponentialTool.complete({}, mark2);
647
+ expect(mark3.building).toBe(false);
648
+ expect(mark3.closed).toBe(true);
649
+ });
650
+
651
+ it('handles rejected second point (zero y)', () => {
652
+ equalPoints.mockReturnValue(false);
653
+ sameAxes.mockReturnValue(false);
654
+
655
+ const firstPoint = { x: 0, y: 1 };
656
+ const mark1 = exponentialTool.addPoint(firstPoint, null);
657
+
658
+ const secondPoint = { x: 5, y: 0 };
659
+ const mark2 = exponentialTool.addPoint(secondPoint, mark1);
660
+
661
+ // Should return unchanged mark
662
+ expect(mark2).toBe(mark1);
663
+ expect(mark2.edge).toBeUndefined();
664
+ });
665
+
666
+ it('handles rejected second point (opposite sign)', () => {
667
+ equalPoints.mockReturnValue(false);
668
+ sameAxes.mockReturnValue(false);
669
+
670
+ const firstPoint = { x: 0, y: 5 };
671
+ const mark1 = exponentialTool.addPoint(firstPoint, null);
672
+
673
+ const secondPoint = { x: 5, y: -10 };
674
+ const mark2 = exponentialTool.addPoint(secondPoint, mark1);
675
+
676
+ // Should return unchanged mark
677
+ expect(mark2).toBe(mark1);
678
+ expect(mark2.edge).toBeUndefined();
679
+ });
680
+
681
+ it('handles rejected second point (same axes)', () => {
682
+ equalPoints.mockReturnValue(false);
683
+ sameAxes.mockReturnValue(true);
684
+
685
+ const firstPoint = { x: 0, y: 5 };
686
+ const mark1 = exponentialTool.addPoint(firstPoint, null);
687
+
688
+ const secondPoint = { x: 10, y: 5 }; // same y
689
+ const mark2 = exponentialTool.addPoint(secondPoint, mark1);
690
+
691
+ // Should return unchanged mark
692
+ expect(mark2).toBe(mark1);
693
+ });
694
+
695
+ it('handles exponential with negative y values', () => {
696
+ equalPoints.mockReturnValue(false);
697
+ sameAxes.mockReturnValue(false);
698
+
699
+ const firstPoint = { x: 0, y: -1 };
700
+ const mark1 = exponentialTool.addPoint(firstPoint, null);
701
+
702
+ const secondPoint = { x: 5, y: -10 };
703
+ const mark2 = exponentialTool.addPoint(secondPoint, mark1);
704
+
705
+ expect(mark2.root).toEqual(firstPoint);
706
+ expect(mark2.edge).toEqual(secondPoint);
707
+ expect(mark2.building).toBe(false);
708
+ expect(mark2.closed).toBe(true);
709
+ });
710
+
711
+ it('handles exponential with properties', () => {
712
+ equalPoints.mockReturnValue(false);
713
+ sameAxes.mockReturnValue(false);
714
+
715
+ const firstPoint = { x: 0, y: 1, label: 'Start' };
716
+ const mark1 = exponentialTool.addPoint(firstPoint, null);
717
+
718
+ // Add properties
719
+ mark1.label = 'Exp A';
720
+ mark1.correctness = { value: 'correct' };
721
+
722
+ const secondPoint = { x: 5, y: 10 };
723
+ const mark2 = exponentialTool.addPoint(secondPoint, mark1);
724
+
725
+ expect(mark2.label).toBe('Exp A');
726
+ expect(mark2.correctness).toEqual({ value: 'correct' });
727
+ });
728
+ });
729
+ });