@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
|
@@ -1,8 +1,1147 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { Core } from '../../core/core/core.js';
|
|
3
|
+
import { DesignCore } from '../../core/designCore.js';
|
|
4
|
+
import { Line } from '../../core/entities/line.js';
|
|
5
|
+
import { Point } from '../../core/entities/point.js';
|
|
6
|
+
import { PromptOptions } from '../../core/lib/inputManager.js';
|
|
7
|
+
import { Input, MouseDown, MouseUp } from '../../core/lib/inputManager.js';
|
|
8
|
+
import { SingleSelection } from '../../core/lib/selectionManager.js';
|
|
9
|
+
import { expect, jest } from '@jest/globals';
|
|
4
10
|
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
const core = new Core();
|
|
12
|
+
const inputManager = core.scene.inputManager;
|
|
7
13
|
|
|
8
|
-
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
// Cancel any in-flight prompt and clear command/snapping state so tests
|
|
16
|
+
// cannot leak into one another regardless of pass/fail or input path taken.
|
|
17
|
+
inputManager.reset();
|
|
18
|
+
// Restore all spies unconditionally so mocks don't bleed across tests.
|
|
19
|
+
jest.restoreAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('requestInput resolves when PromptOptions.respond is called', async () => {
|
|
23
|
+
const po = new PromptOptions('Enter text', [Input.Type.STRING]);
|
|
24
|
+
const p = inputManager.requestInput(po);
|
|
25
|
+
|
|
26
|
+
// resolve via PromptOptions.respond
|
|
27
|
+
po.respond('hello');
|
|
28
|
+
|
|
29
|
+
await expect(p).resolves.toBe('hello');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('snapping is activated for POINT prompt and deactivated after respond', async () => {
|
|
33
|
+
const po = new PromptOptions('Pick a point', [Input.Type.POINT]);
|
|
34
|
+
const p = inputManager.requestInput(po);
|
|
35
|
+
|
|
36
|
+
// snapping should be active while awaiting a POINT
|
|
37
|
+
expect(inputManager.snapping.active).toBeTruthy();
|
|
38
|
+
|
|
39
|
+
// respond with a point to resolve the promise
|
|
40
|
+
po.respond(new Point(1, 2));
|
|
41
|
+
|
|
42
|
+
await p;
|
|
43
|
+
|
|
44
|
+
// reset input manager for cleanup
|
|
45
|
+
inputManager.reset();
|
|
46
|
+
|
|
47
|
+
// snapping should be turned off after input is processed
|
|
48
|
+
expect(inputManager.snapping.active).toBeFalsy();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('reset clears activeCommand, promptOption and deactivates snapping', () => {
|
|
52
|
+
inputManager.activeCommand = new Line();
|
|
53
|
+
inputManager.promptOption = new PromptOptions('Test Prompt', [Input.Type.POINT]);
|
|
54
|
+
inputManager.snapping.active = true;
|
|
55
|
+
|
|
56
|
+
expect(inputManager.activeCommand).toBeDefined();
|
|
57
|
+
expect(inputManager.promptOption).toBeDefined();
|
|
58
|
+
expect(inputManager.snapping.active).toBeTruthy();
|
|
59
|
+
|
|
60
|
+
inputManager.reset();
|
|
61
|
+
|
|
62
|
+
expect(inputManager.activeCommand).toBeUndefined();
|
|
63
|
+
expect(inputManager.promptOption).toBeUndefined();
|
|
64
|
+
expect(inputManager.snapping.active).toBeFalsy();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('mouseMoved draws selection window when mouse button one is down and no promptOption', () => {
|
|
68
|
+
// ensure no active promptOption
|
|
69
|
+
inputManager.promptOption = undefined;
|
|
70
|
+
|
|
71
|
+
// spy on selectionManager.drawSelectionWindow
|
|
72
|
+
const spy = jest.spyOn(core.scene.selectionManager, 'drawSelectionWindow').mockImplementation(() => {});
|
|
73
|
+
|
|
74
|
+
// simulate mouse down state
|
|
75
|
+
core.scene.inputManager.mouseDownCanvasPoint = new Point(0, 0);
|
|
76
|
+
core.mouse.buttonOneDown = true;
|
|
77
|
+
|
|
78
|
+
inputManager.mouseMoved();
|
|
79
|
+
|
|
80
|
+
expect(spy).toHaveBeenCalled();
|
|
81
|
+
|
|
82
|
+
spy.mockRestore();
|
|
83
|
+
core.mouse.buttonOneDown = false;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('test onCommand with no activeCommand or promptOption', async () => {
|
|
87
|
+
// Test Unknown Command
|
|
88
|
+
inputManager.reset();
|
|
89
|
+
inputManager.onCommand('TEST_COMMAND');
|
|
90
|
+
// No activeCommand or promptOption should remain undefined
|
|
91
|
+
expect(inputManager.activeCommand).toBeUndefined();
|
|
92
|
+
expect(inputManager.promptOption).toBeUndefined();
|
|
93
|
+
|
|
94
|
+
// Test Known Entity Command
|
|
95
|
+
inputManager.reset();
|
|
96
|
+
inputManager.onCommand('Line');
|
|
97
|
+
// No activeCommand or promptOption should remain undefined
|
|
98
|
+
expect(inputManager.activeCommand).toBeDefined();
|
|
99
|
+
expect(inputManager.promptOption).toBeDefined();
|
|
100
|
+
|
|
101
|
+
// Test Known Tool Command
|
|
102
|
+
inputManager.reset();
|
|
103
|
+
inputManager.onCommand('Rotate');
|
|
104
|
+
// No activeCommand or promptOption should remain undefined
|
|
105
|
+
expect(inputManager.activeCommand).toBeDefined();
|
|
106
|
+
expect(inputManager.promptOption).toBeDefined();
|
|
107
|
+
|
|
108
|
+
const po = new PromptOptions('Point or Number', [Input.Type.POINT, Input.Type.NUMBER], ['One', 'Two', 'Three']);
|
|
109
|
+
|
|
110
|
+
const numericalInput = core.scene.inputManager.requestInput(po);
|
|
111
|
+
expect(inputManager.promptOption).toBe(po);
|
|
112
|
+
inputManager.onCommand(42); // numerical input
|
|
113
|
+
await expect(numericalInput).resolves.toBe(42);
|
|
114
|
+
|
|
115
|
+
const pointInput = core.scene.inputManager.requestInput(po);
|
|
116
|
+
const point = new Point(5, 10);
|
|
117
|
+
inputManager.onCommand(point); // point input
|
|
118
|
+
await expect(pointInput).resolves.toBe(point);
|
|
119
|
+
|
|
120
|
+
const optionInputOne = core.scene.inputManager.requestInput(po);
|
|
121
|
+
inputManager.onCommand('O'); // option input
|
|
122
|
+
await expect(optionInputOne).resolves.toBe('One');
|
|
123
|
+
|
|
124
|
+
const optionInputTwo = core.scene.inputManager.requestInput(po);
|
|
125
|
+
inputManager.onCommand('T'); // option input
|
|
126
|
+
await expect(optionInputTwo).resolves.toBe('Two');
|
|
127
|
+
|
|
128
|
+
const optionInputThree = core.scene.inputManager.requestInput(po);
|
|
129
|
+
inputManager.onCommand('Th'); // option input
|
|
130
|
+
await expect(optionInputThree).resolves.toBe('Three');
|
|
131
|
+
|
|
132
|
+
const stringInput = core.scene.inputManager.requestInput(po);
|
|
133
|
+
inputManager.onCommand('Some String'); // string input
|
|
134
|
+
inputManager.onCommand('Another String'); // string input
|
|
135
|
+
inputManager.onCommand(123); // numerical input to finally resolve
|
|
136
|
+
await expect(stringInput).resolves.toBe(123);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('Test Input.getType', () => {
|
|
140
|
+
expect(Input.getType(100)).toBe(Input.Type.NUMBER);
|
|
141
|
+
expect(Input.getType('text')).toBe(Input.Type.STRING);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('Test tool switching - click circle then rectangle', () => {
|
|
145
|
+
// Reset input manager
|
|
146
|
+
inputManager.reset();
|
|
147
|
+
|
|
148
|
+
// Initially no active command
|
|
149
|
+
expect(inputManager.activeCommand).toBeUndefined();
|
|
150
|
+
|
|
151
|
+
// Click circle tool (shortcut 'C')
|
|
152
|
+
inputManager.onCommand('C');
|
|
153
|
+
expect(inputManager.activeCommand).not.toBeUndefined();
|
|
154
|
+
expect(inputManager.activeCommand.constructor.name).toBe('Circle');
|
|
155
|
+
|
|
156
|
+
// Click rectangle tool (shortcut 'REC') - should switch tools
|
|
157
|
+
inputManager.onCommand('REC');
|
|
158
|
+
expect(inputManager.activeCommand).not.toBeUndefined();
|
|
159
|
+
expect(inputManager.activeCommand.constructor.name).toBe('Rectangle');
|
|
160
|
+
|
|
161
|
+
// Reset for cleanup
|
|
162
|
+
inputManager.reset();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('Test tool switching - click rectangle then circle', () => {
|
|
166
|
+
// Reset input manager
|
|
167
|
+
inputManager.reset();
|
|
168
|
+
|
|
169
|
+
// Initially no active command
|
|
170
|
+
expect(inputManager.activeCommand).toBeUndefined();
|
|
171
|
+
|
|
172
|
+
// Click rectangle tool (shortcut 'REC')
|
|
173
|
+
inputManager.onCommand('REC');
|
|
174
|
+
expect(inputManager.activeCommand).not.toBeUndefined();
|
|
175
|
+
expect(inputManager.activeCommand.constructor.name).toBe('Rectangle');
|
|
176
|
+
|
|
177
|
+
// Click circle tool (shortcut 'C') - should switch tools
|
|
178
|
+
inputManager.onCommand('C');
|
|
179
|
+
expect(inputManager.activeCommand).not.toBeUndefined();
|
|
180
|
+
expect(inputManager.activeCommand.constructor.name).toBe('Circle');
|
|
181
|
+
|
|
182
|
+
// Reset for cleanup
|
|
183
|
+
inputManager.reset();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('Test tool switching with invalid command', () => {
|
|
187
|
+
// Reset input manager
|
|
188
|
+
inputManager.reset();
|
|
189
|
+
|
|
190
|
+
// Start with circle tool
|
|
191
|
+
inputManager.onCommand('C');
|
|
192
|
+
expect(inputManager.activeCommand.constructor.name).toBe('Circle');
|
|
193
|
+
|
|
194
|
+
// Try to switch to invalid command - should not change active command
|
|
195
|
+
const originalCommand = inputManager.activeCommand;
|
|
196
|
+
inputManager.onCommand('INVALID');
|
|
197
|
+
expect(inputManager.activeCommand).toBe(originalCommand);
|
|
198
|
+
|
|
199
|
+
// Reset for cleanup
|
|
200
|
+
inputManager.reset();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ─── onCommandButton ──────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
test('onCommandButton with valid shortcut - starts command, ignores active prompt', async () => {
|
|
206
|
+
inputManager.reset();
|
|
207
|
+
// Simulate an active command with a STRING prompt waiting
|
|
208
|
+
inputManager.activeCommand = new Line();
|
|
209
|
+
const po = new PromptOptions('Enter text', [Input.Type.STRING]);
|
|
210
|
+
const p = inputManager.requestInput(po);
|
|
211
|
+
|
|
212
|
+
// clicking a toolbar button while STRING prompt is active should switch commands
|
|
213
|
+
inputManager.onCommandButton('L');
|
|
214
|
+
expect(inputManager.activeCommand).toBeDefined();
|
|
215
|
+
expect(inputManager.activeCommand.constructor.name).toBe('Line');
|
|
216
|
+
|
|
217
|
+
// the STRING promise should have been cancelled (resolved with undefined)
|
|
218
|
+
await expect(p).resolves.toBeUndefined();
|
|
219
|
+
inputManager.reset();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('onCommandButton with invalid command - does nothing', () => {
|
|
223
|
+
inputManager.reset();
|
|
224
|
+
inputManager.onCommand('Line');
|
|
225
|
+
const original = inputManager.activeCommand;
|
|
226
|
+
inputManager.onCommandButton('NOTACOMMAND');
|
|
227
|
+
expect(inputManager.activeCommand).toBe(original);
|
|
228
|
+
inputManager.reset();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('onCommandButton with no active command - starts command', () => {
|
|
232
|
+
inputManager.reset();
|
|
233
|
+
expect(inputManager.activeCommand).toBeUndefined();
|
|
234
|
+
inputManager.onCommandButton('REC');
|
|
235
|
+
expect(inputManager.activeCommand).toBeDefined();
|
|
236
|
+
expect(inputManager.activeCommand.constructor.name).toBe('Rectangle');
|
|
237
|
+
inputManager.reset();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// ─── onCommand - command shortcut while non-accepting prompt is active ─────────
|
|
241
|
+
|
|
242
|
+
test('onCommand with valid shortcut while POINT prompt active - switches command without notifying', () => {
|
|
243
|
+
inputManager.reset();
|
|
244
|
+
inputManager.onCommand('Line');
|
|
245
|
+
expect(inputManager.promptOption.types).toContain(Input.Type.POINT);
|
|
246
|
+
|
|
247
|
+
const notifySpy = jest.spyOn(core, 'notify').mockImplementation(() => {});
|
|
248
|
+
inputManager.onCommand('C');
|
|
249
|
+
expect(notifySpy).not.toHaveBeenCalled();
|
|
250
|
+
expect(inputManager.activeCommand.constructor.name).toBe('Circle');
|
|
251
|
+
notifySpy.mockRestore();
|
|
252
|
+
inputManager.reset();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('onCommand with valid shortcut while STRING prompt active - sends string to prompt', async () => {
|
|
256
|
+
inputManager.reset();
|
|
257
|
+
// Simulate an active command with a STRING prompt waiting
|
|
258
|
+
inputManager.activeCommand = new Line();
|
|
259
|
+
const po = new PromptOptions('Enter text', [Input.Type.STRING]);
|
|
260
|
+
const p = inputManager.requestInput(po);
|
|
261
|
+
|
|
262
|
+
// 'L' is the Line shortcut but a STRING prompt accepts any string - should go to prompt
|
|
263
|
+
inputManager.onCommand('L');
|
|
264
|
+
const result = await p;
|
|
265
|
+
expect(result).toBe('L');
|
|
266
|
+
// activeCommand is still the original Line stub, not a newly-started Line
|
|
267
|
+
expect(inputManager.activeCommand.constructor.name).toBe('Line');
|
|
268
|
+
inputManager.reset();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// ─── Input ────────────────────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
test('Input.getType(undefined) returns undefined', () => {
|
|
274
|
+
expect(Input.getType(undefined)).toBeUndefined();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('Input.getType returns Number for numeric values regardless of prompt', () => {
|
|
278
|
+
const po = new PromptOptions('Enter distance', [Input.Type.DYNAMIC]);
|
|
279
|
+
inputManager.requestInput(po);
|
|
280
|
+
expect(Input.getType(42)).toBe(Input.Type.NUMBER);
|
|
281
|
+
po.cancel();
|
|
282
|
+
inputManager.reset();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// ─── onEnterPressed ───────────────────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
test('onEnterPressed with promptOption and defaultValue resolves with defaultValue', async () => {
|
|
288
|
+
inputManager.reset();
|
|
289
|
+
inputManager.activeCommand = new Line();
|
|
290
|
+
const po = new PromptOptions('Enter distance', [Input.Type.NUMBER], [], 5);
|
|
291
|
+
const p = inputManager.requestInput(po);
|
|
292
|
+
inputManager.onEnterPressed();
|
|
293
|
+
await expect(p).resolves.toBe(5);
|
|
294
|
+
inputManager.reset();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('onEnterPressed with no activeCommand and empty lastCommand - does nothing', () => {
|
|
298
|
+
inputManager.reset();
|
|
299
|
+
core.commandLine.lastCommand = [];
|
|
300
|
+
expect(() => inputManager.onEnterPressed()).not.toThrow();
|
|
301
|
+
expect(inputManager.activeCommand).toBeUndefined();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('onEnterPressed with no activeCommand and lastCommand history - repeats last command', () => {
|
|
305
|
+
inputManager.reset();
|
|
306
|
+
core.commandLine.lastCommand = ['Line'];
|
|
307
|
+
inputManager.onEnterPressed();
|
|
308
|
+
expect(inputManager.activeCommand).toBeDefined();
|
|
309
|
+
expect(inputManager.activeCommand.constructor.name).toBe('Line');
|
|
310
|
+
inputManager.reset();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('onEnterPressed with activeCommand but no promptOption - resets', () => {
|
|
314
|
+
inputManager.reset();
|
|
315
|
+
inputManager.activeCommand = new Line();
|
|
316
|
+
inputManager.promptOption = undefined;
|
|
317
|
+
inputManager.onEnterPressed();
|
|
318
|
+
expect(inputManager.activeCommand).toBeUndefined();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('onEnterPressed with SELECTIONSET prompt and unaccepted selectionSet - accepts and responds', async () => {
|
|
322
|
+
inputManager.reset();
|
|
323
|
+
inputManager.activeCommand = new Line();
|
|
324
|
+
const po = new PromptOptions('Select entities', [Input.Type.SELECTIONSET]);
|
|
325
|
+
const p = inputManager.requestInput(po);
|
|
326
|
+
core.scene.selectionManager.selectionSet.accepted = false;
|
|
327
|
+
inputManager.onEnterPressed();
|
|
328
|
+
const result = await p;
|
|
329
|
+
expect(result).toBeDefined();
|
|
330
|
+
inputManager.reset();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// ─── onEscapePressed ──────────────────────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
test('onEscapePressed resets the input manager', () => {
|
|
336
|
+
inputManager.onCommand('Line');
|
|
337
|
+
expect(inputManager.activeCommand).toBeDefined();
|
|
338
|
+
inputManager.onEscapePressed();
|
|
339
|
+
expect(inputManager.activeCommand).toBeUndefined();
|
|
340
|
+
expect(inputManager.promptOption).toBeUndefined();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// ─── onLeftClick ──────────────────────────────────────────────────────────────
|
|
344
|
+
|
|
345
|
+
test('onLeftClick with POINT prompt and no nearby entity - responds with the given point', () => {
|
|
346
|
+
inputManager.reset();
|
|
347
|
+
inputManager.onCommand('Line');
|
|
348
|
+
expect(inputManager.promptOption.types).toContain(Input.Type.POINT);
|
|
349
|
+
const respondSpy = jest.spyOn(inputManager.promptOption, 'respond');
|
|
350
|
+
const point = new Point(5, 5);
|
|
351
|
+
inputManager.onLeftClick(point);
|
|
352
|
+
expect(respondSpy).toHaveBeenCalledWith(point);
|
|
353
|
+
respondSpy.mockRestore();
|
|
354
|
+
inputManager.reset();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('onLeftClick with entity found and no active command - delegates to onSelection', () => {
|
|
358
|
+
inputManager.reset();
|
|
359
|
+
const mockSelection = { selectedItemIndex: 0 };
|
|
360
|
+
jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(0);
|
|
361
|
+
jest.spyOn(core.scene.selectionManager, 'singleSelect').mockReturnValue(mockSelection);
|
|
362
|
+
const onSelectionSpy = jest.spyOn(inputManager, 'onSelection').mockImplementation(() => {});
|
|
363
|
+
inputManager.onLeftClick(new Point(0, 0));
|
|
364
|
+
expect(onSelectionSpy).toHaveBeenCalledWith(mockSelection);
|
|
365
|
+
jest.restoreAllMocks();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// ─── onSelection ──────────────────────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
test('onSelection with SINGLESELECTION prompt - resolves promptOption with the selection', async () => {
|
|
371
|
+
inputManager.reset();
|
|
372
|
+
inputManager.activeCommand = new Line();
|
|
373
|
+
const po = new PromptOptions('Select entity', [Input.Type.SINGLESELECTION]);
|
|
374
|
+
const p = inputManager.requestInput(po);
|
|
375
|
+
const selection = new SingleSelection(0, new Point(0, 0));
|
|
376
|
+
inputManager.onSelection(selection);
|
|
377
|
+
await expect(p).resolves.toBe(selection);
|
|
378
|
+
inputManager.reset();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test('onSelection without active command - adds item index to selection set', () => {
|
|
382
|
+
inputManager.reset();
|
|
383
|
+
const addToSelectionSetSpy = jest.spyOn(core.scene.selectionManager, 'addToSelectionSet').mockImplementation(() => {});
|
|
384
|
+
const selection = new SingleSelection(7, new Point(0, 0));
|
|
385
|
+
inputManager.onSelection(selection);
|
|
386
|
+
expect(addToSelectionSetSpy).toHaveBeenCalledWith(7);
|
|
387
|
+
addToSelectionSetSpy.mockRestore();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ─── mouseDown ────────────────────────────────────────────────────────────────
|
|
391
|
+
|
|
392
|
+
test('mouseDown case 0 calls singleSelect', () => {
|
|
393
|
+
const singleSelectSpy = jest.spyOn(inputManager, 'singleSelect').mockImplementation(() => {});
|
|
394
|
+
inputManager.mouseDown(0);
|
|
395
|
+
expect(singleSelectSpy).toHaveBeenCalled();
|
|
396
|
+
singleSelectSpy.mockRestore();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('mouseDown case 0 with MOUSEDOWN prompt - resolves the prompt', async () => {
|
|
400
|
+
inputManager.reset();
|
|
401
|
+
const singleSelectSpy = jest.spyOn(inputManager, 'singleSelect').mockImplementation(() => {});
|
|
402
|
+
const po = new PromptOptions('', [Input.Type.MOUSEDOWN]);
|
|
403
|
+
const p = inputManager.requestInput(po);
|
|
404
|
+
inputManager.mouseDown(0);
|
|
405
|
+
await expect(p).resolves.toBeInstanceOf(MouseDown);
|
|
406
|
+
singleSelectSpy.mockRestore();
|
|
407
|
+
inputManager.reset();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// ─── mouseUp ──────────────────────────────────────────────────────────────────
|
|
411
|
+
|
|
412
|
+
test('mouseUp case 2 triggers onEnterPressed and resets active command', () => {
|
|
413
|
+
inputManager.onCommand('Line');
|
|
414
|
+
expect(inputManager.activeCommand).toBeDefined();
|
|
415
|
+
inputManager.mouseUp(2);
|
|
416
|
+
expect(inputManager.activeCommand).toBeUndefined();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test('mouseUp case 0 with MOUSEUP prompt - resolves the prompt and returns early', async () => {
|
|
420
|
+
inputManager.reset();
|
|
421
|
+
const windowSelectSpy = jest.spyOn(inputManager, 'windowSelect').mockImplementation(() => {});
|
|
422
|
+
const po = new PromptOptions('', [Input.Type.MOUSEUP]);
|
|
423
|
+
const p = inputManager.requestInput(po);
|
|
424
|
+
inputManager.mouseUp(0);
|
|
425
|
+
await expect(p).resolves.toBeInstanceOf(MouseUp);
|
|
426
|
+
expect(windowSelectSpy).not.toHaveBeenCalled();
|
|
427
|
+
windowSelectSpy.mockRestore();
|
|
428
|
+
inputManager.reset();
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test('mouseUp case 0 without SELECTIONSET - returns early without calling windowSelect', () => {
|
|
432
|
+
inputManager.reset();
|
|
433
|
+
const windowSelectSpy = jest.spyOn(inputManager, 'windowSelect').mockImplementation(() => {});
|
|
434
|
+
const po = new PromptOptions('Enter point', [Input.Type.POINT]);
|
|
435
|
+
inputManager.requestInput(po);
|
|
436
|
+
inputManager.mouseUp(0);
|
|
437
|
+
expect(windowSelectSpy).not.toHaveBeenCalled();
|
|
438
|
+
windowSelectSpy.mockRestore();
|
|
439
|
+
inputManager.reset();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test('mouseUp case 0 with SELECTIONSET and mouse moved - calls windowSelect', () => {
|
|
443
|
+
inputManager.reset();
|
|
444
|
+
const windowSelectSpy = jest.spyOn(inputManager, 'windowSelect').mockImplementation(() => {});
|
|
445
|
+
const po = new PromptOptions('', [Input.Type.SELECTIONSET]);
|
|
446
|
+
inputManager.requestInput(po);
|
|
447
|
+
core.mouse.mouseDownCanvasPoint = new Point(0, 0);
|
|
448
|
+
core.mouse.x = 100;
|
|
449
|
+
core.mouse.y = 100;
|
|
450
|
+
inputManager.mouseUp(0);
|
|
451
|
+
expect(windowSelectSpy).toHaveBeenCalled();
|
|
452
|
+
windowSelectSpy.mockRestore();
|
|
453
|
+
core.mouse.x = 0;
|
|
454
|
+
core.mouse.y = 0;
|
|
455
|
+
inputManager.reset();
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// ─── windowSelect ─────────────────────────────────────────────────────────────
|
|
459
|
+
|
|
460
|
+
test('windowSelect delegates to selectionManager.windowSelect', () => {
|
|
461
|
+
const spy = jest.spyOn(core.scene.selectionManager, 'windowSelect').mockImplementation(() => {});
|
|
462
|
+
inputManager.windowSelect();
|
|
463
|
+
expect(spy).toHaveBeenCalled();
|
|
464
|
+
spy.mockRestore();
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// ─── singleSelect ─────────────────────────────────────────────────────────────
|
|
468
|
+
|
|
469
|
+
test('singleSelect calls onLeftClick with the current mouse scene position', () => {
|
|
470
|
+
const onLeftClickSpy = jest.spyOn(inputManager, 'onLeftClick').mockImplementation(() => {});
|
|
471
|
+
core.mouse.x = 10;
|
|
472
|
+
core.mouse.y = 20;
|
|
473
|
+
inputManager.singleSelect();
|
|
474
|
+
expect(onLeftClickSpy).toHaveBeenCalled();
|
|
475
|
+
expect(onLeftClickSpy.mock.calls[0][0]).toBeInstanceOf(Point);
|
|
476
|
+
onLeftClickSpy.mockRestore();
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// ─── setPrompt ────────────────────────────────────────────────────────────────
|
|
480
|
+
|
|
481
|
+
test('setPrompt with no active command - returns without error', () => {
|
|
482
|
+
inputManager.reset();
|
|
483
|
+
expect(() => inputManager.setPrompt('anything')).not.toThrow();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test('setPrompt with an active command - sends formatted prompt to commandLine', () => {
|
|
487
|
+
inputManager.reset();
|
|
488
|
+
inputManager.onCommand('Line');
|
|
489
|
+
const setPromptSpy = jest.spyOn(core.commandLine, 'setPrompt');
|
|
490
|
+
inputManager.setPrompt('Pick start point');
|
|
491
|
+
expect(setPromptSpy).toHaveBeenCalledWith('Line- Pick start point');
|
|
492
|
+
setPromptSpy.mockRestore();
|
|
493
|
+
inputManager.reset();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// ─── actionCommand / executeCommand ───────────────────────────────────────────
|
|
497
|
+
|
|
498
|
+
test('actionCommand with a Tool as activeCommand - calls its action method', () => {
|
|
499
|
+
inputManager.reset();
|
|
500
|
+
inputManager.onCommand('Rotate');
|
|
501
|
+
expect(inputManager.activeCommand).toBeDefined();
|
|
502
|
+
const actionSpy = jest.spyOn(inputManager.activeCommand, 'action').mockImplementation(() => {});
|
|
503
|
+
inputManager.actionCommand();
|
|
504
|
+
expect(actionSpy).toHaveBeenCalled();
|
|
505
|
+
actionSpy.mockRestore();
|
|
506
|
+
inputManager.reset();
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test('executeCommand with an entity item - adds item to scene then resets', () => {
|
|
510
|
+
inputManager.reset();
|
|
511
|
+
inputManager.onCommand('Line');
|
|
512
|
+
const addItemSpy = jest.spyOn(core.scene, 'addItem').mockReturnValue(1);
|
|
513
|
+
const line = new Line();
|
|
514
|
+
inputManager.executeCommand(line);
|
|
515
|
+
expect(addItemSpy).toHaveBeenCalled();
|
|
516
|
+
expect(inputManager.activeCommand).toBeUndefined();
|
|
517
|
+
addItemSpy.mockRestore();
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// ─── requestInput validation ──────────────────────────────────────────────────
|
|
521
|
+
|
|
522
|
+
test('requestInput throws on undefined Input.Type', () => {
|
|
523
|
+
const po = new PromptOptions('bad', [undefined]);
|
|
524
|
+
expect(() => inputManager.requestInput(po)).toThrow('Undefined Input.Type');
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test('requestInput throws on invalid Input.Type', () => {
|
|
528
|
+
const po = new PromptOptions('bad', ['NotAType']);
|
|
529
|
+
expect(() => inputManager.requestInput(po)).toThrow('Invalid input type');
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// ─── requestInput DYNAMIC conversion ─────────────────────────────────────────
|
|
533
|
+
|
|
534
|
+
test('requestInput with DYNAMIC type converts number to projected point', async () => {
|
|
535
|
+
inputManager.reset();
|
|
536
|
+
inputManager.inputPoint = new Point(0, 0);
|
|
537
|
+
// set mouse position to create a known angle
|
|
538
|
+
core.mouse.x = 100;
|
|
539
|
+
core.mouse.y = 0;
|
|
540
|
+
|
|
541
|
+
const po = new PromptOptions('Distance', [Input.Type.POINT, Input.Type.DYNAMIC]);
|
|
542
|
+
const p = inputManager.requestInput(po);
|
|
543
|
+
po.resolve(50);
|
|
544
|
+
const result = await p;
|
|
545
|
+
|
|
546
|
+
// result should be a Point, not the raw number
|
|
547
|
+
expect(result).toBeInstanceOf(Point);
|
|
548
|
+
inputManager.reset();
|
|
549
|
+
core.mouse.x = 0;
|
|
550
|
+
core.mouse.y = 0;
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test('requestInput with non-DYNAMIC type passes number through unchanged', async () => {
|
|
554
|
+
inputManager.reset();
|
|
555
|
+
const po = new PromptOptions('Value', [Input.Type.NUMBER]);
|
|
556
|
+
const p = inputManager.requestInput(po);
|
|
557
|
+
po.resolve(42);
|
|
558
|
+
const result = await p;
|
|
559
|
+
expect(result).toBe(42);
|
|
560
|
+
inputManager.reset();
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// ─── requestInput inputPoint tracking ─────────────────────────────────────────
|
|
564
|
+
|
|
565
|
+
test('requestInput updates inputPoint when input is a Point', async () => {
|
|
566
|
+
inputManager.reset();
|
|
567
|
+
inputManager.inputPoint = new Point(0, 0);
|
|
568
|
+
const po = new PromptOptions('Point', [Input.Type.POINT]);
|
|
569
|
+
const p = inputManager.requestInput(po);
|
|
570
|
+
const pt = new Point(99, 77);
|
|
571
|
+
po.resolve(pt);
|
|
572
|
+
await p;
|
|
573
|
+
expect(inputManager.inputPoint.x).toBe(99);
|
|
574
|
+
expect(inputManager.inputPoint.y).toBe(77);
|
|
575
|
+
inputManager.reset();
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test('requestInput does not update inputPoint for non-Point input', async () => {
|
|
579
|
+
inputManager.reset();
|
|
580
|
+
inputManager.inputPoint = new Point(5, 5);
|
|
581
|
+
const po = new PromptOptions('Text', [Input.Type.STRING]);
|
|
582
|
+
const p = inputManager.requestInput(po);
|
|
583
|
+
po.resolve('hello');
|
|
584
|
+
await p;
|
|
585
|
+
expect(inputManager.inputPoint.x).toBe(5);
|
|
586
|
+
expect(inputManager.inputPoint.y).toBe(5);
|
|
587
|
+
inputManager.reset();
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
test('requestInput returns undefined when cancelled', async () => {
|
|
591
|
+
inputManager.reset();
|
|
592
|
+
inputManager.inputPoint = new Point(5, 5);
|
|
593
|
+
const po = new PromptOptions('Point', [Input.Type.POINT]);
|
|
594
|
+
const p = inputManager.requestInput(po);
|
|
595
|
+
po.cancel();
|
|
596
|
+
const result = await p;
|
|
597
|
+
expect(result).toBeUndefined();
|
|
598
|
+
// inputPoint should not be changed
|
|
599
|
+
expect(inputManager.inputPoint.x).toBe(5);
|
|
600
|
+
inputManager.reset();
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// ─── Input.getType for all types ──────────────────────────────────────────────
|
|
604
|
+
|
|
605
|
+
test('Input.getType returns correct type for Point', () => {
|
|
606
|
+
expect(Input.getType(new Point(1, 2))).toBe(Input.Type.POINT);
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
test('Input.getType returns correct type for SingleSelection', () => {
|
|
610
|
+
expect(Input.getType(new SingleSelection(0, new Point()))).toBe(Input.Type.SINGLESELECTION);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// ─── handlePromptInput ────────────────────────────────────────────────────────
|
|
614
|
+
|
|
615
|
+
test('handlePromptInput returns false when promptOption is null', () => {
|
|
616
|
+
inputManager.reset();
|
|
617
|
+
inputManager.promptOption = undefined;
|
|
618
|
+
expect(inputManager.handlePromptInput('anything')).toBe(false);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test('handlePromptInput returns false when input is rejected by respond', () => {
|
|
622
|
+
inputManager.reset();
|
|
623
|
+
const notifySpy = jest.spyOn(core, 'notify').mockImplementation(() => {});
|
|
624
|
+
inputManager.activeCommand = new Line();
|
|
625
|
+
const po = new PromptOptions('Enter point', [Input.Type.POINT]);
|
|
626
|
+
inputManager.requestInput(po);
|
|
627
|
+
|
|
628
|
+
// string input should not match POINT type
|
|
629
|
+
const result = inputManager.handlePromptInput('not a point');
|
|
630
|
+
expect(result).toBe(false);
|
|
631
|
+
|
|
632
|
+
notifySpy.mockRestore();
|
|
633
|
+
inputManager.reset();
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// ─── setPrompt edge cases ─────────────────────────────────────────────────────
|
|
637
|
+
|
|
638
|
+
test('setPrompt with empty string omits separator', () => {
|
|
639
|
+
inputManager.reset();
|
|
640
|
+
inputManager.onCommand('Line');
|
|
641
|
+
const setPromptSpy = jest.spyOn(core.commandLine, 'setPrompt');
|
|
642
|
+
inputManager.setPrompt('');
|
|
643
|
+
expect(setPromptSpy).toHaveBeenCalledWith('Line');
|
|
644
|
+
setPromptSpy.mockRestore();
|
|
645
|
+
inputManager.reset();
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// ─── highlightEntityUnderMouse ────────────────────────────────────────────────
|
|
649
|
+
|
|
650
|
+
test('highlightEntityUnderMouse returns false when activeCommand has non-selection prompt', () => {
|
|
651
|
+
inputManager.reset();
|
|
652
|
+
inputManager.onCommand('Line');
|
|
653
|
+
// Line requests POINT, not SINGLESELECTION/SELECTIONSET
|
|
654
|
+
expect(inputManager.highlightEntityUnderMouse()).toBe(false);
|
|
655
|
+
inputManager.reset();
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
test('highlightEntityUnderMouse returns false when no entity is near', () => {
|
|
659
|
+
inputManager.reset();
|
|
660
|
+
// no active command — allows highlighting, but nothing nearby
|
|
661
|
+
jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(undefined);
|
|
662
|
+
expect(inputManager.highlightEntityUnderMouse()).toBe(false);
|
|
663
|
+
jest.restoreAllMocks();
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
test('highlightEntityUnderMouse returns true when entity found and no active command', () => {
|
|
667
|
+
inputManager.reset();
|
|
668
|
+
jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(0);
|
|
669
|
+
// mock entities.get to return a cloneable object
|
|
670
|
+
const mockLine = new Line();
|
|
671
|
+
jest.spyOn(core.scene.entities, 'get').mockReturnValue(mockLine);
|
|
672
|
+
expect(inputManager.highlightEntityUnderMouse()).toBe(true);
|
|
673
|
+
jest.restoreAllMocks();
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
// ─── onLeftClick edge cases ───────────────────────────────────────────────────
|
|
677
|
+
|
|
678
|
+
test('onLeftClick with no promptOption and no nearby entity - does nothing', () => {
|
|
679
|
+
inputManager.reset();
|
|
680
|
+
// no active command, no entity in range
|
|
681
|
+
jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(undefined);
|
|
682
|
+
const onSelectionSpy = jest.spyOn(inputManager, 'onSelection');
|
|
683
|
+
inputManager.onLeftClick(new Point(0, 0));
|
|
684
|
+
expect(onSelectionSpy).not.toHaveBeenCalled();
|
|
685
|
+
jest.restoreAllMocks();
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// ─── mouseDown/mouseUp for other buttons ──────────────────────────────────────
|
|
689
|
+
|
|
690
|
+
test('mouseDown with middle button does not call singleSelect', () => {
|
|
691
|
+
const spy = jest.spyOn(inputManager, 'singleSelect').mockImplementation(() => {});
|
|
692
|
+
inputManager.mouseDown(1);
|
|
693
|
+
expect(spy).not.toHaveBeenCalled();
|
|
694
|
+
spy.mockRestore();
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
test('mouseUp with middle button does not trigger any action', () => {
|
|
698
|
+
const enterSpy = jest.spyOn(inputManager, 'onEnterPressed').mockImplementation(() => {});
|
|
699
|
+
const windowSpy = jest.spyOn(inputManager, 'windowSelect').mockImplementation(() => {});
|
|
700
|
+
inputManager.mouseUp(1);
|
|
701
|
+
expect(enterSpy).not.toHaveBeenCalled();
|
|
702
|
+
expect(windowSpy).not.toHaveBeenCalled();
|
|
703
|
+
enterSpy.mockRestore();
|
|
704
|
+
windowSpy.mockRestore();
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
// ─── onCommand edge cases ─────────────────────────────────────────────────────
|
|
709
|
+
|
|
710
|
+
test('onCommand with active command and no promptOption - does not handle via prompt', () => {
|
|
711
|
+
inputManager.reset();
|
|
712
|
+
inputManager.activeCommand = new Line();
|
|
713
|
+
inputManager.promptOption = undefined;
|
|
714
|
+
// should not throw even though there's no promptOption
|
|
715
|
+
expect(() => inputManager.onCommand('INVALID')).not.toThrow();
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// ─── actionCommand edge cases ─────────────────────────────────────────────────
|
|
719
|
+
|
|
720
|
+
test('actionCommand with undefined item and non-Tool activeCommand - does nothing', () => {
|
|
721
|
+
inputManager.reset();
|
|
722
|
+
inputManager.onCommand('Line');
|
|
723
|
+
// calling with no item should not throw
|
|
724
|
+
expect(() => inputManager.actionCommand(undefined)).not.toThrow();
|
|
725
|
+
inputManager.reset();
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
test('actionCommand returns the item index when adding an entity', () => {
|
|
729
|
+
inputManager.reset();
|
|
730
|
+
inputManager.onCommand('Line');
|
|
731
|
+
const addItemSpy = jest.spyOn(core.scene, 'addItem').mockReturnValue(42);
|
|
732
|
+
const line = new Line();
|
|
733
|
+
const index = inputManager.actionCommand(line);
|
|
734
|
+
expect(index).toBe(42);
|
|
735
|
+
addItemSpy.mockRestore();
|
|
736
|
+
inputManager.reset();
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
test('actionCommand passes index parameter to addItem', () => {
|
|
740
|
+
inputManager.reset();
|
|
741
|
+
inputManager.onCommand('Line');
|
|
742
|
+
const addItemSpy = jest.spyOn(core.scene, 'addItem').mockReturnValue(5);
|
|
743
|
+
const line = new Line();
|
|
744
|
+
inputManager.actionCommand(line, 5);
|
|
745
|
+
expect(addItemSpy).toHaveBeenCalledWith(line.type, expect.any(Object), 5);
|
|
746
|
+
addItemSpy.mockRestore();
|
|
747
|
+
inputManager.reset();
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
// ─── Input.Type completeness ──────────────────────────────────────────────────
|
|
751
|
+
|
|
752
|
+
test('Input.Type contains all expected types', () => {
|
|
753
|
+
expect(Input.Type.POINT).toBeDefined();
|
|
754
|
+
expect(Input.Type.NUMBER).toBeDefined();
|
|
755
|
+
expect(Input.Type.STRING).toBeDefined();
|
|
756
|
+
expect(Input.Type.DYNAMIC).toBeDefined();
|
|
757
|
+
expect(Input.Type.SINGLESELECTION).toBeDefined();
|
|
758
|
+
expect(Input.Type.SELECTIONSET).toBeDefined();
|
|
759
|
+
expect(Input.Type.MOUSEDOWN).toBeDefined();
|
|
760
|
+
expect(Input.Type.MOUSEUP).toBeDefined();
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// ─── reset cancels active promptOption ────────────────────────────────────────
|
|
764
|
+
|
|
765
|
+
test('reset calls cancel on the active promptOption', () => {
|
|
766
|
+
inputManager.reset();
|
|
767
|
+
const po = new PromptOptions('Test', [Input.Type.POINT]);
|
|
768
|
+
inputManager.requestInput(po);
|
|
769
|
+
const cancelSpy = jest.spyOn(po, 'cancel');
|
|
770
|
+
inputManager.reset();
|
|
771
|
+
expect(cancelSpy).toHaveBeenCalled();
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
// ─── mouseMoved – activeCommand.preview() ────────────────────────────────────
|
|
775
|
+
|
|
776
|
+
test('mouseMoved calls activeCommand.preview when command is active and no entity nearby', () => {
|
|
777
|
+
inputManager.reset();
|
|
778
|
+
inputManager.onCommand('Line');
|
|
779
|
+
const previewSpy = jest.spyOn(inputManager.activeCommand, 'preview').mockImplementation(() => {});
|
|
780
|
+
jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(undefined);
|
|
781
|
+
core.mouse.buttonOneDown = false;
|
|
782
|
+
inputManager.mouseMoved();
|
|
783
|
+
expect(previewSpy).toHaveBeenCalled();
|
|
784
|
+
jest.restoreAllMocks();
|
|
785
|
+
inputManager.reset();
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
test('mouseMoved does not call preview when entity is being selected', () => {
|
|
789
|
+
inputManager.reset();
|
|
790
|
+
// with SINGLESELECTION prompt, highlightEntityUnderMouse will run and return true (selecting = true)
|
|
791
|
+
inputManager.activeCommand = new Line();
|
|
792
|
+
const po = new PromptOptions('', [Input.Type.SINGLESELECTION]);
|
|
793
|
+
inputManager.requestInput(po);
|
|
794
|
+
const previewSpy = jest.spyOn(inputManager.activeCommand, 'preview');
|
|
795
|
+
jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(0);
|
|
796
|
+
jest.spyOn(core.scene.entities, 'get').mockReturnValue(new Line());
|
|
797
|
+
core.mouse.buttonOneDown = false;
|
|
798
|
+
inputManager.mouseMoved();
|
|
799
|
+
expect(previewSpy).not.toHaveBeenCalled();
|
|
800
|
+
jest.restoreAllMocks();
|
|
801
|
+
po.cancel();
|
|
802
|
+
inputManager.reset();
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
test('mouseMoved draws selection window with SELECTIONSET prompt and button down', () => {
|
|
806
|
+
inputManager.reset();
|
|
807
|
+
inputManager.onCommand('Line');
|
|
808
|
+
const po = new PromptOptions('', [Input.Type.SELECTIONSET]);
|
|
809
|
+
inputManager.requestInput(po);
|
|
810
|
+
const drawSpy = jest.spyOn(core.scene.selectionManager, 'drawSelectionWindow').mockImplementation(() => {});
|
|
811
|
+
core.mouse.buttonOneDown = true;
|
|
812
|
+
inputManager.mouseMoved();
|
|
813
|
+
expect(drawSpy).toHaveBeenCalled();
|
|
814
|
+
drawSpy.mockRestore();
|
|
815
|
+
core.mouse.buttonOneDown = false;
|
|
816
|
+
po.cancel();
|
|
817
|
+
inputManager.reset();
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
test('mouseMoved does not draw selection window when button is not down', () => {
|
|
821
|
+
inputManager.reset();
|
|
822
|
+
const drawSpy = jest.spyOn(core.scene.selectionManager, 'drawSelectionWindow').mockImplementation(() => {});
|
|
823
|
+
core.mouse.buttonOneDown = false;
|
|
824
|
+
inputManager.mouseMoved();
|
|
825
|
+
expect(drawSpy).not.toHaveBeenCalled();
|
|
826
|
+
drawSpy.mockRestore();
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// ─── mouseMoved – snapping path ───────────────────────────────────────────────
|
|
830
|
+
|
|
831
|
+
test('mouseMoved skips highlightEntityUnderMouse when snapping is active and snap returns a point', () => {
|
|
832
|
+
inputManager.reset();
|
|
833
|
+
inputManager.onCommand('Line');
|
|
834
|
+
// snapping is activated by POINT prompt - requestInput already activated it
|
|
835
|
+
expect(inputManager.snapping.active).toBe(true);
|
|
836
|
+
const highlightSpy = jest.spyOn(inputManager, 'highlightEntityUnderMouse');
|
|
837
|
+
jest.spyOn(inputManager.snapping, 'snap').mockReturnValue(new Point(1, 1));
|
|
838
|
+
core.mouse.buttonOneDown = false;
|
|
839
|
+
inputManager.mouseMoved();
|
|
840
|
+
expect(highlightSpy).not.toHaveBeenCalled();
|
|
841
|
+
jest.restoreAllMocks();
|
|
842
|
+
inputManager.reset();
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
// ─── mouseDown – no MOUSEDOWN prompt ─────────────────────────────────────────
|
|
846
|
+
|
|
847
|
+
test('mouseDown case 0 without MOUSEDOWN prompt does not attempt to resolve a prompt', () => {
|
|
848
|
+
inputManager.reset();
|
|
849
|
+
const singleSelectSpy = jest.spyOn(inputManager, 'singleSelect').mockImplementation(() => {});
|
|
850
|
+
const po = new PromptOptions('', [Input.Type.POINT]);
|
|
851
|
+
inputManager.requestInput(po);
|
|
852
|
+
const respondSpy = jest.spyOn(po, 'respond');
|
|
853
|
+
inputManager.mouseDown(0);
|
|
854
|
+
expect(respondSpy).not.toHaveBeenCalled();
|
|
855
|
+
singleSelectSpy.mockRestore();
|
|
856
|
+
po.cancel();
|
|
857
|
+
inputManager.reset();
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
test('mouseDown with MOUSEDOWN prompt but unresolved promise - does not throw', () => {
|
|
861
|
+
inputManager.reset();
|
|
862
|
+
const singleSelectSpy = jest.spyOn(inputManager, 'singleSelect').mockImplementation(() => {});
|
|
863
|
+
// Assign promptOption directly without requestInput, so resolve is undefined
|
|
864
|
+
const po = new PromptOptions('', [Input.Type.MOUSEDOWN]);
|
|
865
|
+
inputManager.promptOption = po;
|
|
866
|
+
expect(() => inputManager.mouseDown(0)).not.toThrow();
|
|
867
|
+
singleSelectSpy.mockRestore();
|
|
868
|
+
inputManager.reset();
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
// ─── mouseUp – no prompt, no active command ───────────────────────────────────
|
|
872
|
+
|
|
873
|
+
test('mouseUp case 0 with no prompt and no active command and mouse moved - calls windowSelect', () => {
|
|
874
|
+
inputManager.reset();
|
|
875
|
+
const windowSelectSpy = jest.spyOn(inputManager, 'windowSelect').mockImplementation(() => {});
|
|
876
|
+
core.mouse.mouseDownCanvasPoint = new Point(0, 0);
|
|
877
|
+
core.mouse.x = 50;
|
|
878
|
+
core.mouse.y = 50;
|
|
879
|
+
inputManager.mouseUp(0);
|
|
880
|
+
expect(windowSelectSpy).toHaveBeenCalled();
|
|
881
|
+
windowSelectSpy.mockRestore();
|
|
882
|
+
core.mouse.x = 0;
|
|
883
|
+
core.mouse.y = 0;
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
test('mouseUp case 0 with no prompt and no active command and mouse not moved - does not call windowSelect', () => {
|
|
887
|
+
inputManager.reset();
|
|
888
|
+
const windowSelectSpy = jest.spyOn(inputManager, 'windowSelect').mockImplementation(() => {});
|
|
889
|
+
core.mouse.mouseDownCanvasPoint = new Point(0, 0);
|
|
890
|
+
core.mouse.x = 0;
|
|
891
|
+
core.mouse.y = 0;
|
|
892
|
+
inputManager.mouseUp(0);
|
|
893
|
+
expect(windowSelectSpy).not.toHaveBeenCalled();
|
|
894
|
+
windowSelectSpy.mockRestore();
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
// ─── highlightEntityUnderMouse – selection prompts ───────────────────────────
|
|
898
|
+
|
|
899
|
+
test('highlightEntityUnderMouse returns true with SINGLESELECTION prompt and entity found', () => {
|
|
900
|
+
inputManager.reset();
|
|
901
|
+
inputManager.activeCommand = new Line();
|
|
902
|
+
const po = new PromptOptions('', [Input.Type.SINGLESELECTION]);
|
|
903
|
+
inputManager.requestInput(po);
|
|
904
|
+
jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(0);
|
|
905
|
+
jest.spyOn(core.scene.entities, 'get').mockReturnValue(new Line());
|
|
906
|
+
expect(inputManager.highlightEntityUnderMouse()).toBe(true);
|
|
907
|
+
jest.restoreAllMocks();
|
|
908
|
+
po.cancel();
|
|
909
|
+
inputManager.reset();
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
test('highlightEntityUnderMouse returns true with SELECTIONSET prompt and entity found', () => {
|
|
913
|
+
inputManager.reset();
|
|
914
|
+
inputManager.activeCommand = new Line();
|
|
915
|
+
const po = new PromptOptions('', [Input.Type.SELECTIONSET]);
|
|
916
|
+
inputManager.requestInput(po);
|
|
917
|
+
jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(0);
|
|
918
|
+
jest.spyOn(core.scene.entities, 'get').mockReturnValue(new Line());
|
|
919
|
+
expect(inputManager.highlightEntityUnderMouse()).toBe(true);
|
|
920
|
+
jest.restoreAllMocks();
|
|
921
|
+
po.cancel();
|
|
922
|
+
inputManager.reset();
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
// ─── onLeftClick – snap prevents entity selection ────────────────────────────
|
|
926
|
+
|
|
927
|
+
test('onLeftClick with active POINT prompt and snap returns a point - responds with the point', () => {
|
|
928
|
+
inputManager.reset();
|
|
929
|
+
inputManager.onCommand('Line');
|
|
930
|
+
const respondSpy = jest.spyOn(inputManager.promptOption, 'respond');
|
|
931
|
+
// snap returns a snapped point (non-null), so entity selection is blocked
|
|
932
|
+
jest.spyOn(inputManager.snapping, 'snap').mockReturnValue(new Point(5, 5));
|
|
933
|
+
jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(0);
|
|
934
|
+
const clickPoint = new Point(5, 5);
|
|
935
|
+
inputManager.onLeftClick(clickPoint);
|
|
936
|
+
// entity select is blocked by snap, so falls through to point respond
|
|
937
|
+
expect(respondSpy).toHaveBeenCalledWith(clickPoint);
|
|
938
|
+
jest.restoreAllMocks();
|
|
939
|
+
inputManager.reset();
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
test('onLeftClick with active command and non-selection prompt - does not select entity', () => {
|
|
943
|
+
inputManager.reset();
|
|
944
|
+
inputManager.onCommand('Line');
|
|
945
|
+
// Line uses POINT prompt - entity selection should be suppressed
|
|
946
|
+
jest.spyOn(inputManager.snapping, 'snap').mockReturnValue(null);
|
|
947
|
+
jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(0);
|
|
948
|
+
const onSelectionSpy = jest.spyOn(inputManager, 'onSelection');
|
|
949
|
+
inputManager.onLeftClick(new Point(1, 1));
|
|
950
|
+
expect(onSelectionSpy).not.toHaveBeenCalled();
|
|
951
|
+
jest.restoreAllMocks();
|
|
952
|
+
inputManager.reset();
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
test('onLeftClick with SINGLESELECTION prompt and entity nearby - selects the entity', () => {
|
|
956
|
+
inputManager.reset();
|
|
957
|
+
inputManager.activeCommand = new Line();
|
|
958
|
+
const po = new PromptOptions('', [Input.Type.SINGLESELECTION]);
|
|
959
|
+
inputManager.requestInput(po);
|
|
960
|
+
const mockSelection = new SingleSelection(3, new Point(0, 0));
|
|
961
|
+
jest.spyOn(inputManager.snapping, 'snap').mockReturnValue(null);
|
|
962
|
+
jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(3);
|
|
963
|
+
jest.spyOn(core.scene.selectionManager, 'singleSelect').mockReturnValue(mockSelection);
|
|
964
|
+
const onSelectionSpy = jest.spyOn(inputManager, 'onSelection');
|
|
965
|
+
inputManager.onLeftClick(new Point(0, 0));
|
|
966
|
+
expect(onSelectionSpy).toHaveBeenCalledWith(mockSelection);
|
|
967
|
+
jest.restoreAllMocks();
|
|
968
|
+
po.cancel();
|
|
969
|
+
inputManager.reset();
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
// ─── onEnterPressed – SELECTIONSET already accepted ──────────────────────────
|
|
973
|
+
|
|
974
|
+
test('onEnterPressed with SELECTIONSET prompt already accepted - resets', () => {
|
|
975
|
+
inputManager.reset();
|
|
976
|
+
inputManager.activeCommand = new Line();
|
|
977
|
+
const po = new PromptOptions('', [Input.Type.SELECTIONSET]);
|
|
978
|
+
inputManager.requestInput(po);
|
|
979
|
+
core.scene.selectionManager.selectionSet.accepted = true;
|
|
980
|
+
inputManager.onEnterPressed();
|
|
981
|
+
expect(inputManager.activeCommand).toBeUndefined();
|
|
982
|
+
po.cancel();
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
// ─── handlePromptInput – returns true on valid input ─────────────────────────
|
|
986
|
+
|
|
987
|
+
test('handlePromptInput returns true on valid input', () => {
|
|
988
|
+
inputManager.reset();
|
|
989
|
+
inputManager.onCommand('Line');
|
|
990
|
+
const result = inputManager.handlePromptInput(new Point(1, 2));
|
|
991
|
+
expect(result).toBe(true);
|
|
992
|
+
inputManager.reset();
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
// ─── reset – clears inputPoint ────────────────────────────────────────────────
|
|
996
|
+
|
|
997
|
+
test('reset sets inputPoint to null', () => {
|
|
998
|
+
inputManager.inputPoint = new Point(10, 20);
|
|
999
|
+
inputManager.reset();
|
|
1000
|
+
expect(inputManager.inputPoint).toBeNull();
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
// ─── mouseMoved – auxiliaryEntities cleared at start ─────────────────────────
|
|
1004
|
+
|
|
1005
|
+
test('mouseMoved clears auxiliaryEntities and tempEntities at the start', () => {
|
|
1006
|
+
// pre-populate both collections
|
|
1007
|
+
const addTrackLineSpy = jest.spyOn(inputManager.snapping, 'addTrackingLine').mockImplementation(() => {});
|
|
1008
|
+
DesignCore.Scene.auxiliaryEntities.add({ draw: () => {} });
|
|
1009
|
+
DesignCore.Scene.tempEntities.add({ draw: () => {} });
|
|
1010
|
+
inputManager.mouseMoved();
|
|
1011
|
+
// both should have been cleared (count re-started from 0 before any new adds)
|
|
1012
|
+
// After mouseMoved the tracking line may or may not have been re-added, but the
|
|
1013
|
+
// clear happened — verify by checking they were empty after the initial clear
|
|
1014
|
+
// (we can confirm this indirectly: no crash and spies didn't see stale data)
|
|
1015
|
+
expect(() => inputManager.mouseMoved()).not.toThrow();
|
|
1016
|
+
addTrackLineSpy.mockRestore();
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
// ─── mouseMoved – polar tracking line ────────────────────────────────────────
|
|
1020
|
+
|
|
1021
|
+
test('mouseMoved adds a tracking line when polar is active and inputPoint is set', () => {
|
|
1022
|
+
inputManager.reset();
|
|
1023
|
+
inputManager.onCommand('Line');
|
|
1024
|
+
// provide an initial input point so the tracking line branch can fire
|
|
1025
|
+
inputManager.inputPoint = new Point(0, 0);
|
|
1026
|
+
expect(inputManager.snapping.active).toBe(true);
|
|
1027
|
+
|
|
1028
|
+
// no entity snap
|
|
1029
|
+
jest.spyOn(inputManager.snapping, 'snap').mockReturnValue(undefined);
|
|
1030
|
+
// polar is true by default; stub polarSnap to return a definite point
|
|
1031
|
+
const trackPoint = new Point(100, 0);
|
|
1032
|
+
jest.spyOn(inputManager.snapping, 'polarSnap').mockReturnValue(trackPoint);
|
|
1033
|
+
const addTrackLineSpy = jest.spyOn(inputManager.snapping, 'addTrackingLine');
|
|
1034
|
+
|
|
1035
|
+
core.mouse.buttonOneDown = false;
|
|
1036
|
+
core.mouse.buttonTwoDown = false;
|
|
1037
|
+
core.mouse.buttonThreeDown = false;
|
|
1038
|
+
inputManager.mouseMoved();
|
|
1039
|
+
|
|
1040
|
+
expect(addTrackLineSpy).toHaveBeenCalledWith(inputManager.inputPoint, trackPoint);
|
|
1041
|
+
jest.restoreAllMocks();
|
|
1042
|
+
inputManager.reset();
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
test('mouseMoved adds a tracking line when ortho is active and inputPoint is set', () => {
|
|
1046
|
+
inputManager.reset();
|
|
1047
|
+
inputManager.onCommand('Line');
|
|
1048
|
+
inputManager.inputPoint = new Point(0, 0);
|
|
1049
|
+
|
|
1050
|
+
// Switch to ortho mode
|
|
1051
|
+
const savedPolar = DesignCore.Settings.polar;
|
|
1052
|
+
const savedOrtho = DesignCore.Settings.ortho;
|
|
1053
|
+
DesignCore.Settings.polar = false;
|
|
1054
|
+
DesignCore.Settings.ortho = true;
|
|
1055
|
+
|
|
1056
|
+
jest.spyOn(inputManager.snapping, 'snap').mockReturnValue(undefined);
|
|
1057
|
+
const trackPoint = new Point(0, 100);
|
|
1058
|
+
jest.spyOn(inputManager.snapping, 'orthoSnap').mockReturnValue(trackPoint);
|
|
1059
|
+
const addTrackLineSpy = jest.spyOn(inputManager.snapping, 'addTrackingLine');
|
|
1060
|
+
|
|
1061
|
+
core.mouse.buttonOneDown = false;
|
|
1062
|
+
core.mouse.buttonTwoDown = false;
|
|
1063
|
+
core.mouse.buttonThreeDown = false;
|
|
1064
|
+
inputManager.mouseMoved();
|
|
1065
|
+
|
|
1066
|
+
expect(addTrackLineSpy).toHaveBeenCalledWith(inputManager.inputPoint, trackPoint);
|
|
1067
|
+
|
|
1068
|
+
DesignCore.Settings.polar = savedPolar;
|
|
1069
|
+
DesignCore.Settings.ortho = savedOrtho;
|
|
1070
|
+
jest.restoreAllMocks();
|
|
1071
|
+
inputManager.reset();
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
test('mouseMoved does not add tracking line when inputPoint is null', () => {
|
|
1075
|
+
inputManager.reset();
|
|
1076
|
+
inputManager.onCommand('Line');
|
|
1077
|
+
// inputPoint is null after reset
|
|
1078
|
+
expect(inputManager.inputPoint).toBeNull();
|
|
1079
|
+
|
|
1080
|
+
jest.spyOn(inputManager.snapping, 'snap').mockReturnValue(undefined);
|
|
1081
|
+
const addTrackLineSpy = jest.spyOn(inputManager.snapping, 'addTrackingLine');
|
|
1082
|
+
|
|
1083
|
+
core.mouse.buttonOneDown = false;
|
|
1084
|
+
inputManager.mouseMoved();
|
|
1085
|
+
|
|
1086
|
+
expect(addTrackLineSpy).not.toHaveBeenCalled();
|
|
1087
|
+
jest.restoreAllMocks();
|
|
1088
|
+
inputManager.reset();
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
test('mouseMoved does not add tracking line when a mouse button is down', () => {
|
|
1092
|
+
inputManager.reset();
|
|
1093
|
+
inputManager.onCommand('Line');
|
|
1094
|
+
inputManager.inputPoint = new Point(0, 0);
|
|
1095
|
+
|
|
1096
|
+
jest.spyOn(inputManager.snapping, 'snap').mockReturnValue(undefined);
|
|
1097
|
+
jest.spyOn(inputManager.snapping, 'polarSnap').mockReturnValue(new Point(100, 0));
|
|
1098
|
+
const addTrackLineSpy = jest.spyOn(inputManager.snapping, 'addTrackingLine');
|
|
1099
|
+
|
|
1100
|
+
core.mouse.buttonOneDown = true;
|
|
1101
|
+
inputManager.mouseMoved();
|
|
1102
|
+
|
|
1103
|
+
expect(addTrackLineSpy).not.toHaveBeenCalled();
|
|
1104
|
+
core.mouse.buttonOneDown = false;
|
|
1105
|
+
jest.restoreAllMocks();
|
|
1106
|
+
inputManager.reset();
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
test('mouseMoved does not add tracking line when entity snap is active', () => {
|
|
1110
|
+
inputManager.reset();
|
|
1111
|
+
inputManager.onCommand('Line');
|
|
1112
|
+
inputManager.inputPoint = new Point(0, 0);
|
|
1113
|
+
|
|
1114
|
+
// snap returns a point → entity snap is active
|
|
1115
|
+
jest.spyOn(inputManager.snapping, 'snap').mockReturnValue(new Point(10, 10));
|
|
1116
|
+
const addTrackLineSpy = jest.spyOn(inputManager.snapping, 'addTrackingLine');
|
|
1117
|
+
|
|
1118
|
+
core.mouse.buttonOneDown = false;
|
|
1119
|
+
inputManager.mouseMoved();
|
|
1120
|
+
|
|
1121
|
+
expect(addTrackLineSpy).not.toHaveBeenCalled();
|
|
1122
|
+
jest.restoreAllMocks();
|
|
1123
|
+
inputManager.reset();
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
// ─── highlightEntityUnderMouse – button pressed ───────────────────────────────
|
|
1127
|
+
|
|
1128
|
+
test('highlightEntityUnderMouse returns false when buttonOneDown is true', () => {
|
|
1129
|
+
inputManager.reset();
|
|
1130
|
+
core.mouse.buttonOneDown = true;
|
|
1131
|
+
expect(inputManager.highlightEntityUnderMouse()).toBe(false);
|
|
1132
|
+
core.mouse.buttonOneDown = false;
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
test('highlightEntityUnderMouse returns false when buttonTwoDown is true', () => {
|
|
1136
|
+
inputManager.reset();
|
|
1137
|
+
core.mouse.buttonTwoDown = true;
|
|
1138
|
+
expect(inputManager.highlightEntityUnderMouse()).toBe(false);
|
|
1139
|
+
core.mouse.buttonTwoDown = false;
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
test('highlightEntityUnderMouse returns false when buttonThreeDown is true', () => {
|
|
1143
|
+
inputManager.reset();
|
|
1144
|
+
core.mouse.buttonThreeDown = true;
|
|
1145
|
+
expect(inputManager.highlightEntityUnderMouse()).toBe(false);
|
|
1146
|
+
core.mouse.buttonThreeDown = false;
|
|
1147
|
+
});
|