@design-core/core 0.0.6 → 50.1.0

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 (173) hide show
  1. package/.eslintrc.json +4 -0
  2. package/.github/workflows/DesignCoreQA.yml +3 -0
  3. package/TODO.md +49 -6
  4. package/core/core/core.js +0 -3
  5. package/core/designCore.js +1 -1
  6. package/core/dimensions/alignedDimension.js +12 -11
  7. package/core/dimensions/angularDimension.js +38 -37
  8. package/core/dimensions/{baseCircularDimension.js → circularDimensionBase.js} +22 -21
  9. package/core/dimensions/diametricDimension.js +14 -13
  10. package/core/dimensions/dimension.js +62 -55
  11. package/core/dimensions/{baseDimension.js → dimensionBase.js} +73 -107
  12. package/core/dimensions/{baseLinearDimension.js → linearDimensionBase.js} +15 -14
  13. package/core/dimensions/radialDimension.js +12 -11
  14. package/core/dimensions/rotatedDimension.js +20 -19
  15. package/core/entities/arc.js +123 -156
  16. package/core/entities/arctext.js +222 -184
  17. package/core/entities/circle.js +77 -99
  18. package/core/entities/entity.js +114 -93
  19. package/core/entities/hatch.js +346 -273
  20. package/core/entities/insert.js +68 -83
  21. package/core/entities/line.js +21 -142
  22. package/core/entities/lwpolyline.js +16 -11
  23. package/core/entities/point.js +19 -19
  24. package/core/entities/polyline.js +7 -7
  25. package/core/entities/{basePolyline.js → polylineBase.js} +47 -299
  26. package/core/entities/rectangle.js +3 -1
  27. package/core/entities/solid.js +6 -15
  28. package/core/entities/text.js +190 -201
  29. package/core/lib/auxiliary/rubberBand.js +32 -0
  30. package/core/lib/auxiliary/selectionWindow.js +63 -0
  31. package/core/lib/auxiliary/snapPoint.js +113 -0
  32. package/core/lib/auxiliary/trackingLine.js +76 -0
  33. package/core/lib/boundingBox.js +11 -11
  34. package/core/lib/canvas.js +232 -168
  35. package/core/lib/clipboard.js +9 -3
  36. package/core/lib/colour.js +16 -0
  37. package/core/lib/commandLine.js +1 -6
  38. package/core/lib/commandManager.js +18 -8
  39. package/core/lib/constants.js +28 -0
  40. package/core/lib/dxf/dxf.js +15 -14
  41. package/core/lib/dxf/dxfIterator.js +2 -2
  42. package/core/lib/entityManager.js +33 -26
  43. package/core/lib/inputManager.js +36 -12
  44. package/core/lib/intersect.js +25 -0
  45. package/core/lib/matrix.js +1 -1
  46. package/core/lib/patterns.js +9 -2
  47. package/core/lib/plotOptions.js +67 -0
  48. package/core/lib/polylineUtils.js +75 -0
  49. package/core/lib/renderers/cairoRenderer.js +371 -0
  50. package/core/lib/renderers/canvasRenderer.js +315 -0
  51. package/core/lib/renderers/pdfRenderer.js +366 -0
  52. package/core/lib/renderers/rendererBase.js +284 -0
  53. package/core/lib/renderers/svgRenderer.js +343 -0
  54. package/core/lib/scene.js +28 -6
  55. package/core/lib/selectionManager.js +20 -20
  56. package/core/lib/settings.js +4 -2
  57. package/core/lib/snapping.js +4 -191
  58. package/core/lib/stateManager.js +30 -1
  59. package/core/lib/utils.js +26 -7
  60. package/core/properties/entityProperties.js +104 -0
  61. package/core/properties/property.js +106 -2
  62. package/core/properties/propertyManager.js +60 -44
  63. package/core/tables/block.js +50 -81
  64. package/core/tables/dimStyle.js +5 -5
  65. package/core/tables/endblk.js +1 -1
  66. package/core/tables/layer.js +8 -0
  67. package/core/tables/ltypeManager.js +1 -1
  68. package/core/tables/styleManagerBase.js +3 -2
  69. package/core/tables/tableManagerBase.js +8 -3
  70. package/core/tables/vport.js +2 -2
  71. package/core/tools/chamfer.js +117 -143
  72. package/core/tools/chamferFilletBase.js +138 -37
  73. package/core/tools/copy.js +11 -11
  74. package/core/tools/copybase.js +3 -3
  75. package/core/tools/copyclip.js +3 -3
  76. package/core/tools/cornerEntity.js +2 -2
  77. package/core/tools/cutclip.js +3 -3
  78. package/core/tools/explode.js +7 -7
  79. package/core/tools/extend.js +138 -25
  80. package/core/tools/fillet.js +92 -189
  81. package/core/tools/matchProp.js +3 -12
  82. package/core/tools/mirror.js +15 -14
  83. package/core/tools/move.js +8 -8
  84. package/core/tools/offset.js +31 -33
  85. package/core/tools/pasteclip.js +1 -1
  86. package/core/tools/purge.js +10 -10
  87. package/core/tools/rotate.js +19 -16
  88. package/core/tools/scale.js +7 -7
  89. package/core/tools/trim.js +271 -29
  90. package/core/tools/zoom.js +1 -1
  91. package/cspell.json +321 -0
  92. package/jest.config.js +6 -0
  93. package/package.json +5 -3
  94. package/test/dimensions/alignedDimension.test.js +17 -10
  95. package/test/dimensions/angularDimension.test.js +13 -13
  96. package/test/dimensions/diametricDimension.test.js +15 -15
  97. package/test/dimensions/dimension.test.js +12 -12
  98. package/test/dimensions/{baseDimension.test.js → dimensionBase.test.js} +73 -12
  99. package/test/dimensions/linearDimensionBase.test.js +62 -0
  100. package/test/dimensions/radialDimension.test.js +15 -15
  101. package/test/dimensions/rotatedDimension.test.js +18 -11
  102. package/test/entities/arc.test.js +83 -86
  103. package/test/entities/arctext.test.js +184 -57
  104. package/test/entities/circle.test.js +68 -96
  105. package/test/entities/entity.test.js +42 -8
  106. package/test/entities/hatch.test.js +569 -96
  107. package/test/entities/insert.test.js +99 -4
  108. package/test/entities/line.test.js +15 -41
  109. package/test/entities/lwpolyline.test.js +104 -0
  110. package/test/entities/point.test.js +3 -3
  111. package/test/entities/polyline.test.js +11 -0
  112. package/test/entities/{basePolyline.test.js → polylineBase.test.js} +192 -57
  113. package/test/entities/rectangle.test.js +11 -0
  114. package/test/entities/solid.test.js +1 -1
  115. package/test/entities/text.test.js +302 -77
  116. package/test/integration/exportIntegration.reference.dxf +9 -9
  117. package/test/integration/exportIntegration.test.js +5 -5
  118. package/test/integration/intersectionIntegration.test.js +4 -5
  119. package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
  120. package/test/integration/pdfRendererIntegration.test.js +61 -0
  121. package/test/integration/svgRendererIntegration.reference.svg +3343 -0
  122. package/test/integration/svgRendererIntegration.test.js +61 -0
  123. package/test/integration/userInputIntegration.reference.dxf +1165 -105
  124. package/test/integration/userInputIntegration.test.js +239 -132
  125. package/test/lib/boundingBox.test.js +3 -3
  126. package/test/lib/canvas.test.js +744 -125
  127. package/test/lib/constants.test.js +27 -0
  128. package/test/lib/entityManager.test.js +4 -4
  129. package/test/lib/inputManager.test.js +81 -20
  130. package/test/lib/intersect.test.js +83 -0
  131. package/test/lib/plotOptions.test.js +179 -0
  132. package/test/lib/rubberBand.test.js +84 -0
  133. package/test/lib/scene.test.js +9 -9
  134. package/test/lib/selectionManager.test.js +3 -3
  135. package/test/lib/settings.test.js +5 -6
  136. package/test/lib/snapping.test.js +72 -130
  137. package/test/lib/stateManager.test.js +7 -7
  138. package/test/lib/utils.test.js +36 -2
  139. package/test/properties/entityProperties.test.js +132 -0
  140. package/test/properties/property.test.js +53 -0
  141. package/test/properties/propertyManager.test.js +25 -25
  142. package/test/renderers/canvasRenderer.test.js +317 -0
  143. package/test/renderers/pdfRenderer.test.js +271 -0
  144. package/test/renderers/rendererBase.test.js +213 -0
  145. package/test/renderers/svgRenderer.test.js +326 -0
  146. package/test/tables/block.test.js +92 -13
  147. package/test/tables/endblk.test.js +2 -2
  148. package/test/tables/layer.test.js +30 -0
  149. package/test/tables/layerManager.test.js +2 -2
  150. package/test/test-helpers/hatchGridRenderer.js +180 -0
  151. package/test/test-helpers/test-helpers.js +158 -6
  152. package/test/tools/chamfer.test.js +273 -51
  153. package/test/tools/chamferFilletBase.test.js +125 -36
  154. package/test/tools/copy.test.js +8 -8
  155. package/test/tools/copybase.test.js +2 -2
  156. package/test/tools/copyclip.test.js +2 -2
  157. package/test/tools/cornerEntity.test.js +8 -8
  158. package/test/tools/cutclip.test.js +2 -2
  159. package/test/tools/erase.test.js +6 -6
  160. package/test/tools/explode.test.js +5 -5
  161. package/test/tools/extend.test.js +373 -42
  162. package/test/tools/fillet.test.js +230 -49
  163. package/test/tools/matchProp.test.js +11 -11
  164. package/test/tools/mirror.test.js +39 -39
  165. package/test/tools/move.test.js +7 -7
  166. package/test/tools/offset.test.js +222 -55
  167. package/test/tools/pasteclip.test.js +5 -5
  168. package/test/tools/purge.test.js +4 -4
  169. package/test/tools/rotate.test.js +39 -9
  170. package/test/tools/scale.test.js +33 -33
  171. package/test/tools/trim.test.js +579 -53
  172. package/test/tools/zoom.test.js +12 -12
  173. package/core/lib/selectionWindow.js +0 -83
