@design-core/core 0.0.4 → 0.0.6
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/core/entities/arc.js +39 -22
- package/core/entities/arctext.js +6 -4
- package/core/entities/basePolyline.js +29 -20
- package/core/entities/circle.js +33 -15
- package/core/entities/entity.js +1 -1
- package/core/entities/hatch.js +2 -0
- package/core/entities/insert.js +9 -3
- package/core/entities/line.js +16 -11
- package/core/entities/lwpolyline.js +2 -0
- package/core/entities/point.js +3 -1
- package/core/entities/polyline.js +2 -0
- package/core/entities/rectangle.js +2 -0
- package/core/entities/solid.js +2 -0
- package/core/entities/text.js +5 -8
- package/core/lib/input.js +8 -4
- package/core/lib/inputManager.js +3 -5
- package/core/lib/selectionManager.js +4 -0
- package/core/lib/settings.js +9 -5
- package/core/lib/snapping.js +112 -17
- package/core/tables/block.js +2 -0
- package/core/tools/chamfer.js +2 -0
- package/core/tools/chamferFilletBase.js +2 -0
- package/core/tools/copy.js +2 -0
- package/core/tools/copybase.js +2 -0
- package/core/tools/copyclip.js +2 -0
- package/core/tools/cutclip.js +2 -0
- package/core/tools/distance.js +2 -0
- package/core/tools/erase.js +2 -0
- package/core/tools/explode.js +2 -0
- package/core/tools/extend.js +2 -0
- package/core/tools/fillet.js +5 -3
- package/core/tools/identify.js +2 -0
- package/core/tools/matchProp.js +2 -0
- package/core/tools/mirror.js +2 -0
- package/core/tools/move.js +2 -0
- package/core/tools/offset.js +2 -0
- package/core/tools/pan.js +2 -0
- package/core/tools/pasteclip.js +2 -0
- package/core/tools/purge.js +2 -0
- package/core/tools/rotate.js +2 -0
- package/core/tools/scale.js +2 -0
- package/core/tools/tool.js +1 -1
- package/core/tools/trim.js +2 -0
- package/core/tools/zoom.js +2 -0
- package/package.json +1 -1
- package/test/entities/arc.test.js +60 -11
- package/test/entities/arctext.test.js +18 -2
- package/test/entities/basePolyline.test.js +61 -0
- package/test/entities/circle.test.js +48 -22
- package/test/entities/insert.test.js +14 -4
- package/test/entities/line.test.js +53 -0
- package/test/entities/text.test.js +10 -1
- package/test/lib/input.test.js +117 -0
- package/test/lib/settings.test.js +5 -7
- package/test/lib/snapping.test.js +218 -12
- package/test/tools/fillet.test.js +115 -0
|
@@ -626,3 +626,64 @@ test('BasePolyline.fromPolylinePoints round-trip strips closure point for closed
|
|
|
626
626
|
expect(rebuilt.points[3].x).toBe(0);
|
|
627
627
|
expect(rebuilt.points[3].y).toBe(10);
|
|
628
628
|
});
|
|
629
|
+
|
|
630
|
+
// ─── closestPoint: closed polyline closing segment ───────────────────────────
|
|
631
|
+
|
|
632
|
+
test('BasePolyline.closestPoint finds closest point on closing segment of closed polyline', () => {
|
|
633
|
+
// Square: P0=(0,0), P1=(10,0), P2=(10,10), P3=(0,10), closed flag set.
|
|
634
|
+
// Closing segment is P3=(0,10) → P0=(0,0).
|
|
635
|
+
// A test point on the left edge (0, 5) must resolve to (0, 5) on the closing segment.
|
|
636
|
+
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
637
|
+
const poly = new BasePolyline({ points });
|
|
638
|
+
poly.flags.setFlagValue(1);
|
|
639
|
+
|
|
640
|
+
const [closest, dist] = poly.closestPoint(new Point(0, 5));
|
|
641
|
+
expect(closest.x).toBeCloseTo(0);
|
|
642
|
+
expect(closest.y).toBeCloseTo(5);
|
|
643
|
+
expect(dist).toBeCloseTo(0);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
test('BasePolyline.closestPoint closing segment is preferred over other segments', () => {
|
|
647
|
+
// The test point (-1, 5) is clearly nearest the closing segment (left edge),
|
|
648
|
+
// not the bottom, right, or top edges.
|
|
649
|
+
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
650
|
+
const poly = new BasePolyline({ points });
|
|
651
|
+
poly.flags.setFlagValue(1);
|
|
652
|
+
|
|
653
|
+
const [closest, dist] = poly.closestPoint(new Point(-1, 5));
|
|
654
|
+
expect(closest.x).toBeCloseTo(0);
|
|
655
|
+
expect(closest.y).toBeCloseTo(5);
|
|
656
|
+
expect(dist).toBeCloseTo(1);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test('BasePolyline.closestPoint for open polyline does not include closing segment', () => {
|
|
660
|
+
// For point (-1, 1) on the open square:
|
|
661
|
+
// – Nearest on seg P0→P1 (y=0): foot (-1,0) clips to (0,0), dist = √2 ≈ 1.41
|
|
662
|
+
// – Nearest on seg P2→P3 (y=10): foot (-1,10) clips to (0,10), dist ≈ 9.06
|
|
663
|
+
// – If the closing segment existed (x=0, y∈[0,10]): foot (0,1), dist = 1 ← closer!
|
|
664
|
+
// An open polyline must NOT check the closing segment, so it returns (0,0).
|
|
665
|
+
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
666
|
+
const poly = new BasePolyline({ points });
|
|
667
|
+
// flag NOT set — open polyline
|
|
668
|
+
|
|
669
|
+
const [closest] = poly.closestPoint(new Point(-1, 1));
|
|
670
|
+
expect(closest.x).toBeCloseTo(0);
|
|
671
|
+
expect(closest.y).toBeCloseTo(0);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
test('BasePolyline.closestPoint is consistent with getClosestSegmentIndex for closed polyline', () => {
|
|
675
|
+
// For a closed square, both methods should agree that the closing segment
|
|
676
|
+
// is nearest for a point on the left edge.
|
|
677
|
+
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
678
|
+
const poly = new BasePolyline({ points });
|
|
679
|
+
poly.flags.setFlagValue(1);
|
|
680
|
+
|
|
681
|
+
const testPoint = new Point(-2, 5);
|
|
682
|
+
const [closest] = poly.closestPoint(testPoint);
|
|
683
|
+
const segIdx = poly.getClosestSegmentIndex(testPoint);
|
|
684
|
+
|
|
685
|
+
// Closing segment index == points.length == 4
|
|
686
|
+
expect(segIdx).toBe(points.length);
|
|
687
|
+
expect(closest.x).toBeCloseTo(0);
|
|
688
|
+
expect(closest.y).toBeCloseTo(5);
|
|
689
|
+
});
|
|
@@ -292,45 +292,35 @@ test('Circle.draw does not throw', () => {
|
|
|
292
292
|
|
|
293
293
|
test('Circle.snaps returns centre snap', () => {
|
|
294
294
|
const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
295
|
-
DesignCore.Settings.centresnap = true;
|
|
296
|
-
DesignCore.Settings.quadrantsnap = false;
|
|
297
|
-
DesignCore.Settings.nearestsnap = false;
|
|
298
295
|
const snaps = circle.snaps(new Point(5, 0), 100);
|
|
299
|
-
|
|
300
|
-
expect(
|
|
301
|
-
expect(
|
|
296
|
+
const centreSnaps = snaps.filter((s) => s.type === 'centre');
|
|
297
|
+
expect(centreSnaps).toHaveLength(1);
|
|
298
|
+
expect(centreSnaps[0].snapPoint.x).toBe(0);
|
|
299
|
+
expect(centreSnaps[0].snapPoint.y).toBe(0);
|
|
302
300
|
});
|
|
303
301
|
|
|
304
302
|
test('Circle.snaps returns four quadrant snaps', () => {
|
|
305
303
|
const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
306
|
-
DesignCore.Settings.centresnap = false;
|
|
307
|
-
DesignCore.Settings.quadrantsnap = true;
|
|
308
|
-
DesignCore.Settings.nearestsnap = false;
|
|
309
304
|
const snaps = circle.snaps(new Point(5, 0), 100);
|
|
310
|
-
|
|
311
|
-
expect(
|
|
312
|
-
expect(
|
|
313
|
-
expect(
|
|
314
|
-
expect(
|
|
305
|
+
const quadrantSnaps = snaps.filter((s) => s.type === 'quadrant');
|
|
306
|
+
expect(quadrantSnaps).toHaveLength(4);
|
|
307
|
+
expect(quadrantSnaps.some((p) => p.snapPoint.x === 10 && p.snapPoint.y === 0)).toBe(true);// 0°
|
|
308
|
+
expect(quadrantSnaps.some((p) => p.snapPoint.x === 0 && p.snapPoint.y === 10)).toBe(true);// 90°
|
|
309
|
+
expect(quadrantSnaps.some((p) => p.snapPoint.x === -10 && p.snapPoint.y === 0)).toBe(true);// 180°
|
|
310
|
+
expect(quadrantSnaps.some((p) => p.snapPoint.x === 0 && p.snapPoint.y === -10)).toBe(true);// 270°
|
|
315
311
|
});
|
|
316
312
|
|
|
317
313
|
test('Circle.snaps nearest snap fires when close enough', () => {
|
|
318
314
|
const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
319
|
-
DesignCore.Settings.centresnap = false;
|
|
320
|
-
DesignCore.Settings.quadrantsnap = false;
|
|
321
|
-
DesignCore.Settings.nearestsnap = true;
|
|
322
315
|
// Mouse on the circle surface with large delta
|
|
323
316
|
const snaps = circle.snaps(new Point(10, 0), 1000);
|
|
324
|
-
expect(snaps.length).toBeGreaterThanOrEqual(1);
|
|
317
|
+
expect(snaps.filter((s) => s.type === 'nearest').length).toBeGreaterThanOrEqual(1);
|
|
325
318
|
});
|
|
326
319
|
|
|
327
320
|
test('Circle.snaps nearest snap does not fire when too far', () => {
|
|
328
321
|
const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
329
|
-
DesignCore.Settings.centresnap = false;
|
|
330
|
-
DesignCore.Settings.quadrantsnap = false;
|
|
331
|
-
DesignCore.Settings.nearestsnap = true;
|
|
332
322
|
const snaps = circle.snaps(new Point(100, 100), 1);
|
|
333
|
-
expect(snaps.length).toBe(0);
|
|
323
|
+
expect(snaps.filter((s) => s.type === 'nearest').length).toBe(0);
|
|
334
324
|
});
|
|
335
325
|
|
|
336
326
|
test('Circle.fromPolylinePoints round-trip preserves centre and radius', () => {
|
|
@@ -352,3 +342,39 @@ test('Circle.fromPolylinePoints precision drift is negligible', () => {
|
|
|
352
342
|
expect(drift).toBeLessThan(1e-10);
|
|
353
343
|
expect(Math.abs(rebuilt.radius - circle.radius)).toBeLessThan(1e-10);
|
|
354
344
|
});
|
|
345
|
+
|
|
346
|
+
test('Circle.snaps returns two tangent snaps from outside', () => {
|
|
347
|
+
const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
348
|
+
const savedInputPoint = DesignCore.Scene.inputManager.inputPoint;
|
|
349
|
+
// fromPoint=(20,0): distanceToCenter=20 > radius=10
|
|
350
|
+
// tangentHalfAngle = acos(10/20) = π/3; both tangent points at (5, ±8.66) are on the full circle
|
|
351
|
+
DesignCore.Scene.inputManager.inputPoint = new Point(20, 0);
|
|
352
|
+
const snaps = circle.snaps(new Point(5, 0), 100);
|
|
353
|
+
const tangentSnaps = snaps.filter((s) => s.type === 'tangent');
|
|
354
|
+
expect(tangentSnaps.length).toBe(2);
|
|
355
|
+
expect(tangentSnaps.some((s) => s.snapPoint.x > 4.9 && s.snapPoint.y > 8.5)).toBe(true);
|
|
356
|
+
expect(tangentSnaps.some((s) => s.snapPoint.x > 4.9 && s.snapPoint.y < -8.5)).toBe(true);
|
|
357
|
+
DesignCore.Scene.inputManager.inputPoint = savedInputPoint;
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('Circle.snaps returns no tangent when fromPoint is inside the circle', () => {
|
|
361
|
+
const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
362
|
+
const savedInputPoint = DesignCore.Scene.inputManager.inputPoint;
|
|
363
|
+
DesignCore.Scene.inputManager.inputPoint = new Point(1, 1); // distanceToCenter ≈ 1.41 < radius 10
|
|
364
|
+
const snaps = circle.snaps(new Point(5, 0), 100);
|
|
365
|
+
expect(snaps.filter((s) => s.type === 'tangent').length).toBe(0);
|
|
366
|
+
DesignCore.Scene.inputManager.inputPoint = savedInputPoint;
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('Circle.snaps returns perpendicular snap from outside', () => {
|
|
370
|
+
const circle = new Circle({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
371
|
+
const savedInputPoint = DesignCore.Scene.inputManager.inputPoint;
|
|
372
|
+
// fromPoint=(20,0): angleFromCentreToInput=0; perpendicularPoint=(10,0)
|
|
373
|
+
DesignCore.Scene.inputManager.inputPoint = new Point(20, 0);
|
|
374
|
+
const snaps = circle.snaps(new Point(5, 0), 100);
|
|
375
|
+
const perpSnaps = snaps.filter((s) => s.type === 'perpendicular');
|
|
376
|
+
expect(perpSnaps.length).toBe(1);
|
|
377
|
+
expect(perpSnaps[0].snapPoint.x).toBeCloseTo(10);
|
|
378
|
+
expect(perpSnaps[0].snapPoint.y).toBeCloseTo(0);
|
|
379
|
+
DesignCore.Scene.inputManager.inputPoint = savedInputPoint;
|
|
380
|
+
});
|
|
@@ -19,13 +19,15 @@ rotatedInsert.block.addItem(line);
|
|
|
19
19
|
test('Test Insert.snaps', () => {
|
|
20
20
|
const point = new Point(100, 100);
|
|
21
21
|
const snaps = insert.snaps(point, 1);
|
|
22
|
-
|
|
23
|
-
expect(
|
|
22
|
+
const endSnaps = snaps.filter((s) => s.type === 'end');
|
|
23
|
+
expect(endSnaps[0].snapPoint.x).toBeCloseTo(101);
|
|
24
|
+
expect(endSnaps[0].snapPoint.y).toBeCloseTo(102);
|
|
24
25
|
|
|
25
26
|
// Test snaps for a rotated block
|
|
26
27
|
const rotatedSnaps = rotatedInsert.snaps(point);
|
|
27
|
-
|
|
28
|
-
expect(
|
|
28
|
+
const rotatedEndSnaps = rotatedSnaps.filter((s) => s.type === 'end');
|
|
29
|
+
expect(rotatedEndSnaps[0].snapPoint.x).toBeCloseTo(-0.7071);
|
|
30
|
+
expect(rotatedEndSnaps[0].snapPoint.y).toBeCloseTo(143.5426);
|
|
29
31
|
});
|
|
30
32
|
|
|
31
33
|
test('Test Insert.closestPoint', () => {
|
|
@@ -88,3 +90,11 @@ AcDbBlockReference
|
|
|
88
90
|
newInsert.dxf(file);
|
|
89
91
|
expect(file.contents).toEqual(dxfString);
|
|
90
92
|
});
|
|
93
|
+
|
|
94
|
+
test('Insert.snaps returns a node snap at points[0]', () => {
|
|
95
|
+
const nodeInsert = new Insert({ points: [new Point(5, 10)] });
|
|
96
|
+
const nodeSnaps = nodeInsert.snaps(new Point(0, 0), 100).filter((s) => s.type === 'node');
|
|
97
|
+
expect(nodeSnaps.length).toBe(1);
|
|
98
|
+
expect(nodeSnaps[0].snapPoint.x).toBe(5);
|
|
99
|
+
expect(nodeSnaps[0].snapPoint.y).toBe(10);
|
|
100
|
+
});
|
|
@@ -201,3 +201,56 @@ test('Line.fromPolylinePoints round-trip preserves points exactly', () => {
|
|
|
201
201
|
expect(rebuilt.points[1].x).toBe(15);
|
|
202
202
|
expect(rebuilt.points[1].y).toBe(-4);
|
|
203
203
|
});
|
|
204
|
+
|
|
205
|
+
test('Line.snaps returns two end snaps at both endpoints', () => {
|
|
206
|
+
const line = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
207
|
+
const endSnaps = line.snaps(new Point(5, 0), 100).filter((s) => s.type === 'end');
|
|
208
|
+
expect(endSnaps.length).toBe(2);
|
|
209
|
+
expect(endSnaps.some((s) => s.snapPoint.x === 0 && s.snapPoint.y === 0)).toBe(true);
|
|
210
|
+
expect(endSnaps.some((s) => s.snapPoint.x === 10 && s.snapPoint.y === 0)).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('Line.snaps returns mid snap at midpoint', () => {
|
|
214
|
+
const line = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
215
|
+
const midSnaps = line.snaps(new Point(5, 0), 100).filter((s) => s.type === 'mid');
|
|
216
|
+
expect(midSnaps.length).toBe(1);
|
|
217
|
+
expect(midSnaps[0].snapPoint.x).toBe(5);
|
|
218
|
+
expect(midSnaps[0].snapPoint.y).toBe(0);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('Line.snaps nearest fires when mouse is close to the line', () => {
|
|
222
|
+
const line = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
223
|
+
// mouse at (5, 0.5): distance to line = 0.5; delta=10 so delta/10=1; 0.5 < 1 → fires
|
|
224
|
+
const snaps = line.snaps(new Point(5, 0.5), 10);
|
|
225
|
+
expect(snaps.filter((s) => s.type === 'nearest').length).toBe(1);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('Line.snaps nearest does not fire when mouse is too far from the line', () => {
|
|
229
|
+
const line = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
230
|
+
// mouse at (5, 20): distance to line = 20; delta=1 so delta/10=0.1; 20 > 0.1 → no fire
|
|
231
|
+
const snaps = line.snaps(new Point(5, 20), 1);
|
|
232
|
+
expect(snaps.filter((s) => s.type === 'nearest').length).toBe(0);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('Line.snaps perpendicular fires when foot falls on the segment', () => {
|
|
236
|
+
const line = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
237
|
+
const savedInputPoint = DesignCore.Scene.inputManager.inputPoint;
|
|
238
|
+
// fromPoint=(5,5): foot of perpendicular = (5,0), which IS on the segment
|
|
239
|
+
DesignCore.Scene.inputManager.inputPoint = new Point(5, 5);
|
|
240
|
+
const snaps = line.snaps(new Point(5, 0), 100);
|
|
241
|
+
const perpSnaps = snaps.filter((s) => s.type === 'perpendicular');
|
|
242
|
+
expect(perpSnaps.length).toBe(1);
|
|
243
|
+
expect(perpSnaps[0].snapPoint.x).toBeCloseTo(5);
|
|
244
|
+
expect(perpSnaps[0].snapPoint.y).toBeCloseTo(0);
|
|
245
|
+
DesignCore.Scene.inputManager.inputPoint = savedInputPoint;
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('Line.snaps perpendicular does not fire when foot falls off the segment', () => {
|
|
249
|
+
const line = new Line({ points: [new Point(0, 0), new Point(10, 0)] });
|
|
250
|
+
const savedInputPoint = DesignCore.Scene.inputManager.inputPoint;
|
|
251
|
+
// fromPoint=(20,5): foot = (20,0), which is BEYOND the segment end at (10,0)
|
|
252
|
+
DesignCore.Scene.inputManager.inputPoint = new Point(20, 5);
|
|
253
|
+
const snaps = line.snaps(new Point(5, 0), 100);
|
|
254
|
+
expect(snaps.filter((s) => s.type === 'perpendicular').length).toBe(0);
|
|
255
|
+
DesignCore.Scene.inputManager.inputPoint = savedInputPoint;
|
|
256
|
+
});
|
|
@@ -535,7 +535,8 @@ test('Text snaps returns all snap points', () => {
|
|
|
535
535
|
const t = new Text({ points: [new Point(1, 2)] });
|
|
536
536
|
t.boundingRect = { width: 10, height: 10 };
|
|
537
537
|
const snaps = t.snaps(new Point(0, 0), 1);
|
|
538
|
-
expect(snaps.length).toBe(
|
|
538
|
+
expect(snaps.length).toBe(1);
|
|
539
|
+
expect(snaps[0].type).toBe('node');
|
|
539
540
|
});
|
|
540
541
|
|
|
541
542
|
test('Text closestPoint returns correct distance', () => {
|
|
@@ -630,3 +631,11 @@ test('Text.execute does not create entity for empty string', async () => {
|
|
|
630
631
|
|
|
631
632
|
expect(executeCommandCalled).toBe(false);
|
|
632
633
|
});
|
|
634
|
+
|
|
635
|
+
test('Text.snaps node snap position matches points[0]', () => {
|
|
636
|
+
const t = new Text({ points: [new Point(5, 10)] });
|
|
637
|
+
const nodeSnaps = t.snaps(new Point(0, 0), 100).filter((s) => s.type === 'node');
|
|
638
|
+
expect(nodeSnaps.length).toBe(1);
|
|
639
|
+
expect(nodeSnaps[0].snapPoint.x).toBe(5);
|
|
640
|
+
expect(nodeSnaps[0].snapPoint.y).toBe(10);
|
|
641
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Input, MouseDown, MouseUp } from '../../core/lib/inputManager.js';
|
|
2
|
+
import { Point } from '../../core/entities/point.js';
|
|
3
|
+
import { SingleSelection, SelectionSet } from '../../core/lib/selectionManager.js';
|
|
4
|
+
import { expect } from '@jest/globals';
|
|
5
|
+
|
|
6
|
+
// Stub classes that simulate what terser does in a production build:
|
|
7
|
+
// constructor.name is mangled to a single letter but static type is preserved.
|
|
8
|
+
|
|
9
|
+
/** Stub simulating a minified class resolved as Point. */
|
|
10
|
+
class MangledPoint {
|
|
11
|
+
static type = 'Point';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Stub simulating a minified class resolved as SingleSelection. */
|
|
15
|
+
class MangledSingleSelection {
|
|
16
|
+
static type = 'SingleSelection';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Stub simulating a minified class resolved as SelectionSet. */
|
|
20
|
+
class MangledSelectionSet {
|
|
21
|
+
static type = 'SelectionSet';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Stub simulating a minified class resolved as MouseDown. */
|
|
25
|
+
class MangledMouseDown {
|
|
26
|
+
static type = 'MouseDown';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Stub simulating a minified class resolved as MouseUp. */
|
|
30
|
+
class MangledMouseUp {
|
|
31
|
+
static type = 'MouseUp';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Stub with no static type and an unrecognised constructor.name. */
|
|
35
|
+
class UnknownStub {}
|
|
36
|
+
|
|
37
|
+
// ─── Input.Type ───────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
test('Input.Type contains all expected type entries', () => {
|
|
40
|
+
const keys = Object.keys(Input.Type);
|
|
41
|
+
expect(keys).toContain('POINT');
|
|
42
|
+
expect(keys).toContain('SELECTIONSET');
|
|
43
|
+
expect(keys).toContain('SINGLESELECTION');
|
|
44
|
+
expect(keys).toContain('NUMBER');
|
|
45
|
+
expect(keys).toContain('STRING');
|
|
46
|
+
expect(keys).toContain('DYNAMIC');
|
|
47
|
+
expect(keys).toContain('MOUSEDOWN');
|
|
48
|
+
expect(keys).toContain('MOUSEUP');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// ─── Input.getType — undefined ────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
test('Input.getType returns undefined for undefined input', () => {
|
|
54
|
+
expect(Input.getType(undefined)).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ─── Input.getType — constructor.name path (dev / unminified) ─────────────────
|
|
58
|
+
|
|
59
|
+
test('Input.getType resolves Point via constructor.name', () => {
|
|
60
|
+
expect(Input.getType(new Point(1, 2))).toBe(Input.Type.POINT);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('Input.getType resolves SingleSelection via constructor.name', () => {
|
|
64
|
+
expect(Input.getType(new SingleSelection(0, new Point(0, 0)))).toBe(Input.Type.SINGLESELECTION);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('Input.getType resolves SelectionSet via constructor.name', () => {
|
|
68
|
+
expect(Input.getType(new SelectionSet())).toBe(Input.Type.SELECTIONSET);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('Input.getType resolves MouseDown via constructor.name', () => {
|
|
72
|
+
expect(Input.getType(new MouseDown())).toBe(Input.Type.MOUSEDOWN);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('Input.getType resolves MouseUp via constructor.name', () => {
|
|
76
|
+
expect(Input.getType(new MouseUp())).toBe(Input.Type.MOUSEUP);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('Input.getType resolves Number via constructor.name', () => {
|
|
80
|
+
expect(Input.getType(42)).toBe(Input.Type.NUMBER);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('Input.getType resolves String via constructor.name', () => {
|
|
84
|
+
expect(Input.getType('hello')).toBe(Input.Type.STRING);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ─── Input.getType — static type path (minified / production) ─────────────────
|
|
88
|
+
|
|
89
|
+
test('Input.getType resolves via static type when constructor.name is mangled (Point)', () => {
|
|
90
|
+
expect(Input.getType(new MangledPoint())).toBe(Input.Type.POINT);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('Input.getType resolves via static type when constructor.name is mangled (SingleSelection)', () => {
|
|
94
|
+
expect(Input.getType(new MangledSingleSelection())).toBe(Input.Type.SINGLESELECTION);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('Input.getType resolves via static type when constructor.name is mangled (SelectionSet)', () => {
|
|
98
|
+
expect(Input.getType(new MangledSelectionSet())).toBe(Input.Type.SELECTIONSET);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('Input.getType resolves via static type when constructor.name is mangled (MouseDown)', () => {
|
|
102
|
+
expect(Input.getType(new MangledMouseDown())).toBe(Input.Type.MOUSEDOWN);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('Input.getType resolves via static type when constructor.name is mangled (MouseUp)', () => {
|
|
106
|
+
expect(Input.getType(new MangledMouseUp())).toBe(Input.Type.MOUSEUP);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// ─── Input.getType — no match ─────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
test('Input.getType returns undefined when neither static type nor constructor.name matches', () => {
|
|
112
|
+
expect(Input.getType(new UnknownStub())).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('Input.getType returns undefined for a plain object', () => {
|
|
116
|
+
expect(Input.getType({})).toBeUndefined();
|
|
117
|
+
});
|
|
@@ -41,10 +41,8 @@ test('Settings constructor - colour defaults', () => {
|
|
|
41
41
|
const fresh = new (settings.constructor)();
|
|
42
42
|
expect(fresh.canvasbackgroundcolour).toEqual({ r: 30, g: 30, b: 30 });
|
|
43
43
|
expect(fresh.selecteditemscolour).toEqual({ r: 0, g: 255, b: 0 });
|
|
44
|
-
expect(fresh.
|
|
44
|
+
expect(fresh.snaptrackingcolour).toEqual({ r: 55, g: 180, b: 75 });
|
|
45
45
|
expect(fresh.gridcolour).toEqual({ r: 119, g: 118, b: 123 });
|
|
46
|
-
expect(fresh.helpergeometrycolour).toEqual({ r: 0, g: 195, b: 255 });
|
|
47
|
-
expect(fresh.polarsnapcolour).toEqual({ r: 55, g: 180, b: 75 });
|
|
48
46
|
expect(fresh.selectionWindow).toEqual({ r: 0, g: 255, b: 0 });
|
|
49
47
|
expect(fresh.crossingWindow).toEqual({ r: 0, g: 0, b: 255 });
|
|
50
48
|
});
|
|
@@ -84,9 +82,9 @@ test('setSetting with false value does not trigger polar/ortho exclusion', () =>
|
|
|
84
82
|
});
|
|
85
83
|
|
|
86
84
|
test('setSetting updates arbitrary colour settings', () => {
|
|
87
|
-
settings.setSetting('
|
|
88
|
-
expect(settings.
|
|
89
|
-
settings.setSetting('
|
|
85
|
+
settings.setSetting('snaptrackingcolour', { r: 1, g: 2, b: 3 });
|
|
86
|
+
expect(settings.snaptrackingcolour).toEqual({ r: 1, g: 2, b: 3 });
|
|
87
|
+
settings.setSetting('snaptrackingcolour', { r: 55, g: 180, b: 75 });
|
|
90
88
|
});
|
|
91
89
|
|
|
92
90
|
test('setSetting updates polarangle', () => {
|
|
@@ -118,7 +116,7 @@ test('getSetting returns current value after setSetting', () => {
|
|
|
118
116
|
});
|
|
119
117
|
|
|
120
118
|
test('getSetting returns correct type for colour objects', () => {
|
|
121
|
-
const colour = settings.getSetting('
|
|
119
|
+
const colour = settings.getSetting('snaptrackingcolour');
|
|
122
120
|
expect(colour).toHaveProperty('r');
|
|
123
121
|
expect(colour).toHaveProperty('g');
|
|
124
122
|
expect(colour).toHaveProperty('b');
|