@design-core/core 0.0.1 → 0.0.3

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 (254) hide show
  1. package/.eslintrc.json +14 -7
  2. package/.github/workflows/DesignCoreQA.yml +3 -1
  3. package/README.md +11 -4
  4. package/TODO.md +19 -0
  5. package/core/core/core.js +49 -42
  6. package/core/designCore.js +61 -2
  7. package/core/dimensions/alignedDimension.js +142 -0
  8. package/core/dimensions/angularDimension.js +550 -0
  9. package/core/dimensions/baseCircularDimension.js +244 -0
  10. package/core/dimensions/baseDimension.js +558 -0
  11. package/core/dimensions/baseLinearDimension.js +371 -0
  12. package/core/dimensions/diametricDimension.js +82 -0
  13. package/core/dimensions/dimension.js +298 -0
  14. package/core/dimensions/linearDimension.js +29 -0
  15. package/core/dimensions/radialDimension.js +78 -0
  16. package/core/dimensions/rotatedDimension.js +167 -0
  17. package/core/entities/arc.js +314 -60
  18. package/core/entities/arctext.js +642 -0
  19. package/core/entities/basePolyline.js +553 -94
  20. package/core/entities/circle.js +181 -54
  21. package/core/entities/entity.js +154 -74
  22. package/core/entities/hatch.js +807 -0
  23. package/core/entities/insert.js +200 -39
  24. package/core/entities/line.js +196 -134
  25. package/core/entities/lwpolyline.js +21 -44
  26. package/core/entities/point.js +194 -29
  27. package/core/entities/polyline.js +54 -7
  28. package/core/entities/rectangle.js +43 -13
  29. package/core/entities/solid.js +43 -6
  30. package/core/entities/text.js +324 -181
  31. package/core/lib/boundingBox.js +73 -13
  32. package/core/lib/canvas.js +256 -65
  33. package/core/lib/clipboard.js +198 -0
  34. package/core/lib/colour.js +144 -0
  35. package/core/lib/colours.js +367 -301
  36. package/core/lib/commandLine.js +16 -28
  37. package/core/lib/commandManager.js +115 -58
  38. package/core/lib/constants.js +23 -0
  39. package/core/lib/dxf/dxf.js +249 -34
  40. package/core/lib/dxf/dxfFile.js +23 -38
  41. package/core/lib/dxf/dxfIterator.js +17 -5
  42. package/core/lib/dxf/dxfRead.js +23 -7
  43. package/core/lib/dxf/dxfWrite.js +39 -129
  44. package/core/lib/dxf/handleManager.js +97 -0
  45. package/core/lib/dxf/sections/blocks.js +16 -2
  46. package/core/lib/dxf/sections/entities.js +17 -3
  47. package/core/lib/dxf/sections/header.js +10 -1
  48. package/core/lib/dxf/sections/objects.js +36 -0
  49. package/core/lib/dxf/sections/section.js +43 -5
  50. package/core/lib/dxf/sections/tables.js +17 -3
  51. package/core/lib/entityManager.js +178 -0
  52. package/core/lib/fileio.js +14 -4
  53. package/core/lib/headers.js +229 -0
  54. package/core/lib/input.js +46 -0
  55. package/core/lib/inputManager.js +199 -194
  56. package/core/lib/intersect.js +341 -468
  57. package/core/lib/logging.js +25 -1
  58. package/core/lib/matrix.js +6 -8
  59. package/core/lib/mouse.js +24 -79
  60. package/core/lib/patterns.js +690 -0
  61. package/core/lib/promptOptions.js +133 -0
  62. package/core/lib/scene.js +71 -174
  63. package/core/lib/selectionManager.js +74 -48
  64. package/core/lib/selectionWindow.js +24 -10
  65. package/core/lib/settings.js +18 -11
  66. package/core/lib/snapping.js +129 -21
  67. package/core/lib/stateManager.js +288 -0
  68. package/core/lib/strings.js +57 -5
  69. package/core/lib/utils.js +95 -11
  70. package/core/objects/dictionary.js +43 -0
  71. package/core/objects/dictionaryManager.js +80 -0
  72. package/core/properties/dimType.js +117 -0
  73. package/core/properties/flags.js +66 -0
  74. package/core/properties/property.js +26 -0
  75. package/core/properties/propertyManager.js +65 -19
  76. package/core/tables/appId.js +31 -0
  77. package/core/tables/appIdManager.js +44 -0
  78. package/core/tables/block.js +323 -0
  79. package/core/tables/blockManager.js +48 -0
  80. package/core/tables/blockRecord.js +29 -0
  81. package/core/tables/blockRecordManager.js +37 -0
  82. package/core/tables/dimStyle.js +627 -0
  83. package/core/tables/dimStyleManager.js +187 -0
  84. package/core/tables/endblk.js +28 -0
  85. package/core/tables/layer.js +219 -0
  86. package/core/tables/layerManager.js +47 -0
  87. package/core/tables/ltype.js +85 -0
  88. package/core/tables/ltypeManager.js +65 -0
  89. package/core/tables/style.js +145 -0
  90. package/core/tables/styleManager.js +55 -0
  91. package/core/tables/styleManagerBase.js +110 -0
  92. package/core/tables/tableManagerBase.js +354 -0
  93. package/core/tables/ucs.js +28 -0
  94. package/core/tables/ucsManager.js +44 -0
  95. package/core/tables/view.js +28 -0
  96. package/core/tables/viewManager.js +44 -0
  97. package/core/tables/vport.js +70 -0
  98. package/core/tables/vportManager.js +65 -0
  99. package/core/tools/chamfer.js +343 -0
  100. package/core/tools/chamferFilletBase.js +162 -0
  101. package/core/tools/copy.js +68 -40
  102. package/core/tools/copybase.js +65 -0
  103. package/core/tools/copyclip.js +59 -0
  104. package/core/tools/cornerEntity.js +138 -0
  105. package/core/tools/cutclip.js +72 -0
  106. package/core/tools/distance.js +37 -9
  107. package/core/tools/erase.js +43 -13
  108. package/core/tools/explode.js +117 -0
  109. package/core/tools/extend.js +71 -32
  110. package/core/tools/fillet.js +336 -0
  111. package/core/tools/identify.js +33 -6
  112. package/core/tools/matchProp.js +106 -0
  113. package/core/tools/mirror.js +141 -0
  114. package/core/tools/move.js +65 -32
  115. package/core/tools/offset.js +321 -0
  116. package/core/tools/pan.js +54 -0
  117. package/core/tools/pasteclip.js +93 -0
  118. package/core/tools/purge.js +90 -0
  119. package/core/tools/rotate.js +105 -70
  120. package/core/tools/scale.js +166 -0
  121. package/core/tools/tool.js +14 -4
  122. package/core/tools/trim.js +71 -32
  123. package/core/tools/zoom.js +152 -0
  124. package/package.json +3 -305
  125. package/test/dimensions/alignedDimension.test.js +211 -0
  126. package/test/dimensions/angularDimension.test.js +243 -0
  127. package/test/dimensions/baseDimension.test.js +239 -0
  128. package/test/dimensions/diametricDimension.test.js +203 -0
  129. package/test/dimensions/dimension.test.js +102 -0
  130. package/test/dimensions/radialDimension.test.js +203 -0
  131. package/test/dimensions/rotatedDimension.test.js +224 -0
  132. package/test/entities/arc.test.js +463 -41
  133. package/test/entities/arctext.test.js +329 -0
  134. package/test/entities/basePolyline.test.js +459 -10
  135. package/test/entities/circle.test.js +312 -7
  136. package/test/entities/entity.test.js +28 -9
  137. package/test/entities/hatch.test.js +606 -0
  138. package/test/entities/insert.test.js +90 -0
  139. package/test/entities/line.test.js +182 -6
  140. package/test/entities/point.test.js +375 -37
  141. package/test/entities/solid.test.js +156 -0
  142. package/test/entities/text.test.js +533 -7
  143. package/test/integration/exportIntegration.reference.dxf +1706 -0
  144. package/test/integration/exportIntegration.test.js +186 -0
  145. package/test/integration/importExportIntegration.test.js +126 -0
  146. package/test/integration/intersectionIntegration.test.js +880 -0
  147. package/test/integration/userInputIntegration.reference.dxf +1410 -0
  148. package/test/integration/userInputIntegration.test.js +269 -0
  149. package/test/lib/boundingBox.test.js +26 -2
  150. package/test/lib/canvas.test.js +613 -5
  151. package/test/lib/clipboard.test.js +70 -0
  152. package/test/lib/colour.test.js +114 -0
  153. package/test/lib/colours.test.js +63 -18
  154. package/test/lib/commandLine.test.js +159 -22
  155. package/test/lib/commandManager.test.js +4 -4
  156. package/test/lib/constants.test.js +9 -0
  157. package/test/lib/dxf/dxfFile.test.js +6 -2
  158. package/test/lib/dxf/dxfIterator.test.js +1 -1
  159. package/test/lib/dxf/dxfWrite.test.js +173 -0
  160. package/test/lib/dxf/handleManager.test.js +137 -0
  161. package/test/lib/dxf/sections/blocks.test.js +5 -5
  162. package/test/lib/dxf/sections/entities.test.js +4 -4
  163. package/test/lib/dxf/sections/header.test.js +18 -4
  164. package/test/lib/dxf/sections/objects.test.js +132 -0
  165. package/test/lib/dxf/sections/section.test.js +29 -29
  166. package/test/lib/dxf/sections/tables.test.js +3 -3
  167. package/test/lib/entityManager.test.js +163 -0
  168. package/test/lib/headers.test.js +233 -0
  169. package/test/lib/inputManager.test.js +1144 -5
  170. package/test/lib/intersect.test.js +454 -331
  171. package/test/lib/logging.test.js +1 -1
  172. package/test/lib/matrix.test.js +1 -1
  173. package/test/lib/mouse.test.js +327 -4
  174. package/test/lib/patterns.test.js +105 -0
  175. package/test/lib/promptOptions.test.js +179 -0
  176. package/test/lib/scene.test.js +16 -52
  177. package/test/lib/selectionManager.test.js +17 -5
  178. package/test/lib/settings.test.js +111 -5
  179. package/test/lib/snapping.test.js +142 -4
  180. package/test/lib/stateManager.test.js +155 -0
  181. package/test/lib/utils.test.js +81 -1
  182. package/test/objects/dictionary.test.js +81 -0
  183. package/test/objects/dictionaryManager.test.js +34 -0
  184. package/test/properties/dimType.test.js +62 -0
  185. package/test/properties/flags.test.js +164 -0
  186. package/test/properties/property.test.js +13 -0
  187. package/test/properties/propertyManager.test.js +10 -11
  188. package/test/tables/appId.test.js +46 -0
  189. package/test/tables/appIdManager.test.js +72 -0
  190. package/test/tables/block.test.js +157 -0
  191. package/test/tables/blockManager.test.js +52 -0
  192. package/test/tables/blockRecord.test.js +39 -0
  193. package/test/tables/blockRecordManager.test.js +60 -0
  194. package/test/tables/dimstyle.test.js +72 -0
  195. package/test/tables/endblk.test.js +30 -0
  196. package/test/tables/layer.test.js +95 -0
  197. package/test/tables/layerManager.test.js +172 -0
  198. package/test/{styles → tables}/ltype.test.js +7 -7
  199. package/test/{styles → tables}/ltypeManager.test.js +2 -2
  200. package/test/tables/style.test.js +44 -0
  201. package/test/tables/stylemanagerbase.test.js +220 -0
  202. package/test/tables/tableManagerBase.test.js +194 -0
  203. package/test/tables/ucs.test.js +37 -0
  204. package/test/tables/ucsManager.test.js +48 -0
  205. package/test/tables/view.test.js +37 -0
  206. package/test/tables/viewManager.test.js +48 -0
  207. package/test/tables/vport.test.js +117 -0
  208. package/test/tables/vportManager.test.js +54 -0
  209. package/test/test-helpers/test-helpers.js +66 -0
  210. package/test/tools/chamfer.test.js +970 -0
  211. package/test/tools/chamferFilletBase.test.js +446 -0
  212. package/test/tools/copy.test.js +22 -14
  213. package/test/tools/copybase.test.js +93 -0
  214. package/test/tools/copyclip.test.js +92 -0
  215. package/test/tools/cornerEntity.test.js +201 -0
  216. package/test/tools/cutclip.test.js +94 -0
  217. package/test/tools/distance.test.js +140 -18
  218. package/test/tools/erase.test.js +12 -12
  219. package/test/tools/explode.test.js +45 -0
  220. package/test/tools/extend.test.js +326 -44
  221. package/test/tools/fillet.test.js +831 -0
  222. package/test/tools/identify.test.js +115 -16
  223. package/test/tools/matchProp.test.js +84 -0
  224. package/test/tools/mirror.test.js +475 -0
  225. package/test/tools/move.test.js +75 -19
  226. package/test/tools/offset.test.js +748 -0
  227. package/test/tools/pan.test.js +72 -0
  228. package/test/tools/pasteclip.test.js +100 -0
  229. package/test/tools/purge.test.js +89 -0
  230. package/test/tools/rotate.test.js +89 -23
  231. package/test/tools/scale.test.js +529 -0
  232. package/test/tools/trim.test.js +419 -84
  233. package/test/tools/zoom.test.js +504 -0
  234. package/core/entities/alignedDimension.js +0 -279
  235. package/core/entities/angularDimension.js +0 -194
  236. package/core/entities/baseDimension.js +0 -227
  237. package/core/entities/block.js +0 -247
  238. package/core/entities/diametricDimension.js +0 -133
  239. package/core/entities/dimension.js +0 -202
  240. package/core/entities/radialDimension.js +0 -126
  241. package/core/styles/dimStyle.js +0 -719
  242. package/core/styles/dimStyleManager.js +0 -47
  243. package/core/styles/layer.js +0 -133
  244. package/core/styles/layerManager.js +0 -40
  245. package/core/styles/ltype.js +0 -86
  246. package/core/styles/ltypeManager.js +0 -50
  247. package/core/styles/style.js +0 -158
  248. package/core/styles/styleManager.js +0 -43
  249. package/core/styles/styleManagerBase.js +0 -299
  250. package/test/entities/block.test.js +0 -22
  251. package/test/styles/dimstyle.test.js +0 -19
  252. package/test/styles/layerManager.test.js +0 -150
  253. package/test/styles/style.test.js +0 -33
  254. package/test/styles/stylemanagerbase.test.js +0 -219
