@design-core/core 0.0.6 → 0.0.7

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 (145) hide show
  1. package/.eslintrc.json +4 -0
  2. package/TODO.md +49 -6
  3. package/core/core/core.js +0 -3
  4. package/core/dimensions/alignedDimension.js +5 -4
  5. package/core/dimensions/angularDimension.js +6 -5
  6. package/core/dimensions/baseCircularDimension.js +3 -2
  7. package/core/dimensions/baseDimension.js +59 -93
  8. package/core/dimensions/baseLinearDimension.js +6 -5
  9. package/core/dimensions/diametricDimension.js +4 -3
  10. package/core/dimensions/dimension.js +21 -14
  11. package/core/dimensions/radialDimension.js +4 -3
  12. package/core/dimensions/rotatedDimension.js +12 -11
  13. package/core/entities/arc.js +112 -145
  14. package/core/entities/arctext.js +212 -174
  15. package/core/entities/basePolyline.js +30 -282
  16. package/core/entities/circle.js +77 -99
  17. package/core/entities/entity.js +114 -93
  18. package/core/entities/hatch.js +322 -249
  19. package/core/entities/insert.js +66 -81
  20. package/core/entities/line.js +21 -142
  21. package/core/entities/lwpolyline.js +16 -11
  22. package/core/entities/polyline.js +4 -4
  23. package/core/entities/rectangle.js +3 -1
  24. package/core/entities/solid.js +6 -15
  25. package/core/entities/text.js +188 -199
  26. package/core/lib/auxiliary/rubberBand.js +32 -0
  27. package/core/lib/auxiliary/selectionWindow.js +63 -0
  28. package/core/lib/auxiliary/snapPoint.js +113 -0
  29. package/core/lib/auxiliary/trackingLine.js +76 -0
  30. package/core/lib/canvas.js +228 -166
  31. package/core/lib/clipboard.js +7 -1
  32. package/core/lib/colour.js +16 -0
  33. package/core/lib/commandLine.js +1 -6
  34. package/core/lib/commandManager.js +18 -8
  35. package/core/lib/constants.js +28 -0
  36. package/core/lib/dxf/dxf.js +4 -3
  37. package/core/lib/entityManager.js +18 -11
  38. package/core/lib/inputManager.js +35 -11
  39. package/core/lib/intersect.js +25 -0
  40. package/core/lib/patterns.js +8 -1
  41. package/core/lib/plotOptions.js +67 -0
  42. package/core/lib/polylineUtils.js +75 -0
  43. package/core/lib/renderers/cairoRenderer.js +371 -0
  44. package/core/lib/renderers/canvasRenderer.js +315 -0
  45. package/core/lib/renderers/pdfRenderer.js +366 -0
  46. package/core/lib/renderers/rendererBase.js +284 -0
  47. package/core/lib/renderers/svgRenderer.js +343 -0
  48. package/core/lib/scene.js +27 -5
  49. package/core/lib/selectionManager.js +2 -2
  50. package/core/lib/settings.js +4 -2
  51. package/core/lib/snapping.js +4 -191
  52. package/core/lib/stateManager.js +30 -1
  53. package/core/lib/utils.js +21 -2
  54. package/core/properties/entityProperties.js +104 -0
  55. package/core/properties/property.js +106 -2
  56. package/core/properties/propertyManager.js +29 -13
  57. package/core/tables/block.js +4 -37
  58. package/core/tables/endblk.js +1 -1
  59. package/core/tables/layer.js +8 -0
  60. package/core/tables/styleManagerBase.js +3 -2
  61. package/core/tables/tableManagerBase.js +8 -3
  62. package/core/tools/chamfer.js +111 -137
  63. package/core/tools/chamferFilletBase.js +135 -34
  64. package/core/tools/copy.js +3 -3
  65. package/core/tools/explode.js +2 -2
  66. package/core/tools/extend.js +132 -19
  67. package/core/tools/fillet.js +86 -183
  68. package/core/tools/matchProp.js +2 -11
  69. package/core/tools/mirror.js +7 -6
  70. package/core/tools/move.js +3 -3
  71. package/core/tools/offset.js +3 -5
  72. package/core/tools/pasteclip.js +1 -1
  73. package/core/tools/rotate.js +6 -3
  74. package/core/tools/scale.js +1 -1
  75. package/core/tools/trim.js +265 -23
  76. package/core/tools/zoom.js +1 -1
  77. package/jest.config.js +6 -0
  78. package/package.json +5 -3
  79. package/test/dimensions/alignedDimension.test.js +14 -7
  80. package/test/dimensions/angularDimension.test.js +6 -6
  81. package/test/dimensions/baseDimension.test.js +69 -8
  82. package/test/dimensions/baseLinearDimension.test.js +62 -0
  83. package/test/dimensions/diametricDimension.test.js +6 -6
  84. package/test/dimensions/radialDimension.test.js +6 -6
  85. package/test/dimensions/rotatedDimension.test.js +14 -7
  86. package/test/entities/arc.test.js +80 -83
  87. package/test/entities/arctext.test.js +180 -53
  88. package/test/entities/basePolyline.test.js +68 -2
  89. package/test/entities/circle.test.js +57 -85
  90. package/test/entities/entity.test.js +42 -8
  91. package/test/entities/hatch.test.js +543 -70
  92. package/test/entities/insert.test.js +96 -1
  93. package/test/entities/line.test.js +15 -41
  94. package/test/entities/lwpolyline.test.js +104 -0
  95. package/test/entities/polyline.test.js +11 -0
  96. package/test/entities/rectangle.test.js +11 -0
  97. package/test/entities/solid.test.js +1 -1
  98. package/test/entities/text.test.js +294 -69
  99. package/test/integration/exportIntegration.reference.dxf +9 -9
  100. package/test/integration/exportIntegration.test.js +2 -2
  101. package/test/integration/intersectionIntegration.test.js +4 -5
  102. package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
  103. package/test/integration/pdfRendererIntegration.test.js +61 -0
  104. package/test/integration/svgRendererIntegration.reference.svg +3343 -0
  105. package/test/integration/svgRendererIntegration.test.js +61 -0
  106. package/test/integration/userInputIntegration.reference.dxf +1165 -105
  107. package/test/integration/userInputIntegration.test.js +239 -132
  108. package/test/lib/canvas.test.js +742 -123
  109. package/test/lib/constants.test.js +27 -0
  110. package/test/lib/entityManager.test.js +4 -4
  111. package/test/lib/inputManager.test.js +80 -19
  112. package/test/lib/intersect.test.js +83 -0
  113. package/test/lib/plotOptions.test.js +179 -0
  114. package/test/lib/rubberBand.test.js +84 -0
  115. package/test/lib/settings.test.js +5 -6
  116. package/test/lib/snapping.test.js +71 -129
  117. package/test/lib/stateManager.test.js +7 -7
  118. package/test/lib/utils.test.js +36 -2
  119. package/test/properties/entityProperties.test.js +132 -0
  120. package/test/properties/property.test.js +53 -0
  121. package/test/properties/propertyManager.test.js +3 -3
  122. package/test/renderers/canvasRenderer.test.js +317 -0
  123. package/test/renderers/pdfRenderer.test.js +271 -0
  124. package/test/renderers/rendererBase.test.js +213 -0
  125. package/test/renderers/svgRenderer.test.js +326 -0
  126. package/test/tables/block.test.js +31 -1
  127. package/test/tables/endblk.test.js +2 -2
  128. package/test/tables/layer.test.js +30 -0
  129. package/test/test-helpers/hatchGridRenderer.js +180 -0
  130. package/test/test-helpers/test-helpers.js +152 -0
  131. package/test/tools/chamfer.test.js +227 -5
  132. package/test/tools/chamferFilletBase.test.js +98 -9
  133. package/test/tools/copy.test.js +2 -2
  134. package/test/tools/explode.test.js +3 -3
  135. package/test/tools/extend.test.js +331 -0
  136. package/test/tools/fillet.test.js +181 -0
  137. package/test/tools/matchProp.test.js +5 -5
  138. package/test/tools/mirror.test.js +13 -13
  139. package/test/tools/offset.test.js +170 -3
  140. package/test/tools/pasteclip.test.js +5 -5
  141. package/test/tools/rotate.test.js +32 -2
  142. package/test/tools/scale.test.js +5 -5
  143. package/test/tools/trim.test.js +526 -0
  144. package/test/tools/zoom.test.js +7 -7
  145. package/core/lib/selectionWindow.js +0 -83
