@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,85 +1,507 @@
1
- import {Core} from '../../core/core/core.js';
2
- import {Arc} from '../../core/entities/arc.js';
3
- import {Point} from '../../core/entities/point.js';
1
+ import { jest } from '@jest/globals';
2
+ import { Strings } from '../../core/lib/strings.js';
4
3
 
5
- const core = new Core();
6
- const commandline = core.commandLine;
4
+ describe('Arc.execute input validation and notifications', () => {
5
+ let notifySpy;
6
+ beforeEach(() => {
7
+ notifySpy = jest.spyOn(DesignCore.Core, 'notify').mockImplementation(() => {});
8
+ });
9
+ afterEach(() => {
10
+ notifySpy.mockRestore();
11
+ });
7
12
 
8
- test('Test Arc.execute', () => {
9
- // Create arc - point, point, angle
10
- commandline.handleKeys('A');
11
- commandline.enterPressed();
13
+ test('Notifies when arc angle is too small (point input)', async () => {
14
+ // Three points nearly colinear (angle ~0)
15
+ const input1 = new Point(0, 0);
16
+ const input2 = new Point(10, 0);
17
+ const input3 = new Point(20, 0.0001); // Very small angle
18
+ await withMockInput(DesignCore.Scene, [input1, input2, input3, new Point(10, 10)], async () => {
19
+ const arc = new Arc({});
20
+ await arc.execute();
21
+ expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.INVALIDNUMBER));
22
+ expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.MINVALUE));
23
+ });
24
+ });
12
25
 
13
- commandline.command = '0,0';
14
- commandline.update();
15
- commandline.enterPressed();
26
+ test('Notifies when arc angle is too small (angle input)', async () => {
27
+ const input1 = new Point(0, 0);
28
+ const input2 = new Point(10, 0);
29
+ const input3 = 0.5; // degrees, too small
30
+ await withMockInput(DesignCore.Scene, [input1, input2, input3, 90], async () => {
31
+ const arc = new Arc({});
32
+ await arc.execute();
33
+ expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.INVALIDNUMBER));
34
+ expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.MINVALUE));
35
+ });
36
+ });
16
37
 
38
+ test('Notifies on invalid number input', async () => {
39
+ const input1 = new Point(0, 0);
40
+ const input2 = new Point(10, 0);
41
+ const input3 = 'not-a-number';
42
+ await withMockInput(DesignCore.Scene, [input1, input2, input3, 90], async () => {
43
+ const arc = new Arc({});
44
+ await arc.execute();
45
+ expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.INPUT));
46
+ });
47
+ });
17
48
 
18
- commandline.command = '100,0';
19
- commandline.update();
20
- commandline.enterPressed();
49
+ test('Notifies on invalid point input (duplicate)', async () => {
50
+ const input1 = new Point(0, 0);
51
+ const input2 = new Point(10, 0);
52
+ const input3 = new Point(0, 0); // Same as center
53
+ await withMockInput(DesignCore.Scene, [input1, input2, input3, new Point(10, 10)], async () => {
54
+ const arc = new Arc({});
55
+ await arc.execute();
56
+ expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.INVALIDPOINT));
57
+ });
58
+ });
21
59
 
