@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,27 +1,26 @@
1
- import {Core} from '../../core/core/core.js';
2
- import {Line} from '../../core/entities/line';
3
- import {Point} from '../../core/entities/point';
1
+ import { Core } from '../../core/core/core.js';
2
+ import { Line } from '../../core/entities/line';
3
+ import { Point } from '../../core/entities/point';
4
4
 
5
5
 
6
6
  const core = new Core();
7
7
  const scene = core.scene;
8
8
 
9
-
10
9
  test('Test Scene.boundingBox', () => {
11
10
  // empty scene
12
11
  expect(scene.boundingBox()).toBeUndefined();
13
12
 
14
- const line1 = new Line({points: [new Point(-102, -102), new Point(201, 202)]});
13
+ const line1 = new Line({ points: [new Point(-102, -102), new Point(201, 202)] });
15
14
  // add to scene (bypass scene.addItem())
16
- scene.items.push(line1);
15
+ scene.addItem('Line', line1);
17
16
  expect(scene.boundingBox().xMin).toBeCloseTo(-102);
18
17
  expect(scene.boundingBox().xMax).toBeCloseTo(201);
19
18
  expect(scene.boundingBox().yMin).toBeCloseTo(-102);
20
19
  expect(scene.boundingBox().yMax).toBeCloseTo(202);
21
20
 
22
- const line2 = new Line({points: [new Point(1001, -2002), new Point(-2001, 2002)]});
21
+ const line2 = new Line({ points: [new Point(1001, -2002), new Point(-2001, 2002)] });
23
22
  // add to scene (bypass scene.addItem())
24
- scene.items.push(line2);
23
+ scene.addItem('Line', line2);
25
24
  expect(scene.boundingBox().xMin).toBeCloseTo(-2001);
26
25
  expect(scene.boundingBox().xMax).toBeCloseTo(1001);
27
26
  expect(scene.boundingBox().yMin).toBeCloseTo(-2002);
@@ -36,56 +35,21 @@ test('Test Scene.addItem', () => {
36
35
  }).toThrow();
37
36
 
38
37
  // Add a invalid item type
39
- const itemCnt = scene.items.length;
38
+ const itemCnt = scene.entities.count();
40
39
  const returnIndex = scene.addItem('FakeItem', {});
41
- expect(scene.items.length).toBe(itemCnt);
40
+ expect(scene.entities.count()).toBe(itemCnt);
42
41
  expect(returnIndex).toBeUndefined();
43
42
 
44
43
  // Add a valid item with data
45
- const itemCount = scene.items.length;
46
- const index = scene.addItem('Line', {points: [new Point(), new Point(10, 10)]});
47
- expect(scene.items.length).toBe(itemCount + 1);
44
+ const itemCount = scene.entities.count();
45
+ const index = scene.addItem('Line', { points: [new Point(), new Point(10, 10)] });
46
+ expect(scene.entities.count()).toBe(itemCount + 1);
48
47
  expect(index).toBe(itemCount);
49
48
 
50
49
  // Add a valid item with data at index - item count should not increase
51
- scene.addItem('Line', {points: [new Point(123, 123), new Point(321, 321)]}, index);
52
- expect(scene.items.length).toBe(itemCount + 1);
50
+ scene.addItem('Line', { points: [new Point(123, 123), new Point(321, 321)] }, index);
51
+ expect(scene.entities.count()).toBe(itemCount + 1);
53
52
  // check the items points to make sure index was replaced
54
- expect(scene.items[index].points[0].x).toBe(123);
55
- expect(scene.items[index].points[1].x).toBe(321);
56
- });
57
-
58
- test('Test Scene.findItem', () => {
59
- // Add a circle with a known radius
60
- const index = scene.addItem('Circle', {points: [new Point(543, 543)], radius: 123});
61
- // find the item
62
- const foundItems = scene.findItem('Circle', 'radius', 123);
63
- expect(foundItems[0]).toBe(index);
64
- expect(scene.items[index].points[0].x).toBe(543);
65
- expect(scene.items[index].radius).toBe(123);
66
- });
67
-
68
- test('Test Scene.getItem', () => {
69
- // Add a valid item with data at index - item count should not increase
70
- const index = 0;
71
- scene.addItem('Line', {points: [new Point(1234, 1234), new Point(4321, 4321)]}, index);
72
- // check the items points to make sure index was replaced
73
- expect(scene.getItem(index).points[0].x).toBe(1234);
74
- expect(scene.getItem(index).points[1].x).toBe(4321);
75
- });
76
-
77
-
78
- test('Test Scene.removeItem', () => {
79
- // Remove an existing index
80
- const itemCount = scene.items.length;
81
- const index = 0;
82
- const removed = scene.removeItem(index);
83
- expect(removed).toBe(true);
84
- expect(scene.items.length).toBe(itemCount - 1);
85
-
86
- // Remove a non-existing index
87
- const itemCount2 = scene.items.length;
88
- const notRemoved = scene.removeItem(100);
89
- expect(notRemoved).toBe(false);
90
- expect(scene.items.length).toBe(itemCount2);
53
+ expect(scene.entities.get(index).points[0].x).toBe(123);
54
+ expect(scene.entities.get(index).points[1].x).toBe(321);
91
55
  });