@@ -1,6 +1,7 @@
1
1
  import { jest } from '@jest/globals';
2
2
  import { Core } from '../../core/core/core.js';
3
- import { Snapping, SnapPoint } from '../../core/lib/snapping.js';
3
+ import { Snapping } from '../../core/lib/snapping.js';
4
+ import { SnapPoint } from '../../core/lib/auxiliary/snapPoint.js';
4
5
  import { Point } from '../../core/entities/point.js';
5
6
  import { DesignCore } from '../../core/designCore.js';
6
7
 
@@ -69,21 +70,10 @@ test('Test SnapPoint.draw', () => {
69
70
  DesignCore.Scene.auxiliaryEntities.clear();
70
71
  snapping.addSnapPoint(new SnapPoint(new Point(0.5, 0.5), SnapPoint.Type.CENTRE));
71
72
  const snapPointEntity = DesignCore.Scene.auxiliaryEntities.get(0);
72
- const ctx = {
73
- strokeStyle: '',
74
- lineWidth: 0,
75
- setLineDash: jest.fn(),
76
- beginPath: jest.fn(),
77
- moveTo: jest.fn(),
78
- lineTo: jest.fn(),
79
- closePath: jest.fn(),
80
- arc: jest.fn(),
81
- stroke: jest.fn(),
82
- };
83
- snapPointEntity.draw(ctx, 1);
84
- expect(ctx.beginPath).toHaveBeenCalled();
85
- expect(ctx.arc).toHaveBeenCalledWith(0.5, 0.5, expect.any(Number), 0, 6.283);
86
- expect(ctx.stroke).toHaveBeenCalled();
73
+ const renderer = makeSnapRenderer();
74
+ snapPointEntity.draw(renderer, 1);
75
+ expect(renderer.setColour).toHaveBeenCalled();
76
+ expect(renderer.drawShape).toHaveBeenCalled();
87
77
  });
