@design-core/core 0.0.7 → 51.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/.github/workflows/DesignCoreQA.yml +3 -0
  2. package/core/designCore.js +1 -1
  3. package/core/dimensions/alignedDimension.js +7 -7
  4. package/core/dimensions/angularDimension.js +32 -32
  5. package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +19 -19
  6. package/core/dimensions/diametricDimension.js +10 -10
  7. package/core/dimensions/dimension.js +42 -42
  8. package/core/dimensions/{baseDimension.js → dimensionBase.js} +17 -17
  9. package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +9 -9
  10. package/core/dimensions/radialDimension.js +8 -8
  11. package/core/dimensions/rotatedDimension.js +8 -8
  12. package/core/entities/arc.js +22 -22
  13. package/core/entities/arctext.js +13 -13
  14. package/core/entities/circle.js +7 -7
  15. package/core/entities/hatch.js +210 -40
  16. package/core/entities/insert.js +11 -6
  17. package/core/entities/point.js +19 -19
  18. package/core/entities/polyline.js +3 -3
  19. package/core/entities/{basePolyline.js → polylineBase.js} +18 -18
  20. package/core/entities/text.js +4 -4
  21. package/core/lib/boundingBox.js +11 -11
  22. package/core/lib/canvas.js +13 -6
  23. package/core/lib/clipboard.js +2 -2
  24. package/core/lib/dxf/dxf.js +11 -11
  25. package/core/lib/dxf/dxfIterator.js +2 -2
  26. package/core/lib/entityManager.js +19 -19
  27. package/core/lib/inputManager.js +1 -1
  28. package/core/lib/matrix.js +1 -1
  29. package/core/lib/patterns.js +1 -1
  30. package/core/lib/polylineUtils.js +9 -9
  31. package/core/lib/renderers/cairoRenderer.js +5 -5
  32. package/core/lib/renderers/canvasRenderer.js +4 -4
  33. package/core/lib/renderers/pdfRenderer.js +7 -7
  34. package/core/lib/renderers/rendererBase.js +1 -1
  35. package/core/lib/scene.js +1 -1
  36. package/core/lib/selectionManager.js +18 -18
  37. package/core/lib/utils.js +5 -5
  38. package/core/properties/entityProperties.js +2 -1
  39. package/core/properties/flags.js +12 -0
  40. package/core/properties/property.js +8 -3
  41. package/core/properties/propertyManager.js +52 -37
  42. package/core/tables/block.js +50 -46
  43. package/core/tables/dimStyle.js +5 -5
  44. package/core/tables/ltypeManager.js +1 -1
  45. package/core/tables/vport.js +2 -2
  46. package/core/tools/chamfer.js +6 -6
  47. package/core/tools/chamferFilletBase.js +4 -4
  48. package/core/tools/copy.js +8 -8
  49. package/core/tools/copybase.js +3 -3
  50. package/core/tools/copyclip.js +3 -3
  51. package/core/tools/cornerEntity.js +2 -2
  52. package/core/tools/cutclip.js +3 -3
  53. package/core/tools/explode.js +6 -6
  54. package/core/tools/extend.js +13 -13
  55. package/core/tools/fillet.js +6 -6
  56. package/core/tools/matchProp.js +1 -1
  57. package/core/tools/mirror.js +11 -11
  58. package/core/tools/move.js +5 -5
  59. package/core/tools/offset.js +29 -29
  60. package/core/tools/purge.js +10 -10
  61. package/core/tools/rotate.js +16 -16
  62. package/core/tools/scale.js +6 -6
  63. package/core/tools/trim.js +15 -15
  64. package/cspell.json +323 -0
  65. package/package.json +1 -1
  66. package/test/dimensions/alignedDimension.test.js +3 -3
  67. package/test/dimensions/angularDimension.test.js +7 -7
  68. package/test/dimensions/diametricDimension.test.js +9 -9
  69. package/test/dimensions/dimension.test.js +12 -12
  70. package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +12 -12
  71. package/test/dimensions/{baseLinearDimension.test.js → linearDimensionBase.test.js} +7 -7
  72. package/test/dimensions/radialDimension.test.js +9 -9
  73. package/test/dimensions/rotatedDimension.test.js +4 -4
  74. package/test/entities/arc.test.js +10 -10
  75. package/test/entities/arctext.test.js +6 -6
  76. package/test/entities/circle.test.js +18 -18
  77. package/test/entities/hatch.test.js +362 -39
  78. package/test/entities/insert.test.js +9 -9
  79. package/test/entities/point.test.js +3 -3
  80. package/test/entities/{basePolyline.test.js → polylineBase.test.js} +131 -62
  81. package/test/entities/text.test.js +19 -19
  82. package/test/integration/exportIntegration.test.js +3 -3
  83. package/test/integration/userInputIntegration.test.js +5 -5
  84. package/test/lib/boundingBox.test.js +3 -3
  85. package/test/lib/canvas.test.js +9 -9
  86. package/test/lib/clipboard.test.js +73 -0
  87. package/test/lib/inputManager.test.js +4 -4
  88. package/test/lib/scene.test.js +9 -9
  89. package/test/lib/selectionManager.test.js +3 -3
  90. package/test/lib/snapping.test.js +1 -1
  91. package/test/properties/flags.test.js +24 -0
  92. package/test/properties/propertyManager.test.js +93 -22
  93. package/test/renderers/pdfRenderer.test.js +2 -2
  94. package/test/tables/block.test.js +63 -14
  95. package/test/tables/layerManager.test.js +2 -2
  96. package/test/test-helpers/hatchGridRenderer.js +3 -3
  97. package/test/test-helpers/test-helpers.js +6 -6
  98. package/test/tools/chamfer.test.js +60 -60
  99. package/test/tools/chamferFilletBase.test.js +36 -36
  100. package/test/tools/copy.test.js +6 -6
  101. package/test/tools/copybase.test.js +2 -2
  102. package/test/tools/copyclip.test.js +2 -2
  103. package/test/tools/cornerEntity.test.js +8 -8
  104. package/test/tools/cutclip.test.js +2 -2
  105. package/test/tools/erase.test.js +6 -6
  106. package/test/tools/explode.test.js +2 -2
  107. package/test/tools/extend.test.js +84 -84
  108. package/test/tools/fillet.test.js +62 -62
  109. package/test/tools/matchProp.test.js +6 -6
  110. package/test/tools/mirror.test.js +26 -26
  111. package/test/tools/move.test.js +7 -7
  112. package/test/tools/offset.test.js +65 -65
  113. package/test/tools/purge.test.js +4 -4
  114. package/test/tools/rotate.test.js +10 -10
  115. package/test/tools/scale.test.js +28 -28
  116. package/test/tools/trim.test.js +109 -109
  117. package/test/tools/zoom.test.js +15 -17