@@ -0,0 +1,504 @@
1
+ import { Core } from '../../core/core/core.js';
2
+ import { DesignCore } from '../../core/designCore.js';
3
+ import { Point } from '../../core/entities/point.js';
4
+ import { Zoom } from '../../core/tools/zoom.js';
5
+ import { expect, jest } from '@jest/globals';
6
+ import { withMockInput } from '../test-helpers/test-helpers.js';
7
+
8
+ const core = new Core();
9
+
10
+ test('Test Zoom.register', () => {
11
+ const command = Zoom.register();
12
+ expect(command.command).toBe('Zoom');
13
+ expect(command.shortcut).toBe('Z');
14
+ expect(command.type).toBeUndefined();
15
+ });
16
+
17
+ test('Test Zoom.action - Window mode', () => {
18
+ // Set canvas dimensions for zoom calculations
19
+ core.canvas.width = 800;
20
+ core.canvas.height = 600;
21
+
22
+ // Set initial scale
23
+ core.canvas.matrix.scale(1, 1);
24
+
25
+ const initialScale = core.canvas.getScale();
26
+
27
+ const zoom = new Zoom();
28
+ zoom.mode = 'Window';
29
+
30
+ // Set two points for window zoom
31
+ zoom.points.push(new Point(0, 0));
32
+ zoom.points.push(new Point(100, 100));
33
+
34
+ // Perform zoom
35
+ zoom.action();
36
+
37
+ // Verify scale has changed (should be different from initial)
38
+ const finalScale = core.canvas.getScale();
39
+ expect(finalScale).not.toBe(initialScale);
40
+ expect(finalScale).toBeGreaterThan(0);
41
+ });
42
+
43
+ test('Test Zoom.action - Window mode with reversed points', () => {
44
+ // Set canvas dimensions
45
+ core.canvas.width = 800;
46
+ core.canvas.height = 600;
47
+ core.canvas.matrix.scale(1, 1);
48
+
49
+ const initialScale = core.canvas.getScale();
50
+
51
+ const zoom = new Zoom();
52
+ zoom.mode = 'Window';
53
+
54
+ // Points in reverse order (should still work)
55
+ zoom.points.push(new Point(100, 100));
56
+ zoom.points.push(new Point(0, 0));
57
+
58
+ zoom.action();
59
+
60
+ const finalScale = core.canvas.getScale();
61
+ expect(finalScale).not.toBe(initialScale);
62
+ expect(finalScale).toBeGreaterThan(0);
63
+ });
64
+
65
+ test('Test Zoom.action - Window mode with single point (should not zoom)', () => {
66
+ core.canvas.width = 800;
67
+ core.canvas.height = 600;
68
+ core.canvas.matrix.scale(1, 1);
69
+
70
+ const initialScale = core.canvas.getScale();
71
+
72
+ const zoom = new Zoom();
73
+ zoom.mode = 'Window';
74
+
75
+ // Only one point - action should not zoom
76
+ zoom.points.push(new Point(0, 0));
77
+
78
+ zoom.action();
79
+
80
+ // Scale should remain the same
81
+ const finalScale = core.canvas.getScale();
82
+ expect(finalScale).toBe(initialScale);
83
+ });
84
+
85
+ test('Test Zoom.action - All mode (calls zoomExtents)', () => {
86
+ // Add items to scene so extents exist
87
+ DesignCore.Scene.addItem('Line', { points: [new Point(10, 10), new Point(100, 100)] });
88
+ DesignCore.Scene.addItem('Circle', { points: [new Point(50, 50), new Point(60, 60)] });
89
+
90
+ core.canvas.width = 800;
91
+ core.canvas.height = 600;
92
+ core.canvas.matrix.scale(1, 1);
93
+
94
+ const initialScale = core.canvas.getScale();
95
+
96
+ // zoomExtents should change the scale
97
+ core.canvas.zoomExtents();
98
+
99
+ const finalScale = core.canvas.getScale();
100
+ expect(finalScale).not.toBe(initialScale);
101
+ expect(finalScale).toBeGreaterThan(0);
102
+ });
103
+
104
+ test('Test Zoom.preview - Window mode with one point', () => {
105
+ const zoom = new Zoom();
106
+ zoom.mode = 'Window';
107
+ zoom.points.push(new Point(10, 10));
108
+
109
+ // Mock mouse position
110
+ core.mouse.mouseMoved(50, 50);
111
+
112
+ const initialTempItemsCount = DesignCore.Scene.tempEntities.count();
113
+
114
+ zoom.preview();
115
+
116
+ // Should have added a polyline (rectangle preview)
117
+ expect(DesignCore.Scene.tempEntities.count()).toBe(initialTempItemsCount + 1);
118
+
119
+ // Verify it's a polyline
120
+ const tempItem = DesignCore.Scene.tempEntities.get(DesignCore.Scene.tempEntities.count() - 1);
121
+ expect(tempItem.type).toBe('Polyline');
122
+ expect(tempItem.points.length).toBe(4); // Rectangle has 4 points (closed flag set)
123
+ });
124
+
125
+ test('Test Zoom.preview - Window mode with no points (should not preview)', () => {
126
+ const zoom = new Zoom();
127
+ zoom.mode = 'Window';
128
+
129
+ const initialTempItemsCount = DesignCore.Scene.tempEntities.count();
130
+
131
+ zoom.preview();
132
+
133
+ // Should not add anything
134
+ expect(DesignCore.Scene.tempEntities.count()).toBe(initialTempItemsCount);
135
+ });
136
+
137
+ test('Test Zoom.preview - All mode (should not preview)', () => {
138
+ const zoom = new Zoom();
139
+ zoom.mode = 'All';
140
+ zoom.points.push(new Point(10, 10));
141
+
142
+ const initialTempItemsCount = DesignCore.Scene.tempEntities.count();
143
+
144
+ zoom.preview();
145
+
146
+ // Should not add anything for All/Extents modes
147
+ expect(DesignCore.Scene.tempEntities.count()).toBe(initialTempItemsCount);
148
+ });
149
+
150
+ test('Test Canvas.zoomToWindow - valid window', () => {
151
+ core.canvas.width = 800;
152
+ core.canvas.height = 600;
153
+ core.canvas.matrix.scale(1, 1);
154
+
155
+ const initialScale = core.canvas.getScale();
156
+
157
+ const pt1 = new Point(0, 0);
158
+ const pt2 = new Point(100, 100);
159
+
160
+ core.canvas.zoomToWindow(pt1, pt2);
161
+
162
+ const finalScale = core.canvas.getScale();
163
+ expect(finalScale).not.toBe(initialScale);
164
+ expect(finalScale).toBeGreaterThan(0);
165
+ });
166
+
167
+ test('Test Canvas.zoomToWindow - reversed points', () => {
168
+ core.canvas.width = 800;
169
+ core.canvas.height = 600;
170
+ core.canvas.matrix.scale(1, 1);
171
+
172
+ const initialScale = core.canvas.getScale();
173
+
174
+ // Points in reverse order
175
+ const pt1 = new Point(100, 100);
176
+ const pt2 = new Point(0, 0);
177
+
178
+ core.canvas.zoomToWindow(pt1, pt2);
179
+
180
+ const finalScale = core.canvas.getScale();
181
+ expect(finalScale).not.toBe(initialScale);
182
+ });
183
+
184
+ test('Test Canvas.zoomToWindow - zero width (should not zoom)', () => {
185
+ core.canvas.width = 800;
186
+ core.canvas.height = 600;
187
+ core.canvas.matrix.scale(1, 1);
188
+
189
+ const initialScale = core.canvas.getScale();
190
+
191
+ // Points with same x coordinate (zero width)
192
+ const pt1 = new Point(50, 0);
193
+ const pt2 = new Point(50, 100);
194
+
195
+ core.canvas.zoomToWindow(pt1, pt2);
196
+
197
+ // Should not change scale
198
+ const finalScale = core.canvas.getScale();
199
+ expect(finalScale).toBe(initialScale);
200
+ });
201
+
202
+ test('Test Canvas.zoomToWindow - zero height (should not zoom)', () => {
203
+ core.canvas.width = 800;
204
+ core.canvas.height = 600;
205
+ core.canvas.matrix.scale(1, 1);
206
+
207
+ const initialScale = core.canvas.getScale();
208
+
209
+ // Points with same y coordinate (zero height)
210
+ const pt1 = new Point(0, 50);
211
+ const pt2 = new Point(100, 50);
212
+
213
+ core.canvas.zoomToWindow(pt1, pt2);
214
+
215
+ // Should not change scale
216
+ const finalScale = core.canvas.getScale();
217
+ expect(finalScale).toBe(initialScale);
218
+ });
219
+
220
+ test('Test Canvas.zoomToWindow - same point (should not zoom)', () => {
221
+ core.canvas.width = 800;
222
+ core.canvas.height = 600;
223
+ core.canvas.matrix.scale(1, 1);
224
+
225
+ const initialScale = core.canvas.getScale();
226
+
227
+ // Same point
228
+ const pt1 = new Point(50, 50);
229
+ const pt2 = new Point(50, 50);
230
+
231
+ core.canvas.zoomToWindow(pt1, pt2);
232
+
233
+ // Should not change scale
234
+ const finalScale = core.canvas.getScale();
235
+ expect(finalScale).toBe(initialScale);
236
+ });
237
+
238
+ test('Test Zoom.action - Object mode', () => {
239
+ // Add an object to the scene
240
+ const obj = DesignCore.Scene.addItem('Circle', { points: [new Point(20, 20), new Point(40, 40)] });
241
+
242
+ core.canvas.width = 800;
243
+ core.canvas.height = 600;
244
+ core.canvas.matrix.scale(1, 1);
245
+
246
+ const initialScale = core.canvas.getScale();
247
+
248
+ const zoom = new Zoom();
249
+ zoom.mode = 'Object';
250
+
251
+ // Select the object for zoom
252
+ DesignCore.Scene.selectionManager.addToSelectionSet(obj);
253
+
254
+ // Perform zoom to object
255
+ zoom.action();
256
+
257
+ const finalScale = core.canvas.getScale();
258
+ expect(finalScale).not.toBe(initialScale);
259
+ expect(finalScale).toBeGreaterThan(0);
260
+ });
261
+
262
+ // ─── constructor / mode defaults ──────────────────────────────────────────────
263
+
264
+ test('Zoom constructor defaults mode to Window', () => {
265
+ const zoom = new Zoom();
266
+ expect(zoom.mode).toBe('Window');
267
+ });
268
+
269
+ test('Zoom.modes contains All, Extents, Window, Object', () => {
270
+ const zoom = new Zoom();
271
+ expect(zoom.modes.ALL).toBe('All');
272
+ expect(zoom.modes.EXTENTS).toBe('Extents');
273
+ expect(zoom.modes.WINDOW).toBe('Window');
274
+ expect(zoom.modes.OBJECT).toBe('Object');
275
+ });
276
+
277
+ // ─── action: Extents mode ─────────────────────────────────────────────────────
278
+
279
+ test('Zoom.action - Extents mode calls zoomExtents', () => {
280
+ core.canvas.width = 800;
281
+ core.canvas.height = 600;
282
+ core.canvas.matrix.scale(1, 1);
283
+ core.scene.clear();
284
+ core.scene.addItem('Line', { points: [new Point(10, 10), new Point(50, 50)] });
285
+
286
+ const initialScale = core.canvas.getScale();
287
+
288
+ const zoom = new Zoom();
289
+ zoom.mode = 'Extents';
290
+ zoom.action();
291
+
292
+ const finalScale = core.canvas.getScale();
293
+ expect(finalScale).not.toBe(initialScale);
294
+ expect(finalScale).toBeGreaterThan(0);
295
+ });
296
+
297
+ // ─── action: Object mode with empty selection ─────────────────────────────────
298
+
299
+ test('Zoom.action - Object mode with empty selection does not zoom', () => {
300
+ core.canvas.width = 800;
301
+ core.canvas.height = 600;
302
+ core.canvas.matrix.scale(1, 1);
303
+ core.scene.selectionManager.reset();
304
+
305
+ const initialScale = core.canvas.getScale();
306
+
307
+ const zoom = new Zoom();
308
+ zoom.mode = 'Object';
309
+ zoom.action();
310
+
311
+ // Selection is empty — should return early without changing scale
312
+ expect(core.canvas.getScale()).toBe(initialScale);
313
+ });
314
+
315
+ // ─── execute ──────────────────────────────────────────────────────────────────
316
+
317
+ test('Zoom.execute returns early when first input is undefined', async () => {
318
+ const executeCommandSpy = jest.fn();
319
+
320
+ await withMockInput(
321
+ core.scene,
322
+ [],
323
+ async () => {
324
+ const zoom = new Zoom();
325
+ await zoom.execute();
326
+ },
327
+ { extraMethods: { executeCommand: executeCommandSpy } },
328
+ );
329
+
330
+ expect(executeCommandSpy).not.toHaveBeenCalled();
331
+ });
332
+
333
+ test('Zoom.execute Window mode: first input is a Point, collects second point, calls executeCommand', async () => {
334
+ const executeCommandSpy = jest.fn();
335
+
336
+ await withMockInput(
337
+ core.scene,
338
+ [new Point(0, 0), new Point(100, 100)],
339
+ async () => {
340
+ const zoom = new Zoom();
341
+ await zoom.execute();
342
+ },
343
+ { extraMethods: { executeCommand: executeCommandSpy } },
344
+ );
345
+
346
+ expect(executeCommandSpy).toHaveBeenCalled();
347
+ });
348
+
349
+ test('Zoom.execute Window mode: returns early when second point is undefined', async () => {
350
+ const executeCommandSpy = jest.fn();
351
+
352
+ await withMockInput(
353
+ core.scene,
354
+ [new Point(0, 0)],
355
+ async () => {
356
+ const zoom = new Zoom();
357
+ await zoom.execute();
358
+ },
359
+ { extraMethods: { executeCommand: executeCommandSpy } },
360
+ );
361
+
362
+ expect(executeCommandSpy).not.toHaveBeenCalled();
363
+ });
364
+
365
+ test('Zoom.execute Window mode: string input prompts for first point then second, calls executeCommand', async () => {
366
+ const executeCommandSpy = jest.fn();
367
+
368
+ await withMockInput(
369
+ core.scene,
370
+ ['Window', new Point(0, 0), new Point(100, 100)],
371
+ async () => {
372
+ const zoom = new Zoom();
373
+ await zoom.execute();
374
+ },
375
+ { extraMethods: { executeCommand: executeCommandSpy } },
376
+ );
377
+
378
+ expect(executeCommandSpy).toHaveBeenCalled();
379
+ });
380
+
381
+ test('Zoom.execute Window mode: returns early when prompted first point is undefined', async () => {
382
+ const executeCommandSpy = jest.fn();
383
+
384
+ await withMockInput(
385
+ core.scene,
386
+ ['Window'],
387
+ async () => {
388
+ const zoom = new Zoom();
389
+ await zoom.execute();
390
+ },
391
+ { extraMethods: { executeCommand: executeCommandSpy } },
392
+ );
393
+
394
+ expect(executeCommandSpy).not.toHaveBeenCalled();
395
+ });
396
+
397
+ test('Zoom.execute Window mode: returns early when prompted second point is undefined after string branch', async () => {
398
+ const executeCommandSpy = jest.fn();
399
+
400
+ await withMockInput(
401
+ core.scene,
402
+ ['Window', new Point(0, 0)],
403
+ async () => {
404
+ const zoom = new Zoom();
405
+ await zoom.execute();
406
+ },
407
+ { extraMethods: { executeCommand: executeCommandSpy } },
408
+ );
409
+
410
+ expect(executeCommandSpy).not.toHaveBeenCalled();
411
+ });
412
+
413
+ test('Zoom.execute All mode: calls executeCommand without extra point prompts', async () => {
414
+ const executeCommandSpy = jest.fn();
415
+
416
+ await withMockInput(
417
+ core.scene,
418
+ ['All'],
419
+ async () => {
420
+ const zoom = new Zoom();
421
+ await zoom.execute();
422
+ },
423
+ { extraMethods: { executeCommand: executeCommandSpy } },
424
+ );
425
+
426
+ expect(executeCommandSpy).toHaveBeenCalled();
427
+ });
428
+
429
+ test('Zoom.execute Extents mode: calls executeCommand without extra point prompts', async () => {
430
+ const executeCommandSpy = jest.fn();
431
+
432
+ await withMockInput(
433
+ core.scene,
434
+ ['Extents'],
435
+ async () => {
436
+ const zoom = new Zoom();
437
+ await zoom.execute();
438
+ },
439
+ { extraMethods: { executeCommand: executeCommandSpy } },
440
+ );
441
+
442
+ expect(executeCommandSpy).toHaveBeenCalled();
443
+ });
444
+
445
+ test('Zoom.execute Object mode: with existing selection calls executeCommand without prompting', async () => {
446
+ core.scene.clear();
447
+ const idx = core.scene.addItem('Line', { points: [new Point(0, 0), new Point(10, 10)] });
448
+ core.scene.selectionManager.reset();
449
+ core.scene.selectionManager.addToSelectionSet(idx);
450
+
451
+ const executeCommandSpy = jest.fn();
452
+
453
+ await withMockInput(
454
+ core.scene,
455
+ ['Object'],
456
+ async () => {
457
+ const zoom = new Zoom();
458
+ await zoom.execute();
459
+ },
460
+ { extraMethods: { executeCommand: executeCommandSpy } },
461
+ );
462
+
463
+ expect(executeCommandSpy).toHaveBeenCalled();
464
+ core.scene.selectionManager.reset();
465
+ });
466
+
467
+ test('Zoom.execute Object mode: without selection prompts for selection set, returns early when undefined', async () => {
468
+ core.scene.selectionManager.reset();
469
+
470
+ const executeCommandSpy = jest.fn();
471
+
472
+ await withMockInput(
473
+ core.scene,
474
+ ['Object'],
475
+ async () => {
476
+ const zoom = new Zoom();
477
+ await zoom.execute();
478
+ },
479
+ { extraMethods: { executeCommand: executeCommandSpy } },
480
+ );
481
+
482
+ expect(executeCommandSpy).not.toHaveBeenCalled();
483
+ });
484
+
485
+ test('Zoom.execute stores collected window points for action', async () => {
486
+ let capturedZoom;
487
+ const executeCommandSpy = jest.fn();
488
+
489
+ await withMockInput(
490
+ core.scene,
491
+ [new Point(5, 10), new Point(50, 80)],
492
+ async () => {
493
+ capturedZoom = new Zoom();
494
+ await capturedZoom.execute();
495
+ },
496
+ { extraMethods: { executeCommand: executeCommandSpy } },
497
+ );
498
+
499
+ expect(capturedZoom.points).toHaveLength(2);
500
+ expect(capturedZoom.points[0].x).toBe(5);
501
+ expect(capturedZoom.points[0].y).toBe(10);
502
+ expect(capturedZoom.points[1].x).toBe(50);
503
+ expect(capturedZoom.points[1].y).toBe(80);
504
+ });