@@ -2,6 +2,8 @@ import { Core } from '../../core/core/core.js';
2
2
  import { Insert } from '../../core/entities/insert.js';
3
3
  import { Point } from '../../core/entities/point.js';
4
4
  import { Line } from '../../core/entities/line.js';
5
+ import { Utils } from '../../core/lib/utils.js';
6
+ import { jest } from '@jest/globals';
5
7
 
6
8
  import { File } from '../test-helpers/test-helpers.js';
7
9
 
@@ -11,10 +13,10 @@ new Core();
11
13
 
12
14
  const insert = new Insert({ handle: '1', points: [new Point()] });
13
15
  const line = new Line({ points: [new Point(101, 102), new Point(201, 202)] });
14
- insert.block.addItem(line);
16
+ insert.block.addEntity(line);
15
17
 
16
18
  const rotatedInsert = new Insert({ points: [new Point()], rotation: 45 });
17
- rotatedInsert.block.addItem(line);
19
+ rotatedInsert.block.addEntity(line);
18
20
 
19
21
  test('Test Insert.snaps', () => {
20
22
  const point = new Point(100, 100);
@@ -49,7 +51,7 @@ test('Test Insert.boundingBox', () => {
49
51
  expect(insert.boundingBox().yMax).toBeCloseTo(202);
50
52
 
51
53
  const line2 = new Line({ points: [new Point(1001, 1002), new Point(2001, 2002)] });
52
- insert.block.addItem(line2);
54
+ insert.block.addEntity(line2);
53
55
 
54
56
  expect(insert.boundingBox().xMin).toBeCloseTo(101);
55
57
  expect(insert.boundingBox().xMax).toBeCloseTo(2001);
@@ -85,7 +87,7 @@ AcDbBlockReference
85
87
  expect(file.contents).toEqual(dxfString);
86
88
 
87
89
  // create new entity from entity data to ensure all props are loaded
88
- const newInsert = new Insert(insert);
90
+ const newInsert = new Insert({ handle: insert.getProperty('handle'), points: insert.points });
89
91
  file = new File();
90
92
  newInsert.dxf(file);
91
93
  expect(file.contents).toEqual(dxfString);
@@ -98,3 +100,96 @@ test('Insert.snaps returns a node snap at points[0]', () => {
98
100
  expect(nodeSnaps[0].snapPoint.x).toBe(5);
99
101
  expect(nodeSnaps[0].snapPoint.y).toBe(10);
100
102
  });
103
+
104
+ describe('Test Insert.draw', () => {
105
+ /**
106
+ * Create a minimal mock renderer with a jest spy for applyTransform.
107
+ * @return {Object} mock renderer
108
+ */
109
+ function makeMockCtx() {
110
+ return {
111
+ applyTransform: jest.fn(),
112
+ };
113
+ }
114
+
115
+ test('draw() returns the block items array', () => {
116
+ const ins = new Insert({ points: [new Point(0, 0)] });
117
+ const line = new Line({ points: [new Point(0, 0), new Point(1, 1)] });
118
+ ins.block.addEntity(line);
119
+
120
+ const result = ins.draw(makeMockCtx());
121
+ expect(result).toBe(ins.block.entities);
122
+ });
123
+
124
+ test('draw() returns empty array when block has no items', () => {
125
+ const ins = new Insert({ points: [new Point(0, 0)] });
126
+ const result = ins.draw(makeMockCtx());
127
+ expect(result).toEqual([]);
128
+ expect(result).toBe(ins.block.entities);
129
+ });
130
+
131
+ test('draw() calls renderer.applyTransform with the insert point coordinates', () => {
132
+ const ins = new Insert({ points: [new Point(50, 75)] });
133
+ const ctx = makeMockCtx();
134
+ ins.draw(ctx);
135
+ expect(ctx.applyTransform).toHaveBeenCalledWith(expect.objectContaining({ x: 50, y: 75 }));
136
+ });
137
+
138
+ test('draw() calls renderer.applyTransform with rotation converted to radians', () => {
139
+ const ins = new Insert({ points: [new Point(0, 0)], rotation: 90 });
140
+ const ctx = makeMockCtx();
141
+ ins.draw(ctx);
142
+ expect(ctx.applyTransform).toHaveBeenCalledWith(expect.objectContaining({ rotation: Utils.degrees2radians(90) }));
143
+ });
144
+
145
+ test('draw() calls renderer.applyTransform with rotation 0 when rotation is 0', () => {
146
+ const ins = new Insert({ points: [new Point(0, 0)] });
147
+ const ctx = makeMockCtx();
148
+ ins.draw(ctx);
149
+ expect(ctx.applyTransform).toHaveBeenCalledWith(expect.objectContaining({ rotation: 0 }));
150
+ });
151
+
152
+ test('draw() calls renderer.applyTransform exactly once', () => {
153
+ const ins = new Insert({ points: [new Point(10, 20)], rotation: 45 });
154
+ const ctx = makeMockCtx();
155
+ ins.draw(ctx);
156
+ expect(ctx.applyTransform).toHaveBeenCalledTimes(1);
157
+ });
158
+
159
+ test('draw() returns same items reference regardless of scale', () => {
160
+ const ins = new Insert({ points: [new Point(0, 0)] });
161
+ const line = new Line({ points: [new Point(0, 0), new Point(1, 1)] });
162
+ ins.block.addEntity(line);
163
+ expect(ins.draw(makeMockCtx())).toBe(ins.block.entities);
164
+ expect(ins.draw(makeMockCtx())).toBe(ins.block.entities);
165
+ });
166
+ });
167
+
168
+ describe('Insert.fromDxf', () => {
169
+ test('maps group code 2 to blockName', () => {
170
+ const data = Insert.fromDxf({ points: [new Point(0, 0)], 2: 'MYBLOCK' });
171
+ expect(data.blockName).toBe('MYBLOCK');
172
+ });
173
+
174
+ test('maps group code 50 to rotation', () => {
175
+ const data = Insert.fromDxf({ points: [new Point(0, 0)], 50: 45 });
176
+ expect(data.rotation).toBe(45);
177
+ });
178
+
179
+ test('preserves other properties unchanged', () => {
180
+ const data = Insert.fromDxf({ points: [new Point(1, 2)], layer: 'TEST', 2: 'BLK' });
181
+ expect(data.layer).toBe('TEST');
182
+ expect(data.points[0].x).toBe(1);
183
+ });
184
+
185
+ test('result with rotation is equivalent to direct construction', () => {
186
+ const pt = new Point(5, 10);
187
+ const i1 = new Insert(Insert.fromDxf({ points: [pt], 50: 90 }));
188
+ const i2 = new Insert({ points: [pt], rotation: 90 });
189
+ expect(i1.getProperty('rotation')).toBeCloseTo(i2.getProperty('rotation'));
190
+ });
191
+
192
+ test('unimplemented codes do not throw', () => {
193
+ expect(() => Insert.fromDxf({ points: [new Point()], 41: 2, 42: 2, 43: 2, 44: 5, 45: 5, 70: 2, 71: 3 })).not.toThrow();
194
+ });
195
+ });
@@ -2,7 +2,6 @@ import { Line } from '../../core/entities/line.js';
2
2
  import { Point } from '../../core/entities/point.js';
3
3
  import { Core } from '../../core/core/core.js';
4
4
  import { DesignCore } from '../../core/designCore.js';
5
- import { AddState, RemoveState } from '../../core/lib/stateManager.js';
6
5
  import { File, withMockInput } from '../test-helpers/test-helpers.js';
7
6
 
8
7
  // initialise core
@@ -110,13 +109,13 @@ ByLayer
110
109
  31
111
110
  0.0
112
111
  39
113
- 2
112
+ 1
114
113
  `;
115
114
 
116
115
  expect(file.contents).toEqual(dxfString);
117
116
 
118
117
  // create new entity from entity data to ensure all props are loaded
119
- const newLine = new Line(line);
118
+ const newLine = new Line({ handle: line.getProperty('handle'), points: line.points });
120
119
  file = new File();
121
120
  newLine.dxf(file);
122
121
  expect(file.contents).toEqual(dxfString);
@@ -135,45 +134,19 @@ test('Test Line.toPolylinePoints', () => {
135
134
  });
136
135
 
137
136
 
138
- test('Test Line.trim returns remove and add states', () => {
139
- const line = new Line({ points: [new Point(0, 0), new Point(100, 0)], handle: 'A1' });
140
-
141
- // intersections along the line
142
- const i1 = new Point(30, 0);
143
- const i2 = new Point(60, 0);
144
-
145
- // place mouse between intersections so trim targets that segment
146
- DesignCore.Mouse.pointOnScene = () => new Point(45, 0);
147
-
148
- const changes = line.trim([i1, i2]);
149
-
150
- expect(Array.isArray(changes)).toBe(true);
151
- expect(changes.length).toBeGreaterThanOrEqual(2);
152
- expect(changes[0]).toBeInstanceOf(AddState);
153
- expect(changes[1]).toBeInstanceOf(AddState);
154
- expect(changes[2]).toBeInstanceOf(RemoveState);
155
-
156
- // Verify trimmed lines have unique handles (not the original)
157
- expect(changes[0].entity.handle).toBeUndefined();
158
- expect(changes[1].entity.handle).toBeUndefined();
137
+ test('Test Line.draw calls renderer.drawShape with toPolylinePoints', () => {
138
+ const line = new Line({ points: [new Point(0, 0), new Point(10, 5)] });
139
+ let capturedPoints;
140
+ const mockRenderer = {
141
+ drawShape(points) {
142
+ capturedPoints = points;
143
+ },
144
+ };
145
+ line.draw(mockRenderer);
146
+ expect(capturedPoints).toEqual(line.toPolylinePoints());
159
147
  });
160
148
 
161
149
 
162
- test('Test Line.trim returns empty array when provided with empty intersection list', () => {
163
- const line = new Line({ points: [new Point(0, 0), new Point(100, 0)] });
164
-
165
- expect(line.trim([])).toEqual([]);
166
- expect(line.trim()).toEqual([]);
167
- });
168
-
169
-
170
- test('Test Line.extend returns empty array when provided with empty intersection list', () => {
171
- const line = new Line({ points: [new Point(0, 0), new Point(100, 0)] });
172
-
173
- expect(line.extend([])).toEqual([]);
174
- expect(line.extend()).toEqual([]);
175
- });
176
-
177
150
  test('Line.execute handles Close option', async () => {
178
151
  const pt1 = new Point(0, 0);
179
152
  const pt2 = new Point(10, 0);
@@ -183,11 +156,12 @@ test('Line.execute handles Close option', async () => {
183
156
  const line = new Line({});
184
157
  await line.execute();
185
158
 
186
- expect(line.points.length).toBe(4);
159
+ // this.points accumulates all confirmed input points (not the closing segment,
160
+ // which is committed as a separate entity)
161
+ expect(line.points.length).toBe(3);
187
162
  expect(line.points[0]).toBe(pt1);
188
163
  expect(line.points[1]).toBe(pt2);
189
164
  expect(line.points[2]).toBe(pt3);
190
- expect(line.points[3]).toBe(pt1);
191
165
  }, { extraMethods: { actionCommand: () => {} } });
192
166
  });
193
167
 
@@ -0,0 +1,104 @@
1
+ import { jest } from '@jest/globals';
2
+ import { Lwpolyline } from '../../core/entities/lwpolyline.js';
3
+ import { Polyline } from '../../core/entities/polyline.js';
4
+ import { Point } from '../../core/entities/point.js';
5
+ import { Core } from '../../core/core/core.js';
6
+
7
+ // initialise core
8
+ new Core();
9
+
10
+ // ─── constructor returns a Polyline ──────────────────────────────────────────
11
+
12
+ test('Lwpolyline constructor returns a Polyline instance', () => {
13
+ const result = new Lwpolyline({ points: [new Point(0, 0), new Point(10, 0)] });
14
+ expect(result).toBeInstanceOf(Polyline);
15
+ });
16
+
17
+ test('Lwpolyline with no data returns a Polyline instance', () => {
18
+ const result = new Lwpolyline();
19
+ expect(result).toBeInstanceOf(Polyline);
20
+ });
21
+
22
+ // ─── group code 90 vertex count validation ───────────────────────────────────
23
+
24
+ test('Lwpolyline logs no error when group code 90 matches points.length', () => {
25
+ const spy = jest.spyOn(console, 'log').mockImplementation(() => {});
26
+ new Lwpolyline({ 90: 2, points: [new Point(0, 0), new Point(10, 0)] });
27
+ const errorCalls = spy.mock.calls.filter(([msg]) => msg.startsWith('Error:'));
28
+ expect(errorCalls).toHaveLength(0);
29
+ spy.mockRestore();
30
+ });
31
+
32
+ test('Lwpolyline logs error when group code 90 count does not match points.length', () => {
33
+ const spy = jest.spyOn(console, 'log').mockImplementation(() => {});
34
+ new Lwpolyline({ 90: 3, points: [new Point(0, 0), new Point(10, 0)] });
35
+ const errorCalls = spy.mock.calls.filter(([msg]) => msg.startsWith('Error:'));
36
+ expect(errorCalls).toHaveLength(1);
37
+ expect(errorCalls[0][0]).toMatch(/specifies 3 vertices/);
38
+ expect(errorCalls[0][0]).toMatch(/2 were parsed/);
39
+ spy.mockRestore();
40
+ });
41
+
42
+ test('Lwpolyline logs error when group code 90 present but points is undefined', () => {
43
+ const spy = jest.spyOn(console, 'log').mockImplementation(() => {});
44
+ new Lwpolyline({ 90: 2 });
45
+ const errorCalls = spy.mock.calls.filter(([msg]) => msg.startsWith('Error:'));
46
+ expect(errorCalls).toHaveLength(1);
47
+ expect(errorCalls[0][0]).toMatch(/points array is missing or invalid/);
48
+ spy.mockRestore();
49
+ });
50
+
51
+ test('Lwpolyline logs error when group code 90 present but points is not an array', () => {
52
+ const spy = jest.spyOn(console, 'log').mockImplementation(() => {});
53
+ new Lwpolyline({ 90: 1, points: 'bad' });
54
+ const errorCalls = spy.mock.calls.filter(([msg]) => msg.startsWith('Error:'));
55
+ expect(errorCalls).toHaveLength(1);
56
+ expect(errorCalls[0][0]).toMatch(/points array is missing or invalid/);
57
+ spy.mockRestore();
58
+ });
59
+
60
+ test('Lwpolyline does not throw when group code 90 present but points is undefined', () => {
61
+ expect(() => new Lwpolyline({ 90: 2 })).not.toThrow();
62
+ });
63
+
64
+ // ─── closed-polyline deduplication ───────────────────────────────────────────
65
+
66
+ test('Lwpolyline removes duplicate closing point and sets closed flag', () => {
67
+ const pts = [
68
+ new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 0),
69
+ ];
70
+ const result = new Lwpolyline({ points: pts });
71
+ expect(result.points).toHaveLength(3);
72
+ expect(result.flags.hasFlag(1)).toBe(true);
73
+ });
74
+
75
+ test('Lwpolyline preserves existing flags when setting closed bit', () => {
76
+ const pts = [
77
+ new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 0),
78
+ ];
79
+ // pass flag bit 2 (arbitrary extra flag)
80
+ const result = new Lwpolyline({ points: pts, 70: 2 });
81
+ const flagVal = result.flags.getFlagValue();
82
+ expect(flagVal & 1).toBe(1); // closed bit set
83
+ expect(flagVal & 2).toBe(2); // original bit preserved
84
+ });
85
+
86
+ test('Lwpolyline does not remove point when fewer than 4 points with matching ends', () => {
87
+ const pts = [new Point(0, 0), new Point(10, 0), new Point(0, 0)];
88
+ const result = new Lwpolyline({ points: pts });
89
+ expect(result.points).toHaveLength(3);
90
+ expect(result.flags.hasFlag(1)).toBe(false);
91
+ });
92
+
93
+ test('Lwpolyline does not modify open polyline', () => {
94
+ const pts = [new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(5, 5)];
95
+ const result = new Lwpolyline({ points: pts });
96
+ expect(result.points).toHaveLength(4);
97
+ expect(result.flags.hasFlag(1)).toBe(false);
98
+ });
99
+
100
+ // ─── register ────────────────────────────────────────────────────────────────
101
+
102
+ test('Lwpolyline.register returns command object', () => {
103
+ expect(Lwpolyline.register()).toEqual({ command: 'Lwpolyline' });
104
+ });
@@ -701,7 +701,7 @@ test('Test Point.bulgeAngle', () => {
701
701
 
702
702
  test('Test Point.bulgeRadius', () => {
703
703
  // start point: 100,0
704
- // center: 100,50
704
+ // centre: 100,50
705
705
  // radius: 50
706
706
  const point = new Point(100, 0);
707
707
 
@@ -771,7 +771,7 @@ test('Test Point.bulgeRadius', () => {
771
771
 
772
772
  test('Test Point.apothem', () => {
773
773
  // start point: 100,0
774
- // center: 100,50
774
+ // centre: 100,50
775
775
  // radius: 50
776
776
  const point = new Point(100, 0);
777
777
 
@@ -829,7 +829,7 @@ test('Test Point.apothem', () => {
829
829
 
830
830
  test('Test Point.bulgeCentrePoint', () => {
831
831
  // start point: 100,0
832
- // center: 100,50
832
+ // centre: 100,50
833
833
  // radius: 50
834
834
  const point = new Point(100, 0);
835
835
 
@@ -0,0 +1,11 @@
1
+ import { Polyline } from '../../core/entities/polyline.js';
2
+ import { Core } from '../../core/core/core.js';
3
+
4
+ // initialise core
5
+ new Core();
6
+
7
+ // ─── register ────────────────────────────────────────────────────────────────
8
+
9
+ test('Polyline.register returns correct command object', () => {
10
+ expect(Polyline.register()).toEqual({ command: 'Polyline', shortcut: 'PL', type: 'Entity' });
11
+ });