@@ -169,7 +169,7 @@ test('Test Canvas.getSceneOffset returns extents', () => {
169
169
  });
170
170
 
171
171
  test('Test Canvas.zoomExtents with entities', () => {
172
- core.scene.addItem('Line', { points: [new Point(0, 0), new Point(500, 500)] });
172
+ core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(500, 500)] });
173
173
  canvas.matrix = new Matrix();
174
174
  canvas.width = 800;
175
175
  canvas.height = 600;
@@ -496,7 +496,7 @@ test('Test Canvas.doubleClick left and right buttons do not throw', () => {
496
496
  test('Test Canvas.doubleClick middle button triggers zoomExtents', () => {
497
497
  const testCore = new Core();
498
498
  testCore.activate();
499
- testCore.scene.addItem('Line', { points: [new Point(0, 0), new Point(500, 500)] });
499
+ testCore.scene.addEntity('Line', { points: [new Point(0, 0), new Point(500, 500)] });
500
500
  testCore.canvas.width = 800;
501
501
  testCore.canvas.height = 600;
502
502
  testCore.canvas.matrix = new Matrix();
@@ -1138,13 +1138,13 @@ describe('Canvas.exportTo', () => {
1138
1138
  });
1139
1139
 
1140
1140
  test('returns true when scene has entities', () => {
1141
- exportCore.scene.addItem('Line', { points: [new Point(0, 0), new Point(100, 100)] });
1141
+ exportCore.scene.addEntity('Line', { points: [new Point(0, 0), new Point(100, 100)] });
1142
1142
  const options = new PlotOptions(595, 842);
1143
1143
  expect(exportCanvas.exportTo(renderer, options)).toBe(true);
1144
1144
  });
1145
1145
 
1146
1146
  test('calls renderer.setTransform with a valid matrix', () => {
1147
- exportCore.scene.addItem('Line', { points: [new Point(0, 0), new Point(100, 100)] });
1147
+ exportCore.scene.addEntity('Line', { points: [new Point(0, 0), new Point(100, 100)] });
1148
1148
  let receivedMatrix;
1149
1149
  renderer.setTransform = (m) => {
1150
1150
  receivedMatrix = m;
@@ -1157,7 +1157,7 @@ describe('Canvas.exportTo', () => {
1157
1157
  });
1158
1158
 
1159
1159
  test('calls renderer.setBackgroundColour with white', () => {
1160
- exportCore.scene.addItem('Line', { points: [new Point(0, 0), new Point(100, 100)] });
1160
+ exportCore.scene.addEntity('Line', { points: [new Point(0, 0), new Point(100, 100)] });
1161
1161
  let bgColour;
1162
1162
  renderer.setBackgroundColour = (c) => {
1163
1163
  bgColour = c;
@@ -1168,7 +1168,7 @@ describe('Canvas.exportTo', () => {
1168
1168
  });
1169
1169
 
1170
1170
  test('calls renderer.setStyle with the plotOptions style', () => {
1171
- exportCore.scene.addItem('Line', { points: [new Point(0, 0), new Point(100, 100)] });
1171
+ exportCore.scene.addEntity('Line', { points: [new Point(0, 0), new Point(100, 100)] });
1172
1172
  const options = new PlotOptions(595, 842);
1173
1173
  const spyCalls = [];
1174
1174
  renderer.setStyle = (fn) => {
@@ -1179,7 +1179,7 @@ describe('Canvas.exportTo', () => {
1179
1179
  });
1180
1180
 
1181
1181
  test('EXTENTS uses Scene.boundingBox regardless of viewport', () => {
1182
- exportCore.scene.addItem('Line', { points: [new Point(0, 0), new Point(500, 500)] });
1182
+ exportCore.scene.addEntity('Line', { points: [new Point(0, 0), new Point(500, 500)] });
1183
1183
  // Pan far away so viewport does not contain the entity
1184
1184
  exportCanvas.matrix.translate(-10000, -10000);
1185
1185
  let receivedMatrix;
@@ -1195,7 +1195,7 @@ describe('Canvas.exportTo', () => {
1195
1195
  });
1196
1196
 
1197
1197
  test('DISPLAY uses current viewport instead of entity extents', () => {
1198
- exportCore.scene.addItem('Line', { points: [new Point(0, 0), new Point(500, 500)] });
1198
+ exportCore.scene.addEntity('Line', { points: [new Point(0, 0), new Point(500, 500)] });
1199
1199
  let extentsMatrix;
1200
1200
  let displayMatrix;
1201
1201
 
@@ -1222,7 +1222,7 @@ describe('Canvas.exportTo', () => {
1222
1222
  });
1223
1223
 
1224
1224
  test('explicit plotScale is forwarded to buildExportMatrix', () => {
1225
- exportCore.scene.addItem('Line', { points: [new Point(0, 0), new Point(100, 100)] });
1225
+ exportCore.scene.addEntity('Line', { points: [new Point(0, 0), new Point(100, 100)] });
1226
1226
  let receivedMatrix;
1227
1227
  renderer.setTransform = (m) => {
1228
1228
  receivedMatrix = m;
@@ -4,6 +4,8 @@ import { Point } from '../../core/entities/point.js';
4
4
  import { DesignCore } from '../../core/designCore.js';
5
5
  import { Logging } from '../../core/lib/logging.js';
6
6
  import { jest } from '@jest/globals';
7
+ import { Polyline } from '../../core/entities/polyline.js';
8
+ import { Text } from '../../core/entities/text.js';
7
9
 
8
10
  // Initialize core (sets DesignCore.Core)
9
11
  new Core();
@@ -67,4 +69,75 @@ describe('Clipboard', () => {
67
69
  expect(fresh.Entities).toHaveLength(0);
68
70
  errorSpy.mockRestore();
69
71
  });
72
+
73
+ describe('stringify/parse flag survival', () => {
74
+ test('closed Polyline retains closed flag after stringify/parse roundtrip', () => {
75
+ // Create a closed polyline (flags bit 1 = closed)
76
+ const poly = new Polyline({
77
+ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)],
78
+ flags: 1,
79
+ });
80
+ expect(poly.getProperty('closed')).toBe(true);
81
+
82
+ DesignCore.Clipboard.Entities = [poly];
83
+ const json = DesignCore.Clipboard.stringify();
84
+
85
+ const fresh = new Clipboard();
86
+ fresh.parse(json);
87
+
88
+ expect(fresh.Entities).toHaveLength(1);
89
+ expect(fresh.Entities[0].getProperty('closed')).toBe(true);
90
+ });
91
+
92
+ test('closed Polyline retains correct point count after stringify/parse roundtrip', () => {
93
+ // A closed polyline stores 4 points (not 5 — the closing duplicate is removed)
94
+ const poly = new Polyline({
95
+ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10)],
96
+ flags: 1,
97
+ });
98
+ expect(poly.points).toHaveLength(4);
99
+
100
+ DesignCore.Clipboard.Entities = [poly];
101
+ const json = DesignCore.Clipboard.stringify();
102
+
103
+ const fresh = new Clipboard();
104
+ fresh.parse(json);
105
+
106
+ expect(fresh.Entities[0].points).toHaveLength(4);
107
+ });
108
+
109
+ test('backwards (mirrored) Text retains backwards flag after stringify/parse roundtrip', () => {
110
+ // Text flags bit 2 = backwards
111
+ const text = new Text({
112
+ points: [new Point(0, 0), new Point(10, 0)],
113
+ textValue: 'hello',
114
+ flags: 2,
115
+ });
116
+ expect(text.getBackwards()).toBe(true);
117
+
118
+ DesignCore.Clipboard.Entities = [text];
119
+ const json = DesignCore.Clipboard.stringify();
120
+
121
+ const fresh = new Clipboard();
122
+ fresh.parse(json);
123
+
124
+ expect(fresh.Entities).toHaveLength(1);
125
+ expect(fresh.Entities[0].getBackwards()).toBe(true);
126
+ });
127
+
128
+ test('non-flagged entities are unaffected', () => {
129
+ const poly = new Polyline({
130
+ points: [new Point(0, 0), new Point(10, 0), new Point(10, 10)],
131
+ });
132
+ expect(poly.getProperty('closed')).toBe(false);
133
+
134
+ DesignCore.Clipboard.Entities = [poly];
135
+ const json = DesignCore.Clipboard.stringify();
136
+
137
+ const fresh = new Clipboard();
138
+ fresh.parse(json);
139
+
140
+ expect(fresh.Entities[0].getProperty('closed')).toBe(false);
141
+ });
142
+ });
70
143
  });
@@ -241,7 +241,7 @@ test('onCommandButton with no active command - starts command', () => {
241
241
 
242
242
  test('starting an entity command from idle clears the selection', () => {
243
243
  inputManager.reset();
244
- core.scene.addItem('Line', { points: [new Point(0, 0), new Point(10, 0)] });
244
+ core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(10, 0)] });
245
245
  core.scene.selectionManager.addToSelectionSet(0);
246
246
  expect(core.scene.selectionManager.selectionSet.selectionSet.length).toBe(1);
247
247
 
@@ -252,7 +252,7 @@ test('starting an entity command from idle clears the selection', () => {
252
252
 
253
253
  test('starting a tool command from idle preserves the selection', () => {
254
254
  inputManager.reset();
255
- core.scene.addItem('Line', { points: [new Point(0, 0), new Point(10, 0)] });
255
+ core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(10, 0)] });
256
256
  core.scene.selectionManager.addToSelectionSet(0);
257
257
  expect(core.scene.selectionManager.selectionSet.selectionSet.length).toBe(1);
258
258
 
@@ -388,7 +388,7 @@ test('onLeftClick with POINT prompt and no nearby entity - responds with the giv
388
388
 
389
389
  test('onLeftClick with entity found and no active command - delegates to onSelection', () => {
390
390
  inputManager.reset();
391
- const mockSelection = { selectedItemIndex: 0 };
391
+ const mockSelection = { selectedEntityIndex: 0 };
392
392
  jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(0);
393
393
  jest.spyOn(core.scene.selectionManager, 'singleSelect').mockReturnValue(mockSelection);
394
394
  const onSelectionSpy = jest.spyOn(inputManager, 'onSelection').mockImplementation(() => {});
@@ -1183,7 +1183,7 @@ test('highlightEntityUnderMouse returns false when buttonThreeDown is true', ()
1183
1183
 
1184
1184
  test('refreshPreview clears hoverEntities in addition to previewEntities and auxiliaryEntities', () => {
1185
1185
  core.scene.clear();
1186
- core.scene.addItem('Line', { points: [new Point(0, 0), new Point(10, 0)] });
1186
+ core.scene.addEntity('Line', { points: [new Point(0, 0), new Point(10, 0)] });
1187
1187
 
1188
1188
  // Simulate a stale hover glow left by the previous mouseMoved()
1189
1189
  DesignCore.Scene.hoverEntities.add(core.scene.entities.get(0));
@@ -11,16 +11,16 @@ test('Test Scene.boundingBox', () => {
11
11
  expect(scene.boundingBox()).toBeUndefined();
12
12
 
13
13
  const line1 = new Line({ points: [new Point(-102, -102), new Point(201, 202)] });
14
- // add to scene (bypass scene.addItem())
15
- scene.addItem('Line', line1);
14
+ // add to scene
15
+ scene.addEntity('Line', line1);
16
16
  expect(scene.boundingBox().xMin).toBeCloseTo(-102);
17
17
  expect(scene.boundingBox().xMax).toBeCloseTo(201);
18
18
  expect(scene.boundingBox().yMin).toBeCloseTo(-102);
19
19
  expect(scene.boundingBox().yMax).toBeCloseTo(202);
20
20
 
21
21
  const line2 = new Line({ points: [new Point(1001, -2002), new Point(-2001, 2002)] });
22
- // add to scene (bypass scene.addItem())
23
- scene.addItem('Line', line2);
22
+ // add to scene
23
+ scene.addEntity('Line', line2);
24
24
  expect(scene.boundingBox().xMin).toBeCloseTo(-2001);
25
25
  expect(scene.boundingBox().xMax).toBeCloseTo(1001);
26
26
  expect(scene.boundingBox().yMin).toBeCloseTo(-2002);
@@ -28,26 +28,26 @@ test('Test Scene.boundingBox', () => {
28
28
  });
29
29
 
30
30
 
31
- test('Test Scene.addItem', () => {
31
+ test('Test Scene.addEntity', () => {
32
32
  // Throw error on missing data
33
33
  expect(() => {
34
- scene.addItem('Line');
34
+ scene.addEntity('Line');
35
35
  }).toThrow();
36
36
 
37
37
  // Add a invalid item type
38
38
  const itemCnt = scene.entities.count();
39
- const returnIndex = scene.addItem('FakeItem', {});
39
+ const returnIndex = scene.addEntity('FakeItem', {});
40
40
  expect(scene.entities.count()).toBe(itemCnt);
41
41
  expect(returnIndex).toBeUndefined();
42
42
 
43
43
  // Add a valid item with data
44
44
  const itemCount = scene.entities.count();
45
- const index = scene.addItem('Line', { points: [new Point(), new Point(10, 10)] });
45
+ const index = scene.addEntity('Line', { points: [new Point(), new Point(10, 10)] });
46
46
  expect(scene.entities.count()).toBe(itemCount + 1);
47
47
  expect(index).toBe(itemCount);
48
48
 
49
49
  // Add a valid item with data at index - item count should not increase
50
- scene.addItem('Line', { points: [new Point(123, 123), new Point(321, 321)] }, index);
50
+ scene.addEntity('Line', { points: [new Point(123, 123), new Point(321, 321)] }, index);
51
51
  expect(scene.entities.count()).toBe(itemCount + 1);
52
52
  // check the items points to make sure index was replaced
53
53
  expect(scene.entities.get(index).points[0].x).toBe(123);
@@ -5,9 +5,9 @@ const core = new Core();
5
5
  const selectionManager = core.scene.selectionManager;
6
6
 
7
7
  // Add a valid item to scene
8
- core.scene.addItem('Line', { points: [new Point(10, 10), new Point(100, 10)] });
9
- core.scene.addItem('Line', { points: [new Point(10, 20), new Point(100, 20)] });
10
- core.scene.addItem('Circle', { points: [new Point(), new Point(5, 0)] });
8
+ core.scene.addEntity('Line', { points: [new Point(10, 10), new Point(100, 10)] });
9
+ core.scene.addEntity('Line', { points: [new Point(10, 20), new Point(100, 20)] });
10
+ core.scene.addEntity('Circle', { points: [new Point(), new Point(5, 0)] });
11
11
 
12
12
 
13
13
  test('Test SelectionManager.findClosestItem', () => {
@@ -9,7 +9,7 @@ const core = new Core();
9
9
  const snapping = new Snapping();
10
10
 
11
11
  // Add a valid item to scene
12
- core.scene.addItem('Line', { points: [new Point(10, 10), new Point(100, 10)] });
12
+ core.scene.addEntity('Line', { points: [new Point(10, 10), new Point(100, 10)] });
13
13
 
14
14
  afterEach(() => {
15
15
  // Reset all mouse button states so tests don't affect each other via
@@ -162,3 +162,27 @@ test('Test flags.removeValue', () => {
162
162
  // 2 shouldn't exist again
163
163
  expect(flags.hasFlag(2)).toBe(false);
164
164
  });
165
+
166
+ test('Test flags.toJSON returns the numeric flag value', () => {
167
+ const flags = new Flags();
168
+ expect(flags.toJSON()).toBe(0);
169
+
170
+ flags.setFlagValue(7);
171
+ expect(flags.toJSON()).toBe(7);
172
+ });
173
+
174
+ test('Test flags.toJSON round-trips through JSON.stringify/parse', () => {
175
+ const flags = new Flags();
176
+ flags.setFlagValue(1);
177
+
178
+ // JSON.stringify should produce the plain number, not a nested object
179
+ const json = JSON.stringify({ flags });
180
+ expect(json).toBe('{"flags":1}');
181
+
182
+ // The parsed value is a plain number — Flags constructor accepts it via setFlagValue
183
+ const parsed = JSON.parse(json);
184
+ const restored = new Flags();
185
+ restored.setFlagValue(parsed.flags);
186
+ expect(restored.getFlagValue()).toBe(1);
187
+ expect(restored.hasFlag(1)).toBe(true);
188
+ });
@@ -14,45 +14,45 @@ const data = {
14
14
  colour: { r: 100, g: 100, b: 100 },
15
15
  };
16
16
 
17
- DesignCore.Scene.addItem('Line', data);
18
- DesignCore.Scene.addItem('Circle', data);
19
- DesignCore.Scene.addItem('Text', data);
17
+ DesignCore.Scene.addEntity('Line', data);
18
+ DesignCore.Scene.addEntity('Circle', data);
19
+ DesignCore.Scene.addEntity('Text', data);
20
20
  // Add Arc with a different Colour
21
- DesignCore.Scene.addItem('Arc', { points: [point1, point2], colour: { r: 130, g: 130, b: 130 } });
21
+ DesignCore.Scene.addEntity('Arc', { points: [point1, point2], colour: { r: 130, g: 130, b: 130 } });
22
22
 
23
- test('Test propertyManager.getItemTypes', () => {
23
+ test('Test propertyManager.getEntityTypes', () => {
24
24
  // Add an item to the selectionSet
25
25
  DesignCore.Scene.selectionManager.selectionSet.selectionSet.push(0);
26
- let types = propertiesManager.getItemTypes();
26
+ let types = propertiesManager.getEntityTypes();
27
27
  expect(types.length).toBe(1);
28
28
 
29
29
  // Add the same item to the selectionSet - shouldn't change the count
30
30
  DesignCore.Scene.selectionManager.selectionSet.selectionSet.push(0);
31
- types = propertiesManager.getItemTypes();
31
+ types = propertiesManager.getEntityTypes();
32
32
  expect(types.length).toBe(1);
33
33
 
34
34
  // Add a new item to the selectionSet - result should include 'All'
35
35
  DesignCore.Scene.selectionManager.selectionSet.selectionSet.push(1);
36
- types = propertiesManager.getItemTypes();
36
+ types = propertiesManager.getEntityTypes();
37
37
  expect(types.length).toBe(3);
38
38
  expect(types[0]).toBe('All');
39
39
  });
40
40
 
41
- test('Test propertyManager.setItemProperties', () => {
41
+ test('Test propertyManager.setEntityProperties', () => {
42
42
  // clear the selection set
43
43
  DesignCore.Scene.reset();
44
44
  // add the text entity
45
45
  DesignCore.Scene.selectionManager.selectionSet.selectionSet.push(2);
46
46
  // set the string attribute of the text entity
47
47
  const string = 'test text';
48
- propertiesManager.setItemProperties('string', string);
48
+ propertiesManager.setEntityProperties('string', string);
49
49
 
50
50
  let text = DesignCore.Scene.entities.get(2);
51
51
  expect(text.type).toBe('Text');
52
52
  expect(text.getProperty('string')).toBe(string);
53
53
 
54
54
  // try and set a non-existent property
55
- propertiesManager.setItemProperties('faux-prop', string);
55
+ propertiesManager.setEntityProperties('faux-prop', string);
56
56
  text = DesignCore.Scene.entities.get(2);
57
57
  expect(text['faux-prop']).toBeUndefined();
58
58
 
@@ -63,53 +63,124 @@ test('Test propertyManager.setItemProperties', () => {
63
63
  // get the current radius
64
64
  const radius = DesignCore.Scene.entities.get(1).getProperty('radius');
65
65
  // try and set an incorrect value
66
- propertiesManager.setItemProperties('radius', string);
66
+ propertiesManager.setEntityProperties('radius', string);
67
67
  const circle = DesignCore.Scene.entities.get(1);
68
68
  expect(circle.getProperty('radius')).toBe(radius);
69
69
  });
70
70
 
71
- test('Test propertyManager.getItemProperties', () => {
71
+ test('Test propertyManager.getEntityProperties', () => {
72
72
  // clear the selection set
73
73
  DesignCore.Scene.reset();
74
74
 
75
75
  // get props with nothing selected - should be undefined
76
- let properties = propertiesManager.getItemProperties();
76
+ let properties = propertiesManager.getEntityProperties();
77
77
  expect(properties).toBeUndefined();
78
78
 
79
79
  // Add the line entity to the selectionSet
80
80
  DesignCore.Scene.selectionManager.selectionSet.selectionSet.push(0);
81
- properties = propertiesManager.getItemProperties('Line');
81
+ properties = propertiesManager.getEntityProperties('Line');
82
82
  expect(properties.length).toBeGreaterThan(0);
83
83
 
84
84
  // check properties for All itemTypes
85
- properties = propertiesManager.getItemProperties('All');
85
+ properties = propertiesManager.getEntityProperties('All');
86
86
  expect(properties.length).toBeGreaterThan(0);
87
87
 
88
88
  // get circle props with a line selected - should be 0
89
- properties = propertiesManager.getItemProperties('Circle');
89
+ properties = propertiesManager.getEntityProperties('Circle');
90
90
  expect(properties.length).toBe(0);
91
91
  });
92
92
 
93
- test('Test propertyManager.getItemPropertyValue', () => {
93
+ test('Test propertyManager.getEntityPropertyValue', () => {
94
94
  // clear the selection set
95
95
  DesignCore.Scene.reset();
96
96
 
97
97
  // get props with nothing selected - should be undefined
98
- let propertyValues = propertiesManager.getItemPropertyValue();
98
+ let propertyValues = propertiesManager.getEntityPropertyValue();
99
99
  expect(propertyValues).toBeUndefined();
100
100
 
101
101
  // Add the line entity to the selectionSet
102
102
  DesignCore.Scene.selectionManager.selectionSet.selectionSet.push(0);
103
103
  // get the line colour
104
- propertyValues = propertiesManager.getItemPropertyValue('Line', 'colour');
104
+ propertyValues = propertiesManager.getEntityPropertyValue('Line', 'colour');
105
105
  expect(propertyValues).toEqual(data.colour);
106
106
 
107
107
  // get circle props with a line selected - should be undefined
108
- propertyValues = propertiesManager.getItemPropertyValue('Circle', 'colour');
108
+ propertyValues = propertiesManager.getEntityPropertyValue('Circle', 'colour');
109
109
  expect(propertyValues).toBeUndefined();
110
110
 
111
111
  // get props for all selected types - index 0 and 3 colour should be Varies
112
112
  DesignCore.Scene.selectionManager.selectionSet.selectionSet.push(3);
113
- propertyValues = propertiesManager.getItemPropertyValue('All', 'colour');
113
+ propertyValues = propertiesManager.getEntityPropertyValue('All', 'colour');
114
114
  expect(propertyValues).toBe('Varies');
115
115
  });
116
+
117
+ test('Test propertyManager.getEntityPropertyDefinition', () => {
118
+ // clear the selection set
119
+ DesignCore.Scene.reset();
120
+
121
+ // returns undefined when nothing is selected
122
+ let def = propertiesManager.getEntityPropertyDefinition('All', 'colour');
123
+ expect(def).toBeUndefined();
124
+
125
+ // select the line entity
126
+ DesignCore.Scene.selectionManager.selectionSet.selectionSet.push(0);
127
+
128
+ // returns a Property descriptor for a valid property
129
+ def = propertiesManager.getEntityPropertyDefinition('All', 'colour');
130
+ expect(def).toBeDefined();
131
+ expect(typeof def.type).toBe('string');
132
+
133
+ // returns undefined for a property the entity does not have
134
+ def = propertiesManager.getEntityPropertyDefinition('All', 'non-existent-prop');
135
+ expect(def).toBeUndefined();
136
+
137
+ // filters by entityType — Circle not selected, so returns undefined
138
+ def = propertiesManager.getEntityPropertyDefinition('Circle', 'colour');
139
+ expect(def).toBeUndefined();
140
+ });
141
+
142
+ test('Test propertyManager.getEntityForProperty', () => {
143
+ // clear the selection set
144
+ DesignCore.Scene.reset();
145
+
146
+ // returns undefined when nothing is selected
147
+ let entity = propertiesManager.getEntityForProperty('All', 'colour');
148
+ expect(entity).toBeUndefined();
149
+
150
+ // select the line (index 0) and circle (index 1)
151
+ DesignCore.Scene.selectionManager.selectionSet.selectionSet.push(0);
152
+ DesignCore.Scene.selectionManager.selectionSet.selectionSet.push(1);
153
+
154
+ // returns an entity for 'All' and a known property
155
+ entity = propertiesManager.getEntityForProperty('All', 'colour');
156
+ expect(entity).toBeDefined();
157
+ expect(entity.type === 'Line' || entity.type === 'Circle').toBe(true);
158
+
159
+ // filters by entityType — only Circle entities
160
+ entity = propertiesManager.getEntityForProperty('Circle', 'colour');
161
+ expect(entity).toBeDefined();
162
+ expect(entity.type).toBe('Circle');
163
+
164
+ // returns undefined when the property does not exist on any selected entity
165
+ entity = propertiesManager.getEntityForProperty('All', 'non-existent-prop');
166
+ expect(entity).toBeUndefined();
167
+ });
168
+
169
+ test('Test propertyManager.setPropertyCallbackFunction and selectionSetChanged', () => {
170
+ let callCount = 0;
171
+ const callback = () => {
172
+ callCount++;
173
+ };
174
+
175
+ // selectionSetChanged with no callback set should not throw
176
+ propertiesManager.selectionSetChanged();
177
+
178
+ propertiesManager.setPropertyCallbackFunction(callback);
179
+
180
+ // callback is invoked when selectionSetChanged is called
181
+ propertiesManager.selectionSetChanged();
182
+ expect(callCount).toBe(1);
183
+
184
+ propertiesManager.selectionSetChanged();
185
+ expect(callCount).toBe(2);
186
+ });
@@ -110,7 +110,7 @@ describe('PdfRenderer — drawing', () => {
110
110
 
111
111
  test('90° arc splits into 1 Bézier curve', () => {
112
112
  const renderer = new PdfRenderer(100, 100);
113
- // start=(100,0) bulge=tan(π/8)≈0.4142 → 90° arc, end=(150,50), center=(100,50), r=50
113
+ // start=(100,0) bulge=tan(π/8)≈0.4142 → 90° arc, end=(150,50), centre=(100,50), r=50
114
114
  const start = new Point(100, 0, Math.tan(Math.PI / 8));
115
115
  const end = new Point(150, 50);
116
116
  renderer.drawShape([start, end], {});
@@ -121,7 +121,7 @@ describe('PdfRenderer — drawing', () => {
121
121
 
122
122
  test('full circle (two 180° arcs) emits 4 Bézier curves', () => {
123
123
  const renderer = new PdfRenderer(100, 100);
124
- // Circle center=(50,50) r=50 — mirrors Circle.toPolylinePoints() output
124
+ // Circle centre=(50,50) r=50 — mirrors Circle.toPolylinePoints() output
125
125
  const p1 = new Point(100, 50, 1);
126
126
  const p2 = new Point(0, 50, 1);
127
127
  const p3 = new Point(100, 50);
@@ -9,25 +9,38 @@ import { DesignCore } from '../../core/designCore.js';
9
9
  import { Utils } from '../../core/lib/utils.js';
10
10
  import { DXFFile } from '../../core/lib/dxf/dxfFile.js';
11
11
 
12
+ /**
13
+ * Create a mock requestInput function that returns values from the provided array in order.
14
+ * @param {Array} inputs
15
+ * @return {Function}
16
+ */
17
+ function mockRequestInput(inputs) {
18
+ let i = 0;
19
+ return async () => {
20
+ if (i < inputs.length) return inputs[i++];
21
+ throw new Error('cancelled');
22
+ };
23
+ }
24
+
12
25
  new Core();
13
26
 
14
27
  test('Test Block', () => {
15
28
  const line = new Line({ points: [new Point(101, 102), new Point(201, 202)] });
16
29
  const flags = new Flags();
17
30
  flags.addValue(1);
18
- const block = new Block({ items: [line], flags: flags });
31
+ const block = new Block({ entities: [line], flags: flags });
19
32
 
20
- expect(block.items.length).toBe(1);
33
+ expect(block.entities.length).toBe(1);
21
34
  expect(block.flags.getFlagValue()).toBe(1);
22
35
  });
23
36
 
24
37
  test('Test Block.clearItems', () => {
25
38
  const line = new Line({ points: [new Point(101, 102), new Point(201, 202)] });
26
- const block = new Block({ items: [line] });
27
- expect(block.items.length).toBe(1);
39
+ const block = new Block({ entities: [line] });
40
+ expect(block.entities.length).toBe(1);
28
41
 
29
- block.clearItems();
30
- expect(block.items.length).toBe(0);
42
+ block.clearEntities();
43
+ expect(block.entities.length).toBe(0);
31
44
  });
32
45
 
33
46
  test('Test Block.snaps', () => {
@@ -36,7 +49,7 @@ test('Test Block.snaps', () => {
36
49
  expect(block.snaps()).toHaveLength(0);
37
50
 
38
51
  const line = new Line({ points: [new Point(101, 102), new Point(201, 202)] });
39
- block.addItem(line);
52
+ block.addEntity(line);
40
53
  expect(block.snaps()).toBeInstanceOf(Array);
41
54
  expect(block.snaps()).not.toHaveLength(0);
42
55
  });
@@ -45,9 +58,9 @@ test('Test Block', () => {
45
58
  const line = new Line({ points: [new Point(101, 102), new Point(201, 202)] });
46
59
  const flags = new Flags();
47
60
  flags.addValue(1);
48
- const block = new Block({ items: [line], flags: flags });
61
+ const block = new Block({ entities: [line], flags: flags });
49
62
 
50
- expect(block.items.length).toBe(1);
63
+ expect(block.entities.length).toBe(1);
51
64
  expect(block.flags.getFlagValue()).toBe(1);
52
65
  });
53
66
 
@@ -63,7 +76,7 @@ test('Test Block.closestPoint', () => {
63
76
  expect(block.closestPoint(point)[1]).toBe(Infinity);
64
77
 
65
78
  const line = new Line({ points: [new Point(101, 102), new Point(201, 202)] });
66
- block.addItem(line);
79
+ block.addEntity(line);
67
80
  expect(block.closestPoint(point)).toBeInstanceOf(Array);
68
81
  expect(block.closestPoint(point)).toHaveLength(2);
69
82
  expect(block.closestPoint(point)[0]).toBeInstanceOf(Point);
@@ -75,7 +88,7 @@ test('Test Block.closestPoint', () => {
75
88
  test('Test Block.dxf', () => {
76
89
  const block = new Block({ handle: '1', endblkHandle: '1' });
77
90
  const line = new Line({ handle: '1', points: [new Point(101, 102), new Point(201, 202)] });
78
- block.addItem(line);
91
+ block.addEntity(line);
79
92
 
80
93
  const file = new File();
81
94
  block.dxf(file);
@@ -154,7 +167,7 @@ test('Test Block.touched', () => {
154
167
  expect(block.touched(selectionExtremesTrue)).toBe(false);
155
168
 
156
169
  const line = new Line({ points: [new Point(101, 102), new Point(201, 202)] });
157
- block.addItem(line);
170
+ block.addEntity(line);
158
171
  expect(block.touched(selectionExtremesTrue)).toBe(true);
159
172
  expect(block.touched(selectionExtremesFalse)).toBe(false);
160
173
  });
@@ -165,7 +178,7 @@ test('Test Block.dxf throws when a block item has an undefined handle', () => {
165
178
  const line = new Line({ handle: '3', points: [new Point(0, 0), new Point(1, 1)] });
166
179
  const clone = Utils.cloneObject(line);
167
180
  expect(clone.getProperty('handle')).toBeUndefined();
168
- block.addItem(clone);
181
+ block.addEntity(clone);
169
182
 
170
183
  // Use a real DXFFile (R2000+) so the version-gated handle validation fires
171
184
  const file = new DXFFile('R2000');
@@ -179,9 +192,45 @@ test('Test Block.dxf succeeds when block items have valid handles', () => {
179
192
  // simulate the handle assignment done in Block.execute()
180
193
  clone.setProperty('handle', DesignCore.HandleManager.next());
181
194
  expect(clone.getProperty('handle')).toMatch(/^[0-9A-F]+$/i);
182
- block.addItem(clone);
195
+ block.addEntity(clone);
183
196
 
184
197
  const file = new DXFFile('R2000');
185
198
  expect(() => block.dxf(file)).not.toThrow();
186
199
  expect(file.contents).toContain(clone.getProperty('handle'));
187
200
  });
201
+
202
+ test('Test Block.execute offsets entity points by the insert point', async () => {
203
+ new Core();
204
+
205
+ // Add a line to the scene at world coordinates (100,200) → (300,400)
206
+ const line = new Line({ points: [new Point(100, 200), new Point(300, 400)] });
207
+ DesignCore.Scene.entities.add(line);
208
+ const lineIndex = DesignCore.Scene.entities.count() - 1;
209
+
210
+ // Pre-select the line so Block.execute() skips the selection-set prompt
211
+ DesignCore.Scene.selectionManager.reset();
212
+ DesignCore.Scene.selectionManager.addToSelectionSet(lineIndex);
213
+
214
+ // Feed: block name, then insert point (50, 75)
215
+ const insertPoint = new Point(50, 75);
216
+ const origRequestInput = DesignCore.Scene.inputManager.requestInput.bind(DesignCore.Scene.inputManager);
217
+ DesignCore.Scene.inputManager.requestInput = mockRequestInput(['TestBlock', insertPoint]);
218
+
219
+ await new Block().execute();
220
+
221
+ DesignCore.Scene.inputManager.requestInput = origRequestInput;
222
+
223
+ // The block should exist in the block manager
224
+ const block = DesignCore.Scene.blockManager.getItemByName('TestBlock');
225
+ expect(block).toBeDefined();
226
+ expect(block.entities).toHaveLength(1);
227
+
228
+ // Points inside the block must be offset by -insertPoint so that
229
+ // when Insert renders them via applyTransform(+insertPoint) they land
230
+ // back at their original world coordinates.
231
+ const blockLine = block.entities[0];
232
+ expect(blockLine.points[0].x).toBeCloseTo(100 - insertPoint.x);
233
+ expect(blockLine.points[0].y).toBeCloseTo(200 - insertPoint.y);
234
+ expect(blockLine.points[1].x).toBeCloseTo(300 - insertPoint.x);
235
+ expect(blockLine.points[1].y).toBeCloseTo(400 - insertPoint.y);
236
+ });