60
+ test('Accepts valid input and does not notify errors', async () => {
61
+ const input1 = new Point(0, 0);
62
+ const input2 = new Point(10, 0);
63
+ const input3 = new Point(10, 10);
64
+ await withMockInput(DesignCore.Scene, [input1, input2, input3], async () => {
65
+ const arc = new Arc({});
66
+ await arc.execute();
67
+ // Should not notify any errors
68
+ expect(notifySpy).not.toHaveBeenCalled();
69
+ });
70
+ });
71
+ });
72
+ import { Core } from '../../core/core/core.js';
73
+ import { Arc } from '../../core/entities/arc.js';
74
+ import { Point } from '../../core/entities/point.js';
75
+ import { DesignCore } from '../../core/designCore.js';
76
+ import { AddState, RemoveState } from '../../core/lib/stateManager.js';
77
+
78
+ import { File, withMockInput } from '../test-helpers/test-helpers.js';
79
+
80
+ // initialise core
81
+ new Core();
82
+
83
+ const arcInputScenarios = [
84
+ {
85
+ desc: 'two points and an angle',
86
+ input1: new Point(0, 0),
87
+ input2: new Point(10, 0),
88
+ input3: 90, // degrees
89
+ expectedPt2: (input1, input2) => input2.rotate(input1, Math.PI / 2),
90
+ expectedStart: 0,
91
+ expectedEnd: Math.PI / 2,
92
+ },
93
+ {
94
+ desc: 'three points',
95
+ input1: new Point(0, 0),
96
+ input2: new Point(10, 0),
97
+ input3: new Point(10, 10),
98
+ expectedPt2: (input1, input2, input3) => input3,
99
+ expectedStart: 0,
100
+ expectedEnd: Math.PI / 4,
101
+ },
102
+ ];
103
+
104
+ test.each(arcInputScenarios)('Arc.execute handles $desc', async (scenario) => {
105
+ const { input1, input2, input3, expectedPt2, expectedStart, expectedEnd } = scenario;
106
+
107
+ await withMockInput(DesignCore.Scene, [input1, input2, input3, input3], async () => {
108
+ const arc = new Arc({});
109
+ await arc.execute();
22
110
 
23
- commandline.command = '100,100';
24
- commandline.update();
25
- commandline.enterPressed();
111
+ expect(arc.points.length).toBe(3);
112
+ expect(arc.points[0]).toBe(input1);
113
+ expect(arc.points[1]).toBe(input2);
114
+ const expPt2 = expectedPt2(input1, input2, input3);
115
+ expect(arc.points[2].x).toBeCloseTo(expPt2.x);
116
+ expect(arc.points[2].y).toBeCloseTo(expPt2.y);
26
117
 
27
- // TODO: work out how to test user input for commands
28
- // commented out because it fails. looks like the commands above run before the execute command because its async
29
- // need to await enter pressed or similar without affecting user experience
30
- // expect(DesignCore.Scene.items.length).toBe(1),
118
+ expect(arc.startAngle()).toBe(expectedStart);
119
+ expect(arc.endAngle()).toBeCloseTo(expectedEnd);
120
+ });
31
121
  });
32
122
 
33
123
  test('Test Arc.startAngle', () => {
34
124
  // clockwise 45 degrees 0 - 45
35
- let arc = new Arc({points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)]});
125
+ let arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)] });
36
126
  expect(arc.startAngle()).toBe(0);
37
127
 
38
128
  // clockwise 45 degrees 45 - 90
39
- arc = new Arc({points: [new Point(100, 100), new Point(170.71, 170.71), new Point(100, 200)]});
129
+ arc = new Arc({ points: [new Point(100, 100), new Point(170.71, 170.71), new Point(100, 200)] });
40
130
  expect(arc.startAngle()).toBeCloseTo(Math.PI / 4);
41
131
  });
42
132
 
43
133
  test('Test Arc.endAngle', () => {
44
134
  // clockwise 45 degrees 0 - 45
45
- let arc = new Arc({points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)]});
135
+ let arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)] });
46
136
  expect(arc.endAngle()).toBe(Math.PI / 4);
47
137
 
48
138
  // clockwise 45 degrees 45 - 90
49
- arc = new Arc({points: [new Point(100, 100), new Point(170.71, 170.71), new Point(100, 200)]});
139
+ arc = new Arc({ points: [new Point(100, 100), new Point(170.71, 170.71), new Point(100, 200)] });
50
140
  expect(arc.endAngle()).toBeCloseTo(Math.PI / 2);
51
141
  });
52
142
 
