@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.
- package/.eslintrc.json +14 -7
- package/.github/workflows/DesignCoreQA.yml +3 -1
- package/README.md +11 -4
- package/TODO.md +19 -0
- package/core/core/core.js +49 -42
- package/core/designCore.js +61 -2
- package/core/dimensions/alignedDimension.js +142 -0
- package/core/dimensions/angularDimension.js +550 -0
- package/core/dimensions/baseCircularDimension.js +244 -0
- package/core/dimensions/baseDimension.js +558 -0
- package/core/dimensions/baseLinearDimension.js +371 -0
- package/core/dimensions/diametricDimension.js +82 -0
- package/core/dimensions/dimension.js +298 -0
- package/core/dimensions/linearDimension.js +29 -0
- package/core/dimensions/radialDimension.js +78 -0
- package/core/dimensions/rotatedDimension.js +167 -0
- package/core/entities/arc.js +314 -60
- package/core/entities/arctext.js +642 -0
- package/core/entities/basePolyline.js +553 -94
- package/core/entities/circle.js +181 -54
- package/core/entities/entity.js +154 -74
- package/core/entities/hatch.js +807 -0
- package/core/entities/insert.js +200 -39
- package/core/entities/line.js +196 -134
- package/core/entities/lwpolyline.js +21 -44
- package/core/entities/point.js +194 -29
- package/core/entities/polyline.js +54 -7
- package/core/entities/rectangle.js +43 -13
- package/core/entities/solid.js +43 -6
- package/core/entities/text.js +324 -181
- package/core/lib/boundingBox.js +73 -13
- package/core/lib/canvas.js +256 -65
- package/core/lib/clipboard.js +198 -0
- package/core/lib/colour.js +144 -0
- package/core/lib/colours.js +367 -301
- package/core/lib/commandLine.js +16 -28
- package/core/lib/commandManager.js +115 -58
- package/core/lib/constants.js +23 -0
- package/core/lib/dxf/dxf.js +249 -34
- package/core/lib/dxf/dxfFile.js +23 -38
- package/core/lib/dxf/dxfIterator.js +17 -5
- package/core/lib/dxf/dxfRead.js +23 -7
- package/core/lib/dxf/dxfWrite.js +39 -129
- package/core/lib/dxf/handleManager.js +97 -0
- package/core/lib/dxf/sections/blocks.js +16 -2
- package/core/lib/dxf/sections/entities.js +17 -3
- package/core/lib/dxf/sections/header.js +10 -1
- package/core/lib/dxf/sections/objects.js +36 -0
- package/core/lib/dxf/sections/section.js +43 -5
- package/core/lib/dxf/sections/tables.js +17 -3
- package/core/lib/entityManager.js +178 -0
- package/core/lib/fileio.js +14 -4
- package/core/lib/headers.js +229 -0
- package/core/lib/input.js +46 -0
- package/core/lib/inputManager.js +199 -194
- package/core/lib/intersect.js +341 -468
- package/core/lib/logging.js +25 -1
- package/core/lib/matrix.js +6 -8
- package/core/lib/mouse.js +24 -79
- package/core/lib/patterns.js +690 -0
- package/core/lib/promptOptions.js +133 -0
- package/core/lib/scene.js +71 -174
- package/core/lib/selectionManager.js +74 -48
- package/core/lib/selectionWindow.js +24 -10
- package/core/lib/settings.js +18 -11
- package/core/lib/snapping.js +129 -21
- package/core/lib/stateManager.js +288 -0
- package/core/lib/strings.js +57 -5
- package/core/lib/utils.js +95 -11
- package/core/objects/dictionary.js +43 -0
- package/core/objects/dictionaryManager.js +80 -0
- package/core/properties/dimType.js +117 -0
- package/core/properties/flags.js +66 -0
- package/core/properties/property.js +26 -0
- package/core/properties/propertyManager.js +65 -19
- package/core/tables/appId.js +31 -0
- package/core/tables/appIdManager.js +44 -0
- package/core/tables/block.js +323 -0
- package/core/tables/blockManager.js +48 -0
- package/core/tables/blockRecord.js +29 -0
- package/core/tables/blockRecordManager.js +37 -0
- package/core/tables/dimStyle.js +627 -0
- package/core/tables/dimStyleManager.js +187 -0
- package/core/tables/endblk.js +28 -0
- package/core/tables/layer.js +219 -0
- package/core/tables/layerManager.js +47 -0
- package/core/tables/ltype.js +85 -0
- package/core/tables/ltypeManager.js +65 -0
- package/core/tables/style.js +145 -0
- package/core/tables/styleManager.js +55 -0
- package/core/tables/styleManagerBase.js +110 -0
- package/core/tables/tableManagerBase.js +354 -0
- package/core/tables/ucs.js +28 -0
- package/core/tables/ucsManager.js +44 -0
- package/core/tables/view.js +28 -0
- package/core/tables/viewManager.js +44 -0
- package/core/tables/vport.js +70 -0
- package/core/tables/vportManager.js +65 -0
- package/core/tools/chamfer.js +343 -0
- package/core/tools/chamferFilletBase.js +162 -0
- package/core/tools/copy.js +68 -40
- package/core/tools/copybase.js +65 -0
- package/core/tools/copyclip.js +59 -0
- package/core/tools/cornerEntity.js +138 -0
- package/core/tools/cutclip.js +72 -0
- package/core/tools/distance.js +37 -9
- package/core/tools/erase.js +43 -13
- package/core/tools/explode.js +117 -0
- package/core/tools/extend.js +71 -32
- package/core/tools/fillet.js +336 -0
- package/core/tools/identify.js +33 -6
- package/core/tools/matchProp.js +106 -0
- package/core/tools/mirror.js +141 -0
- package/core/tools/move.js +65 -32
- package/core/tools/offset.js +321 -0
- package/core/tools/pan.js +54 -0
- package/core/tools/pasteclip.js +93 -0
- package/core/tools/purge.js +90 -0
- package/core/tools/rotate.js +105 -70
- package/core/tools/scale.js +166 -0
- package/core/tools/tool.js +14 -4
- package/core/tools/trim.js +71 -32
- package/core/tools/zoom.js +152 -0
- package/package.json +3 -305
- package/test/dimensions/alignedDimension.test.js +211 -0
- package/test/dimensions/angularDimension.test.js +243 -0
- package/test/dimensions/baseDimension.test.js +239 -0
- package/test/dimensions/diametricDimension.test.js +203 -0
- package/test/dimensions/dimension.test.js +102 -0
- package/test/dimensions/radialDimension.test.js +203 -0
- package/test/dimensions/rotatedDimension.test.js +224 -0
- package/test/entities/arc.test.js +463 -41
- package/test/entities/arctext.test.js +329 -0
- package/test/entities/basePolyline.test.js +459 -10
- package/test/entities/circle.test.js +312 -7
- package/test/entities/entity.test.js +28 -9
- package/test/entities/hatch.test.js +606 -0
- package/test/entities/insert.test.js +90 -0
- package/test/entities/line.test.js +182 -6
- package/test/entities/point.test.js +375 -37
- package/test/entities/solid.test.js +156 -0
- package/test/entities/text.test.js +533 -7
- package/test/integration/exportIntegration.reference.dxf +1706 -0
- package/test/integration/exportIntegration.test.js +186 -0
- package/test/integration/importExportIntegration.test.js +126 -0
- package/test/integration/intersectionIntegration.test.js +880 -0
- package/test/integration/userInputIntegration.reference.dxf +1410 -0
- package/test/integration/userInputIntegration.test.js +269 -0
- package/test/lib/boundingBox.test.js +26 -2
- package/test/lib/canvas.test.js +613 -5
- package/test/lib/clipboard.test.js +70 -0
- package/test/lib/colour.test.js +114 -0
- package/test/lib/colours.test.js +63 -18
- package/test/lib/commandLine.test.js +159 -22
- package/test/lib/commandManager.test.js +4 -4
- package/test/lib/constants.test.js +9 -0
- package/test/lib/dxf/dxfFile.test.js +6 -2
- package/test/lib/dxf/dxfIterator.test.js +1 -1
- package/test/lib/dxf/dxfWrite.test.js +173 -0
- package/test/lib/dxf/handleManager.test.js +137 -0
- package/test/lib/dxf/sections/blocks.test.js +5 -5
- package/test/lib/dxf/sections/entities.test.js +4 -4
- package/test/lib/dxf/sections/header.test.js +18 -4
- package/test/lib/dxf/sections/objects.test.js +132 -0
- package/test/lib/dxf/sections/section.test.js +29 -29
- package/test/lib/dxf/sections/tables.test.js +3 -3
- package/test/lib/entityManager.test.js +163 -0
- package/test/lib/headers.test.js +233 -0
- package/test/lib/inputManager.test.js +1144 -5
- package/test/lib/intersect.test.js +454 -331
- package/test/lib/logging.test.js +1 -1
- package/test/lib/matrix.test.js +1 -1
- package/test/lib/mouse.test.js +327 -4
- package/test/lib/patterns.test.js +105 -0
- package/test/lib/promptOptions.test.js +179 -0
- package/test/lib/scene.test.js +16 -52
- package/test/lib/selectionManager.test.js +17 -5
- package/test/lib/settings.test.js +111 -5
- package/test/lib/snapping.test.js +142 -4
- package/test/lib/stateManager.test.js +155 -0
- package/test/lib/utils.test.js +81 -1
- package/test/objects/dictionary.test.js +81 -0
- package/test/objects/dictionaryManager.test.js +34 -0
- package/test/properties/dimType.test.js +62 -0
- package/test/properties/flags.test.js +164 -0
- package/test/properties/property.test.js +13 -0
- package/test/properties/propertyManager.test.js +10 -11
- package/test/tables/appId.test.js +46 -0
- package/test/tables/appIdManager.test.js +72 -0
- package/test/tables/block.test.js +157 -0
- package/test/tables/blockManager.test.js +52 -0
- package/test/tables/blockRecord.test.js +39 -0
- package/test/tables/blockRecordManager.test.js +60 -0
- package/test/tables/dimstyle.test.js +72 -0
- package/test/tables/endblk.test.js +30 -0
- package/test/tables/layer.test.js +95 -0
- package/test/tables/layerManager.test.js +172 -0
- package/test/{styles → tables}/ltype.test.js +7 -7
- package/test/{styles → tables}/ltypeManager.test.js +2 -2
- package/test/tables/style.test.js +44 -0
- package/test/tables/stylemanagerbase.test.js +220 -0
- package/test/tables/tableManagerBase.test.js +194 -0
- package/test/tables/ucs.test.js +37 -0
- package/test/tables/ucsManager.test.js +48 -0
- package/test/tables/view.test.js +37 -0
- package/test/tables/viewManager.test.js +48 -0
- package/test/tables/vport.test.js +117 -0
- package/test/tables/vportManager.test.js +54 -0
- package/test/test-helpers/test-helpers.js +66 -0
- package/test/tools/chamfer.test.js +970 -0
- package/test/tools/chamferFilletBase.test.js +446 -0
- package/test/tools/copy.test.js +22 -14
- package/test/tools/copybase.test.js +93 -0
- package/test/tools/copyclip.test.js +92 -0
- package/test/tools/cornerEntity.test.js +201 -0
- package/test/tools/cutclip.test.js +94 -0
- package/test/tools/distance.test.js +140 -18
- package/test/tools/erase.test.js +12 -12
- package/test/tools/explode.test.js +45 -0
- package/test/tools/extend.test.js +326 -44
- package/test/tools/fillet.test.js +831 -0
- package/test/tools/identify.test.js +115 -16
- package/test/tools/matchProp.test.js +84 -0
- package/test/tools/mirror.test.js +475 -0
- package/test/tools/move.test.js +75 -19
- package/test/tools/offset.test.js +748 -0
- package/test/tools/pan.test.js +72 -0
- package/test/tools/pasteclip.test.js +100 -0
- package/test/tools/purge.test.js +89 -0
- package/test/tools/rotate.test.js +89 -23
- package/test/tools/scale.test.js +529 -0
- package/test/tools/trim.test.js +419 -84
- package/test/tools/zoom.test.js +504 -0
- package/core/entities/alignedDimension.js +0 -279
- package/core/entities/angularDimension.js +0 -194
- package/core/entities/baseDimension.js +0 -227
- package/core/entities/block.js +0 -247
- package/core/entities/diametricDimension.js +0 -133
- package/core/entities/dimension.js +0 -202
- package/core/entities/radialDimension.js +0 -126
- package/core/styles/dimStyle.js +0 -719
- package/core/styles/dimStyleManager.js +0 -47
- package/core/styles/layer.js +0 -133
- package/core/styles/layerManager.js +0 -40
- package/core/styles/ltype.js +0 -86
- package/core/styles/ltypeManager.js +0 -50
- package/core/styles/style.js +0 -158
- package/core/styles/styleManager.js +0 -43
- package/core/styles/styleManagerBase.js +0 -299
- package/test/entities/block.test.js +0 -22
- package/test/styles/dimstyle.test.js +0 -19
- package/test/styles/layerManager.test.js +0 -150
- package/test/styles/style.test.js +0 -33
- package/test/styles/stylemanagerbase.test.js +0 -219
package/test/lib/canvas.test.js
CHANGED
|
@@ -1,8 +1,616 @@
|
|
|
1
|
+
import { Core } from '../../core/core/core.js';
|
|
2
|
+
import { Point } from '../../core/entities/point.js';
|
|
3
|
+
import { Matrix } from '../../core/lib/matrix.js';
|
|
4
|
+
import { Input } from '../../core/lib/input.js';
|
|
5
|
+
import { jest } from '@jest/globals';
|
|
1
6
|
|
|
2
|
-
|
|
3
|
-
|
|
7
|
+
const core = new Core();
|
|
8
|
+
const canvas = core.canvas;
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Create a mock canvas rendering context for testing
|
|
12
|
+
* @return {object} mock context
|
|
13
|
+
*/
|
|
14
|
+
function createMockContext() {
|
|
15
|
+
return {
|
|
16
|
+
setTransform: () => {},
|
|
17
|
+
translate: () => {},
|
|
18
|
+
scale: () => {},
|
|
19
|
+
fillStyle: '',
|
|
20
|
+
fillRect: () => {},
|
|
21
|
+
strokeStyle: '',
|
|
22
|
+
lineWidth: 0,
|
|
23
|
+
setLineDash: () => {},
|
|
24
|
+
beginPath: () => {},
|
|
25
|
+
moveTo: () => {},
|
|
26
|
+
lineTo: () => {},
|
|
27
|
+
stroke: () => {},
|
|
28
|
+
fill: () => {},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
7
31
|
|
|
8
|
-
test
|
|
32
|
+
test('Test Canvas constructor defaults', () => {
|
|
33
|
+
expect(canvas.cvs).toBeNull();
|
|
34
|
+
expect(canvas.width).toBe(1);
|
|
35
|
+
expect(canvas.height).toBe(1);
|
|
36
|
+
expect(canvas.minScaleFactor).toBe(0.05);
|
|
37
|
+
expect(canvas.maxScaleFactor).toBe(300);
|
|
38
|
+
expect(canvas.flipped).toBe(false);
|
|
39
|
+
expect(canvas.panDelta).toBeDefined();
|
|
40
|
+
expect(canvas.lastDelta).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('Test Canvas.getScale', () => {
|
|
44
|
+
expect(canvas.getScale()).toBe(canvas.matrix.getScale());
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('Test Canvas.paintStates', () => {
|
|
48
|
+
expect(canvas.paintStates.ENTITIES).toBe('ENTITIES');
|
|
49
|
+
expect(canvas.paintStates.TEMPORARY).toBe('TEMPORARY');
|
|
50
|
+
expect(canvas.paintStates.SELECTED).toBe('SELECTED');
|
|
51
|
+
expect(canvas.paintStates.AUXILLARY).toBe('AUXILLARY');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('Test Canvas.setExternalPaintCallbackFunction', () => {
|
|
55
|
+
let called = false;
|
|
56
|
+
const callback = () => {
|
|
57
|
+
called = true;
|
|
58
|
+
};
|
|
59
|
+
canvas.setExternalPaintCallbackFunction(callback);
|
|
60
|
+
canvas.requestPaint();
|
|
61
|
+
expect(called).toBe(true);
|
|
62
|
+
canvas.setExternalPaintCallbackFunction(undefined);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('Test Canvas.requestPaint does nothing without callback', () => {
|
|
66
|
+
canvas.setExternalPaintCallbackFunction(undefined);
|
|
67
|
+
expect(() => canvas.requestPaint()).not.toThrow();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('Test Canvas.wheel zooms in', () => {
|
|
71
|
+
const scaleBefore = canvas.getScale();
|
|
72
|
+
canvas.wheel(1);
|
|
73
|
+
expect(canvas.getScale()).toBeGreaterThan(scaleBefore);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('Test Canvas.wheel zooms out', () => {
|
|
77
|
+
const scaleBefore = canvas.getScale();
|
|
78
|
+
canvas.wheel(-1);
|
|
79
|
+
expect(canvas.getScale()).toBeLessThan(scaleBefore);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('Test Canvas.wheel respects min scale limit', () => {
|
|
83
|
+
canvas.matrix = new Matrix();
|
|
84
|
+
// zoom out repeatedly
|
|
85
|
+
for (let i = 0; i < 200; i++) {
|
|
86
|
+
canvas.wheel(-1);
|
|
87
|
+
}
|
|
88
|
+
// scale should stop decreasing near the min limit
|
|
89
|
+
const scaleBefore = canvas.getScale();
|
|
90
|
+
canvas.wheel(-1);
|
|
91
|
+
expect(canvas.getScale()).toBe(scaleBefore);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('Test Canvas.wheel respects max scale limit', () => {
|
|
95
|
+
canvas.matrix = new Matrix();
|
|
96
|
+
// zoom in repeatedly
|
|
97
|
+
for (let i = 0; i < 200; i++) {
|
|
98
|
+
canvas.wheel(1);
|
|
99
|
+
}
|
|
100
|
+
// scale should stop increasing near the max limit
|
|
101
|
+
const scaleBefore = canvas.getScale();
|
|
102
|
+
canvas.wheel(1);
|
|
103
|
+
expect(canvas.getScale()).toBe(scaleBefore);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('Test Canvas.zoomToWindow', () => {
|
|
107
|
+
canvas.matrix = new Matrix();
|
|
108
|
+
|
|
109
|
+
canvas.width = 800;
|
|
110
|
+
canvas.height = 600;
|
|
111
|
+
canvas.zoomToWindow(new Point(0, 0), new Point(100, 100));
|
|
112
|
+
|
|
113
|
+
const scale = canvas.getScale();
|
|
114
|
+
expect(scale).toBeGreaterThan(1);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('Test Canvas.zoomToWindow with zero-size window', () => {
|
|
118
|
+
const scaleBefore = canvas.getScale();
|
|
119
|
+
canvas.zoomToWindow(new Point(50, 50), new Point(50, 50));
|
|
120
|
+
expect(canvas.getScale()).toBe(scaleBefore);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('Test Canvas.paint sets width and height', () => {
|
|
124
|
+
const context = createMockContext();
|
|
125
|
+
canvas.matrix = new Matrix();
|
|
126
|
+
canvas.flipped = false;
|
|
127
|
+
canvas.paint(context, 1024, 768);
|
|
128
|
+
expect(canvas.width).toBe(1024);
|
|
129
|
+
expect(canvas.height).toBe(768);
|
|
130
|
+
expect(canvas.flipped).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('Test Canvas.paint flips only once', () => {
|
|
134
|
+
const context = createMockContext();
|
|
135
|
+
canvas.flipped = true;
|
|
136
|
+
canvas.paint(context, 1024, 768);
|
|
137
|
+
// matrix should not have additional flip translation
|
|
138
|
+
expect(canvas.flipped).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('Test Canvas.mouseUp resets pan state on middle button', () => {
|
|
142
|
+
canvas.lastDelta = new Point(50, 50);
|
|
143
|
+
canvas.mouseUp(1);
|
|
144
|
+
expect(canvas.lastDelta.x).toBe(0);
|
|
145
|
+
expect(canvas.lastDelta.y).toBe(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('Test Canvas.getSceneOffset returns extents', () => {
|
|
149
|
+
canvas.matrix = new Matrix();
|
|
150
|
+
canvas.width = 800;
|
|
151
|
+
canvas.height = 600;
|
|
152
|
+
const offset = canvas.getSceneOffset();
|
|
153
|
+
expect(offset).toHaveProperty('xmin');
|
|
154
|
+
expect(offset).toHaveProperty('xmax');
|
|
155
|
+
expect(offset).toHaveProperty('ymin');
|
|
156
|
+
expect(offset).toHaveProperty('ymax');
|
|
157
|
+
expect(offset.xmax).toBeGreaterThan(offset.xmin);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('Test Canvas.zoomExtents with entities', () => {
|
|
161
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(500, 500)] });
|
|
162
|
+
canvas.matrix = new Matrix();
|
|
163
|
+
canvas.width = 800;
|
|
164
|
+
canvas.height = 600;
|
|
165
|
+
canvas.flipped = false;
|
|
166
|
+
canvas.zoomExtents();
|
|
167
|
+
expect(canvas.getScale()).not.toBe(1);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('Test Canvas.zoomExtents with no entities', () => {
|
|
171
|
+
const emptyCore = new Core();
|
|
172
|
+
const emptyCanvas = emptyCore.canvas;
|
|
173
|
+
emptyCanvas.matrix = new Matrix();
|
|
174
|
+
const scaleBefore = emptyCanvas.getScale();
|
|
175
|
+
emptyCanvas.zoomExtents();
|
|
176
|
+
expect(emptyCanvas.getScale()).toBe(scaleBefore);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const aci7MockEntity = {
|
|
180
|
+
getDrawColour: () => ({ r: 254, g: 254, b: 254 }),
|
|
181
|
+
getLineType: () => ({ getPattern: () => [] }),
|
|
182
|
+
lineWidth: 1,
|
|
183
|
+
entityColour: { aci: 7, byLayer: false, byBlock: false },
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// An entity with ACI 3 whose draw colour happens to be near-white — NOT ACI 7, should not be recoloured
|
|
187
|
+
const nearWhiteMockEntity = {
|
|
188
|
+
getDrawColour: () => ({ r: 254, g: 254, b: 254 }),
|
|
189
|
+
getLineType: () => ({ getPattern: () => [] }),
|
|
190
|
+
lineWidth: 1,
|
|
191
|
+
entityColour: { aci: 3, byLayer: false, byBlock: false },
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// An entity with a true colour whose aci fell back to 7 (non-ACI-table colour)
|
|
195
|
+
const trueColourFallbackAci7MockEntity = {
|
|
196
|
+
getDrawColour: () => ({ r: 200, g: 100, b: 50 }),
|
|
197
|
+
getLineType: () => ({ getPattern: () => [] }),
|
|
198
|
+
lineWidth: 1,
|
|
199
|
+
entityColour: { aci: 7, isTrueColour: true, byLayer: false, byBlock: false },
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
describe('Test Canvas.setContext ACI 7 background-dependent colour', () => {
|
|
203
|
+
let originalBackground;
|
|
204
|
+
|
|
205
|
+
beforeEach(() => {
|
|
206
|
+
core.activate();
|
|
207
|
+
originalBackground = core.settings.canvasbackgroundcolour;
|
|
208
|
+
canvas.paintState = canvas.paintStates.ENTITIES;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
afterEach(() => {
|
|
212
|
+
core.settings.canvasbackgroundcolour = originalBackground;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('ACI 7 is white on dark background', () => {
|
|
216
|
+
const context = createMockContext();
|
|
217
|
+
core.settings.canvasbackgroundcolour = { r: 30, g: 30, b: 30 };
|
|
218
|
+
canvas.setContext(aci7MockEntity, context);
|
|
219
|
+
expect(context.strokeStyle).toBe('rgb(255, 255, 255)');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('ACI 7 is black on light background', () => {
|
|
223
|
+
const context = createMockContext();
|
|
224
|
+
core.settings.canvasbackgroundcolour = { r: 246, g: 245, b: 244 };
|
|
225
|
+
canvas.setContext(aci7MockEntity, context);
|
|
226
|
+
expect(context.strokeStyle).toBe('rgb(0, 0, 0)');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('non-ACI-7 near-white colour is not recoloured', () => {
|
|
230
|
+
const context = createMockContext();
|
|
231
|
+
core.settings.canvasbackgroundcolour = { r: 30, g: 30, b: 30 };
|
|
232
|
+
canvas.setContext(nearWhiteMockEntity, context);
|
|
233
|
+
expect(context.strokeStyle).toBe('rgb(254, 254, 254)');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('true colour with fallback aci=7 is not recoloured', () => {
|
|
237
|
+
const context = createMockContext();
|
|
238
|
+
core.settings.canvasbackgroundcolour = { r: 30, g: 30, b: 30 };
|
|
239
|
+
canvas.setContext(trueColourFallbackAci7MockEntity, context);
|
|
240
|
+
expect(context.strokeStyle).toBe('rgb(200, 100, 50)');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('ACI 7 via ByLayer resolves white on dark background', () => {
|
|
244
|
+
core.settings.canvasbackgroundcolour = { r: 30, g: 30, b: 30 };
|
|
245
|
+
const context = createMockContext();
|
|
246
|
+
const byLayerEntity = {
|
|
247
|
+
getDrawColour: () => ({ r: 254, g: 254, b: 254 }),
|
|
248
|
+
getLineType: () => ({ getPattern: () => [] }),
|
|
249
|
+
lineWidth: 1,
|
|
250
|
+
entityColour: { aci: 256, byLayer: true, byBlock: false },
|
|
251
|
+
layer: '0',
|
|
252
|
+
};
|
|
253
|
+
canvas.setContext(byLayerEntity, context);
|
|
254
|
+
expect(context.strokeStyle).toBe('rgb(255, 255, 255)');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('ACI 7 via ByBlock (direct) resolves white on dark background', () => {
|
|
258
|
+
core.settings.canvasbackgroundcolour = { r: 30, g: 30, b: 30 };
|
|
259
|
+
const context = createMockContext();
|
|
260
|
+
const byBlockItem = {
|
|
261
|
+
getDrawColour: () => ({ r: 254, g: 254, b: 254 }),
|
|
262
|
+
getLineType: () => ({ getPattern: () => [] }),
|
|
263
|
+
lineWidth: 1,
|
|
264
|
+
entityColour: { aci: 0, byLayer: false, byBlock: true },
|
|
265
|
+
};
|
|
266
|
+
const block = {
|
|
267
|
+
getDrawColour: () => ({ r: 254, g: 254, b: 254 }),
|
|
268
|
+
entityColour: { aci: 7, byLayer: false, byBlock: false },
|
|
269
|
+
};
|
|
270
|
+
canvas.setContext(byBlockItem, context, block);
|
|
271
|
+
expect(context.strokeStyle).toBe('rgb(255, 255, 255)');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('ACI 7 via ByBlock → ByLayer resolves white on dark background', () => {
|
|
275
|
+
core.settings.canvasbackgroundcolour = { r: 30, g: 30, b: 30 };
|
|
276
|
+
const context = createMockContext();
|
|
277
|
+
const byBlockItem = {
|
|
278
|
+
getDrawColour: () => ({ r: 254, g: 254, b: 254 }),
|
|
279
|
+
getLineType: () => ({ getPattern: () => [] }),
|
|
280
|
+
lineWidth: 1,
|
|
281
|
+
entityColour: { aci: 0, byLayer: false, byBlock: true },
|
|
282
|
+
};
|
|
283
|
+
const block = {
|
|
284
|
+
getDrawColour: () => ({ r: 254, g: 254, b: 254 }),
|
|
285
|
+
entityColour: { aci: 256, byLayer: true, byBlock: false },
|
|
286
|
+
layer: '0',
|
|
287
|
+
};
|
|
288
|
+
canvas.setContext(byBlockItem, context, block);
|
|
289
|
+
expect(context.strokeStyle).toBe('rgb(255, 255, 255)');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('ACI 7 is not recoloured in SELECTED state', () => {
|
|
293
|
+
core.settings.canvasbackgroundcolour = { r: 30, g: 30, b: 30 };
|
|
294
|
+
const context = createMockContext();
|
|
295
|
+
canvas.paintState = canvas.paintStates.SELECTED;
|
|
296
|
+
canvas.setContext(aci7MockEntity, context);
|
|
297
|
+
const sel = core.settings.selecteditemscolour;
|
|
298
|
+
expect(context.strokeStyle).toBe(`rgb(${sel.r}, ${sel.g}, ${sel.b})`);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('Test Canvas.setContext ByBlock colour', () => {
|
|
303
|
+
beforeEach(() => {
|
|
304
|
+
core.activate();
|
|
305
|
+
canvas.paintState = canvas.paintStates.ENTITIES;
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const byBlockItem = {
|
|
309
|
+
getDrawColour: () => ({ r: 0, g: 0, b: 0 }),
|
|
310
|
+
getLineType: () => ({ getPattern: () => [] }),
|
|
311
|
+
lineWidth: 1,
|
|
312
|
+
entityColour: { aci: 0, byLayer: false, byBlock: true },
|
|
313
|
+
};
|
|
314
|
+
const block = {
|
|
315
|
+
getDrawColour: () => ({ r: 255, g: 0, b: 0 }),
|
|
316
|
+
entityColour: { aci: 1, byLayer: false, byBlock: false },
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
test('ByBlock item inherits draw colour from block', () => {
|
|
320
|
+
const context = createMockContext();
|
|
321
|
+
canvas.setContext(byBlockItem, context, block);
|
|
322
|
+
expect(context.strokeStyle).toBe('rgb(255, 0, 0)');
|
|
323
|
+
expect(context.fillStyle).toBe('rgb(255, 0, 0)');
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('ByBlock colour is not applied in SELECTED state', () => {
|
|
327
|
+
const context = createMockContext();
|
|
328
|
+
canvas.paintState = canvas.paintStates.SELECTED;
|
|
329
|
+
canvas.setContext(byBlockItem, context, block);
|
|
330
|
+
const sel = core.settings.selecteditemscolour;
|
|
331
|
+
expect(context.strokeStyle).toBe(`rgb(${sel.r}, ${sel.g}, ${sel.b})`);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('non-ByBlock item with block keeps its own colour', () => {
|
|
335
|
+
const context = createMockContext();
|
|
336
|
+
const directItem = {
|
|
337
|
+
getDrawColour: () => ({ r: 0, g: 255, b: 0 }),
|
|
338
|
+
getLineType: () => ({ getPattern: () => [] }),
|
|
339
|
+
lineWidth: 1,
|
|
340
|
+
entityColour: { aci: 2, byLayer: false, byBlock: false },
|
|
341
|
+
};
|
|
342
|
+
canvas.setContext(directItem, context, block);
|
|
343
|
+
expect(context.strokeStyle).toBe('rgb(0, 255, 0)');
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe('Test Canvas.setContext paint states', () => {
|
|
348
|
+
const redEntity = {
|
|
349
|
+
getDrawColour: () => ({ r: 255, g: 0, b: 0 }),
|
|
350
|
+
getLineType: () => ({ getPattern: () => [] }),
|
|
351
|
+
lineWidth: 10,
|
|
352
|
+
entityColour: { aci: 1, byLayer: false, byBlock: false },
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
beforeEach(() => {
|
|
356
|
+
core.activate();
|
|
357
|
+
canvas.matrix = new Matrix();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('ENTITIES state uses entity colour and normal lineWidth', () => {
|
|
361
|
+
const context = createMockContext();
|
|
362
|
+
canvas.paintState = canvas.paintStates.ENTITIES;
|
|
363
|
+
canvas.setContext(redEntity, context);
|
|
364
|
+
expect(context.strokeStyle).toBe('rgb(255, 0, 0)');
|
|
365
|
+
expect(context.fillStyle).toBe('rgb(255, 0, 0)');
|
|
366
|
+
expect(context.lineWidth).toBe(10 / canvas.getScale());
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('SELECTED state overrides colour with selecteditemscolour and doubles lineWidth', () => {
|
|
370
|
+
const context = createMockContext();
|
|
371
|
+
canvas.paintState = canvas.paintStates.SELECTED;
|
|
372
|
+
canvas.setContext(redEntity, context);
|
|
373
|
+
const sel = core.settings.selecteditemscolour;
|
|
374
|
+
expect(context.strokeStyle).toBe(`rgb(${sel.r}, ${sel.g}, ${sel.b})`);
|
|
375
|
+
expect(context.lineWidth).toBe(10 * 2 / canvas.getScale());
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test('TEMPORARY state keeps entity colour and doubles lineWidth', () => {
|
|
379
|
+
const context = createMockContext();
|
|
380
|
+
canvas.paintState = canvas.paintStates.TEMPORARY;
|
|
381
|
+
canvas.setContext(redEntity, context);
|
|
382
|
+
expect(context.strokeStyle).toBe('rgb(255, 0, 0)');
|
|
383
|
+
expect(context.lineWidth).toBe(10 * 2 / canvas.getScale());
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test('setContext applies line type dash pattern to context', () => {
|
|
387
|
+
const context = createMockContext();
|
|
388
|
+
let appliedPattern = null;
|
|
389
|
+
context.setLineDash = (pattern) => {
|
|
390
|
+
appliedPattern = pattern;
|
|
391
|
+
};
|
|
392
|
+
const dashedEntity = {
|
|
393
|
+
getDrawColour: () => ({ r: 255, g: 0, b: 0 }),
|
|
394
|
+
getLineType: () => ({ getPattern: () => [5, 5] }),
|
|
395
|
+
lineWidth: 1,
|
|
396
|
+
entityColour: { aci: 1, byLayer: false, byBlock: false },
|
|
397
|
+
};
|
|
398
|
+
canvas.paintState = canvas.paintStates.ENTITIES;
|
|
399
|
+
canvas.setContext(dashedEntity, context);
|
|
400
|
+
expect(appliedPattern).toEqual([5, 5]);
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe('Test Canvas.zoom', () => {
|
|
405
|
+
beforeEach(() => {
|
|
406
|
+
canvas.matrix = new Matrix();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
test('zoom increases scale', () => {
|
|
410
|
+
canvas.zoom(2);
|
|
411
|
+
expect(canvas.getScale()).toBeGreaterThan(1);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test('zoom decreases scale', () => {
|
|
415
|
+
canvas.zoom(0.5);
|
|
416
|
+
expect(canvas.getScale()).toBeLessThan(1);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test('Test Canvas.doubleClick left and right buttons do not throw', () => {
|
|
421
|
+
expect(() => canvas.doubleClick(0)).not.toThrow();
|
|
422
|
+
expect(() => canvas.doubleClick(2)).not.toThrow();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test('Test Canvas.doubleClick middle button triggers zoomExtents', () => {
|
|
426
|
+
const testCore = new Core();
|
|
427
|
+
testCore.activate();
|
|
428
|
+
testCore.scene.addItem('Line', { points: [new Point(0, 0), new Point(500, 500)] });
|
|
429
|
+
testCore.canvas.width = 800;
|
|
430
|
+
testCore.canvas.height = 600;
|
|
431
|
+
testCore.canvas.matrix = new Matrix();
|
|
432
|
+
testCore.canvas.doubleClick(1);
|
|
433
|
+
expect(testCore.canvas.getScale()).not.toBe(1);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test('Test Canvas.zoomToWindow with inverted coordinate order', () => {
|
|
437
|
+
core.activate();
|
|
438
|
+
canvas.matrix = new Matrix();
|
|
439
|
+
canvas.width = 800;
|
|
440
|
+
canvas.height = 600;
|
|
441
|
+
// pt1 > pt2 — Math.min/max should handle this the same as normal order
|
|
442
|
+
canvas.zoomToWindow(new Point(100, 100), new Point(0, 0));
|
|
443
|
+
expect(canvas.getScale()).toBeGreaterThan(1);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test('Test Canvas.paint resets paintState to undefined after painting', () => {
|
|
447
|
+
core.activate();
|
|
448
|
+
canvas.matrix = new Matrix();
|
|
449
|
+
canvas.flipped = true;
|
|
450
|
+
canvas.paint(createMockContext(), 800, 600);
|
|
451
|
+
expect(canvas.paintState).toBeUndefined();
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('Test Canvas.getSceneOffset visible area shrinks when zoomed in', () => {
|
|
455
|
+
core.activate();
|
|
456
|
+
canvas.matrix = new Matrix();
|
|
457
|
+
canvas.width = 800;
|
|
458
|
+
canvas.height = 600;
|
|
459
|
+
const before = canvas.getSceneOffset();
|
|
460
|
+
const widthBefore = before.xmax - before.xmin;
|
|
461
|
+
canvas.zoom(2);
|
|
462
|
+
const after = canvas.getSceneOffset();
|
|
463
|
+
const widthAfter = after.xmax - after.xmin;
|
|
464
|
+
expect(widthAfter).toBeLessThan(widthBefore);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
describe('Test Canvas cursor states', () => {
|
|
468
|
+
let cursorState;
|
|
469
|
+
|
|
470
|
+
beforeEach(() => {
|
|
471
|
+
core.activate();
|
|
472
|
+
canvas.matrix = new Matrix();
|
|
473
|
+
canvas.setCursorForInputTypes([]); // reset to DEFAULT
|
|
474
|
+
cursorState = canvas.cursorStates.DEFAULT;
|
|
475
|
+
canvas.setCursorCallbackFunction((state) => {
|
|
476
|
+
cursorState = state;
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
afterEach(() => {
|
|
481
|
+
canvas.setCursorCallbackFunction(undefined);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test('initial cursorState is DEFAULT', () => {
|
|
485
|
+
expect(canvas.cursorState).toBe(canvas.cursorStates.DEFAULT);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test('middle mouseDown sets cursor to GRABBING after delay', () => {
|
|
489
|
+
jest.useFakeTimers();
|
|
490
|
+
canvas.mouseDown(1);
|
|
491
|
+
expect(canvas.cursorState).toBe(canvas.cursorStates.DEFAULT); // not yet
|
|
492
|
+
jest.advanceTimersByTime(250);
|
|
493
|
+
expect(cursorState).toBe(canvas.cursorStates.GRABBING);
|
|
494
|
+
expect(canvas.cursorState).toBe(canvas.cursorStates.GRABBING);
|
|
495
|
+
jest.useRealTimers();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test('double-click on middle button does not flicker cursor to PAN', () => {
|
|
499
|
+
canvas.mouseDown(1);
|
|
500
|
+
canvas.mouseUp(1); // cancel pending PAN timeout
|
|
501
|
+
// cursor should never have changed to PAN
|
|
502
|
+
expect(canvas.cursorState).toBe(canvas.cursorStates.DEFAULT);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test('middle mouseUp restores cursor to base state (DEFAULT when idle)', () => {
|
|
506
|
+
jest.useFakeTimers();
|
|
507
|
+
canvas.mouseDown(1);
|
|
508
|
+
jest.advanceTimersByTime(250); // let PAN cursor set
|
|
509
|
+
canvas.mouseUp(1);
|
|
510
|
+
expect(cursorState).toBe(canvas.cursorStates.DEFAULT);
|
|
511
|
+
expect(canvas.cursorState).toBe(canvas.cursorStates.DEFAULT);
|
|
512
|
+
jest.useRealTimers();
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test('middle mouseUp restores cursor to SELECTION when selection was active', () => {
|
|
516
|
+
jest.useFakeTimers();
|
|
517
|
+
canvas.setCursorForInputTypes([Input.Type.SELECTIONSET]); // cursor = SELECTION
|
|
518
|
+
canvas.mouseDown(1); // GRABBING (delayed)
|
|
519
|
+
jest.advanceTimersByTime(250);
|
|
520
|
+
canvas.mouseUp(1); // should restore to SELECTION, not DEFAULT
|
|
521
|
+
expect(cursorState).toBe(canvas.cursorStates.SELECTION);
|
|
522
|
+
expect(canvas.cursorState).toBe(canvas.cursorStates.SELECTION);
|
|
523
|
+
jest.useRealTimers();
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
test('wheel is ignored while panning (middle button down)', () => {
|
|
527
|
+
jest.useFakeTimers();
|
|
528
|
+
const scaleBefore = canvas.getScale();
|
|
529
|
+
core.mouse.mouseDown(1); // start pan — sets buttonTwoDown
|
|
530
|
+
jest.advanceTimersByTime(250); // let GRABBING cursor set
|
|
531
|
+
canvas.wheel(1); // should be blocked
|
|
532
|
+
expect(canvas.getScale()).toBe(scaleBefore);
|
|
533
|
+
expect(canvas.cursorState).toBe(canvas.cursorStates.GRABBING);
|
|
534
|
+
core.mouse.mouseUp(1);
|
|
535
|
+
jest.useRealTimers();
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test('left and right mouseDown do not change cursor', () => {
|
|
539
|
+
canvas.mouseDown(0);
|
|
540
|
+
expect(cursorState).toBe(canvas.cursorStates.DEFAULT);
|
|
541
|
+
canvas.mouseDown(2);
|
|
542
|
+
expect(cursorState).toBe(canvas.cursorStates.DEFAULT);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test('callback is not fired when cursor state does not change', () => {
|
|
546
|
+
jest.useFakeTimers();
|
|
547
|
+
let callCount = 0;
|
|
548
|
+
canvas.setCursorCallbackFunction(() => {
|
|
549
|
+
callCount++;
|
|
550
|
+
});
|
|
551
|
+
canvas.mouseDown(1);
|
|
552
|
+
jest.advanceTimersByTime(250); // cursor becomes GRABBING — callCount = 1
|
|
553
|
+
canvas.mouseDown(1);
|
|
554
|
+
jest.advanceTimersByTime(250); // already GRABBING — no-op
|
|
555
|
+
expect(callCount).toBe(1);
|
|
556
|
+
jest.useRealTimers();
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test('setCursorForInputTypes uses cursor hint when provided', () => {
|
|
560
|
+
canvas.setCursorForInputTypes([Input.Type.MOUSEDOWN], canvas.cursorStates.GRAB);
|
|
561
|
+
expect(cursorState).toBe(canvas.cursorStates.GRAB);
|
|
562
|
+
expect(canvas.cursorState).toBe(canvas.cursorStates.GRAB);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test('setCursorForInputTypes MOUSEDOWN with GRAB hint sets GRAB cursor', () => {
|
|
566
|
+
canvas.setCursorForInputTypes([Input.Type.MOUSEDOWN], canvas.cursorStates.GRAB);
|
|
567
|
+
expect(canvas.cursorState).toBe(canvas.cursorStates.GRAB);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
test('setCursorForInputTypes MOUSEUP with GRABBING hint sets GRABBING cursor', () => {
|
|
571
|
+
canvas.setCursorForInputTypes([Input.Type.MOUSEUP], canvas.cursorStates.GRABBING);
|
|
572
|
+
expect(cursorState).toBe(canvas.cursorStates.GRABBING);
|
|
573
|
+
expect(canvas.cursorState).toBe(canvas.cursorStates.GRABBING);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test('setCursorForInputTypes sets SELECTION for SelectionSet', () => {
|
|
577
|
+
canvas.setCursorForInputTypes([Input.Type.SELECTIONSET]);
|
|
578
|
+
expect(cursorState).toBe(canvas.cursorStates.SELECTION);
|
|
579
|
+
expect(canvas.cursorState).toBe(canvas.cursorStates.SELECTION);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test('setCursorForInputTypes sets SELECTION for SingleSelection', () => {
|
|
583
|
+
canvas.setCursorForInputTypes([Input.Type.SINGLESELECTION]);
|
|
584
|
+
expect(cursorState).toBe(canvas.cursorStates.SELECTION);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test('setCursorForInputTypes sets DEFAULT for Point input', () => {
|
|
588
|
+
canvas.setCursorForInputTypes([Input.Type.SELECTIONSET]); // set to SELECTION first
|
|
589
|
+
canvas.setCursorForInputTypes([Input.Type.POINT]);
|
|
590
|
+
expect(cursorState).toBe(canvas.cursorStates.DEFAULT);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
test('setCursorForInputTypes resets to DEFAULT for empty types', () => {
|
|
594
|
+
canvas.setCursorForInputTypes([Input.Type.SELECTIONSET]);
|
|
595
|
+
canvas.setCursorForInputTypes([]);
|
|
596
|
+
expect(cursorState).toBe(canvas.cursorStates.DEFAULT);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test('setCursorCallbackFunction can be unregistered with undefined', () => {
|
|
600
|
+
canvas.setCursorForInputTypes([]); // ensure DEFAULT
|
|
601
|
+
let called = false;
|
|
602
|
+
canvas.setCursorCallbackFunction(() => {
|
|
603
|
+
called = true;
|
|
604
|
+
});
|
|
605
|
+
canvas.setCursorForInputTypes([Input.Type.SELECTIONSET]); // fires callback
|
|
606
|
+
expect(called).toBe(true);
|
|
607
|
+
called = false;
|
|
608
|
+
canvas.setCursorCallbackFunction(undefined);
|
|
609
|
+
canvas.setCursorForInputTypes([]); // state changes but callback cleared
|
|
610
|
+
expect(called).toBe(false);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
test('mouseMoved does not throw when not panning', () => {
|
|
614
|
+
expect(() => canvas.mouseMoved()).not.toThrow();
|
|
615
|
+
});
|
|
616
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Core } from '../../core/core/core.js';
|
|
2
|
+
import { Clipboard } from '../../core/lib/clipboard.js';
|
|
3
|
+
import { Point } from '../../core/entities/point.js';
|
|
4
|
+
import { DesignCore } from '../../core/designCore.js';
|
|
5
|
+
import { Logging } from '../../core/lib/logging.js';
|
|
6
|
+
import { jest } from '@jest/globals';
|
|
7
|
+
|
|
8
|
+
// Initialize core (sets DesignCore.Core)
|
|
9
|
+
new Core();
|
|
10
|
+
|
|
11
|
+
describe('Clipboard', () => {
|
|
12
|
+
test('BasePoint setter invokes callback', () => {
|
|
13
|
+
const cb = jest.fn();
|
|
14
|
+
DesignCore.Clipboard.setClipboardCallbackFunction(cb);
|
|
15
|
+
const p = new Point(10, 20);
|
|
16
|
+
DesignCore.Clipboard.BasePoint = p;
|
|
17
|
+
expect(DesignCore.Clipboard.BasePoint.x).toBe(10);
|
|
18
|
+
expect(DesignCore.Clipboard.BasePoint.y).toBe(20);
|
|
19
|
+
expect(cb).toHaveBeenCalledTimes(1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('Entities setter sets basePoint to bounding box bottom-left and invokes callback', () => {
|
|
23
|
+
const cb = jest.fn();
|
|
24
|
+
DesignCore.Clipboard.setClipboardCallbackFunction(cb);
|
|
25
|
+
const e1 = DesignCore.CommandManager.createNew('Line', { layer: '0', points: [new Point(5, 5), new Point(15, 10)] });
|
|
26
|
+
const e2 = DesignCore.CommandManager.createNew('Line', { layer: '0', points: [new Point(-2, 50), new Point(3, 55)] });
|
|
27
|
+
DesignCore.Clipboard.Entities = [e1, e2];
|
|
28
|
+
// Bounding box min should be x=-2, y=5
|
|
29
|
+
expect(DesignCore.Clipboard.BasePoint.x).toBe(-2);
|
|
30
|
+
expect(DesignCore.Clipboard.BasePoint.y).toBe(5);
|
|
31
|
+
expect(cb).toHaveBeenCalledTimes(1); // Entities setter triggers callback once
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('isValid false when empty, true after setting entities', () => {
|
|
35
|
+
DesignCore.Clipboard.Entities = [];
|
|
36
|
+
expect(DesignCore.Clipboard.isValid).toBe(false);
|
|
37
|
+
const e = DesignCore.CommandManager.createNew('Line', { layer: '0', points: [new Point(0, 0), new Point(1, 1)] });
|
|
38
|
+
DesignCore.Clipboard.Entities = [e];
|
|
39
|
+
expect(DesignCore.Clipboard.isValid).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('stringify and parse roundtrip restores basePoint and entities points', () => {
|
|
43
|
+
const e1 = DesignCore.CommandManager.createNew('Line', { layer: '0', points: [new Point(10, 20), new Point(30, 40)] });
|
|
44
|
+
const e2 = DesignCore.CommandManager.createNew('Line', { layer: '0', points: [new Point(-5, -6), new Point(-1, -2)] });
|
|
45
|
+
DesignCore.Clipboard.Entities = [e1, e2];
|
|
46
|
+
const originalBase = DesignCore.Clipboard.BasePoint;
|
|
47
|
+
const json = DesignCore.Clipboard.stringify();
|
|
48
|
+
|
|
49
|
+
// New clipboard instance to parse into (simulate external paste)
|
|
50
|
+
const fresh = new Clipboard();
|
|
51
|
+
fresh.parse(json);
|
|
52
|
+
|
|
53
|
+
expect(fresh.BasePoint.x).toBe(originalBase.x);
|
|
54
|
+
expect(fresh.BasePoint.y).toBe(originalBase.y);
|
|
55
|
+
expect(fresh.Entities).toHaveLength(2);
|
|
56
|
+
expect(fresh.Entities[0].points[0].x).toBe(10);
|
|
57
|
+
expect(fresh.Entities[0].points[0].y).toBe(20);
|
|
58
|
+
expect(fresh.Entities[1].points[0].x).toBe(-5);
|
|
59
|
+
expect(fresh.Entities[1].points[0].y).toBe(-6);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('parse invalid json logs error and leaves clipboard empty', () => {
|
|
63
|
+
const errorSpy = jest.spyOn(Logging.instance, 'error').mockImplementation(() => {});
|
|
64
|
+
const fresh = new Clipboard();
|
|
65
|
+
fresh.parse('{"bad": true}');
|
|
66
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
67
|
+
expect(fresh.Entities).toHaveLength(0);
|
|
68
|
+
errorSpy.mockRestore();
|
|
69
|
+
});
|
|
70
|
+
});
|