@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
@@ -1,4 +1,4 @@
1
- import {Logging} from '../../core/lib/logging';
1
+ import { Logging } from '../../core/lib/logging';
2
2
 
3
3
  // mock console.log for tests
4
4
  const msg = 'testMessage';
@@ -1,4 +1,4 @@
1
- import {Matrix} from '../../core/lib/matrix';
1
+ import { Matrix } from '../../core/lib/matrix';
2
2
 
3
3
 
4
4
  test('Test Matrix.scale and Matrix.getScale', () => {
@@ -1,5 +1,328 @@
1
- // import {Point} from '../../entities/point.js';
2
- // import {Utils} from '../../lib/utils.js';
3
- // import {Mouse} from '../../lib/mouse';
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 { Matrix } from '../../core/lib/matrix.js';
5
+ import { expect, jest } from '@jest/globals';
4
6
 
5
- test.todo('Test mouse');
7
+ const core = new Core();
8
+ const mouse = core.mouse;
9
+
10
+ test('Test Mouse constructor defaults', () => {
11
+ expect(mouse.x).toBe(0);
12
+ expect(mouse.y).toBe(0);
13
+ expect(mouse.buttonOneDown).toBe(false);
14
+ expect(mouse.buttonTwoDown).toBe(false);
15
+ expect(mouse.buttonThreeDown).toBe(false);
16
+ expect(mouse.mouseDownCanvasPoint).toBeDefined();
17
+ expect(mouse.lastClick).toBe(0);
18
+ expect(mouse.lastButton).toBe(0);
19
+ });
20
+
21
+ test('Test Mouse.pointOnCanvas', () => {
22
+ mouse.x = 150;
23
+ mouse.y = 200;
24
+ const pt = mouse.pointOnCanvas();
25
+ expect(pt).toBeInstanceOf(Point);
26
+ expect(pt.x).toBe(150);
27
+ expect(pt.y).toBe(200);
28
+ });
29
+
30
+ test('Test Mouse.pointOnScene returns Point', () => {
31
+ mouse.x = 100;
32
+ mouse.y = 50;
33
+ const pt = mouse.pointOnScene();
34
+ expect(pt).toBeInstanceOf(Point);
35
+ expect(typeof pt.x).toBe('number');
36
+ expect(typeof pt.y).toBe('number');
37
+ });
38
+
39
+ test('Test Mouse.transformToScene and transformToCanvas are inverse', () => {
40
+ core.canvas.matrix = new Matrix();
41
+ core.canvas.height = 600;
42
+
43
+ const scenePoint = new Point(250, 300);
44
+ const canvasPoint = mouse.transformToCanvas(scenePoint);
45
+ const roundTrip = mouse.transformToScene(canvasPoint);
46
+
47
+ expect(roundTrip.x).toBeCloseTo(scenePoint.x, 5);
48
+ expect(roundTrip.y).toBeCloseTo(scenePoint.y, 5);
49
+ });
50
+
51
+ test('Test Mouse.positionString format', () => {
52
+ mouse.x = 0;
53
+ mouse.y = 0;
54
+ const str = mouse.positionString();
55
+ expect(typeof str).toBe('string');
56
+ expect(str).toMatch(/X: .+ Y: .+/);
57
+ });
58
+
59
+ test('Test Mouse.positionString returns empty string when buttonOneDown', () => {
60
+ mouse.lastClick = 0;
61
+ mouse.mouseDown(0);
62
+ expect(mouse.positionString()).toBe('');
63
+ mouse.mouseUp(0);
64
+ });
65
+
66
+ test('Test Mouse.positionString returns empty string when buttonTwoDown', () => {
67
+ mouse.lastClick = 0;
68
+ mouse.mouseDown(1);
69
+ expect(mouse.positionString()).toBe('');
70
+ mouse.mouseUp(1);
71
+ });
72
+
73
+ test('Test Mouse.positionString returns empty string when buttonThreeDown', () => {
74
+ mouse.lastClick = 0;
75
+ mouse.mouseDown(2);
76
+ expect(mouse.positionString()).toBe('');
77
+ mouse.mouseUp(2);
78
+ });
79
+
80
+ test('Test Mouse.positionString returns coordinate string when no buttons down', () => {
81
+ mouse.buttonOneDown = false;
82
+ mouse.buttonTwoDown = false;
83
+ mouse.buttonThreeDown = false;
84
+ expect(mouse.positionString()).toMatch(/X: .+ Y: .+/);
85
+ });
86
+
87
+ test('Test Mouse.mouseDown sets button states', () => {
88
+ mouse.lastClick = 0;
89
+
90
+ mouse.mouseDown(0);
91
+ expect(mouse.buttonOneDown).toBe(true);
92
+ mouse.mouseUp(0);
93
+
94
+ mouse.mouseDown(1);
95
+ expect(mouse.buttonTwoDown).toBe(true);
96
+ mouse.mouseUp(1);
97
+
98
+ mouse.mouseDown(2);
99
+ expect(mouse.buttonThreeDown).toBe(true);
100
+ mouse.mouseUp(2);
101
+ });
102
+
103
+ test('Test Mouse.mouseUp clears button states', () => {
104
+ mouse.lastClick = 0;
105
+
106
+ mouse.mouseDown(0);
107
+ mouse.mouseUp(0);
108
+ expect(mouse.buttonOneDown).toBe(false);
109
+
110
+ mouse.mouseDown(1);
111
+ mouse.mouseUp(1);
112
+ expect(mouse.buttonTwoDown).toBe(false);
113
+
114
+ mouse.mouseDown(2);
115
+ mouse.mouseUp(2);
116
+ expect(mouse.buttonThreeDown).toBe(false);
117
+ });
118
+
119
+ test('Test Mouse.mouseDown sets mouseDownCanvasPoint', () => {
120
+ mouse.x = 42;
121
+ mouse.y = 84;
122
+ mouse.lastClick = 0;
123
+ mouse.mouseDown(0);
124
+ expect(mouse.mouseDownCanvasPoint.x).toBe(42);
125
+ expect(mouse.mouseDownCanvasPoint.y).toBe(84);
126
+ mouse.mouseUp(0);
127
+ });
128
+
129
+ test('Test Mouse.mouseMoved updates position', () => {
130
+ core.canvas.height = 600;
131
+ mouse.mouseMoved(300, 400);
132
+ expect(mouse.x).toBe(300);
133
+ // y is inverted: -y + canvas.height = -400 + 600 = 200
134
+ expect(mouse.y).toBe(200);
135
+ });
136
+
137
+ test('Test Mouse.mouseMoved updates position when button is held down', () => {
138
+ core.canvas.height = 600;
139
+ mouse.lastClick = 0;
140
+ mouse.mouseDown(1); // buttonTwoDown (pan)
141
+ mouse.mouseMoved(100, 200);
142
+ expect(mouse.x).toBe(100);
143
+ expect(mouse.y).toBe(400); // -200 + 600
144
+ mouse.mouseUp(1);
145
+ });
146
+
147
+ test('Test Mouse.mouseMoved skips polar snap when a button is held down', () => {
148
+ core.canvas.height = 600;
149
+ const prevPolar = DesignCore.Settings.polar;
150
+ DesignCore.Settings.polar = true;
151
+ const polarSpy = jest.spyOn(core.scene.inputManager.snapping, 'polarSnap');
152
+ try {
153
+ mouse.lastClick = 0;
154
+ mouse.mouseDown(1);
155
+ mouse.mouseMoved(200, 300);
156
+ expect(polarSpy).not.toHaveBeenCalled();
157
+ mouse.mouseUp(1);
158
+ } finally {
159
+ DesignCore.Settings.polar = prevPolar;
160
+ polarSpy.mockRestore();
161
+ }
162
+ });
163
+
164
+ test('Test Mouse.mouseMoved skips ortho snap when a button is held down', () => {
165
+ core.canvas.height = 600;
166
+ const prevPolar = DesignCore.Settings.polar;
167
+ const prevOrtho = DesignCore.Settings.ortho;
168
+ DesignCore.Settings.polar = false;
169
+ DesignCore.Settings.ortho = true;
170
+ const orthoSpy = jest.spyOn(core.scene.inputManager.snapping, 'orthoSnap');
171
+ try {
172
+ mouse.lastClick = 0;
173
+ mouse.mouseDown(1);
174
+ mouse.mouseMoved(200, 300);
175
+ expect(orthoSpy).not.toHaveBeenCalled();
176
+ mouse.mouseUp(1);
177
+ } finally {
178
+ DesignCore.Settings.polar = prevPolar;
179
+ DesignCore.Settings.ortho = prevOrtho;
180
+ orthoSpy.mockRestore();
181
+ }
182
+ });
183
+
184
+ test('Test Mouse.setPosFromScenePoint', () => {
185
+ core.canvas.matrix = new Matrix();
186
+ core.canvas.height = 600;
187
+
188
+ const scenePoint = new Point(100, 200);
189
+ mouse.setPosFromScenePoint(scenePoint);
190
+
191
+ // Convert back to verify round-trip
192
+ const result = mouse.pointOnScene();
193
+ expect(result.x).toBeCloseTo(scenePoint.x, 5);
194
+ expect(result.y).toBeCloseTo(scenePoint.y, 5);
195
+ });
196
+
197
+ test('Test Mouse.isDoubleClick', () => {
198
+ // First click is never a double click
199
+ mouse.lastClick = 0;
200
+ expect(mouse.isDoubleClick(0)).toBe(false);
201
+
202
+ // Immediate second click should be a double click
203
+ expect(mouse.isDoubleClick(0)).toBe(true);
204
+ });
205
+
206
+ test('Test Mouse.isDoubleClick with different buttons', () => {
207
+ // Different buttons in quick succession should not be a double click
208
+ mouse.lastClick = 0;
209
+ mouse.isDoubleClick(0); // first click - left button
210
+ expect(mouse.isDoubleClick(1)).toBe(false); // second click - middle button
211
+ });
212
+
213
+ test('Test Mouse.wheel delegates to canvas', () => {
214
+ const scaleBefore = core.canvas.getScale();
215
+ mouse.wheel(1);
216
+ expect(core.canvas.getScale()).not.toBe(scaleBefore);
217
+ });
218
+
219
+ // ─── isDoubleClick ────────────────────────────────────────────────────────────
220
+
221
+ test('Test Mouse.isDoubleClick - slow second click is not a double click', () => {
222
+ const doubleClickThreshold = 250;
223
+ mouse.lastClick = 0;
224
+ mouse.isDoubleClick(0); // first click
225
+ // advance lastClick far enough back to exceed the threshold
226
+ mouse.lastClick = new Date().getTime() - doubleClickThreshold - 1;
227
+ expect(mouse.isDoubleClick(0)).toBe(false);
228
+ });
229
+
230
+ test('Test Mouse.isDoubleClick - updates lastButton', () => {
231
+ mouse.lastClick = 0;
232
+ mouse.lastButton = 0;
233
+ mouse.isDoubleClick(2);
234
+ expect(mouse.lastButton).toBe(2);
235
+ });
236
+
237
+ // ─── doubleClick ──────────────────────────────────────────────────────────────
238
+
239
+ test('Test Mouse.doubleClick delegates to Canvas.doubleClick', () => {
240
+ const spy = jest.spyOn(core.canvas, 'doubleClick').mockImplementation(() => {});
241
+ mouse.doubleClick(0);
242
+ expect(spy).toHaveBeenCalledWith(0);
243
+ spy.mockRestore();
244
+ });
245
+
246
+ test('Test Mouse.mouseDown triggers doubleClick on rapid second click', () => {
247
+ const spy = jest.spyOn(mouse, 'doubleClick').mockImplementation(() => {});
248
+ mouse.lastClick = 0;
249
+ mouse.mouseDown(0); // first click
250
+ mouse.mouseUp(0);
251
+ mouse.mouseDown(0); // immediate second click — should be double click
252
+ expect(spy).toHaveBeenCalledWith(0);
253
+ spy.mockRestore();
254
+ mouse.mouseUp(0);
255
+ });
256
+
257
+ // ─── mouseDown / mouseUp – unhandled button ───────────────────────────────────
258
+
259
+ test('Test Mouse.mouseDown with unhandled button does not change state', () => {
260
+ mouse.buttonOneDown = false;
261
+ mouse.buttonTwoDown = false;
262
+ mouse.buttonThreeDown = false;
263
+ mouse.lastClick = 0;
264
+ mouse.mouseDown(99);
265
+ expect(mouse.buttonOneDown).toBe(false);
266
+ expect(mouse.buttonTwoDown).toBe(false);
267
+ expect(mouse.buttonThreeDown).toBe(false);
268
+ });
269
+
270
+ test('Test Mouse.mouseUp with unhandled button does not change state', () => {
271
+ mouse.buttonOneDown = true;
272
+ mouse.buttonTwoDown = true;
273
+ mouse.buttonThreeDown = true;
274
+ mouse.mouseUp(99);
275
+ expect(mouse.buttonOneDown).toBe(true);
276
+ expect(mouse.buttonTwoDown).toBe(true);
277
+ expect(mouse.buttonThreeDown).toBe(true);
278
+ // restore
279
+ mouse.buttonOneDown = false;
280
+ mouse.buttonTwoDown = false;
281
+ mouse.buttonThreeDown = false;
282
+ });
283
+
284
+ // ─── mouseUp – delegates to Canvas ───────────────────────────────────────────
285
+
286
+ test('Test Mouse.mouseUp delegates to Canvas.mouseUp', () => {
287
+ mouse.lastClick = 0;
288
+ mouse.mouseDown(0);
289
+ const spy = jest.spyOn(core.canvas, 'mouseUp').mockImplementation(() => {});
290
+ mouse.mouseUp(0);
291
+ expect(spy).toHaveBeenCalledWith(0);
292
+ spy.mockRestore();
293
+ });
294
+
295
+ // ─── mouseMoved – delegates to Canvas ────────────────────────────────────────
296
+
297
+ test('Test Mouse.mouseMoved delegates to Canvas.mouseMoved', () => {
298
+ const spy = jest.spyOn(core.canvas, 'mouseMoved').mockImplementation(() => {});
299
+ mouse.mouseMoved(10, 20);
300
+ expect(spy).toHaveBeenCalled();
301
+ spy.mockRestore();
302
+ });
303
+
304
+ // ─── positionString – correct coordinate values ───────────────────────────────
305
+
306
+ test('Test Mouse.positionString reports current scene coordinates', () => {
307
+ mouse.buttonOneDown = false;
308
+ mouse.buttonTwoDown = false;
309
+ mouse.buttonThreeDown = false;
310
+ const scenePoint = mouse.pointOnScene();
311
+ const str = mouse.positionString();
312
+ expect(str).toContain(`X: ${scenePoint.x.toFixed(1)}`);
313
+ expect(str).toContain(`Y: ${scenePoint.y.toFixed(1)}`);
314
+ });
315
+
316
+ // ─── setPosFromScenePoint – x/y updated directly ─────────────────────────────
317
+
318
+ test('Test Mouse.setPosFromScenePoint sets x and y from scene coordinates', () => {
319
+ core.canvas.height = 600;
320
+
321
+ const scenePoint = new Point(123, 456);
322
+ mouse.setPosFromScenePoint(scenePoint);
323
+
324
+ // The canvas point is what transformToCanvas returns
325
+ const expected = mouse.transformToCanvas(scenePoint);
326
+ expect(mouse.x).toBeCloseTo(expected.x, 5);
327
+ expect(mouse.y).toBeCloseTo(expected.y, 5);
328
+ });
@@ -0,0 +1,105 @@
1
+ import { PatternLine, Patterns } from '../../core/lib/patterns.js';
2
+
3
+ test('Test Patterns.getPattern', () => {
4
+ const pattern = Patterns.getPattern('HONEY');
5
+ expect(pattern.length).toBe(3);
6
+ pattern.forEach((patternLine) => {
7
+ expect(patternLine instanceof PatternLine).toBe(true);
8
+ });
9
+ });
10
+
11
+ test('Test Patterns.getPattern returns cached result', () => {
12
+ const first = Patterns.getPattern('ANSI31');
13
+ const second = Patterns.getPattern('ANSI31');
14
+ expect(first).toBe(second);
15
+ });
16
+
17
+ test('Test Patterns.getPattern cache is case-insensitive', () => {
18
+ const upper = Patterns.getPattern('HONEY');
19
+ const lower = Patterns.getPattern('honey');
20
+ const mixed = Patterns.getPattern('Honey');
21
+ expect(upper).toBe(lower);
22
+ expect(upper).toBe(mixed);
23
+ });
24
+
25
+ test('Test Patterns.getPattern returns frozen pattern array and objects', () => {
26
+ const pattern = Patterns.getPattern('ANSI31');
27
+ expect(Object.isFrozen(pattern)).toBe(true);
28
+ pattern.forEach((patternLine) => {
29
+ expect(Object.isFrozen(patternLine)).toBe(true);
30
+ expect(Object.isFrozen(patternLine.dashes)).toBe(true);
31
+ });
32
+ });
33
+
34
+ test('Test Patterns.getPattern frozen cache is not corrupted by caller mutation attempts', () => {
35
+ const pattern = Patterns.getPattern('ANSI31');
36
+ const originalAngle = pattern[0].angle;
37
+ const originalDashLength = pattern[0].dashes.length;
38
+ const originalLength = pattern.length;
39
+
40
+ // In strict mode (ESM), mutating frozen objects throws TypeError
41
+ expect(() => {
42
+ pattern[0].angle = 999;
43
+ }).toThrow(TypeError);
44
+ expect(() => {
45
+ pattern[0].dashes.push(999);
46
+ }).toThrow(TypeError);
47
+ expect(() => {
48
+ pattern.push(null);
49
+ }).toThrow(TypeError);
50
+
51
+ // Cache is unaffected
52
+ const patternAgain = Patterns.getPattern('ANSI31');
53
+ expect(patternAgain[0].angle).toBe(originalAngle);
54
+ expect(patternAgain[0].dashes.length).toBe(originalDashLength);
55
+ expect(patternAgain.length).toBe(originalLength);
56
+ });
57
+
58
+
59
+ test('Test Patterns.getPatternLineCount', () => {
60
+ expect(Patterns.getPatternLineCount('HONEY')).toBe(3);
61
+ expect(Patterns.getPatternLineCount('hOnEy')).toBe(3);
62
+ expect(Patterns.getPatternLineCount('ansi34')).toBe(4);
63
+ });
64
+
65
+ test('Test Patterns.patternExists', () => {
66
+ expect(Patterns.patternExists('HONEY')).toBe(true);
67
+ expect(Patterns.patternExists('hOnEy')).toBe(true);
68
+ expect(Patterns.patternExists('ansi34')).toBe(true);
69
+
70
+ expect(Patterns.patternExists('fakeName')).toBe(false);
71
+ });
72
+
73
+
74
+ test('Test PatternLine.loadPatternLine', () => {
75
+ const patternLine = new PatternLine();
76
+ const patternString = `45, 0, 0, 0, 3.175`;
77
+
78
+ patternLine.loadPatternLine(patternString);
79
+
80
+ expect(patternLine.angle).toBe(45);
81
+ expect(patternLine.xOrigin).toBe(0);
82
+ expect(patternLine.yOrigin).toBe(0);
83
+ expect(patternLine.xDelta).toBe(0);
84
+ expect(patternLine.yDelta).toBe(3.175);
85
+ expect(patternLine.dashes.length).toBe(0);
86
+
87
+ const patternStringwithDashes = `120, 0, 0, 4.7625, 2.749630645, 3.175, -6.35 `;
88
+
89
+ patternLine.loadPatternLine(patternStringwithDashes);
90
+
91
+ expect(patternLine.angle).toBe(120);
92
+ expect(patternLine.xOrigin).toBe(0);
93
+ expect(patternLine.yOrigin).toBe(0);
94
+ expect(patternLine.xDelta).toBe(4.7625);
95
+ expect(patternLine.yDelta).toBe(2.749630645);
96
+ expect(patternLine.dashes.length).toBe(2);
97
+ expect(patternLine.dashes[0]).toBe(3.175);
98
+ expect(patternLine.dashes[1]).toBe(-6.35);
99
+ });
100
+
101
+ test('Test PatternLine.getDashLength', () => {
102
+ const patternStringwithDashes = `120, 0, 0, 4.7625, 2.749630645, 3.175, -6.35 `;
103
+ const patternLine = new PatternLine(patternStringwithDashes);
104
+ expect(patternLine.getDashLength()).toBeCloseTo(9.5249);
105
+ });
@@ -0,0 +1,179 @@
1
+ import { Core } from '../../core/core/core.js';
2
+ import { Point } from '../../core/entities/point.js';
3
+ import { Input, PromptOptions } from '../../core/lib/inputManager.js';
4
+ import { expect, jest } from '@jest/globals';
5
+
6
+ // Core must be initialised so that DesignCore.Core is available for
7
+ // PromptOptions.respond() to call Core.notify() on bad input.
8
+ const core = new Core();
9
+
10
+ // ─── Constructor ─────────────────────────────────────────────────────────────
11
+
12
+ test('constructor uses default values when no arguments are supplied', () => {
13
+ const po = new PromptOptions();
14
+ expect(po.promptMessage).toBe('error');
15
+ expect(po.types).toEqual([]);
16
+ expect(po.options).toEqual([]);
17
+ expect(po.defaultValue).toBeUndefined();
18
+ expect(po.resolve).toBeUndefined();
19
+ expect(po.reject).toBeUndefined();
20
+ });
21
+
22
+ test('constructor stores all supplied arguments', () => {
23
+ const types = [Input.Type.POINT, Input.Type.NUMBER];
24
+ const options = ['One', 'Two', 'Three'];
25
+ const po = new PromptOptions('Select a point', types, options, 42);
26
+ expect(po.promptMessage).toBe('Select a point');
27
+ expect(po.types).toBe(types);
28
+ expect(po.options).toBe(options);
29
+ expect(po.defaultValue).toBe(42);
30
+ });
31
+
32
+ test('constructor defaultValue is undefined when not provided', () => {
33
+ const po = new PromptOptions('Enter value', [Input.Type.NUMBER]);
34
+ expect(po.defaultValue).toBeUndefined();
35
+ });
36
+
37
+ // ─── acceptsInput ─────────────────────────────────────────────────────────────
38
+
39
+ test('acceptsInput returns true for exact matching type', () => {
40
+ const po = new PromptOptions('', [Input.Type.POINT]);
41
+ expect(po.acceptsInput(new Point(1, 2))).toBe(true);
42
+ expect(po.acceptsInput('text')).toBe(false);
43
+ expect(po.acceptsInput(42)).toBe(false);
44
+ });
45
+
46
+ test('acceptsInput returns true for DYNAMIC type with numeric input', () => {
47
+ const po = new PromptOptions('', [Input.Type.DYNAMIC]);
48
+ expect(po.acceptsInput(42)).toBe(true);
49
+ expect(po.acceptsInput(3.14)).toBe(true);
50
+ expect(po.acceptsInput('text')).toBe(false);
51
+ expect(po.acceptsInput(new Point())).toBe(false);
52
+ });
53
+
54
+ test('acceptsInput returns true when any of multiple types matches', () => {
55
+ const po = new PromptOptions('', [Input.Type.POINT, Input.Type.NUMBER]);
56
+ expect(po.acceptsInput(new Point(1, 2))).toBe(true);
57
+ expect(po.acceptsInput(42)).toBe(true);
58
+ expect(po.acceptsInput('text')).toBe(false);
59
+ });
60
+
61
+ // ─── createPromise ────────────────────────────────────────────────────────────
62
+
63
+ test('createPromise returns a Promise and sets resolve and reject', async () => {
64
+ const po = new PromptOptions('', [Input.Type.STRING]);
65
+ const p = po.createPromise();
66
+ expect(p).toBeInstanceOf(Promise);
67
+ expect(typeof po.resolve).toBe('function');
68
+ expect(typeof po.reject).toBe('function');
69
+ po.resolve('done');
70
+ await expect(p).resolves.toBe('done');
71
+ });
72
+
73
+ // ─── respond ─────────────────────────────────────────────────────────────────
74
+
75
+ test('respond returns true and resolves the promise when input matches accepted type', async () => {
76
+ const po = new PromptOptions('Enter text', [Input.Type.STRING]);
77
+ const p = po.createPromise();
78
+ const result = po.respond('hello');
79
+ expect(result).toBe(true);
80
+ await expect(p).resolves.toBe('hello');
81
+ });
82
+
83
+ test('respond returns true and resolves the promise when input matches an option', async () => {
84
+ const po = new PromptOptions('Choose', [Input.Type.POINT], ['Yes', 'No']);
85
+ const p = po.createPromise();
86
+ const result = po.respond('Y');
87
+ expect(result).toBe(true);
88
+ await expect(p).resolves.toBe('Yes');
89
+ });
90
+
91
+ test('respond returns false and calls Core.notify when input is invalid', () => {
92
+ const notifySpy = jest.spyOn(core, 'notify').mockImplementation(() => {});
93
+ const po = new PromptOptions('Enter point', [Input.Type.POINT]);
94
+ po.createPromise();
95
+ const result = po.respond('invalid string');
96
+ expect(result).toBe(false);
97
+ expect(notifySpy).toHaveBeenCalled();
98
+ notifySpy.mockRestore();
99
+ po.cancel();
100
+ });
101
+
102
+ // ─── parseInputToOption ───────────────────────────────────────────────────────
103
+
104
+ test('parseInputToOption returns undefined when no options are defined', () => {
105
+ const po = new PromptOptions('Enter value', [Input.Type.STRING]);
106
+ expect(po.parseInputToOption('A')).toBeUndefined();
107
+ expect(po.parseInputToOption('X')).toBeUndefined();
108
+ });
109
+
110
+ test('parseInputToOption returns undefined for non-string input', () => {
111
+ const po = new PromptOptions('Choose', [], ['Apple']);
112
+ expect(po.parseInputToOption(42)).toBeUndefined();
113
+ expect(po.parseInputToOption(new Point())).toBeUndefined();
114
+ });
115
+
116
+ test('parseInputToOption matches options by case-insensitive prefix', () => {
117
+ const po = new PromptOptions('Select option', [Input.Type.STRING], ['Apple', 'Banana', 'Cherry']);
118
+ expect(po.parseInputToOption('A')).toBe('Apple');
119
+ expect(po.parseInputToOption('B')).toBe('Banana');
120
+ expect(po.parseInputToOption('C')).toBe('Cherry');
121
+ expect(po.parseInputToOption('a')).toBe('Apple');
122
+ expect(po.parseInputToOption('Apple')).toBe('Apple');
123
+ });
124
+
125
+ test('parseInputToOption returns undefined when no option matches', () => {
126
+ const po = new PromptOptions('Choose', [], ['Apple', 'Banana']);
127
+ expect(po.parseInputToOption('X')).toBeUndefined();
128
+ });
129
+
130
+ // ─── cancel ──────────────────────────────────────────────────────────────────
131
+
132
+ test('cancel resolves the pending promise with undefined', async () => {
133
+ const po = new PromptOptions('Enter point', [Input.Type.POINT]);
134
+ const p = po.createPromise();
135
+ po.cancel();
136
+ await expect(p).resolves.toBeUndefined();
137
+ });
138
+
139
+ test('cancel is a no-op when no promise has been created', () => {
140
+ const po = new PromptOptions('', [Input.Type.STRING]);
141
+ expect(() => po.cancel()).not.toThrow();
142
+ });
143
+
144
+ // ─── getPrompt ────────────────────────────────────────────────────────────────
145
+
146
+ test('getPrompt returns bare message when no options or defaultValue are set', () => {
147
+ const po = new PromptOptions('Enter value', [Input.Type.NUMBER]);
148
+ expect(po.getPrompt()).toBe('Enter value');
149
+ });
150
+
151
+ test('getPrompt includes OR keyword and underscored shortcuts when options are set', () => {
152
+ const po = new PromptOptions('Select', [Input.Type.STRING], ['Apple', 'Banana']);
153
+ const prompt = po.getPrompt();
154
+ expect(prompt).toContain('Select');
155
+ expect(prompt).toContain('or');
156
+ expect(prompt).toContain('A\u0332pple');
157
+ expect(prompt).toContain('B\u0332anana');
158
+ });
159
+
160
+ test('getPrompt appends <defaultValue> when defaultValue is set', () => {
161
+ const po = new PromptOptions('Enter distance', [Input.Type.NUMBER], [], 10);
162
+ expect(po.getPrompt()).toBe('Enter distance <10>');
163
+ });
164
+
165
+ test('getPrompt appends <defaultValue> after the options bracket when both are set', () => {
166
+ const po = new PromptOptions('Select', [Input.Type.STRING], ['Apple', 'Banana'], 'Apple');
167
+ const prompt = po.getPrompt();
168
+ expect(prompt).toContain('A\u0332pple');
169
+ expect(prompt).toMatch(/<Apple>$/);
170
+ });
171
+
172
+ // ─── getOptionWithShortcut ────────────────────────────────────────────────────
173
+
174
+ test('getOptionWithShortcut underlines the first character of the option', () => {
175
+ const po = new PromptOptions();
176
+ expect(po.getOptionWithShortcut('input')).toBe('i\u0332nput');
177
+ expect(po.getOptionWithShortcut('Through')).toBe('T\u0332hrough');
178
+ expect(po.getOptionWithShortcut('A')).toBe('A\u0332');
179
+ });