53
- test('Test Arc.closestPoint', () => {
143
+ test('Test Arc.totalAngle', () => {
54
144
  // clockwise 45 degrees 0 - 45
55
- const arc = new Arc({points: [new Point(100, 100), new Point(170.71, 170.71), new Point(200, 100)]});
145
+ let arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)], direction: 0 });
146
+ expect(arc.totalAngle).toBe(315);
147
+
148
+ // clockwise 45 degrees 45 - 90
149
+ arc = new Arc({ points: [new Point(100, 100), new Point(170.71, 170.71), new Point(100, 200)], direction: 0 });
150
+ expect(arc.totalAngle).toBeCloseTo(315);
151
+
152
+ // clockwise 90 degrees 0 - 90
153
+ arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(100, 200)], direction: 0 });
154
+ expect(arc.totalAngle).toBeCloseTo(270);
155
+
156
+ // clockwise 90 degrees 45 - 315
157
+ arc = new Arc({ points: [new Point(100, 100), new Point(170.71, 170.71), new Point(170.71, 29.29)], direction: 0 });
158
+ expect(arc.totalAngle).toBeCloseTo(90);
159
+
160
+ // clockwise 180 degrees 0 - 180
161
+ arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(0, 100)], direction: 0 });
162
+ expect(arc.totalAngle).toBeCloseTo(180);
163
+
164
+ // clockwise 180 degrees 90 - 270
165
+ arc = new Arc({ points: [new Point(100, 100), new Point(100, 200), new Point(100, 0)], direction: 0 });
166
+ expect(arc.totalAngle).toBeCloseTo(180);
167
+
168
+ // clockwise 360 degrees 0 - 360
169
+ arc = new Arc({ points: [new Point(100, 100), new Point(100, 200), new Point(100, 200)], direction: 0 });
170
+ expect(arc.totalAngle).toBeCloseTo(360);
171
+
172
+ /* counter clockwise */
173
+
174
+ // counter clockwise 45 degrees 0 - 45
175
+ arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)], direction: 1 });
176
+ expect(arc.totalAngle).toBeCloseTo(-45);
177
+
178
+ // counter clockwise 315 degrees 45 - 0
179
+ arc = new Arc({ points: [new Point(100, 100), new Point(170.71, 170.71), new Point(200, 100)], direction: 1 });
180
+ expect(arc.totalAngle).toBeCloseTo(-315);
181
+
182
+ // counter clockwise 180 degrees 270 - 90
183
+ arc = new Arc({ points: [new Point(100, 100), new Point(100, 0), new Point(100, 200)], direction: 1 });
184
+ expect(arc.totalAngle).toBeCloseTo(-180);
185
+
186
+ // counter clockwise 135 degrees 270 - 45
187
+ arc = new Arc({ points: [new Point(100, 100), new Point(100, 0), new Point(170.71, 170.71)], direction: 1 });
188
+ expect(arc.totalAngle).toBeCloseTo(-135);
189
+
190
+ // counter clockwise 270 degrees 45 - 315
191
+ arc = new Arc({ points: [new Point(100, 100), new Point(170.71, 170.71), new Point(170.71, 29.29)], direction: 1 });
192
+ expect(arc.totalAngle).toBeCloseTo(-270);
193
+
194
+ // clockwise 0 degrees 0 - 360
195
+ arc = new Arc({ points: [new Point(100, 100), new Point(100, 200), new Point(100, 200)], direction: 1 });
196
+ expect(arc.totalAngle).toBeCloseTo(-360);
197
+ });
198
+
199
+
200
+ test('Test Arc.closestPoint', () => {
201
+ // clockwise 315 degrees 0 - 45
202
+ // direction: ccw > 0, cw <= 0
203
+ let arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)], direction: 0 });
204
+ // inside
205
+ let point = new Point(128.3525, 169.4344);
206
+ let closest = arc.closestPoint(point);
207
+ expect(closest[0].x).toBeCloseTo(137.8);
208
+ expect(closest[0].y).toBeCloseTo(192.578);
209
+ expect(closest[1]).toBeCloseTo(25);
210
+
211
+ // outside
212
+ point = new Point(147.2541, 215.7240);
213
+ closest = arc.closestPoint(point);
214
+ expect(closest[0].x).toBeCloseTo(137.8);
215
+ expect(closest[0].y).toBeCloseTo(192.578);
216
+ expect(closest[1]).toBeCloseTo(25);
217
+
218
+ // counter clockwise 45 degrees 0 - 45
219
+ // direction: ccw > 0, cw <= 0
220
+ arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)], direction: 1 });
56
221
  // inside
