@design-core/core 0.0.5 → 0.0.7
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/.eslintrc.json +4 -0
- package/TODO.md +49 -6
- package/core/core/core.js +0 -3
- package/core/dimensions/alignedDimension.js +5 -4
- package/core/dimensions/angularDimension.js +6 -5
- package/core/dimensions/baseCircularDimension.js +3 -2
- package/core/dimensions/baseDimension.js +59 -93
- package/core/dimensions/baseLinearDimension.js +6 -5
- package/core/dimensions/diametricDimension.js +4 -3
- package/core/dimensions/dimension.js +21 -14
- package/core/dimensions/radialDimension.js +4 -3
- package/core/dimensions/rotatedDimension.js +12 -11
- package/core/entities/arc.js +112 -145
- package/core/entities/arctext.js +212 -174
- package/core/entities/basePolyline.js +33 -284
- package/core/entities/circle.js +77 -99
- package/core/entities/entity.js +114 -93
- package/core/entities/hatch.js +322 -249
- package/core/entities/insert.js +66 -81
- package/core/entities/line.js +21 -142
- package/core/entities/lwpolyline.js +16 -11
- package/core/entities/polyline.js +4 -4
- package/core/entities/rectangle.js +3 -1
- package/core/entities/solid.js +6 -15
- package/core/entities/text.js +188 -199
- package/core/lib/auxiliary/rubberBand.js +32 -0
- package/core/lib/auxiliary/selectionWindow.js +63 -0
- package/core/lib/auxiliary/snapPoint.js +113 -0
- package/core/lib/auxiliary/trackingLine.js +76 -0
- package/core/lib/canvas.js +228 -166
- package/core/lib/clipboard.js +7 -1
- package/core/lib/colour.js +16 -0
- package/core/lib/commandLine.js +1 -6
- package/core/lib/commandManager.js +18 -8
- package/core/lib/constants.js +28 -0
- package/core/lib/dxf/dxf.js +4 -3
- package/core/lib/entityManager.js +18 -11
- package/core/lib/inputManager.js +36 -13
- package/core/lib/intersect.js +25 -0
- package/core/lib/patterns.js +8 -1
- package/core/lib/plotOptions.js +67 -0
- package/core/lib/polylineUtils.js +75 -0
- package/core/lib/renderers/cairoRenderer.js +371 -0
- package/core/lib/renderers/canvasRenderer.js +315 -0
- package/core/lib/renderers/pdfRenderer.js +366 -0
- package/core/lib/renderers/rendererBase.js +284 -0
- package/core/lib/renderers/svgRenderer.js +343 -0
- package/core/lib/scene.js +27 -5
- package/core/lib/selectionManager.js +2 -2
- package/core/lib/settings.js +4 -2
- package/core/lib/snapping.js +4 -191
- package/core/lib/stateManager.js +30 -1
- package/core/lib/utils.js +21 -2
- package/core/properties/entityProperties.js +104 -0
- package/core/properties/property.js +106 -2
- package/core/properties/propertyManager.js +29 -13
- package/core/tables/block.js +4 -37
- package/core/tables/endblk.js +1 -1
- package/core/tables/layer.js +8 -0
- package/core/tables/styleManagerBase.js +3 -2
- package/core/tables/tableManagerBase.js +8 -3
- package/core/tools/chamfer.js +111 -137
- package/core/tools/chamferFilletBase.js +135 -34
- package/core/tools/copy.js +3 -3
- package/core/tools/explode.js +2 -2
- package/core/tools/extend.js +132 -19
- package/core/tools/fillet.js +86 -183
- package/core/tools/matchProp.js +2 -11
- package/core/tools/mirror.js +7 -6
- package/core/tools/move.js +3 -3
- package/core/tools/offset.js +3 -5
- package/core/tools/pasteclip.js +1 -1
- package/core/tools/rotate.js +6 -3
- package/core/tools/scale.js +1 -1
- package/core/tools/trim.js +265 -23
- package/core/tools/zoom.js +1 -1
- package/jest.config.js +6 -0
- package/package.json +5 -3
- package/test/dimensions/alignedDimension.test.js +14 -7
- package/test/dimensions/angularDimension.test.js +6 -6
- package/test/dimensions/baseDimension.test.js +69 -8
- package/test/dimensions/baseLinearDimension.test.js +62 -0
- package/test/dimensions/diametricDimension.test.js +6 -6
- package/test/dimensions/radialDimension.test.js +6 -6
- package/test/dimensions/rotatedDimension.test.js +14 -7
- package/test/entities/arc.test.js +80 -83
- package/test/entities/arctext.test.js +180 -53
- package/test/entities/basePolyline.test.js +129 -2
- package/test/entities/circle.test.js +57 -85
- package/test/entities/entity.test.js +42 -8
- package/test/entities/hatch.test.js +543 -70
- package/test/entities/insert.test.js +96 -1
- package/test/entities/line.test.js +15 -41
- package/test/entities/lwpolyline.test.js +104 -0
- package/test/entities/polyline.test.js +11 -0
- package/test/entities/rectangle.test.js +11 -0
- package/test/entities/solid.test.js +1 -1
- package/test/entities/text.test.js +294 -69
- package/test/integration/exportIntegration.reference.dxf +9 -9
- package/test/integration/exportIntegration.test.js +2 -2
- package/test/integration/intersectionIntegration.test.js +4 -5
- package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
- package/test/integration/pdfRendererIntegration.test.js +61 -0
- package/test/integration/svgRendererIntegration.reference.svg +3343 -0
- package/test/integration/svgRendererIntegration.test.js +61 -0
- package/test/integration/userInputIntegration.reference.dxf +1165 -105
- package/test/integration/userInputIntegration.test.js +239 -132
- package/test/lib/canvas.test.js +742 -123
- package/test/lib/constants.test.js +27 -0
- package/test/lib/entityManager.test.js +4 -4
- package/test/lib/inputManager.test.js +80 -19
- package/test/lib/intersect.test.js +83 -0
- package/test/lib/plotOptions.test.js +179 -0
- package/test/lib/rubberBand.test.js +84 -0
- package/test/lib/settings.test.js +5 -6
- package/test/lib/snapping.test.js +71 -129
- package/test/lib/stateManager.test.js +7 -7
- package/test/lib/utils.test.js +36 -2
- package/test/properties/entityProperties.test.js +132 -0
- package/test/properties/property.test.js +53 -0
- package/test/properties/propertyManager.test.js +3 -3
- package/test/renderers/canvasRenderer.test.js +317 -0
- package/test/renderers/pdfRenderer.test.js +271 -0
- package/test/renderers/rendererBase.test.js +213 -0
- package/test/renderers/svgRenderer.test.js +326 -0
- package/test/tables/block.test.js +31 -1
- package/test/tables/endblk.test.js +2 -2
- package/test/tables/layer.test.js +30 -0
- package/test/test-helpers/hatchGridRenderer.js +180 -0
- package/test/test-helpers/test-helpers.js +152 -0
- package/test/tools/chamfer.test.js +227 -5
- package/test/tools/chamferFilletBase.test.js +98 -9
- package/test/tools/copy.test.js +2 -2
- package/test/tools/explode.test.js +3 -3
- package/test/tools/extend.test.js +331 -0
- package/test/tools/fillet.test.js +296 -0
- package/test/tools/matchProp.test.js +5 -5
- package/test/tools/mirror.test.js +13 -13
- package/test/tools/offset.test.js +170 -3
- package/test/tools/pasteclip.test.js +5 -5
- package/test/tools/rotate.test.js +32 -2
- package/test/tools/scale.test.js +5 -5
- package/test/tools/trim.test.js +526 -0
- package/test/tools/zoom.test.js +7 -7
- package/core/lib/selectionWindow.js +0 -83
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Core } from '../../core/core/core.js';
|
|
2
2
|
import { Point } from '../../core/entities/point.js';
|
|
3
3
|
import { Fillet } from '../../core/tools/fillet.js';
|
|
4
|
+
import { DesignCore } from '../../core/designCore.js';
|
|
4
5
|
import { Strings } from '../../core/lib/strings.js';
|
|
5
6
|
import { SingleSelection } from '../../core/lib/selectionManager.js';
|
|
6
7
|
import { expect, jest } from '@jest/globals';
|
|
@@ -829,3 +830,298 @@ test('Fillet.action radius>0 trimMode=false Line + Lwpolyline: standalone arc ad
|
|
|
829
830
|
expect(core.scene.entities.get(0).points.length).toBe(2);
|
|
830
831
|
expect(core.scene.entities.get(1).points.length).toBe(4);
|
|
831
832
|
});
|
|
833
|
+
|
|
834
|
+
// ─── action: closed polyline closing-wrap arc direction (regression) ──────────
|
|
835
|
+
//
|
|
836
|
+
// Square: P0=(0,0), P1=(10,0), P2=(10,10), P3=(0,10), closed flag=1.
|
|
837
|
+
// Closing segment (idx=4): P3=(0,10)→P0=(0,0) — i.e. the left edge, travelling downward.
|
|
838
|
+
// Segment 1: P0=(0,0)→P1=(10,0) — i.e. the bottom edge, travelling rightward.
|
|
839
|
+
// The shared corner is P0=(0,0).
|
|
840
|
+
//
|
|
841
|
+
// With radius 2 the fillet must sit inside the square (bottom-left interior corner).
|
|
842
|
+
// The arc centre is at (2,2).
|
|
843
|
+
// – lowerTangent = (0,2) on the closing segment (left edge, y=2)
|
|
844
|
+
// – upperTangent = (2,0) on segment 1 (bottom edge, x=2)
|
|
845
|
+
// – bulge on lowerTangent must be POSITIVE (CCW arc from (0,2) to (2,0) through ≈(0.59,0.59))
|
|
846
|
+
// (BasePolyline convention: –ve bulge = CW, +ve bulge = CCW)
|
|
847
|
+
//
|
|
848
|
+
// Before the fix the lowerTangent/upperTangent assignment was inverted, producing
|
|
849
|
+
// an arc that traversed the exterior of the square.
|
|
850
|
+
|
|
851
|
+
test('Fillet.action closing-wrap: lowerTangent is on closing segment (left edge)', () => {
|
|
852
|
+
core.scene.clear();
|
|
853
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
854
|
+
const polyEntity = core.scene.entities.get(0);
|
|
855
|
+
polyEntity.flags.setFlagValue(1);
|
|
856
|
+
|
|
857
|
+
core.scene.headers.filletRadius = 2;
|
|
858
|
+
core.scene.headers.trimMode = true;
|
|
859
|
+
|
|
860
|
+
// firstPick = closing segment (idx=4, left edge), secondPick = segment 1 (bottom edge)
|
|
861
|
+
const fillet = new Fillet();
|
|
862
|
+
fillet.firstPick.entity = polyEntity;
|
|
863
|
+
fillet.secondPick.entity = polyEntity;
|
|
864
|
+
fillet.firstPick.segment = polyEntity.getClosestSegment(new Point(0, 5));
|
|
865
|
+
fillet.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(0, 5));
|
|
866
|
+
fillet.firstPick.clickPoint = new Point(0, 5);
|
|
867
|
+
fillet.secondPick.segment = polyEntity.getClosestSegment(new Point(5, 0));
|
|
868
|
+
fillet.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(5, 0));
|
|
869
|
+
fillet.secondPick.clickPoint = new Point(5, 0);
|
|
870
|
+
fillet.action();
|
|
871
|
+
|
|
872
|
+
expect(core.scene.entities.count()).toBe(1);
|
|
873
|
+
const poly = core.scene.entities.get(0);
|
|
874
|
+
expect(poly.points.length).toBe(5);
|
|
875
|
+
|
|
876
|
+
// After the splice at cornerIdx=0:
|
|
877
|
+
// points[0] = lowerTangent (arcStartPoint), points[1] = upperTangent
|
|
878
|
+
// lowerTangent must be on the closing segment (left edge, x≈0)
|
|
879
|
+
expect(poly.points[0].x).toBeCloseTo(0);
|
|
880
|
+
expect(poly.points[0].y).toBeCloseTo(2);
|
|
881
|
+
// upperTangent must be on segment 1 (bottom edge, y≈0)
|
|
882
|
+
expect(poly.points[1].x).toBeCloseTo(2);
|
|
883
|
+
expect(poly.points[1].y).toBeCloseTo(0);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
test('Fillet.action closing-wrap: arc bulge sign is positive (CCW, fillet inside square)', () => {
|
|
887
|
+
// Same setup. The CCW arc from (0,2) to (2,0) sweeps through (≈0.59, ≈0.59)
|
|
888
|
+
// staying inside the square — a positive bulge value.
|
|
889
|
+
// Before the fix the lowerTangent/upperTangent were swapped, producing an arc
|
|
890
|
+
// that traversed the exterior of the square.
|
|
891
|
+
core.scene.clear();
|
|
892
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
893
|
+
const polyEntity = core.scene.entities.get(0);
|
|
894
|
+
polyEntity.flags.setFlagValue(1);
|
|
895
|
+
|
|
896
|
+
core.scene.headers.filletRadius = 2;
|
|
897
|
+
core.scene.headers.trimMode = true;
|
|
898
|
+
|
|
899
|
+
const fillet = new Fillet();
|
|
900
|
+
fillet.firstPick.entity = polyEntity;
|
|
901
|
+
fillet.secondPick.entity = polyEntity;
|
|
902
|
+
fillet.firstPick.segment = polyEntity.getClosestSegment(new Point(0, 5));
|
|
903
|
+
fillet.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(0, 5));
|
|
904
|
+
fillet.firstPick.clickPoint = new Point(0, 5);
|
|
905
|
+
fillet.secondPick.segment = polyEntity.getClosestSegment(new Point(5, 0));
|
|
906
|
+
fillet.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(5, 0));
|
|
907
|
+
fillet.secondPick.clickPoint = new Point(5, 0);
|
|
908
|
+
fillet.action();
|
|
909
|
+
|
|
910
|
+
const poly = core.scene.entities.get(0);
|
|
911
|
+
// points[0] carries the bulge for the arc from lowerTangent to upperTangent
|
|
912
|
+
expect(poly.points[0].bulge).toBeGreaterThan(0);
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
test('Fillet.action closing-wrap reversed pick order: arc bulge sign and tangent positions are correct', () => {
|
|
916
|
+
// Swap firstPick/secondPick — should produce the same geometry.
|
|
917
|
+
core.scene.clear();
|
|
918
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)] });
|
|
919
|
+
const polyEntity = core.scene.entities.get(0);
|
|
920
|
+
polyEntity.flags.setFlagValue(1);
|
|
921
|
+
|
|
922
|
+
core.scene.headers.filletRadius = 2;
|
|
923
|
+
core.scene.headers.trimMode = true;
|
|
924
|
+
|
|
925
|
+
// firstPick = segment 1 (bottom edge), secondPick = closing segment (left edge, idx=4)
|
|
926
|
+
const fillet = new Fillet();
|
|
927
|
+
fillet.firstPick.entity = polyEntity;
|
|
928
|
+
fillet.secondPick.entity = polyEntity;
|
|
929
|
+
fillet.firstPick.segment = polyEntity.getClosestSegment(new Point(5, 0));
|
|
930
|
+
fillet.firstPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(5, 0));
|
|
931
|
+
fillet.firstPick.clickPoint = new Point(5, 0);
|
|
932
|
+
fillet.secondPick.segment = polyEntity.getClosestSegment(new Point(0, 5));
|
|
933
|
+
fillet.secondPick.segmentIndex = polyEntity.getClosestSegmentIndex(new Point(0, 5));
|
|
934
|
+
fillet.secondPick.clickPoint = new Point(0, 5);
|
|
935
|
+
fillet.action();
|
|
936
|
+
|
|
937
|
+
expect(core.scene.entities.count()).toBe(1);
|
|
938
|
+
const poly = core.scene.entities.get(0);
|
|
939
|
+
expect(poly.points.length).toBe(5);
|
|
940
|
+
|
|
941
|
+
// Regardless of pick order: lowerTangent at (0,2), upperTangent at (2,0), bulge > 0 (CCW)
|
|
942
|
+
expect(poly.points[0].x).toBeCloseTo(0);
|
|
943
|
+
expect(poly.points[0].y).toBeCloseTo(2);
|
|
944
|
+
expect(poly.points[1].x).toBeCloseTo(2);
|
|
945
|
+
expect(poly.points[1].y).toBeCloseTo(0);
|
|
946
|
+
expect(poly.points[0].bulge).toBeGreaterThan(0);
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
// ─── action: entity property inheritance ─────────────────────────────────────
|
|
950
|
+
|
|
951
|
+
test('Fillet.action arc inherits layer, lineWidth, lineType from first entity', () => {
|
|
952
|
+
// Perpendicular L-corner at origin; first entity on a non-default layer.
|
|
953
|
+
core.scene.clear();
|
|
954
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)], layer: 'walls', lineWidth: 5, lineType: 'DASHED' });
|
|
955
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
956
|
+
|
|
957
|
+
core.scene.headers.filletRadius = 2;
|
|
958
|
+
core.scene.headers.trimMode = false;
|
|
959
|
+
|
|
960
|
+
const fillet = new Fillet();
|
|
961
|
+
fillet.firstPick.entity = core.scene.entities.get(0);
|
|
962
|
+
fillet.secondPick.entity = core.scene.entities.get(1);
|
|
963
|
+
fillet.firstPick.clickPoint = new Point(-5, 0);
|
|
964
|
+
fillet.secondPick.clickPoint = new Point(0, 5);
|
|
965
|
+
fillet.action();
|
|
966
|
+
|
|
967
|
+
expect(core.scene.entities.count()).toBe(3);
|
|
968
|
+
const arc = core.scene.entities.get(2);
|
|
969
|
+
expect(arc.type).toBe('Arc');
|
|
970
|
+
expect(arc.getProperty('layer')).toBe('walls');
|
|
971
|
+
expect(arc.getProperty('lineWidth')).toBe(5);
|
|
972
|
+
expect(arc.getProperty('lineType')).toBe('DASHED');
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
test('Fillet.preview does not throw', () => {
|
|
976
|
+
const fillet = new Fillet();
|
|
977
|
+
expect(() => fillet.preview()).not.toThrow();
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
test('Fillet.preview returns early when findClosestItem returns undefined', () => {
|
|
981
|
+
core.scene.clear();
|
|
982
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
983
|
+
DesignCore.Scene.previewEntities.clear();
|
|
984
|
+
|
|
985
|
+
const fillet = new Fillet();
|
|
986
|
+
fillet.firstPick.entity = core.scene.entities.get(0);
|
|
987
|
+
fillet.firstPick.clickPoint = new Point(-5, 0);
|
|
988
|
+
|
|
989
|
+
const findSpy = jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(undefined);
|
|
990
|
+
DesignCore.Mouse.pointOnScene = () => new Point(0, 5);
|
|
991
|
+
fillet.preview();
|
|
992
|
+
findSpy.mockRestore();
|
|
993
|
+
|
|
994
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(0);
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
test('Fillet.preview adds fillet arc to previewEntities in no-trim mode', () => {
|
|
998
|
+
core.scene.clear();
|
|
999
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
1000
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
1001
|
+
DesignCore.Scene.previewEntities.clear();
|
|
1002
|
+
|
|
1003
|
+
core.scene.headers.filletRadius = 2;
|
|
1004
|
+
core.scene.headers.trimMode = false;
|
|
1005
|
+
|
|
1006
|
+
const fillet = new Fillet();
|
|
1007
|
+
fillet.firstPick.entity = core.scene.entities.get(0);
|
|
1008
|
+
fillet.firstPick.clickPoint = new Point(-5, 0);
|
|
1009
|
+
|
|
1010
|
+
const findSpy = jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(1);
|
|
1011
|
+
DesignCore.Mouse.pointOnScene = () => new Point(0, 5);
|
|
1012
|
+
fillet.preview();
|
|
1013
|
+
findSpy.mockRestore();
|
|
1014
|
+
|
|
1015
|
+
expect(DesignCore.Scene.previewEntities.count()).toBeGreaterThanOrEqual(1);
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
test('Fillet.preview adds dulled segments and arc to previewEntities in trim mode', () => {
|
|
1019
|
+
core.scene.clear();
|
|
1020
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
1021
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
1022
|
+
DesignCore.Scene.previewEntities.clear();
|
|
1023
|
+
|
|
1024
|
+
core.scene.headers.filletRadius = 2;
|
|
1025
|
+
core.scene.headers.trimMode = true;
|
|
1026
|
+
|
|
1027
|
+
const fillet = new Fillet();
|
|
1028
|
+
fillet.firstPick.entity = core.scene.entities.get(0);
|
|
1029
|
+
fillet.firstPick.clickPoint = new Point(-5, 0);
|
|
1030
|
+
|
|
1031
|
+
const findSpy = jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(1);
|
|
1032
|
+
DesignCore.Mouse.pointOnScene = () => new Point(0, 5);
|
|
1033
|
+
fillet.preview();
|
|
1034
|
+
findSpy.mockRestore();
|
|
1035
|
+
|
|
1036
|
+
// dulled firstSeg + dulled secondSeg + firstTrimLine + secondTrimLine + arc = 5
|
|
1037
|
+
expect(DesignCore.Scene.previewEntities.count()).toBeGreaterThanOrEqual(5);
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
test('Fillet.execute returns early when second-entity input is undefined (inner loop)', async () => {
|
|
1041
|
+
core.scene.clear();
|
|
1042
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(0, 0)] });
|
|
1043
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(0, 10)] });
|
|
1044
|
+
|
|
1045
|
+
const executeCommandSpy = jest.fn();
|
|
1046
|
+
// Only one input: the first selection. Second requestInput returns undefined → inner loop returns.
|
|
1047
|
+
await withMockInput(
|
|
1048
|
+
core.scene,
|
|
1049
|
+
[new SingleSelection(0, new Point(-5, 0))],
|
|
1050
|
+
async () => {
|
|
1051
|
+
const fillet = new Fillet();
|
|
1052
|
+
await fillet.execute();
|
|
1053
|
+
expect(executeCommandSpy).not.toHaveBeenCalled();
|
|
1054
|
+
},
|
|
1055
|
+
{ extraMethods: { executeCommand: executeCommandSpy } },
|
|
1056
|
+
);
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
test('Fillet.execute notifies NONCONSECUTIVESEGMENTS for non-adjacent polyline segments', async () => {
|
|
1060
|
+
// 5-point polyline → 4 segments. Pick segment 1 (near -15,0) then segment 3 (near 5,0).
|
|
1061
|
+
// Neither consecutive nor open-ends → NONCONSECUTIVESEGMENTS, then undefined to exit.
|
|
1062
|
+
core.scene.clear();
|
|
1063
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(-20, 0), new Point(-10, 0), new Point(0, 0), new Point(10, 0), new Point(20, 0)] });
|
|
1064
|
+
|
|
1065
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
1066
|
+
await withMockInput(
|
|
1067
|
+
core.scene,
|
|
1068
|
+
[new SingleSelection(0, new Point(-15, 0)), new SingleSelection(0, new Point(5, 0)), undefined],
|
|
1069
|
+
async () => {
|
|
1070
|
+
const fillet = new Fillet();
|
|
1071
|
+
await fillet.execute();
|
|
1072
|
+
},
|
|
1073
|
+
);
|
|
1074
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Message.NONCONSECUTIVESEGMENTS));
|
|
1075
|
+
notifySpy.mockRestore();
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
test('Fillet.execute Trim→Trim keeps trimMode=true', async () => {
|
|
1079
|
+
core.scene.headers.trimMode = true;
|
|
1080
|
+
|
|
1081
|
+
await withMockInput(
|
|
1082
|
+
core.scene,
|
|
1083
|
+
['Trim', 'Trim', undefined],
|
|
1084
|
+
async () => {
|
|
1085
|
+
const fillet = new Fillet();
|
|
1086
|
+
await fillet.execute();
|
|
1087
|
+
},
|
|
1088
|
+
);
|
|
1089
|
+
|
|
1090
|
+
expect(core.scene.headers.trimMode).toBe(true);
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
// ─── regression: bulge must not leak onto Line endpoints ─────────────────────
|
|
1094
|
+
|
|
1095
|
+
test('Fillet.action two fillets on the same line: trimmed endpoints have bulge = 0', () => {
|
|
1096
|
+
// Line A: (-10, 0) → (10, 0) horizontal
|
|
1097
|
+
// Line B: (-10, 2) → (-10, -2) vertical at left end
|
|
1098
|
+
// Line C: ( 10, 2) → ( 10, -2) vertical at right end
|
|
1099
|
+
// Fillet A–B at the left corner, then A–C at the right corner.
|
|
1100
|
+
// arc.toPolylinePoints() sets bulge on the start point;
|
|
1101
|
+
// that value must not bleed onto the Line endpoints.
|
|
1102
|
+
core.scene.clear();
|
|
1103
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(10, 0)] });
|
|
1104
|
+
core.scene.addItem('Line', { points: [new Point(-10, 2), new Point(-10, -2)] });
|
|
1105
|
+
core.scene.addItem('Line', { points: [new Point(10, 2), new Point(10, -2)] });
|
|
1106
|
+
|
|
1107
|
+
core.scene.headers.filletRadius = 1;
|
|
1108
|
+
core.scene.headers.trimMode = true;
|
|
1109
|
+
|
|
1110
|
+
const fillet1 = new Fillet();
|
|
1111
|
+
fillet1.firstPick.entity = core.scene.entities.get(0);
|
|
1112
|
+
fillet1.secondPick.entity = core.scene.entities.get(1);
|
|
1113
|
+
fillet1.firstPick.clickPoint = new Point(-5, 0);
|
|
1114
|
+
fillet1.secondPick.clickPoint = new Point(-10, 1);
|
|
1115
|
+
fillet1.action();
|
|
1116
|
+
|
|
1117
|
+
const fillet2 = new Fillet();
|
|
1118
|
+
fillet2.firstPick.entity = core.scene.entities.get(0);
|
|
1119
|
+
fillet2.secondPick.entity = core.scene.entities.get(2);
|
|
1120
|
+
fillet2.firstPick.clickPoint = new Point(5, 0);
|
|
1121
|
+
fillet2.secondPick.clickPoint = new Point(10, 1);
|
|
1122
|
+
fillet2.action();
|
|
1123
|
+
|
|
1124
|
+
const line = core.scene.entities.get(0);
|
|
1125
|
+
expect(line.points[0].bulge).toBe(0);
|
|
1126
|
+
expect(line.points[1].bulge).toBe(0);
|
|
1127
|
+
});
|
|
@@ -69,16 +69,16 @@ test('Test MatchProp.action matches properties from source to targets', () => {
|
|
|
69
69
|
// Validate that layer property was matched to targets
|
|
70
70
|
for (let i = 0; i < match.destinationSetIndices.length; i++) {
|
|
71
71
|
const tgt = core.scene.entities.get(match.destinationSetIndices[i]);
|
|
72
|
-
expect(tgt.layer).toBe('test');
|
|
72
|
+
expect(tgt.getProperty('layer')).toBe('test');
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
const circle = core.scene.entities.get(1);
|
|
76
|
-
expect(circle.radius).toBe(10); // original radius
|
|
76
|
+
expect(circle.getProperty('radius')).toBe(10); // original radius
|
|
77
77
|
|
|
78
78
|
const arc = core.scene.entities.get(3);
|
|
79
|
-
expect(arc.radius).toBe(10); // original radius
|
|
79
|
+
expect(arc.getProperty('radius')).toBe(10); // original radius
|
|
80
80
|
|
|
81
81
|
const text = core.scene.entities.get(5);
|
|
82
|
-
expect(text.height).toBe(10); // original height
|
|
83
|
-
expect(text.string).toBe('text test'); // original string
|
|
82
|
+
expect(text.getProperty('height')).toBe(10); // original height
|
|
83
|
+
expect(text.getProperty('string')).toBe('text test'); // original string
|
|
84
84
|
});
|
|
@@ -231,8 +231,8 @@ test('Test Mirror.action - keep source (default)', () => {
|
|
|
231
231
|
for (let i = 0; i < entityCount; i++) {
|
|
232
232
|
const original = core.scene.entities.get(i);
|
|
233
233
|
const copy = core.scene.entities.get(i + entityCount);
|
|
234
|
-
expect(copy.handle).toBeDefined();
|
|
235
|
-
expect(copy.handle).not.toBe(original.handle);
|
|
234
|
+
expect(copy.getProperty('handle')).toBeDefined();
|
|
235
|
+
expect(copy.getProperty('handle')).not.toBe(original.getProperty('handle'));
|
|
236
236
|
}
|
|
237
237
|
});
|
|
238
238
|
|
|
@@ -310,8 +310,8 @@ test('Test Mirror.action - arc direction is reversed', () => {
|
|
|
310
310
|
const original = core.scene.entities.get(0);
|
|
311
311
|
const mirrored = core.scene.entities.get(1);
|
|
312
312
|
|
|
313
|
-
expect(original.direction).toBe(1);
|
|
314
|
-
expect(mirrored.direction).toBe(-1);
|
|
313
|
+
expect(original.getProperty('direction')).toBe(1);
|
|
314
|
+
expect(mirrored.getProperty('direction')).toBe(-1);
|
|
315
315
|
});
|
|
316
316
|
|
|
317
317
|
test('Test Mirror.getMirroredPoints', () => {
|
|
@@ -395,7 +395,7 @@ test('Mirror.preview - no points set, adds no temp entities', () => {
|
|
|
395
395
|
// No points pushed — guard condition should prevent any work
|
|
396
396
|
mirror.preview();
|
|
397
397
|
|
|
398
|
-
expect(core.scene.
|
|
398
|
+
expect(core.scene.auxiliaryEntities.count()).toBe(0);
|
|
399
399
|
});
|
|
400
400
|
|
|
401
401
|
test('Mirror.preview - one point set, draws temp mirror line using mouse position', () => {
|
|
@@ -409,8 +409,8 @@ test('Mirror.preview - one point set, draws temp mirror line using mouse positio
|
|
|
409
409
|
mirror.points.push(new Point(3, 3)); // pt1 distinct from default mouse (0,0) scene position
|
|
410
410
|
mirror.preview();
|
|
411
411
|
|
|
412
|
-
//
|
|
413
|
-
expect(core.scene.
|
|
412
|
+
// Rubber-band mirror line should have been created
|
|
413
|
+
expect(core.scene.auxiliaryEntities.count()).toBeGreaterThanOrEqual(1);
|
|
414
414
|
});
|
|
415
415
|
|
|
416
416
|
test('Mirror.preview - two distinct points, draws mirror line and mirrors selected items', () => {
|
|
@@ -425,8 +425,8 @@ test('Mirror.preview - two distinct points, draws mirror line and mirrors select
|
|
|
425
425
|
mirror.points.push(new Point(1, 0)); // pt2 — mirror across X axis
|
|
426
426
|
mirror.preview();
|
|
427
427
|
|
|
428
|
-
//
|
|
429
|
-
expect(core.scene.
|
|
428
|
+
// Rubber-band mirror line should exist
|
|
429
|
+
expect(core.scene.auxiliaryEntities.count()).toBeGreaterThanOrEqual(1);
|
|
430
430
|
|
|
431
431
|
// The selectedItems copy should have its points mirrored (y negated)
|
|
432
432
|
const previewItem = core.scene.selectionManager.selectedItems[0];
|
|
@@ -445,8 +445,8 @@ test('Mirror.preview - identical points, draws line but skips mirroring selected
|
|
|
445
445
|
mirror.points.push(new Point(5, 5)); // pt2 — same as pt1, zero-length mirror line
|
|
446
446
|
mirror.preview();
|
|
447
447
|
|
|
448
|
-
//
|
|
449
|
-
expect(core.scene.
|
|
448
|
+
// Rubber-band line is still drawn
|
|
449
|
+
expect(core.scene.auxiliaryEntities.count()).toBeGreaterThanOrEqual(1);
|
|
450
450
|
|
|
451
451
|
// Selected item points should be unchanged (mirroring skipped)
|
|
452
452
|
const previewItem = core.scene.selectionManager.selectedItems[0];
|
|
@@ -467,9 +467,9 @@ test('Mirror.preview - reverses direction for entities with a direction property
|
|
|
467
467
|
mirror.preview();
|
|
468
468
|
|
|
469
469
|
// Original entity direction is unchanged
|
|
470
|
-
expect(core.scene.entities.get(0).direction).toBe(1);
|
|
470
|
+
expect(core.scene.entities.get(0).getProperty('direction')).toBe(1);
|
|
471
471
|
|
|
472
472
|
// Preview copy has direction reversed
|
|
473
473
|
const previewItem = core.scene.selectionManager.selectedItems[0];
|
|
474
|
-
expect(previewItem.direction).toBe(-1);
|
|
474
|
+
expect(previewItem.getProperty('direction')).toBe(-1);
|
|
475
475
|
});
|
|
@@ -4,6 +4,7 @@ import { Offset } from '../../core/tools/offset.js';
|
|
|
4
4
|
import { Line } from '../../core/entities/line.js';
|
|
5
5
|
import { Arc } from '../../core/entities/arc.js';
|
|
6
6
|
import { Circle } from '../../core/entities/circle.js';
|
|
7
|
+
import { DesignCore } from '../../core/designCore.js';
|
|
7
8
|
import { expect, jest } from '@jest/globals';
|
|
8
9
|
|
|
9
10
|
const core = new Core();
|
|
@@ -267,7 +268,7 @@ test('Test Offset.action - offset circle outward', () => {
|
|
|
267
268
|
expect(newCircle.points[0].x).toBeCloseTo(0);
|
|
268
269
|
expect(newCircle.points[0].y).toBeCloseTo(0);
|
|
269
270
|
// New radius should be 15
|
|
270
|
-
expect(newCircle.radius).toBeCloseTo(15);
|
|
271
|
+
expect(newCircle.getProperty('radius')).toBeCloseTo(15);
|
|
271
272
|
});
|
|
272
273
|
|
|
273
274
|
test('Test Offset.action - offset circle inward', () => {
|
|
@@ -286,7 +287,7 @@ test('Test Offset.action - offset circle inward', () => {
|
|
|
286
287
|
const newCircle = core.scene.entities.get(1);
|
|
287
288
|
expect(newCircle.points[0].x).toBeCloseTo(0);
|
|
288
289
|
expect(newCircle.points[0].y).toBeCloseTo(0);
|
|
289
|
-
expect(newCircle.radius).toBeCloseTo(7);
|
|
290
|
+
expect(newCircle.getProperty('radius')).toBeCloseTo(7);
|
|
290
291
|
});
|
|
291
292
|
|
|
292
293
|
test('Test Offset.action - offset circle inward returns null when radius would be zero', () => {
|
|
@@ -464,7 +465,7 @@ test('Test Offset.getOffsetPoints - arc inward offset shrinks radius', () => {
|
|
|
464
465
|
expect(result).not.toBeNull();
|
|
465
466
|
const rebuilt = new Arc({});
|
|
466
467
|
rebuilt.fromPolylinePoints(result);
|
|
467
|
-
expect(rebuilt.radius).toBeCloseTo(7);
|
|
468
|
+
expect(rebuilt.getProperty('radius')).toBeCloseTo(7);
|
|
468
469
|
});
|
|
469
470
|
|
|
470
471
|
test('Test Offset.getOffsetPoints - arc returns null when inward offset collapses radius', () => {
|
|
@@ -746,3 +747,169 @@ test('Offset.execute - Through mode offsets entity to the through point distance
|
|
|
746
747
|
|
|
747
748
|
core.scene.inputManager = origInputManager;
|
|
748
749
|
});
|
|
750
|
+
|
|
751
|
+
// ─── register ─────────────────────────────────────────────────────────────────
|
|
752
|
+
|
|
753
|
+
test('Offset.register returns correct metadata', () => {
|
|
754
|
+
const reg = Offset.register();
|
|
755
|
+
expect(reg.command).toBe('Offset');
|
|
756
|
+
expect(reg.shortcut).toBe('O');
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// ─── preview ──────────────────────────────────────────────────────────────────
|
|
760
|
+
|
|
761
|
+
test('Offset.preview does not add preview entity when selectedItem is null', () => {
|
|
762
|
+
core.scene.clear();
|
|
763
|
+
DesignCore.Scene.previewEntities.clear();
|
|
764
|
+
core.scene.headers.offsetDistance = 5;
|
|
765
|
+
|
|
766
|
+
const offset = new Offset();
|
|
767
|
+
offset.selectedItem = null;
|
|
768
|
+
expect(() => offset.preview()).not.toThrow();
|
|
769
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(0);
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
test('Offset.preview does not add preview entity when offsetDistance is 0', () => {
|
|
773
|
+
core.scene.clear();
|
|
774
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
775
|
+
DesignCore.Scene.previewEntities.clear();
|
|
776
|
+
core.scene.headers.offsetDistance = 0;
|
|
777
|
+
|
|
778
|
+
const savedPointOnScene = DesignCore.Mouse.pointOnScene;
|
|
779
|
+
DesignCore.Mouse.pointOnScene = () => new Point(5, 5);
|
|
780
|
+
|
|
781
|
+
const offset = new Offset();
|
|
782
|
+
offset.selectedItem = core.scene.entities.get(0);
|
|
783
|
+
offset.preview();
|
|
784
|
+
|
|
785
|
+
DesignCore.Mouse.pointOnScene = savedPointOnScene;
|
|
786
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(0);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
test('Offset.preview adds entity to previewEntities for a valid line', () => {
|
|
790
|
+
core.scene.clear();
|
|
791
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
792
|
+
DesignCore.Scene.previewEntities.clear();
|
|
793
|
+
core.scene.headers.offsetDistance = 5;
|
|
794
|
+
|
|
795
|
+
const savedPointOnScene = DesignCore.Mouse.pointOnScene;
|
|
796
|
+
DesignCore.Mouse.pointOnScene = () => new Point(5, 5);
|
|
797
|
+
|
|
798
|
+
const offset = new Offset();
|
|
799
|
+
offset.selectedItem = core.scene.entities.get(0);
|
|
800
|
+
offset.preview();
|
|
801
|
+
|
|
802
|
+
DesignCore.Mouse.pointOnScene = savedPointOnScene;
|
|
803
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(1);
|
|
804
|
+
expect(DesignCore.Scene.previewEntities.get(0).points[0].y).toBeCloseTo(5);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
// ─── Lwpolyline action ─────────────────────────────────────────────────────────
|
|
808
|
+
|
|
809
|
+
test('Test Offset.action - offset open Lwpolyline', () => {
|
|
810
|
+
core.scene.clear();
|
|
811
|
+
core.scene.selectionManager.reset();
|
|
812
|
+
|
|
813
|
+
// L-shape: (0,0) → (10,0) → (10,10)
|
|
814
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(0, 0), new Point(10, 0), new Point(10, 10)] });
|
|
815
|
+
|
|
816
|
+
const offset = new Offset();
|
|
817
|
+
offset.selectedItem = core.scene.entities.get(0);
|
|
818
|
+
core.scene.headers.offsetDistance = 1;
|
|
819
|
+
offset.points = [new Point(5, 2)]; // above the bottom segment → offset upward/left
|
|
820
|
+
|
|
821
|
+
offset.action();
|
|
822
|
+
|
|
823
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
824
|
+
const newPoly = core.scene.entities.get(1);
|
|
825
|
+
expect(newPoly.points[0].x).toBeCloseTo(0);
|
|
826
|
+
expect(newPoly.points[0].y).toBeCloseTo(1);
|
|
827
|
+
expect(newPoly.points[2].x).toBeCloseTo(9);
|
|
828
|
+
expect(newPoly.points[2].y).toBeCloseTo(10);
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
test('Test Offset.action - offset closed Lwpolyline outward', () => {
|
|
832
|
+
core.scene.clear();
|
|
833
|
+
core.scene.selectionManager.reset();
|
|
834
|
+
|
|
835
|
+
// Closed square: 5 points where first === last → constructor sets flag=1
|
|
836
|
+
core.scene.addItem('Lwpolyline', {
|
|
837
|
+
points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10), new Point(0, 0)],
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
const polyline = core.scene.entities.get(0);
|
|
841
|
+
expect(polyline.flags.hasFlag(1)).toBe(true); // confirm closed
|
|
842
|
+
|
|
843
|
+
const offset = new Offset();
|
|
844
|
+
offset.selectedItem = polyline;
|
|
845
|
+
core.scene.headers.offsetDistance = 1;
|
|
846
|
+
offset.points = [new Point(-5, 5)]; // outside the left edge
|
|
847
|
+
|
|
848
|
+
offset.action();
|
|
849
|
+
|
|
850
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
851
|
+
const newPoly = core.scene.entities.get(1);
|
|
852
|
+
expect(newPoly.flags.hasFlag(1)).toBe(true); // closed flag preserved
|
|
853
|
+
const xs = newPoly.points.map((p) => p.x);
|
|
854
|
+
const ys = newPoly.points.map((p) => p.y);
|
|
855
|
+
expect(Math.min(...xs)).toBeCloseTo(-1);
|
|
856
|
+
expect(Math.max(...xs)).toBeCloseTo(11);
|
|
857
|
+
expect(Math.min(...ys)).toBeCloseTo(-1);
|
|
858
|
+
expect(Math.max(...ys)).toBeCloseTo(11);
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
// ─── closed inward offset ──────────────────────────────────────────────────────
|
|
862
|
+
|
|
863
|
+
test('Test Offset.getOffsetPolylinePoints - closed rectangle offset inward', () => {
|
|
864
|
+
const offset = new Offset();
|
|
865
|
+
const points = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)];
|
|
866
|
+
|
|
867
|
+
// sidePoint inside the rectangle
|
|
868
|
+
const result = offset.getOffsetPolylinePoints(points, true, new Point(5, 5), 1);
|
|
869
|
+
|
|
870
|
+
expect(result).not.toBeNull();
|
|
871
|
+
expect(result.length).toBe(4);
|
|
872
|
+
const xs = result.map((p) => p.x);
|
|
873
|
+
const ys = result.map((p) => p.y);
|
|
874
|
+
expect(Math.min(...xs)).toBeCloseTo(1);
|
|
875
|
+
expect(Math.max(...xs)).toBeCloseTo(9);
|
|
876
|
+
expect(Math.min(...ys)).toBeCloseTo(1);
|
|
877
|
+
expect(Math.max(...ys)).toBeCloseTo(9);
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
// ─── Through mode with circle ──────────────────────────────────────────────────
|
|
881
|
+
|
|
882
|
+
test('Offset.execute - Through mode offsets circle to the through point', async () => {
|
|
883
|
+
core.scene.clear();
|
|
884
|
+
core.scene.selectionManager.reset();
|
|
885
|
+
|
|
886
|
+
// Circle: center (0,0), radius 10
|
|
887
|
+
core.scene.addItem('Circle', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
888
|
+
|
|
889
|
+
const origInputManager = core.scene.inputManager;
|
|
890
|
+
let callCount = 0;
|
|
891
|
+
// Through → select circle → through point at (15,0) → undefined exits loop
|
|
892
|
+
const inputs = ['Through', { selectedItemIndex: 0 }, new Point(15, 0)];
|
|
893
|
+
|
|
894
|
+
const offset = new Offset();
|
|
895
|
+
core.scene.inputManager = {
|
|
896
|
+
requestInput: async () => {
|
|
897
|
+
if (callCount < inputs.length) return inputs[callCount++];
|
|
898
|
+
},
|
|
899
|
+
actionCommand: () => {
|
|
900
|
+
offset.action();
|
|
901
|
+
},
|
|
902
|
+
executeCommand: () => {},
|
|
903
|
+
reset: () => {},
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
await offset.execute();
|
|
907
|
+
|
|
908
|
+
// getThroughDistance(circle at radius 10, throughPoint at r=15) = |15 - 10| = 5
|
|
909
|
+
expect(core.scene.headers.offsetDistance).toBeCloseTo(5);
|
|
910
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
911
|
+
const newCircle = core.scene.entities.get(1);
|
|
912
|
+
expect(newCircle.getProperty('radius')).toBeCloseTo(15); // original 10 + outward 5
|
|
913
|
+
|
|
914
|
+
core.scene.inputManager = origInputManager;
|
|
915
|
+
});
|
|
@@ -12,7 +12,7 @@ describe('Pasteclip Tool', () => {
|
|
|
12
12
|
beforeEach(() => {
|
|
13
13
|
DesignCore.Scene.entities.clear();
|
|
14
14
|
DesignCore.Scene.selectionManager.reset();
|
|
15
|
-
DesignCore.Scene.
|
|
15
|
+
DesignCore.Scene.previewEntities.clear();
|
|
16
16
|
DesignCore.Clipboard.Entities = []; // also resets basePoint
|
|
17
17
|
});
|
|
18
18
|
|
|
@@ -57,8 +57,8 @@ describe('Pasteclip Tool', () => {
|
|
|
57
57
|
|
|
58
58
|
const tool = new Pasteclip();
|
|
59
59
|
tool.preview();
|
|
60
|
-
expect(DesignCore.Scene.
|
|
61
|
-
const tempLine = DesignCore.Scene.
|
|
60
|
+
expect(DesignCore.Scene.previewEntities.count()).toBeGreaterThan(0);
|
|
61
|
+
const tempLine = DesignCore.Scene.previewEntities.get(0);
|
|
62
62
|
const deltaX = targetPoint.x - basePoint.x;
|
|
63
63
|
const deltaY = targetPoint.y - basePoint.y;
|
|
64
64
|
expect(tempLine.points[0].x).toBe(line.points[0].x + deltaX);
|
|
@@ -94,7 +94,7 @@ describe('Pasteclip Tool', () => {
|
|
|
94
94
|
expect(pasted.points[0].y).toBe(0);
|
|
95
95
|
|
|
96
96
|
// Verify pasted entity has a unique handle
|
|
97
|
-
expect(newEntity.handle).toBeDefined();
|
|
98
|
-
expect(newEntity.handle).not.toBe(line.handle);
|
|
97
|
+
expect(newEntity.getProperty('handle')).toBeDefined();
|
|
98
|
+
expect(newEntity.getProperty('handle')).not.toBe(line.getProperty('handle'));
|
|
99
99
|
});
|
|
100
100
|
});
|
|
@@ -57,9 +57,9 @@ test.each(inputScenarios)('Rotate.execute handles $desc', async (scenario) => {
|
|
|
57
57
|
const rotate = new Rotate();
|
|
58
58
|
await rotate.execute();
|
|
59
59
|
|
|
60
|
-
expect(core.scene.
|
|
60
|
+
expect(core.scene.previewEntities.count()).toBe(0); // no temp entities before preview
|
|
61
61
|
rotate.preview(); // to set up any preview state if needed
|
|
62
|
-
expect(core.scene.
|
|
62
|
+
expect(core.scene.auxiliaryEntities.count()).toBeGreaterThanOrEqual(1); // preview should create rubber-band
|
|
63
63
|
|
|
64
64
|
rotate.action();
|
|
65
65
|
|
|
@@ -131,3 +131,33 @@ test('Test Rotate.action', () => {
|
|
|
131
131
|
expect(core.scene.entities.get(i).points[0].y).toBeCloseTo(0);
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
|
+
|
|
135
|
+
test('Rotate.execute with reference - resets inputPoint to rotation center', async () => {
|
|
136
|
+
const origInputManager = core.scene.inputManager;
|
|
137
|
+
|
|
138
|
+
const basePoint = new Point(10, 0);
|
|
139
|
+
// inputs: basePoint, 'r' (reference option), refBase, refEnd, then undefined to exit the while loop
|
|
140
|
+
const inputs = [basePoint, 'r', new Point(5, 0), new Point(5, 10), undefined];
|
|
141
|
+
let callCount = 0;
|
|
142
|
+
const mockInputManager = {
|
|
143
|
+
requestInput: async () => {
|
|
144
|
+
if (callCount < inputs.length) return inputs[callCount++];
|
|
145
|
+
},
|
|
146
|
+
executeCommand: () => {},
|
|
147
|
+
inputPoint: null,
|
|
148
|
+
};
|
|
149
|
+
core.scene.inputManager = mockInputManager;
|
|
150
|
+
|
|
151
|
+
core.scene.clear();
|
|
152
|
+
core.scene.addItem('Line', { points: [new Point(10, 0), new Point(20, 0)] });
|
|
153
|
+
core.scene.selectionManager.addToSelectionSet(0);
|
|
154
|
+
|
|
155
|
+
const rotate = new Rotate();
|
|
156
|
+
await rotate.execute();
|
|
157
|
+
|
|
158
|
+
// After the reference sequence completes, inputPoint must be reset to the
|
|
159
|
+
// rotation center (points[0]) so the tracking line uses the correct origin.
|
|
160
|
+
expect(mockInputManager.inputPoint).toEqual(basePoint);
|
|
161
|
+
|
|
162
|
+
core.scene.inputManager = origInputManager;
|
|
163
|
+
});
|