@design-core/core 0.0.6 → 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 +30 -282
- 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 +35 -11
- 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 +68 -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 +181 -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';
|
|
@@ -944,3 +945,183 @@ test('Fillet.action closing-wrap reversed pick order: arc bulge sign and tangent
|
|
|
944
945
|
expect(poly.points[1].y).toBeCloseTo(0);
|
|
945
946
|
expect(poly.points[0].bulge).toBeGreaterThan(0);
|
|
946
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
|
+
});
|
package/test/tools/scale.test.js
CHANGED
|
@@ -438,7 +438,7 @@ test('Scale.preview - no points set, adds no temp entities', () => {
|
|
|
438
438
|
// No points — guard condition prevents any work
|
|
439
439
|
scale.preview();
|
|
440
440
|
|
|
441
|
-
expect(core.scene.
|
|
441
|
+
expect(core.scene.previewEntities.count()).toBe(0);
|
|
442
442
|
});
|
|
443
443
|
|
|
444
444
|
test('Scale.preview - referencePoint set, suppresses preview', () => {
|
|
@@ -454,7 +454,7 @@ test('Scale.preview - referencePoint set, suppresses preview', () => {
|
|
|
454
454
|
|
|
455
455
|
scale.preview();
|
|
456
456
|
|
|
457
|
-
expect(core.scene.
|
|
457
|
+
expect(core.scene.previewEntities.count()).toBe(0);
|
|
458
458
|
});
|
|
459
459
|
|
|
460
460
|
test('Scale.preview - mouse on base point (distance 0), draws line but skips scaling', () => {
|
|
@@ -472,7 +472,7 @@ test('Scale.preview - mouse on base point (distance 0), draws line but skips sca
|
|
|
472
472
|
scale.preview();
|
|
473
473
|
|
|
474
474
|
// Temp line is still created
|
|
475
|
-
expect(core.scene.
|
|
475
|
+
expect(core.scene.previewEntities.count()).toBeGreaterThanOrEqual(1);
|
|
476
476
|
|
|
477
477
|
// Selected item points should be unchanged (scaling skipped)
|
|
478
478
|
const previewItem = core.scene.selectionManager.selectedItems[0];
|
|
@@ -494,7 +494,7 @@ test('Scale.preview - one point set, draws temp line and scales selected items',
|
|
|
494
494
|
scale.preview();
|
|
495
495
|
|
|
496
496
|
// Temp line should be created
|
|
497
|
-
expect(core.scene.
|
|
497
|
+
expect(core.scene.previewEntities.count()).toBeGreaterThanOrEqual(1);
|
|
498
498
|
|
|
499
499
|
// Preview item should have been scaled (x differs from original 1.0)
|
|
500
500
|
const previewItem = core.scene.selectionManager.selectedItems[0];
|
|
@@ -520,7 +520,7 @@ test('Scale.preview - reference mode uses referenceLength to compute factor', ()
|
|
|
520
520
|
// before and after with a known referenceLength to confirm scaling occurred.
|
|
521
521
|
scale.preview();
|
|
522
522
|
|
|
523
|
-
expect(core.scene.
|
|
523
|
+
expect(core.scene.previewEntities.count()).toBeGreaterThanOrEqual(1);
|
|
524
524
|
|
|
525
525
|
// The preview item's first point should differ from the original (10,0)
|
|
526
526
|
// because the factor (distance/10) is applied
|