57
- const point1 = new Point(128.3525, 169.4344);
58
- const closest1 = arc.closestPoint(point1);
59
- expect(closest1[0].x).toBeCloseTo(137.8);
60
- expect(closest1[0].y).toBeCloseTo(192.578);
61
- expect(closest1[1]).toBeCloseTo(25);
222
+ point = new Point(128.3525, 169.4344);
223
+ closest = arc.closestPoint(point);
224
+ expect(closest[0].x).toBeCloseTo(point.x);
225
+ expect(closest[0].y).toBeCloseTo(point.y);
226
+ expect(closest[1]).toBeCloseTo(Infinity);
62
227
 
63
228
  // outside
64
- const point2 = new Point(147.2541, 215.7240);
65
- const closest2 = arc.closestPoint(point2);
66
- expect(closest2[0].x).toBeCloseTo(137.8);
67
- expect(closest2[0].y).toBeCloseTo(192.578);
68
- expect(closest2[1]).toBeCloseTo(25);
229
+ point = new Point(147.2541, 215.7240);
230
+ closest = arc.closestPoint(point);
231
+ expect(closest[0].x).toBeCloseTo(point.x);
232
+ expect(closest[0].y).toBeCloseTo(point.y);
233
+ expect(closest[1]).toBeCloseTo(Infinity);
69
234
  });
70
235
 
71
236
  test('Test Arc.boundingBox', () => {
72
- // clockwise 45 degrees 45 - 0
73
- let arc = new Arc({points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)]});
237
+ // clockwise 315 degrees 0 - 45
238
+ let arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)] });
74
239
  expect(arc.boundingBox().xMin).toBeCloseTo(170.71);
75
240
  expect(arc.boundingBox().xMax).toBeCloseTo(200);
76
241
  expect(arc.boundingBox().yMin).toBeCloseTo(100);
77
242
  expect(arc.boundingBox().yMax).toBeCloseTo(170.71);
78
243
 
79
244
  // anticlockwise 45 degrees: 0 - 45
80
- arc = new Arc({points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)], direction: -1});
245
+ arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)], direction: -1 });
81
246
  expect(arc.boundingBox().xMin).toBeCloseTo(0);
82
247
  expect(arc.boundingBox().xMax).toBeCloseTo(200);
83
248
  expect(arc.boundingBox().yMin).toBeCloseTo(0);
84
249
  expect(arc.boundingBox().yMax).toBeCloseTo(200);
85
250
  });