@@ -1,13 +1,13 @@
1
- import {Core} from '../../core/core/core.js';
2
- import {Point} from '../../core/entities/point.js';
1
+ import { Core } from '../../core/core/core.js';
2
+ import { Point } from '../../core/entities/point.js';
3
3
 
4
4
  const core = new Core();
5
5
  const selectionManager = core.scene.selectionManager;
6
6
 
7
7
  // Add a valid item to scene
8
- core.scene.addItem('Line', {points: [new Point(10, 10), new Point(100, 10)]});
9
- core.scene.addItem('Line', {points: [new Point(10, 20), new Point(100, 20)]});
10
- core.scene.addItem('Circle', {points: [new Point(), new Point(5, 0)]});
8
+ core.scene.addItem('Line', { points: [new Point(10, 10), new Point(100, 10)] });
9
+ core.scene.addItem('Line', { points: [new Point(10, 20), new Point(100, 20)] });
10
+ core.scene.addItem('Circle', { points: [new Point(), new Point(5, 0)] });
11
11
 
12
12
 
13
13
  test('Test SelectionManager.findClosestItem', () => {
@@ -59,3 +59,15 @@ test('Test SelectionManager.reset()', () => {
59
59
  expect(selectionManager.selectionSet.selectionSet.length).toBe(0);
60
60
  });
61
61
 
62
+ test('Test SelectionManager.selectAll()', () => {
63
+ // Start with some selections
64
+ selectionManager.reset();
65
+ expect(selectionManager.selectionSet.selectionSet.length).toBe(0);
66
+
67
+ // Select all
68
+ selectionManager.selectAll();
69
+ expect(selectionManager.selectionSet.selectionSet.length).toBe(core.scene.entities.count());
70
+ // The indices should match all scene items
71
+ expect(selectionManager.selectionSet.selectionSet).toEqual([0, 1, 2]);
72
+ });
73
+
@@ -1,4 +1,4 @@
1
- import {Core} from '../../core/core/core.js';
1
+ import { Core } from '../../core/core/core.js';
2
2
 
3
3
  const core = new Core();
4
4
  const settings = core.settings;
@@ -8,12 +8,118 @@ test('Test Settings.setSetting', () => {
8
8
  settings.setSetting('endsnap', false);
9
9
  expect(settings.endsnap).toBe(false);
10
10
 
11
- expect(settings.canvasbackgroundcolour).toBe('#1e1e1e');
12
- settings.setSetting('canvasbackgroundcolour', '#000000');
13
- expect(settings.canvasbackgroundcolour).toBe('#000000');
11
+ expect(settings.canvasbackgroundcolour).toEqual({ 'r': 30, 'g': 30, 'b': 30 });
12
+ settings.setSetting('canvasbackgroundcolour', { 'r': 130, 'g': 130, 'b': 130 });
13
+ expect(settings.canvasbackgroundcolour).toEqual({ 'r': 130, 'g': 130, 'b': 130 });
14
14
  });
15
15
 
16
16
  test('Test Settings.getSetting', () => {
17
17
  expect(settings.getSetting('endsnap')).toBe(false);
18
- expect(settings.getSetting('canvasbackgroundcolour')).toBe('#000000');
18
+ expect(settings.getSetting('canvasbackgroundcolour')).toEqual({ 'r': 130, 'g': 130, 'b': 130 });
19
+ });
20
+
21
+ test('polar and ortho are mutually exclusive', () => {
22
+ settings.setSetting('polar', false);
23
+ settings.setSetting('ortho', false);
24
+
25
+ settings.setSetting('polar', true);
26
+ expect(settings.polar).toBe(true);
27
+ expect(settings.ortho).toBe(false);
28
+
29
+ settings.setSetting('ortho', true);
30
+ expect(settings.ortho).toBe(true);
31
+ expect(settings.polar).toBe(false);
32
+
33
+ settings.setSetting('ortho', false);
34
+ expect(settings.ortho).toBe(false);
35
+ expect(settings.polar).toBe(false);
36
+ });
37
+
38
+ // ─── constructor defaults ─────────────────────────────────────────────────────
39
+
40
+ test('Settings constructor - colour defaults', () => {
41
+ const fresh = new (settings.constructor)();
42
+ expect(fresh.canvasbackgroundcolour).toEqual({ r: 30, g: 30, b: 30 });
43
+ expect(fresh.selecteditemscolour).toEqual({ r: 0, g: 255, b: 0 });
44
+ expect(fresh.snapcolour).toEqual({ r: 255, g: 0, b: 0 });
45
+ expect(fresh.gridcolour).toEqual({ r: 119, g: 118, b: 123 });
46
+ expect(fresh.helpergeometrycolour).toEqual({ r: 0, g: 195, b: 255 });
47
+ expect(fresh.polarsnapcolour).toEqual({ r: 55, g: 180, b: 75 });
48
+ expect(fresh.selectionWindow).toEqual({ r: 0, g: 255, b: 0 });
49
+ expect(fresh.crossingWindow).toEqual({ r: 0, g: 0, b: 255 });
50
+ });
51
+
52
+ test('Settings constructor - font defaults', () => {
53
+ expect(settings.font).toBe('Arial');
54
+ expect(settings.fontupsidedown).toBe(false);
55
+ expect(settings.fontbackwards).toBe(false);
56
+ });
57
+
58
+ test('Settings constructor - snap defaults', () => {
59
+ expect(settings.endsnap).toBeDefined();
60
+ expect(settings.midsnap).toBe(true);
61
+ expect(settings.centresnap).toBe(true);
62
+ expect(settings.nearestsnap).toBe(false);
63
+ expect(settings.quadrantsnap).toBe(false);
64
+ });
65
+
66
+ test('Settings constructor - polar/ortho/grid defaults', () => {
67
+ // restore to known state after previous tests may have modified them
68
+ const fresh = new (settings.constructor)();
69
+ expect(fresh.polar).toBe(true);
70
+ expect(fresh.ortho).toBe(false);
71
+ expect(fresh.polarangle).toBe(45);
72
+ expect(fresh.drawgrid).toBe(true);
73
+ });
74
+
75
+ // ─── setSetting ───────────────────────────────────────────────────────────────
76
+
77
+ test('setSetting with false value does not trigger polar/ortho exclusion', () => {
78
+ settings.setSetting('polar', true);
79
+ settings.setSetting('ortho', false);
80
+ // setting polar to false should not flip ortho
81
+ settings.setSetting('polar', false);
82
+ expect(settings.polar).toBe(false);
83
+ expect(settings.ortho).toBe(false);
84
+ });
85
+
86
+ test('setSetting updates arbitrary colour settings', () => {
87
+ settings.setSetting('polarsnapcolour', { r: 1, g: 2, b: 3 });
88
+ expect(settings.polarsnapcolour).toEqual({ r: 1, g: 2, b: 3 });
89
+ settings.setSetting('polarsnapcolour', { r: 55, g: 180, b: 75 });
90
+ });
91
+
92
+ test('setSetting updates polarangle', () => {
93
+ settings.setSetting('polarangle', 90);
94
+ expect(settings.polarangle).toBe(90);
95
+ settings.setSetting('polarangle', 45);
96
+ });
97
+
98
+ test('setSetting updates font settings', () => {
99
+ settings.setSetting('font', 'Courier');
100
+ expect(settings.font).toBe('Courier');
101
+ settings.setSetting('fontupsidedown', true);
102
+ expect(settings.fontupsidedown).toBe(true);
103
+ settings.setSetting('fontbackwards', true);
104
+ expect(settings.fontbackwards).toBe(true);
105
+ // restore
106
+ settings.setSetting('font', 'Arial');
107
+ settings.setSetting('fontupsidedown', false);
108
+ settings.setSetting('fontbackwards', false);
109
+ });
110
+
111
+ // ─── getSetting ───────────────────────────────────────────────────────────────
112
+
113
+ test('getSetting returns current value after setSetting', () => {
114
+ settings.setSetting('drawgrid', false);
115
+ expect(settings.getSetting('drawgrid')).toBe(false);
116
+ settings.setSetting('drawgrid', true);
117
+ expect(settings.getSetting('drawgrid')).toBe(true);
118
+ });
119
+
120
+ test('getSetting returns correct type for colour objects', () => {
121
+ const colour = settings.getSetting('snapcolour');
122
+ expect(colour).toHaveProperty('r');
123
+ expect(colour).toHaveProperty('g');
124
+ expect(colour).toHaveProperty('b');
19
125
  });
@@ -1,12 +1,22 @@
1
- import {Core} from '../../core/core/core.js';
2
- import {Snapping} from '../../core/lib/snapping.js';
3
- import {Point} from '../../core/entities/point.js';
1
+ import { jest } from '@jest/globals';
2
+ import { Core } from '../../core/core/core.js';
3
+ import { Snapping } from '../../core/lib/snapping.js';
4
+ import { Point } from '../../core/entities/point.js';
5
+ import { DesignCore } from '../../core/designCore.js';
4
6
 
5
7
  const core = new Core();
6
8
  const snapping = new Snapping();
7
9
 
8
10
  // Add a valid item to scene
9
- core.scene.addItem('Line', {points: [new Point(10, 10), new Point(100, 10)]});
11
+ core.scene.addItem('Line', { points: [new Point(10, 10), new Point(100, 10)] });
12
+
13
+ afterEach(() => {
14
+ // Reset all mouse button states so tests don't affect each other via
15
+ // Canvas.mouseMoved() / InputManager.mouseMoved() behaviour
16
+ core.mouse.buttonOneDown = false;
17
+ core.mouse.buttonTwoDown = false;
18
+ core.mouse.buttonThreeDown = false;
19
+ });
10
20
 
11
21
 
12
22
  test('Test Snapping.getSnapPoint', () => {
@@ -31,6 +41,98 @@ test('Test Snapping.getSnapPoint', () => {
31
41
  expect(snapping.getSnapPoint()).toBeUndefined();
32
42
  });
33
43
 
44
+ test('Test Snapping.snap active', () => {
45
+ DesignCore.Scene.auxiliaryEntities.clear();
46
+ snapping.active = true;
47
+ // mouse near (10, 10) endpoint of the line
48
+ core.mouse.mouseMoved(8, 8);
49
+ const result = snapping.snap();
50
+ expect(result).toBeDefined();
51
+ expect(result.x).toBe(10);
52
+ expect(result.y).toBe(10);
53
+ expect(DesignCore.Scene.auxiliaryEntities.count()).toBeGreaterThan(0);
54
+ });
55
+
56
+ test('Test Snapping.snap inactive', () => {
57
+ snapping.active = false;
58
+ core.mouse.mouseMoved(8, 8);
59
+ expect(snapping.snap()).toBeUndefined();
60
+ });
61
+
62
+ test('Test Snapping.addSnapPoint', () => {
63
+ DesignCore.Scene.auxiliaryEntities.clear();
64
+ snapping.addSnapPoint(new Point(10, 10));
65
+ expect(DesignCore.Scene.auxiliaryEntities.count()).toBe(1);
66
+ });
67
+
68
+ test('Test SnapPoint.draw', () => {
69
+ DesignCore.Scene.auxiliaryEntities.clear();
70
+ snapping.addSnapPoint(new Point(0.5, 0.5));
71
+ const snapPointEntity = DesignCore.Scene.auxiliaryEntities.get(0);
72
+ const ctx = {
73
+ fillStyle: '',
74
+ beginPath: jest.fn(),
75
+ arc: jest.fn(),
76
+ fill: jest.fn(),
77
+ };
78
+ snapPointEntity.draw(ctx, 1);
79
+ expect(ctx.beginPath).toHaveBeenCalled();
80
+ expect(ctx.arc).toHaveBeenCalledWith(0.5, 0.5, expect.any(Number), 0, 6.283);
81
+ expect(ctx.fill).toHaveBeenCalled();
82
+ });
83
+
84
+ test('Test Snapping.addTrackingLine', () => {
85
+ DesignCore.Scene.auxiliaryEntities.clear();
86
+ snapping.addTrackingLine(new Point(0.2, 0.5), new Point(0.8, 0.5));
87
+ expect(DesignCore.Scene.auxiliaryEntities.count()).toBe(1);
88
+ });
89
+
90
+ test('Test TrackingLine.draw', () => {
91
+ DesignCore.Scene.auxiliaryEntities.clear();
92
+ // horizontal tracking line within the default scene bounds ([0,1] x [-1,0])
93
+ // note: the default matrix has d=-1, so scene y is in [-1, 0] for a 1x1 canvas
94
+ snapping.addTrackingLine(new Point(0.2, -0.5), new Point(0.8, -0.5));
95
+ const trackingLine = DesignCore.Scene.auxiliaryEntities.get(0);
96
+ const ctx = {
97
+ save: jest.fn(),
98
+ restore: jest.fn(),
99
+ beginPath: jest.fn(),
100
+ strokeStyle: '',
101
+ lineWidth: 0,
102
+ setLineDash: jest.fn(),
103
+ moveTo: jest.fn(),
104
+ lineTo: jest.fn(),
105
+ stroke: jest.fn(),
106
+ };
107
+ trackingLine.draw(ctx, 1);
108
+ expect(ctx.save).toHaveBeenCalled();
109
+ expect(ctx.moveTo).toHaveBeenCalled();
110
+ expect(ctx.lineTo).toHaveBeenCalled();
111
+ expect(ctx.stroke).toHaveBeenCalled();
112
+ expect(ctx.restore).toHaveBeenCalled();
113
+ });
114
+
115
+ test('Test TrackingLine.draw degenerate zero direction', () => {
116
+ DesignCore.Scene.auxiliaryEntities.clear();
117
+ // inputPoint equals snapPoint → direction is zero vector → draw returns early
118
+ snapping.addTrackingLine(new Point(0.5, 0.5), new Point(0.5, 0.5));
119
+ const trackingLine = DesignCore.Scene.auxiliaryEntities.get(0);
120
+ const ctx = {
121
+ save: jest.fn(),
122
+ restore: jest.fn(),
123
+ beginPath: jest.fn(),
124
+ strokeStyle: '',
125
+ lineWidth: 0,
126
+ setLineDash: jest.fn(),
127
+ moveTo: jest.fn(),
128
+ lineTo: jest.fn(),
129
+ stroke: jest.fn(),
130
+ };
131
+ trackingLine.draw(ctx, 1);
132
+ expect(ctx.moveTo).not.toHaveBeenCalled();
133
+ expect(ctx.stroke).not.toHaveBeenCalled();
134
+ });
135
+
34
136
  test('Test Snapping.polarSnap', () => {
35
137
  // set the mouse position
36
138
  // note mouseMoved flips the y axis
@@ -52,6 +154,42 @@ test('Test Snapping.polarSnap', () => {
52
154
  // expect(polarSnapOne.y).toBeCloseTo(100);
53
155
  });
54
156
 
157
+ test('Test Snapping.polarSnap within tolerance', () => {
158
+ snapping.active = true;
159
+ // Mouse at (100, 2) from previous (0, 0): angle ≈ 1.15° which is within 4° tolerance of 0°
160
+ core.mouse.mouseMoved(100, 2);
161
+ const snapPoint = snapping.polarSnap(new Point(0, 0));
162
+ expect(snapPoint).toBeDefined();
163
+ // Snapped to 0° direction → y should be approximately 0
164
+ expect(snapPoint.y).toBeCloseTo(0, 0);
165
+ });
166
+
167
+ test('Test Snapping.polarSnap inactive', () => {
168
+ snapping.active = false;
169
+ core.mouse.mouseMoved(100, 2);
170
+ expect(snapping.polarSnap(new Point(0, 0))).toBeUndefined();
171
+ });
172
+
55
173
  test('Test Snapping.orthoSnap', () => {
174
+ snapping.active = true;
175
+
176
+ // Mouse far right of previous: x delta dominates → y locked to previous y
177
+ // note: mouseMoved(x, -y) maps to scene (x, y) due to y-axis flip in the matrix
178
+ core.mouse.mouseMoved(100, -20); // scene (100, 20), previous (0, 0): |dx|=100 > |dy|=20
179
+ const snapPointX = snapping.orthoSnap(new Point(0, 0));
180
+ expect(snapPointX).toBeDefined();
181
+ expect(snapPointX.x).toBeCloseTo(100);
182
+ expect(snapPointX.y).toBeCloseTo(0);
183
+
184
+ // Mouse far above previous: y delta dominates → x locked to previous x
185
+ core.mouse.mouseMoved(20, -100); // scene (20, 100), previous (0, 0): |dx|=20 < |dy|=100
186
+ const snapPointY = snapping.orthoSnap(new Point(0, 0));
187
+ expect(snapPointY).toBeDefined();
188
+ expect(snapPointY.x).toBeCloseTo(0);
189
+ expect(snapPointY.y).toBeCloseTo(100);
56
190
 
191
+ // Inactive: returns undefined
192
+ snapping.active = false;
193
+ core.mouse.mouseMoved(100, -20);
194
+ expect(snapping.orthoSnap(new Point(0, 0))).toBeUndefined();
57
195
  });
@@ -0,0 +1,155 @@
1
+ import { Core } from '../../core/core/core.js';
2
+ import { StateManager } from '../../core/lib/stateManager.js';
3
+ import { EntityManager } from '../../core/lib/entityManager.js';
4
+ import { AddState, RemoveState, UpdateState } from '../../core/lib/stateManager.js';
5
+ import { Line } from '../../core/entities/line.js';
6
+ import { Point } from '../../core/entities/point.js';
7
+ import { jest } from '@jest/globals';
8
+
9
+ new Core();
10
+
11
+ describe('StateManager', () => {
12
+ let sm;
13
+
14
+ beforeEach(() => {
15
+ sm = new StateManager();
16
+ });
17
+
18
+ test('limitHistory only allows max history states', () => {
19
+ const em = new EntityManager();
20
+ for (let i = 0; i < 20; i++) {
21
+ sm.commit(em, [new AddState({ id: `e${i}` })]);
22
+ expect(sm.getHistoryLength()).toBeLessThanOrEqual(10);
23
+ }
24
+ });
25
+
26
+ test('add() creates an AddState, performs do, undo and redo', () => {
27
+ const em = new EntityManager();
28
+ const entity = { id: 'e1' };
29
+ sm.commit(em, [new AddState(entity)]);
30
+ expect(em.count()).toBe(1);
31
+ expect(sm.getHistoryLength()).toBe(1);
32
+ sm.undo();
33
+ expect(em.count()).toBe(0);
34
+ expect(sm.getHistoryLength()).toBe(1);
35
+ sm.redo();
36
+ expect(em.count()).toBe(1);
37
+ });
38
+
39
+ test('remove() creates a RemoveState, performs do, undo and redo', () => {
40
+ const em = new EntityManager();
41
+ const entity = { id: 'e2' };
42
+ sm.commit(em, [new AddState(entity)]);
43
+ expect(em.count()).toBe(1);
44
+ sm.commit(em, [new RemoveState(entity)]);
45
+ expect(em.count()).toBe(0);
46
+ sm.undo();
47
+ expect(em.count()).toBe(1);
48
+ sm.redo();
49
+ expect(em.count()).toBe(0);
50
+ });
51
+
52
+ test('update() creates UpdateState and undo restores previous properties', () => {
53
+ const em = new EntityManager();
54
+ // seed entities
55
+ for (let i = 0; i < 5; i++) {
56
+ const line = new Line({ layer: 'testLayer', points: [new Point(i, i), new Point(10, 11)] });
57
+ sm.commit(em, [new AddState(line)]);
58
+ }
59
+ const target = em.get(2);
60
+ sm.commit(em, [new UpdateState(target, { layer: 'newLayer' })]);
61
+ expect(em.get(2).layer).toBe('newLayer');
62
+ sm.undo();
63
+ expect(em.get(2).layer).toBe('testLayer');
64
+ sm.redo();
65
+ expect(em.get(2).layer).toBe('newLayer');
66
+ });
67
+
68
+ test('addState removed future states', () => {
69
+ const em = new EntityManager();
70
+ const line = new Line({ layer: 'testLayer', points: [new Point(), new Point(10, 11)] });
71
+ sm.commit(em, [new AddState(line)]);
72
+ expect(em.get(0).layer).toBe('testLayer');
73
+ sm.commit(em, [new UpdateState(line, { layer: 'differentLayer', points: [new Point(1, 2), new Point(101, 110)] })]);
74
+ expect(em.get(0).layer).toBe('differentLayer');
75
+ sm.undo();
76
+ expect(em.get(0).layer).toBe('testLayer');
77
+ sm.redo();
78
+ expect(em.get(0).layer).toBe('differentLayer');
79
+ });
80
+
81
+ test('clearHistory resets history and indices', () => {
82
+ const em = new EntityManager();
83
+ sm.commit(em, [new AddState({ id: 'a' })]);
84
+ expect(sm.getHistoryLength()).toBe(1);
85
+ sm.clearHistory();
86
+ expect(sm.getHistoryLength()).toBe(0);
87
+ expect(sm.canUndo()).toBe(false);
88
+ expect(sm.canRedo()).toBe(false);
89
+ });
90
+
91
+ test('canUndo and canRedo basic lifecycle', () => {
92
+ const em = new EntityManager();
93
+ expect(sm.canUndo()).toBe(false);
94
+ expect(sm.canRedo()).toBe(false);
95
+ sm.commit(em, [new AddState({ id: 'first' })]);
96
+ expect(sm.canUndo()).toBe(true);
97
+ expect(sm.canRedo()).toBe(false);
98
+ sm.undo();
99
+ expect(sm.canUndo()).toBe(false);
100
+ expect(sm.canRedo()).toBe(true);
101
+ sm.redo();
102
+ expect(sm.canUndo()).toBe(true);
103
+ expect(sm.canRedo()).toBe(false);
104
+ });
105
+
106
+ test('canRedo cleared after new commit following undo', () => {
107
+ const em = new EntityManager();
108
+ sm.commit(em, [new AddState({ id: 'A' })]);
109
+ sm.commit(em, [new AddState({ id: 'B' })]);
110
+ expect(sm.canUndo()).toBe(true);
111
+ expect(sm.canRedo()).toBe(false);
112
+ sm.undo();
113
+ expect(sm.canRedo()).toBe(true);
114
+ sm.commit(em, [new AddState({ id: 'C' })]);
115
+ expect(sm.canRedo()).toBe(false);
116
+ });
117
+
118
+ test('isModified getter reflects internal flag and stateChanged invokes callback and respects parameter', () => {
119
+ // initial state should be not modified
120
+ expect(sm.isModified).toBe(false);
121
+
122
+ const cb = jest.fn();
123
+ sm.setStateCallbackFunction(cb);
124
+
125
+ // calling stateChanged with default should set modified to true and call callback
126
+ sm.stateChanged();
127
+ expect(sm.isModified).toBe(true);
128
+ expect(cb).toHaveBeenCalledTimes(1);
129
+
130
+ // calling stateChanged(false) should clear modified flag and call callback
131
+ sm.stateChanged(false);
132
+ expect(sm.isModified).toBe(false);
133
+ expect(cb).toHaveBeenCalledTimes(2);
134
+ });
135
+
136
+ test('stateChanged is invoked after commit/undo/redo', () => {
137
+ const em = new EntityManager();
138
+ const cb = jest.fn();
139
+ sm.setStateCallbackFunction(cb);
140
+
141
+ // commit should call stateChanged once
142
+ sm.commit(em, [new AddState({ id: 'x' })]);
143
+ expect(cb).toHaveBeenCalledTimes(1);
144
+ expect(sm.isModified).toBe(true);
145
+
146
+ // undo should call stateChanged again
147
+ sm.undo();
148
+ expect(cb).toHaveBeenCalledTimes(2);
149
+
150
+ // redo should call stateChanged again
151
+ sm.redo();
152
+ expect(cb).toHaveBeenCalledTimes(3);
153
+ });
154
+ });
155
+
@@ -1,4 +1,6 @@
1
- import {Utils} from '../../core/lib/utils.js';
1
+ import { Arc } from '../../core/entities/arc.js';
2
+ import { Utils } from '../../core/lib/utils.js';
3
+ import { Point } from '../../core/entities/point.js';
2
4
 
3
5
  test('Test Utils.degrees2radians', () => {
4
6
  // Positive
@@ -6,6 +8,8 @@ test('Test Utils.degrees2radians', () => {
6
8
  expect(Utils.degrees2radians(45)).toBe(Math.PI/4);
7
9
  expect(Utils.degrees2radians(90)).toBe(Math.PI/2);
8
10
  expect(Utils.degrees2radians(180)).toBe(Math.PI);
11
+ expect(Utils.degrees2radians(270)).toBe(Math.PI * 1.5);
12
+ expect(Utils.degrees2radians(360)).toBe(Math.PI * 2);
9
13
 
10
14
  // Negative
11
15
  expect(Utils.degrees2radians(-22.5)).toBe(-Math.PI/8);
@@ -34,3 +38,79 @@ test('Test Utils.getLevenshteinDistance', () => {
34
38
  expect(Utils.getLevenshteinDistance('design', 'resign')).toBe(1);
35
39
  expect(Utils.getLevenshteinDistance('design', 'sign')).toBe(2);
36
40
  });
41
+
42
+ test('Test Utils.sortPointsByDistance mutates array ordered by distance', () => {
43
+ const ref = { x: 0, y: 0 };
44
+ const pts = [
45
+ { x: 3, y: 4 }, // dist^2 = 25
46
+ { x: 1, y: 1 }, // dist^2 = 2
47
+ { x: -2, y: 0 }, // dist^2 = 4
48
+ { x: 0, y: 5 }, // dist^2 = 25
49
+ ];
50
+ Utils.sortPointsByDistance(pts, ref);
51
+ // Expect ascending order: (1,1) ( -2,0 ) then the two distance=25 points (order between them not critical)
52
+ expect(pts[0].x).toBe(1);
53
+ expect(pts[0].y).toBe(1);
54
+ expect(pts[1].x).toBe(-2);
55
+ expect(pts[1].y).toBe(0);
56
+ const lastTwo = pts.slice(2).map((p) => `${p.x},${p.y}`);
57
+ expect(lastTwo.sort()).toEqual(['0,5', '3,4']);
58
+ });
59
+
60
+ test('Test Utils.sortPointsByDistance with empty array does nothing', () => {
61
+ const ref = { x: 5, y: 5 };
62
+ const pts = [];
63
+ Utils.sortPointsByDistance(pts, ref);
64
+ expect(pts).toEqual([]);
65
+ });
66
+
67
+ test('Test Utils.sortPointsOnArc mutates array ordered by angular position', () => {
68
+ const pts = [
69
+ new Point(7.1, 7.1), // 45 degree
70
+ new Point(10, 0), // end
71
+ new Point(-10, 0), // start
72
+ new Point(-7.1, 7.1), // 45 degree
73
+ new Point(10, 10), // mid
74
+ ];
75
+
76
+ const arc = new Arc({ points: [new Point(0, 0), new Point(10, 0), new Point(-10, 0)], direction: 1 });
77
+ Utils.sortPointsOnArc(pts, arc.points[1], arc.points[2], arc.points[0], arc.direction);
78
+ // Expect order: (10,0), (7.1, 7.1), (10,10), (-7.1, 7.1), (-10,0)
79
+ expect(pts[0].x).toBe(10);
80
+ expect(pts[0].y).toBe(0);
81
+
82
+ expect(pts[1].x).toBe(7.1);
83
+ expect(pts[1].y).toBe(7.1);
84
+
85
+ expect(pts[2].x).toBe(10);
86
+ expect(pts[2].y).toBe(10);
87
+
88
+ expect(pts[3].x).toBe(-7.1);
89
+ expect(pts[3].y).toBe(7.1);
90
+
91
+ expect(pts[4].x).toBe(-10);
92
+ expect(pts[4].y).toBe(0);
93
+
94
+ const cwArc = new Arc({ points: [new Point(0, 0), new Point(-10, 0), new Point(10, 0)], direction: -1 });
95
+ Utils.sortPointsOnArc(pts, cwArc.points[1], cwArc.points[2], cwArc.points[0], cwArc.direction);
96
+ // Expect order: (-10,0), (-7.1, 7.1), (10,10), (7.1, 7.1), (10,0)
97
+ expect(pts[0].x).toBe(-10);
98
+ expect(pts[0].y).toBe(0);
99
+
100
+ expect(pts[1].x).toBe(-7.1);
101
+ expect(pts[1].y).toBe(7.1);
102
+
103
+ expect(pts[2].x).toBe(10);
104
+ expect(pts[2].y).toBe(10);
105
+
106
+ expect(pts[3].x).toBe(7.1);
107
+ expect(pts[3].y).toBe(7.1);
108
+
109
+ expect(pts[4].x).toBe(10);
110
+ expect(pts[4].y).toBe(0);
111
+ });
112
+
113
+ test('Test Utils.areaOfTriangle', () => {
114
+ expect(Utils.areaOfTriangle(new Point(0, 0), new Point(10, 0), new Point(0, 10))).toBe(50);
115
+ expect(Utils.areaOfTriangle(new Point(-5, -5), new Point(5, -5), new Point(0, 5))).toBe(50);
116
+ });