88
78
 
89
79
  test('Test Snapping.addTrackingLine', () => {
@@ -98,23 +88,9 @@ test('Test TrackingLine.draw', () => {
98
88
  // note: the default matrix has d=-1, so scene y is in [-1, 0] for a 1x1 canvas
99
89
  snapping.addTrackingLine(new Point(0.2, -0.5), new Point(0.8, -0.5));
100
90
  const trackingLine = DesignCore.Scene.auxiliaryEntities.get(0);
101
- const ctx = {
102
- save: jest.fn(),
103
- restore: jest.fn(),
104
- beginPath: jest.fn(),
105
- strokeStyle: '',
106
- lineWidth: 0,
107
- setLineDash: jest.fn(),
108
- moveTo: jest.fn(),
109
- lineTo: jest.fn(),
110
- stroke: jest.fn(),
111
- };
112
- trackingLine.draw(ctx, 1);
113
- expect(ctx.save).toHaveBeenCalled();
114
- expect(ctx.moveTo).toHaveBeenCalled();
115
- expect(ctx.lineTo).toHaveBeenCalled();
116
- expect(ctx.stroke).toHaveBeenCalled();
117
- expect(ctx.restore).toHaveBeenCalled();
91
+ const renderer = makeTrackingRenderer();
92
+ trackingLine.draw(renderer, 1);
93
+ expect(renderer.drawShape).toHaveBeenCalled();
118
94
  });
119
95
 
120
96
  test('Test TrackingLine.draw degenerate zero direction', () => {
@@ -122,20 +98,10 @@ test('Test TrackingLine.draw degenerate zero direction', () => {
122
98
  // inputPoint equals snapPoint → direction is zero vector → draw returns early
123
99
  snapping.addTrackingLine(new Point(0.5, 0.5), new Point(0.5, 0.5));
124
100
  const trackingLine = DesignCore.Scene.auxiliaryEntities.get(0);
125
- const ctx = {
126
- save: jest.fn(),
127
- restore: jest.fn(),
128
- beginPath: jest.fn(),
129
- strokeStyle: '',
130
- lineWidth: 0,
131
- setLineDash: jest.fn(),
132
- moveTo: jest.fn(),
133
- lineTo: jest.fn(),
134
- stroke: jest.fn(),
135
- };
136
- trackingLine.draw(ctx, 1);
137
- expect(ctx.moveTo).not.toHaveBeenCalled();
138
- expect(ctx.stroke).not.toHaveBeenCalled();
101
+ const renderer = makeTrackingRenderer();
102
+ trackingLine.draw(renderer, 1);
103
+ expect(renderer.drawShape).not.toHaveBeenCalled();
104
+ expect(renderer.restore).not.toHaveBeenCalled();
139
105
  });
140
106
 
141
107
  test('Test Snapping.polarSnap', () => {
@@ -199,35 +165,27 @@ test('Test Snapping.orthoSnap', () => {
199
165
  expect(snapping.orthoSnap(new Point(0, 0))).toBeUndefined();
200
166
  });
201
167
 
202
- // ─── Context mock helpers ────────────────────────────────────────────────────
168
+ // ─── Renderer mock helpers ──────────────────────────────────────────────────
203
169
 
204
- /** @return {object} mock HTML canvas context for SnapPoint.draw tests */
205
- function makeSnapCtx() {
170
+ /** @return {object} mock renderer for SnapPoint.draw tests */
171
+ function makeSnapRenderer() {
206
172
  return {
207
- strokeStyle: '',
208
- lineWidth: 0,
209
- setLineDash: jest.fn(),
210
- beginPath: jest.fn(),
211
- moveTo: jest.fn(),
212
- lineTo: jest.fn(),
213
- closePath: jest.fn(),
214
- arc: jest.fn(),
215
- stroke: jest.fn(),
173
+ setColour: jest.fn(),
174
+ setLineWidth: jest.fn(),
175
+ setDash: jest.fn(),
176
+ drawShape: jest.fn(),
216
177
  };
217
178
  }
218
179
 
219
- /** @return {object} mock HTML canvas context for TrackingLine.draw tests */
220
- function makeTrackingCtx() {
180
+ /** @return {object} mock renderer for TrackingLine.draw tests */
181
+ function makeTrackingRenderer() {
221
182
  return {
222
183
  save: jest.fn(),
223
184
  restore: jest.fn(),
224
- beginPath: jest.fn(),
225
- strokeStyle: '',
226
- lineWidth: 0,
227
- setLineDash: jest.fn(),
228
- moveTo: jest.fn(),
229
- lineTo: jest.fn(),
230
- stroke: jest.fn(),
185
+ setColour: jest.fn(),
186
+ setLineWidth: jest.fn(),
187
+ setDash: jest.fn(),
188
+ drawShape: jest.fn(),
231
189
  };
232
190
  }
233
191
 
@@ -294,74 +252,58 @@ test('Test Snapping.getSnapPoint with non-matching override returns undefined',
294
252
 
295
253
  // ─── SnapPoint.draw – all snap types ────────────────────────────────────────
296
254
 
297
- test('Test SnapPoint.draw END type draws closed polygon without arc', () => {
255
+ test('Test SnapPoint.draw END type draws closed polygon', () => {
298
256
  const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.END);
299
- const ctx = makeSnapCtx();
300
- sp.draw(ctx, 1);
301
- expect(ctx.beginPath).toHaveBeenCalled();
302
- expect(ctx.closePath).toHaveBeenCalled();
303
- expect(ctx.stroke).toHaveBeenCalled();
304
- expect(ctx.arc).not.toHaveBeenCalled();
257
+ const renderer = makeSnapRenderer();
258
+ sp.draw(renderer, 1);
259
+ expect(renderer.drawShape).toHaveBeenCalledTimes(1);
260
+ expect(renderer.drawShape).toHaveBeenCalledWith(expect.any(Array), expect.objectContaining({ closed: true }));
305
261
  });
306
262
 
307
- test('Test SnapPoint.draw MID type draws closed polygon without arc', () => {
263
+ test('Test SnapPoint.draw MID type draws closed polygon', () => {
308
264
  const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.MID);
309
- const ctx = makeSnapCtx();
310
- sp.draw(ctx, 1);
311
- expect(ctx.beginPath).toHaveBeenCalled();
312
- expect(ctx.closePath).toHaveBeenCalled();
313
- expect(ctx.stroke).toHaveBeenCalled();
314
- expect(ctx.arc).not.toHaveBeenCalled();
265
+ const renderer = makeSnapRenderer();
266
+ sp.draw(renderer, 1);
267
+ expect(renderer.drawShape).toHaveBeenCalledTimes(1);
268
+ expect(renderer.drawShape).toHaveBeenCalledWith(expect.any(Array), expect.objectContaining({ closed: true }));
315
269
  });
316
270
 
317
- test('Test SnapPoint.draw QUADRANT type draws closed polygon without arc', () => {
271
+ test('Test SnapPoint.draw QUADRANT type draws closed polygon', () => {
318
272
  const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.QUADRANT);
319
- const ctx = makeSnapCtx();
320
- sp.draw(ctx, 1);
321
- expect(ctx.beginPath).toHaveBeenCalled();
322
- expect(ctx.closePath).toHaveBeenCalled();
323
- expect(ctx.stroke).toHaveBeenCalled();
324
- expect(ctx.arc).not.toHaveBeenCalled();
273
+ const renderer = makeSnapRenderer();
274
+ sp.draw(renderer, 1);
275
+ expect(renderer.drawShape).toHaveBeenCalledTimes(1);
276
+ expect(renderer.drawShape).toHaveBeenCalledWith(expect.any(Array), expect.objectContaining({ closed: true }));
325
277
  });
326
278
 
327
- test('Test SnapPoint.draw NEAREST type draws closed polygon without arc', () => {
279
+ test('Test SnapPoint.draw NEAREST type draws closed polygon', () => {
328
280
  const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.NEAREST);
329
- const ctx = makeSnapCtx();
330
- sp.draw(ctx, 1);
331
- expect(ctx.beginPath).toHaveBeenCalled();
332
- expect(ctx.closePath).toHaveBeenCalled();
333
- expect(ctx.stroke).toHaveBeenCalled();
334
- expect(ctx.arc).not.toHaveBeenCalled();
281
+ const renderer = makeSnapRenderer();
282
+ sp.draw(renderer, 1);
283
+ expect(renderer.drawShape).toHaveBeenCalledTimes(1);
284
+ expect(renderer.drawShape).toHaveBeenCalledWith(expect.any(Array), expect.objectContaining({ closed: true }));
335
285
  });
336
286
 
337
- test('Test SnapPoint.draw TANGENT type draws arc with tangent line', () => {
287
+ test('Test SnapPoint.draw TANGENT type draws circle and tangent line', () => {
338
288
  const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.TANGENT);
339
- const ctx = makeSnapCtx();
340
- sp.draw(ctx, 1);
341
- expect(ctx.beginPath).toHaveBeenCalled();
342
- expect(ctx.arc).toHaveBeenCalledWith(5, 5, expect.any(Number), 0, 6.283);
343
- expect(ctx.stroke).toHaveBeenCalled();
344
- expect(ctx.closePath).not.toHaveBeenCalled();
289
+ const renderer = makeSnapRenderer();
290
+ sp.draw(renderer, 1);
291
+ expect(renderer.drawShape).toHaveBeenCalledTimes(2);
345
292
  });
346
293
 
347
- test('Test SnapPoint.draw NODE type draws arc with X inside', () => {
294
+ test('Test SnapPoint.draw NODE type draws circle and X lines', () => {
348
295
  const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.NODE);
349
- const ctx = makeSnapCtx();
350
- sp.draw(ctx, 1);
351
- expect(ctx.beginPath).toHaveBeenCalled();
352
- expect(ctx.arc).toHaveBeenCalledWith(5, 5, expect.any(Number), 0, 6.283);
353
- expect(ctx.stroke).toHaveBeenCalled();
354
- expect(ctx.closePath).not.toHaveBeenCalled();
296
+ const renderer = makeSnapRenderer();
297
+ sp.draw(renderer, 1);
298
+ expect(renderer.drawShape).toHaveBeenCalledTimes(3);
355
299
  });
356
300
 
357
- test('Test SnapPoint.draw PERPENDICULAR type draws L-shape without arc', () => {
301
+ test('Test SnapPoint.draw PERPENDICULAR type draws L-shape', () => {
358
302
  const sp = new SnapPoint(new Point(5, 5), SnapPoint.Type.PERPENDICULAR);
359
- const ctx = makeSnapCtx();
360
- sp.draw(ctx, 1);
361
- expect(ctx.beginPath).toHaveBeenCalled();
362
- expect(ctx.stroke).toHaveBeenCalled();
363
- expect(ctx.closePath).not.toHaveBeenCalled();
364
- expect(ctx.arc).not.toHaveBeenCalled();
303
+ const renderer = makeSnapRenderer();
304
+ sp.draw(renderer, 1);
305
+ expect(renderer.drawShape).toHaveBeenCalledTimes(1);
306
+ expect(renderer.drawShape).not.toHaveBeenCalledWith(expect.any(Array), expect.objectContaining({ closed: true }));
365
307
  });
366
308
 
367
309
  // ─── TrackingLine.draw – out-of-bounds cases ─────────────────────────────────
@@ -371,10 +313,10 @@ test('Test TrackingLine.draw vertical line outside x bounds', () => {
371
313
  // dir.x === 0 and from.x = 2 > boundsMax.x = 1 → draw returns early
372
314
  snapping.addTrackingLine(new Point(2, -0.5), new Point(2, 0.5));
373
315
  const trackingLine = DesignCore.Scene.auxiliaryEntities.get(0);
374
- const ctx = makeTrackingCtx();
375
- trackingLine.draw(ctx, 1);
376
- expect(ctx.moveTo).not.toHaveBeenCalled();
377
- expect(ctx.stroke).not.toHaveBeenCalled();
316
+ const renderer = makeTrackingRenderer();
317
+ trackingLine.draw(renderer, 1);
318
+ expect(renderer.drawShape).not.toHaveBeenCalled();
319
+ expect(renderer.restore).not.toHaveBeenCalled();
378
320
  });
379
321
 
380
322
  test('Test TrackingLine.draw horizontal line outside y bounds', () => {
@@ -382,10 +324,10 @@ test('Test TrackingLine.draw horizontal line outside y bounds', () => {
382
324
  // dir.y === 0 and from.y = -5 < boundsMin.y = -1 → draw returns early
383
325
  snapping.addTrackingLine(new Point(0.5, -5), new Point(0.8, -5));
384
326
  const trackingLine = DesignCore.Scene.auxiliaryEntities.get(0);
385
- const ctx = makeTrackingCtx();
386
- trackingLine.draw(ctx, 1);
387
- expect(ctx.moveTo).not.toHaveBeenCalled();
388
- expect(ctx.stroke).not.toHaveBeenCalled();
327
+ const renderer = makeTrackingRenderer();
328
+ trackingLine.draw(renderer, 1);
329
+ expect(renderer.drawShape).not.toHaveBeenCalled();
330
+ expect(renderer.restore).not.toHaveBeenCalled();
389
331
  });
390
332
 
391
333
  test('Test TrackingLine.draw line does not intersect canvas bounds', () => {
@@ -394,8 +336,8 @@ test('Test TrackingLine.draw line does not intersect canvas bounds', () => {
394
336
  // tMax (-1) < tMin (-0.5) → draw returns early
395
337
  snapping.addTrackingLine(new Point(2, -0.5), new Point(3, 0.5));
396
338
  const trackingLine = DesignCore.Scene.auxiliaryEntities.get(0);
397
- const ctx = makeTrackingCtx();
398
- trackingLine.draw(ctx, 1);
399
- expect(ctx.moveTo).not.toHaveBeenCalled();
400
- expect(ctx.stroke).not.toHaveBeenCalled();
339
+ const renderer = makeTrackingRenderer();
340
+ trackingLine.draw(renderer, 1);
341
+ expect(renderer.drawShape).not.toHaveBeenCalled();
342
+ expect(renderer.restore).not.toHaveBeenCalled();
401
343
  });
@@ -58,24 +58,24 @@ describe('StateManager', () => {
58
58
  }
59
59
  const target = em.get(2);
60
60
  sm.commit(em, [new UpdateState(target, { layer: 'newLayer' })]);
61
- expect(em.get(2).layer).toBe('newLayer');
61
+ expect(em.get(2).getProperty('layer')).toBe('newLayer');
62
62
  sm.undo();
63
- expect(em.get(2).layer).toBe('testLayer');
63
+ expect(em.get(2).getProperty('layer')).toBe('testLayer');
64
64
  sm.redo();
65
- expect(em.get(2).layer).toBe('newLayer');
65
+ expect(em.get(2).getProperty('layer')).toBe('newLayer');
66
66
  });
67
67
 
68
68
  test('addState removed future states', () => {
69
69
  const em = new EntityManager();
70
70
  const line = new Line({ layer: 'testLayer', points: [new Point(), new Point(10, 11)] });
71
71
  sm.commit(em, [new AddState(line)]);
72
- expect(em.get(0).layer).toBe('testLayer');
72
+ expect(em.get(0).getProperty('layer')).toBe('testLayer');
73
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');
74
+ expect(em.get(0).getProperty('layer')).toBe('differentLayer');
75
75
  sm.undo();
76
- expect(em.get(0).layer).toBe('testLayer');
76
+ expect(em.get(0).getProperty('layer')).toBe('testLayer');
77
77
  sm.redo();
78
- expect(em.get(0).layer).toBe('differentLayer');
78
+ expect(em.get(0).getProperty('layer')).toBe('differentLayer');
79
79
  });
80
80
 
81
81
  test('clearHistory resets history and indices', () => {
@@ -1,4 +1,5 @@
1
1
  import { Arc } from '../../core/entities/arc.js';
2
+ import { Hatch } from '../../core/entities/hatch.js';
2
3
  import { Utils } from '../../core/lib/utils.js';
3
4
  import { Point } from '../../core/entities/point.js';
4
5
 
@@ -74,7 +75,7 @@ test('Test Utils.sortPointsOnArc mutates array ordered by angular position', ()
74
75
  ];
75
76
 
76
77
  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
+ Utils.sortPointsOnArc(pts, arc.points[1], arc.points[2], arc.points[0], arc.getProperty('direction'));
78
79
  // Expect order: (10,0), (7.1, 7.1), (10,10), (-7.1, 7.1), (-10,0)
79
80
  expect(pts[0].x).toBe(10);
80
81
  expect(pts[0].y).toBe(0);
@@ -92,7 +93,7 @@ test('Test Utils.sortPointsOnArc mutates array ordered by angular position', ()
92
93
  expect(pts[4].y).toBe(0);
93
94
 
94
95
  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
+ Utils.sortPointsOnArc(pts, cwArc.points[1], cwArc.points[2], cwArc.points[0], cwArc.getProperty('direction'));
96
97
  // Expect order: (-10,0), (-7.1, 7.1), (10,10), (7.1, 7.1), (10,0)
97
98
  expect(pts[0].x).toBe(-10);
98
99
  expect(pts[0].y).toBe(0);
@@ -110,6 +111,39 @@ test('Test Utils.sortPointsOnArc mutates array ordered by angular position', ()
110
111
  expect(pts[4].y).toBe(0);
111
112
  });
112
113
 
114
+ test('Test Utils.cloneProperties copies default properties from entity', () => {
115
+ const arc = new Arc({ layer: 'walls', lineWidth: 5, lineType: 'DASHED' });
116
+ const props = Utils.cloneProperties(arc);
117
+ expect(props.layer).toBe('walls');
118
+ expect(props.lineWidth).toBe(5);
119
+ expect(props.lineType).toBe('DASHED');
120
+ expect(Object.keys(props)).toEqual(expect.arrayContaining(['layer', 'colour', 'lineWidth', 'lineType']));
121
+ });
122
+
123
+ test('Test Utils.cloneProperties respects custom properties list', () => {
124
+ const arc = new Arc({ layer: 'walls', lineWidth: 5, lineType: 'DASHED' });
125
+ const props = Utils.cloneProperties(arc, ['layer', 'lineWidth']);
126
+ expect(props.layer).toBe('walls');
127
+ expect(props.lineWidth).toBe(5);
128
+ expect(props.lineType).toBeUndefined();
129
+ expect(props.colour).toBeUndefined();
130
+ });
131
+
132
+ test('Test Utils.cloneProperties copies patternName from Hatch', () => {
133
+ const hatch = new Hatch({ patternName: 'ANSI37' });
134
+ const props = Utils.cloneProperties(hatch);
135
+ expect(props.patternName).toBe('ANSI37');
136
+ // lineType is hidden on Hatch
137
+ expect(props.lineType).toBeUndefined();
138
+ expect(props.lineWidth).toBeDefined();
139
+ });
140
+
141
+ test('Test Utils.cloneProperties does not include patternName for non-hatch entities', () => {
142
+ const arc = new Arc({ layer: 'walls' });
143
+ const props = Utils.cloneProperties(arc);
144
+ expect(props.patternName).toBeUndefined();
145
+ });
146
+
113
147
  test('Test Utils.areaOfTriangle', () => {
114
148
  expect(Utils.areaOfTriangle(new Point(0, 0), new Point(10, 0), new Point(0, 10))).toBe(50);
115
149
  expect(Utils.areaOfTriangle(new Point(-5, -5), new Point(5, -5), new Point(0, 5))).toBe(50);
@@ -0,0 +1,132 @@
1
+ import { Core } from '../../core/core/core.js';
2
+ import { EntityProperties } from '../../core/properties/entityProperties.js';
3
+ import { Property } from '../../core/properties/property.js';
4
+ import { Entity } from '../../core/entities/entity.js';
5
+
6
+ // initialise core
7
+ new Core();
8
+
9
+ /**
10
+ * Minimal entity-like object for isolated EntityProperties tests.
11
+ * @return {Object} plain object with layer and lineWidth fields
12
+ */
13
+ function makeTarget() {
14
+ return { layer: '0', lineWidth: 1 };
15
+ }
16
+
17
+ describe('EntityProperties.add / get', () => {
18
+ test('get returns value after add', () => {
19
+ const ep = new EntityProperties(makeTarget());
20
+ ep.add('layer', { type: Property.Type.LIST, value: '0' });
21
+ expect(ep.get('layer')).toBe('0');
22
+ });
23
+
24
+ test('get returns undefined for unknown property', () => {
25
+ const ep = new EntityProperties(makeTarget());
26
+ expect(ep.get('unknown')).toBeUndefined();
27
+ });
28
+ });
29
+
30
+ describe('EntityProperties.set', () => {
31
+ test('updates value in _store, readable via get', () => {
32
+ const target = makeTarget();
33
+ const ep = new EntityProperties(target);
34
+ ep.add('layer', { type: Property.Type.LIST, value: target.layer });
35
+ ep.set('layer', 'WALLS');
36
+ expect(ep.get('layer')).toBe('WALLS');
37
+ });
38
+
39
+ test('no-op for unknown property', () => {
40
+ const target = makeTarget();
41
+ const ep = new EntityProperties(target);
42
+ expect(() => ep.set('unknown', 'X')).not.toThrow();
43
+ });
44
+
45
+ test('no-op when property is readOnly', () => {
46
+ const target = makeTarget();
47
+ const ep = new EntityProperties(target);
48
+ ep.add('type', { type: Property.Type.LABEL, value: 'Line', readOnly: true });
49
+ ep.set('type', 'Circle');
50
+ expect(ep.get('type')).toBe('Line');
51
+ });
52
+ });
53
+
54
+ describe('EntityProperties.has', () => {
55
+ test('returns true for registered property', () => {
56
+ const ep = new EntityProperties(makeTarget());
57
+ ep.add('layer', { type: Property.Type.LIST, value: '0' });
58
+ expect(ep.has('layer')).toBe(true);
59
+ });
60
+
61
+ test('returns false for unregistered property', () => {
62
+ const ep = new EntityProperties(makeTarget());
63
+ expect(ep.has('layer')).toBe(false);
64
+ });
65
+ });
66
+
67
+ describe('EntityProperties.remove', () => {
68
+ test('removes a registered property', () => {
69
+ const ep = new EntityProperties(makeTarget());
70
+ ep.add('layer', { type: Property.Type.LIST, value: '0' });
71
+ ep.remove('layer');
72
+ expect(ep.has('layer')).toBe(false);
73
+ });
74
+ });
75
+
76
+ describe('EntityProperties.list', () => {
77
+ test('returns names of visible properties in registration order', () => {
78
+ const ep = new EntityProperties(makeTarget());
79
+ ep.add('layer', { type: Property.Type.LIST, value: '0' });
80
+ ep.add('lineWidth', { type: Property.Type.NUMBER, value: 1 });
81
+ ep.add('hidden', { type: Property.Type.LABEL, value: 'x', visible: false });
82
+ expect(ep.list()).toEqual(['layer', 'lineWidth']);
83
+ });
84
+ });
85
+
86
+ describe('EntityProperties.definition', () => {
87
+ test('returns the Property descriptor', () => {
88
+ const ep = new EntityProperties(makeTarget());
89
+ ep.add('layer', { type: Property.Type.LIST, value: '0', dxfCode: 8 });
90
+ const def = ep.definition('layer');
91
+ expect(def).toBeInstanceOf(Property);
92
+ expect(def.type).toBe(Property.Type.LIST);
93
+ expect(def.dxfCode).toBe(8);
94
+ });
95
+
96
+ test('returns undefined for unknown property', () => {
97
+ const ep = new EntityProperties(makeTarget());
98
+ expect(ep.definition('unknown')).toBeUndefined();
99
+ });
100
+ });
101
+
102
+ describe('Entity.properties integration', () => {
103
+ test('entity has properties registered for layer, lineType, lineWidth, colour', () => {
104
+ const e = new Entity();
105
+ expect(e.properties.has('layer')).toBe(true);
106
+ expect(e.properties.has('lineType')).toBe(true);
107
+ expect(e.properties.has('lineWidth')).toBe(true);
108
+ expect(e.properties.has('colour')).toBe(true);
109
+ });
110
+
111
+ test('properties values match entity fields at construction', () => {
112
+ const e = new Entity({ layer: 'TEST', lineWidth: 5 });
113
+ expect(e.properties.get('layer')).toBe('TEST');
114
+ expect(e.properties.get('lineWidth')).toBe(5);
115
+ });
116
+
117
+ test('set updates _store value, readable via get', () => {
118
+ const e = new Entity();
119
+ e.properties.set('layer', 'DOORS');
120
+ expect(e.properties.get('layer')).toBe('DOORS');
121
+ });
122
+
123
+ test('list returns the 4 registered property names', () => {
124
+ const e = new Entity();
125
+ expect(e.properties.list()).toEqual(['layer', 'lineType', 'lineWidth', 'colour']);
126
+ });
127
+
128
+ test('properties is non-enumerable on entity', () => {
129
+ const e = new Entity();
130
+ expect(Object.keys(e)).not.toContain('properties');
131
+ });
132
+ });
@@ -11,3 +11,56 @@ test('Test property.loadValue', () => {
11
11
  flags.setFlagValue(4);
12
12
  expect(Property.loadValue([flags], 3)).toBe(4);
13
13
  });
14
+
15
+ describe('Property.Type', () => {
16
+ test('has all expected members', () => {
17
+ expect(Property.Type.NUMBER).toBe('NUMBER');
18
+ expect(Property.Type.STRING).toBe('STRING');
19
+ expect(Property.Type.BOOLEAN).toBe('BOOLEAN');
20
+ expect(Property.Type.LIST).toBe('LIST');
21
+ expect(Property.Type.COLOUR).toBe('COLOUR');
22
+ expect(Property.Type.LABEL).toBe('LABEL');
23
+ expect(Property.Type.ENTITIES).toBe('ENTITIES');
24
+ });
25
+
26
+ test('has exactly 7 members', () => {
27
+ expect(Object.keys(Property.Type)).toHaveLength(7);
28
+ });
29
+ });
30
+
31
+ describe('Property instance', () => {
32
+ test('stores type and value', () => {
33
+ const p = new Property({ type: Property.Type.NUMBER, value: 42 });
34
+ expect(p.type).toBe(Property.Type.NUMBER);
35
+ expect(p.value).toBe(42);
36
+ });
37
+
38
+ test('defaults readOnly to false and visible to true', () => {
39
+ const p = new Property({ type: Property.Type.STRING, value: 'hello' });
40
+ expect(p.readOnly).toBe(false);
41
+ expect(p.visible).toBe(true);
42
+ });
43
+
44
+ test('set value updates when not readOnly', () => {
45
+ const p = new Property({ type: Property.Type.NUMBER, value: 1 });
46
+ p.value = 99;
47
+ expect(p.value).toBe(99);
48
+ });
49
+
50
+ test('set value is ignored when readOnly', () => {
51
+ const p = new Property({ type: Property.Type.LABEL, value: 'fixed', readOnly: true });
52
+ p.value = 'changed';
53
+ expect(p.value).toBe('fixed');
54
+ });
55
+
56
+ test('stores dxfCode', () => {
57
+ const p = new Property({ type: Property.Type.LIST, value: '0', dxfCode: 8 });
58
+ expect(p.dxfCode).toBe(8);
59
+ });
60
+
61
+ test('no-arg constructor produces undefined fields without throwing', () => {
62
+ const p = new Property();
63
+ expect(p.type).toBeUndefined();
64
+ expect(p.value).toBeUndefined();
65
+ });
66
+ });
@@ -49,7 +49,7 @@ test('Test propertyManager.setItemProperties', () => {
49
49
 
50
50
  let text = DesignCore.Scene.entities.get(2);
51
51
  expect(text.type).toBe('Text');
52
- expect(text.string).toBe(string);
52
+ expect(text.getProperty('string')).toBe(string);
53
53
 
54
54
  // try and set a non-existent property
55
55
  propertiesManager.setItemProperties('faux-prop', string);
@@ -61,11 +61,11 @@ test('Test propertyManager.setItemProperties', () => {
61
61
  // Select the circle element
62
62
  DesignCore.Scene.selectionManager.selectionSet.selectionSet.push(1);
63
63
  // get the current radius
64
- const radius = DesignCore.Scene.entities.get(1).radius;
64
+ const radius = DesignCore.Scene.entities.get(1).getProperty('radius');
65
65
  // try and set an incorrect value
66
66
  propertiesManager.setItemProperties('radius', string);
67
67
  const circle = DesignCore.Scene.entities.get(1);
68
- expect(circle.radius).toBe(radius);
68
+ expect(circle.getProperty('radius')).toBe(radius);
69
69
  });
70
70
 
71
71
  test('Test propertyManager.getItemProperties', () => {