251
+
252
+ test('Test Arc.dxf', () => {
253
+ const arc = new Arc({ handle: '1', points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)] });
254
+ let file = new File();
255
+ arc.dxf(file);
256
+ // console.log(file.contents);
257
+
258
+ const dxfString = `0
259
+ ARC
260
+ 5
261
+ 1
262
+ 100
263
+ AcDbEntity
264
+ 100
265
+ AcDbCircle
266
+ 8
267
+ 0
268
+ 6
269
+ ByLayer
270
+ 10
271
+ 100
272
+ 20
273
+ 100
274
+ 30
275
+ 0.0
276
+ 39
277
+ 2
278
+ 40
279
+ 100
280
+ 100
281
+ AcDbArc
282
+ 50
283
+ 0
284
+ 51
285
+ 45
286
+ `;
287
+
288
+ expect(file.contents).toEqual(dxfString);
289
+
290
+ // create new entity from entity data to ensure all props are loaded
291
+ const newArc = new Arc(arc);
292
+ file = new File();
293
+ newArc.dxf(file);
294
+ expect(file.contents).toEqual(dxfString);
295
+ });
296
+
297
+
298
+ test('Test Arc.toPolylinePoints', () => {
299
+ // clockwise 45 degrees 0 - 45
300
+ let arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)], direction: 0 });
301
+ let arcPoints = arc.toPolylinePoints();
302
+ expect(arcPoints[0].x).toBe(200);
303
+ expect(arcPoints[0].y).toBe(100);
304
+ expect(arcPoints[0].bulge).toBeCloseTo(-5.02734);
305
+
306
+ expect(arcPoints[1].x).toBeCloseTo(170.71);
307
+ expect(arcPoints[1].y).toBeCloseTo(170.71);
308
+ expect(arcPoints[1].bulge).toBeCloseTo(0);
309
+
310
+
311
+ // counter clockwise 45 degrees 0 - 45
312
+ arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)], direction: 1 });
313
+ arcPoints = arc.toPolylinePoints();
314
+ expect(arcPoints[0].x).toBe(200);
315
+ expect(arcPoints[0].y).toBe(100);
316
+ expect(arcPoints[0].bulge).toBeCloseTo(0.1989);
317
+
318
+ expect(arcPoints[1].x).toBeCloseTo(170.71);
319
+ expect(arcPoints[1].y).toBeCloseTo(170.71);
320
+ expect(arcPoints[1].bulge).toBeCloseTo(0);
321
+ });
322
+
323
+ test('Arc constructor handles missing/invalid data', () => {
324
+ // No points
325
+ expect(() => new Arc({})).not.toThrow();
326
+ // Invalid points
327
+ expect(() => new Arc({ points: [null, undefined, {}] })).not.toThrow();
328
+ });
329
+
330
+ test('Arc.trim returns empty for no intersections', () => {
331
+ const arc = new Arc({ points: [new Point(100, 100), new Point(200, 100), new Point(170.71, 170.71)] });
332
+ expect(arc.trim([])).toEqual([]);
333
+ expect(arc.trim()).toEqual([]);
334
+ });
335
+
336
+
337
+ test('Arc.trim returns remove + add states when trimming between two intersections', () => {
338
+ const arc = new Arc({ points: [new Point(), new Point(10, 0), new Point(-10, 0)], handle: 'B2' });
339
+ // Intersection points on the arc.
340
+ const i1 = new Point(7.71, 7.71);
341
+ const i2 = new Point(-7.71, 7.71);
342
+ const intersections = [i1, i2];
343
+
344
+ // set the mouse position to mid point of the arc
345
+ DesignCore.Mouse.pointOnScene = () => new Point(0, 10);
346
+
347
+ const changes = arc.trim(intersections);
348
+
349
+ expect(Array.isArray(changes)).toBe(true);
350
+ expect(changes.length).toBeGreaterThanOrEqual(2);
351
+
352
+ expect(changes[0]).toBeInstanceOf(AddState);
353
+ expect(changes[1]).toBeInstanceOf(AddState);
354
+ expect(changes[2]).toBeInstanceOf(RemoveState);
355
+
356
+ // Verify trimmed arcs have unique handles (not the original)
357
+ expect(changes[0].entity.handle).toBeUndefined();
358
+ expect(changes[1].entity.handle).toBeUndefined();
359
+
360
+ expect(changes[0].entity.points[0].x).toBe(0);
361
+ expect(changes[0].entity.points[0].y).toBe(0);
362
+
363
+ expect(changes[0].entity.points[1].x).toBeCloseTo(10);
364
+ expect(changes[0].entity.points[1].y).toBeCloseTo(0);
365
+ expect(changes[0].entity.points[2].x).toBeCloseTo(7.71);
366
+ expect(changes[0].entity.points[2].y).toBeCloseTo(7.71);
367
+
368
+ expect(changes[1].entity.points[1].x).toBeCloseTo(-7.71);
369
+ expect(changes[1].entity.points[1].y).toBeCloseTo(7.71);
370
+ expect(changes[1].entity.points[2].x).toBe(-10);
371
+ expect(changes[1].entity.points[2].y).toBe(0);
372
+ });
373
+
374
+ test('Arc.getRadius returns the arc radius', () => {
375
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
376
+ expect(arc.getRadius()).toBe(10);
377
+ });
378
+
379
+ test('Arc constructor loads from DXF group codes', () => {
380
+ const arc = new Arc({ points: [new Point(100, 100)], 40: 50, 50: 0, 51: 90 });
381
+ expect(arc.radius).toBe(50);
382
+ expect(arc.points[1].x).toBeCloseTo(150);
383
+ expect(arc.points[1].y).toBeCloseTo(100);
384
+ expect(arc.points[2].x).toBeCloseTo(100);
385
+ expect(arc.points[2].y).toBeCloseTo(150);
386
+ });
387
+
388
+ test('Arc constructor loads direction from data', () => {
389
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)], direction: -1 });
390
+ expect(arc.direction).toBe(-1);
391
+ });
392
+
393
+ test('Arc.toPolylinePoints handles a full circle (360 degrees)', () => {
394
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(10, 0)], direction: 1 });
395
+ const pts = arc.toPolylinePoints();
396
+ expect(pts).toHaveLength(3);
397
+ expect(pts[0].bulge).toBe(1);
398
+ expect(pts[1].bulge).toBe(1);
399
+ expect(pts[2].x).toBeCloseTo(pts[0].x);
400
+ expect(pts[2].y).toBeCloseTo(pts[0].y);
401
+ });
402
+
403
+ test('Arc.preview does not throw with 1, 2 or 3 points', () => {
404
+ const origCreate = DesignCore.Scene.tempEntities.create;
405
+ const origMouse = DesignCore.Mouse.pointOnScene;
406
+ DesignCore.Scene.tempEntities.create = () => {};
407
+ DesignCore.Mouse.pointOnScene = () => new Point(5, 5);
408
+
409
+ const arc1 = new Arc({});
410
+ arc1.points = [new Point(0, 0)];
411
+ expect(() => arc1.preview()).not.toThrow();
412
+
413
+ const arc2 = new Arc({});
414
+ arc2.points = [new Point(0, 0), new Point(10, 0)];
415
+ expect(() => arc2.preview()).not.toThrow();
416
+
417
+ const arc3 = new Arc({});
418
+ arc3.points = [new Point(0, 0), new Point(10, 0), new Point(0, 10)];
419
+ expect(() => arc3.preview()).not.toThrow();
420
+
421
+ DesignCore.Scene.tempEntities.create = origCreate;
422
+ DesignCore.Mouse.pointOnScene = origMouse;
423
+ });
424
+
425
+ test('Arc.draw does not throw (ccw and cw)', () => {
426
+ const ctx = { arc: jest.fn(), stroke: jest.fn() };
427
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)], direction: 1 });
428
+ expect(() => arc.draw(ctx, 1)).not.toThrow();
429
+ expect(ctx.arc).toHaveBeenCalledTimes(1);
430
+ expect(ctx.stroke).toHaveBeenCalledTimes(1);
431
+
432
+ const ctx2 = { arc: jest.fn(), stroke: jest.fn() };
433
+ const arcCw = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)], direction: -1 });
434
+ expect(() => arcCw.draw(ctx2, 1)).not.toThrow();
435
+ });
436
+
437
+ test('Arc.snaps returns end, centre and nearest snap points', () => {
438
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)], direction: 1 });
439
+
440
+ DesignCore.Settings.endsnap = true;
441
+ DesignCore.Settings.centresnap = true;
442
+ DesignCore.Settings.nearestsnap = false;
443
+ const snaps = arc.snaps(new Point(5, 5), 10);
444
+ expect(snaps.length).toBe(3); // start, end, centre
445
+
446
+ // nearestsnap: closestPoint returns [pnt, distance] (no third element),
447
+ // so closest[2] === true is never satisfied and no snap is pushed
448
+ DesignCore.Settings.endsnap = false;
449
+ DesignCore.Settings.centresnap = false;
450
+ DesignCore.Settings.nearestsnap = true;
451
+ const nearSnaps = arc.snaps(new Point(10, 0), 100);
452
+ expect(nearSnaps.length).toBe(0);
453
+ });
454
+
455
+ test('Arc.execute handles negative angle (clockwise)', async () => {
456
+ const input1 = new Point(0, 0);
457
+ const input2 = new Point(10, 0);
458
+ const input3 = -90; // degrees — clockwise
459
+ await withMockInput(DesignCore.Scene, [input1, input2, input3], async () => {
460
+ const arc = new Arc({});
461
+ await arc.execute();
462
+ expect(arc.points.length).toBe(3);
463
+ expect(arc.direction).toBe(-1);
464
+ });
465
+ });
466
+
467
+ test('Arc.execute notifies on angle > 360', async () => {
468
+ const notifySpy = jest.spyOn(DesignCore.Core, 'notify').mockImplementation(() => {});
469
+ await withMockInput(DesignCore.Scene, [new Point(0, 0), new Point(10, 0), 361, 90], async () => {
470
+ const arc = new Arc({});
471
+ await arc.execute();
472
+ expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Error.INVALIDNUMBER));
473
+ });
474
+ notifySpy.mockRestore();
475
+ });
476
+
477
+ test('Arc.trim returns empty when only intersection is at an endpoint', () => {
478
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(-10, 0)] });
479
+ // intersection exactly at the start point — filtered out, nothing to trim
480
+ const changes = arc.trim([new Point(10, 0)]);
481
+ expect(changes).toEqual([]);
482
+ });
483
+
484
+ test('Arc.fromPolylinePoints round-trip preserves centre, radius and angles', () => {
485
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(0, 10)] });
486
+ arc.radius = 10;
487
+ const polyPts = arc.toPolylinePoints();
488
+ const rebuilt = new Arc({});
489
+ rebuilt.fromPolylinePoints(polyPts);
490
+ expect(rebuilt.points[0].x).toBeCloseTo(0, 10);
491
+ expect(rebuilt.points[0].y).toBeCloseTo(0, 10);
492
+ expect(rebuilt.radius).toBeCloseTo(10, 10);
493
+ expect(rebuilt.startAngle()).toBeCloseTo(arc.startAngle(), 10);
494
+ expect(rebuilt.endAngle()).toBeCloseTo(arc.endAngle(), 10);
495
+ });
496
+
497
+ test('Arc.fromPolylinePoints precision drift is negligible', () => {
498
+ const arc = new Arc({ points: [new Point(50, 50), new Point(50, 50).project(0.7, 30), new Point(50, 50).project(2.1, 30)] });
499
+ arc.radius = 30;
500
+ const polyPts = arc.toPolylinePoints();
501
+ const rebuilt = new Arc({});
502
+ rebuilt.fromPolylinePoints(polyPts);
503
+ const centerDrift = Math.hypot(rebuilt.points[0].x - arc.points[0].x, rebuilt.points[0].y - arc.points[0].y);
504
+ expect(centerDrift).toBeLessThan(1e-10);
505
+ expect(Math.abs(rebuilt.radius - arc.radius)).toBeLessThan(1e-10);
506
+ });
507
+